aboutsummaryrefslogtreecommitdiff
path: root/engines/scumm/players
diff options
context:
space:
mode:
Diffstat (limited to 'engines/scumm/players')
-rw-r--r--engines/scumm/players/player_ad.cpp959
-rw-r--r--engines/scumm/players/player_ad.h190
-rw-r--r--engines/scumm/players/player_apple2.cpp500
-rw-r--r--engines/scumm/players/player_apple2.h297
-rw-r--r--engines/scumm/players/player_mac.cpp419
-rw-r--r--engines/scumm/players/player_mac.h133
-rw-r--r--engines/scumm/players/player_mod.cpp223
-rw-r--r--engines/scumm/players/player_mod.h100
-rw-r--r--engines/scumm/players/player_nes.cpp1067
-rw-r--r--engines/scumm/players/player_nes.h114
-rw-r--r--engines/scumm/players/player_pce.cpp756
-rw-r--r--engines/scumm/players/player_pce.h133
-rw-r--r--engines/scumm/players/player_sid.cpp1384
-rw-r--r--engines/scumm/players/player_sid.h276
-rw-r--r--engines/scumm/players/player_towns.cpp753
-rw-r--r--engines/scumm/players/player_towns.h180
-rw-r--r--engines/scumm/players/player_v1.cpp607
-rw-r--r--engines/scumm/players/player_v1.h98
-rw-r--r--engines/scumm/players/player_v2.cpp327
-rw-r--r--engines/scumm/players/player_v2.h72
-rw-r--r--engines/scumm/players/player_v2a.cpp1954
-rw-r--r--engines/scumm/players/player_v2a.h73
-rw-r--r--engines/scumm/players/player_v2base.cpp654
-rw-r--r--engines/scumm/players/player_v2base.h145
-rw-r--r--engines/scumm/players/player_v2cms.cpp787
-rw-r--r--engines/scumm/players/player_v2cms.h177
-rw-r--r--engines/scumm/players/player_v3a.cpp357
-rw-r--r--engines/scumm/players/player_v3a.h101
-rw-r--r--engines/scumm/players/player_v3m.cpp214
-rw-r--r--engines/scumm/players/player_v3m.h54
-rw-r--r--engines/scumm/players/player_v4a.cpp190
-rw-r--r--engines/scumm/players/player_v4a.h96
-rw-r--r--engines/scumm/players/player_v5m.cpp246
-rw-r--r--engines/scumm/players/player_v5m.h57
34 files changed, 13693 insertions, 0 deletions
diff --git a/engines/scumm/players/player_ad.cpp b/engines/scumm/players/player_ad.cpp
new file mode 100644
index 0000000000..20630e1cb9
--- /dev/null
+++ b/engines/scumm/players/player_ad.cpp
@@ -0,0 +1,959 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/players/player_ad.h"
+#include "scumm/imuse/imuse.h"
+#include "scumm/scumm.h"
+#include "scumm/resource.h"
+
+#include "audio/fmopl.h"
+
+#include "common/textconsole.h"
+#include "common/config-manager.h"
+
+namespace Scumm {
+
+#define AD_CALLBACK_FREQUENCY 472
+
+Player_AD::Player_AD(ScummEngine *scumm, Audio::Mixer *mixer)
+ : _vm(scumm), _mixer(mixer), _rate(mixer->getOutputRate()) {
+ _opl2 = OPL::Config::create();
+ if (!_opl2->init(_rate)) {
+ error("Could not initialize OPL2 emulator");
+ }
+
+ _samplesPerCallback = _rate / AD_CALLBACK_FREQUENCY;
+ _samplesPerCallbackRemainder = _rate % AD_CALLBACK_FREQUENCY;
+ _samplesTillCallback = 0;
+ _samplesTillCallbackRemainder = 0;
+
+ memset(_registerBackUpTable, 0, sizeof(_registerBackUpTable));
+ writeReg(0x01, 0x00);
+ writeReg(0xBD, 0x00);
+ writeReg(0x08, 0x00);
+ writeReg(0x01, 0x20);
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+
+ _engineMusicTimer = 0;
+ _soundPlaying = -1;
+
+ _curOffset = 0;
+
+ _sfxTimer = 4;
+ _rndSeed = 1;
+
+ memset(_channels, 0, sizeof(_channels));
+ memset(_sfxResource, 0, sizeof(_sfxResource));
+ memset(_sfxPriority, 0, sizeof(_sfxPriority));
+}
+
+Player_AD::~Player_AD() {
+ _mixer->stopHandle(_soundHandle);
+
+ stopAllSounds();
+ Common::StackLock lock(_mutex);
+ delete _opl2;
+ _opl2 = 0;
+}
+
+void Player_AD::setMusicVolume(int vol) {
+ // HACK: We ignore the parameter and set up the volume specified in the
+ // config manager. This allows us to differentiate between music and sfx
+ // volume changes.
+ setupVolume();
+}
+
+void Player_AD::startSound(int sound) {
+ Common::StackLock lock(_mutex);
+
+ // Query the sound resource
+ const byte *res = _vm->getResourceAddress(rtSound, sound);
+
+ if (res[2] == 0x80) {
+ // Stop the current sounds
+ stopAllSounds();
+
+ // Lock the new music resource
+ _soundPlaying = sound;
+ _vm->_res->lock(rtSound, _soundPlaying);
+
+ // Start the new music resource
+ _resource = res;
+ startMusic();
+ } else {
+ // Only try to start a sfx when no music is playing.
+ if (_soundPlaying == -1) {
+ const byte priority = res[0];
+ const byte channel = res[1];
+
+ // Check for out of bounds access
+ if (channel >= 3) {
+ warning("AdLib sfx resource %d uses channel %d", sound, channel);
+ return;
+ }
+
+ // Check whether the channel is free or the priority of the new
+ // sfx resource is above the old one.
+ if (_channels[channel * 3 + 0].state
+ || _channels[channel * 3 + 1].state
+ || _channels[channel * 3 + 2].state) {
+ if (_sfxPriority[channel] > priority) {
+ return;
+ }
+ }
+
+ // Lock the new resource
+ _sfxResource[channel] = sound;
+ _sfxPriority[channel] = priority;
+ _vm->_res->lock(rtSound, sound);
+
+ // Start the actual sfx resource
+ _resource = res;
+ startSfx();
+ }
+ }
+
+ // Setup the sound volume
+ setupVolume();
+}
+
+void Player_AD::stopSound(int sound) {
+ Common::StackLock lock(_mutex);
+
+ if (sound == _soundPlaying) {
+ stopAllSounds();
+ } else {
+ for (int i = 0; i < 3; ++i) {
+ if (_sfxResource[i] == sound) {
+ if (_channels[i * 3 + 0].state
+ || _channels[i * 3 + 1].state
+ || _channels[i * 3 + 2].state) {
+ // Unlock the sound resource
+ _vm->_res->unlock(rtSound, sound);
+
+ // Stop the actual sfx playback
+ _channels[i * 3 + 0].state = 0;
+ _channels[i * 3 + 1].state = 0;
+ _channels[i * 3 + 2].state = 0;
+ clearChannel(i * 3 + 0);
+ clearChannel(i * 3 + 1);
+ clearChannel(i * 3 + 2);
+ }
+ }
+ }
+ }
+}
+
+void Player_AD::stopAllSounds() {
+ Common::StackLock lock(_mutex);
+
+ // Unlock the music resource if present
+ if (_soundPlaying != -1) {
+ _vm->_res->unlock(rtSound, _soundPlaying);
+ _soundPlaying = -1;
+ }
+
+ // Stop the music playback
+ _curOffset = 0;
+
+ // Unloack all used sfx resources
+ for (int i = 0; i < 3; ++i) {
+ if (_channels[i * 3 + 0].state || _channels[i * 3 + 1].state || _channels[i * 3 + 2].state) {
+ _vm->_res->unlock(rtSound, _sfxResource[i]);
+ }
+ }
+
+ // Reset all the sfx channels
+ for (int i = 0; i < 9; ++i) {
+ _channels[i].state = 0;
+ clearChannel(i);
+ }
+
+ writeReg(0xBD, 0x00);
+}
+
+int Player_AD::getMusicTimer() {
+ return _engineMusicTimer;
+}
+
+int Player_AD::getSoundStatus(int sound) const {
+ return (sound == _soundPlaying);
+}
+
+void Player_AD::saveLoadWithSerializer(Serializer *ser) {
+ Common::StackLock lock(_mutex);
+
+ if (ser->getVersion() < VER(95)) {
+ IMuse *dummyImuse = IMuse::create(_vm->_system, NULL, NULL);
+ dummyImuse->save_or_load(ser, _vm, false);
+ delete dummyImuse;
+ return;
+ }
+
+ // TODO: Be nicer than the original and save the data to continue the
+ // currently played sound resources on load?
+}
+
+int Player_AD::readBuffer(int16 *buffer, const int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ int len = numSamples;
+
+ while (len > 0) {
+ if (!_samplesTillCallback) {
+ // Run the update callback for music or sfx depending on which is
+ // active.
+ if (_curOffset) {
+ updateMusic();
+ } else {
+ updateSfx();
+ }
+
+ _samplesTillCallback = _samplesPerCallback;
+ _samplesTillCallbackRemainder += _samplesPerCallbackRemainder;
+ if (_samplesTillCallbackRemainder >= AD_CALLBACK_FREQUENCY) {
+ ++_samplesTillCallback;
+ _samplesTillCallbackRemainder -= AD_CALLBACK_FREQUENCY;
+ }
+ }
+
+ const int samplesToRead = MIN(len, _samplesTillCallback);
+ _opl2->readBuffer(buffer, samplesToRead);
+
+ buffer += samplesToRead;
+ len -= samplesToRead;
+ _samplesTillCallback -= samplesToRead;
+ }
+
+ return numSamples;
+}
+
+void Player_AD::setupVolume() {
+ // Setup the correct volume
+ int soundVolumeMusic = CLIP<int>(ConfMan.getInt("music_volume"), 0, Audio::Mixer::kMaxChannelVolume);
+ int soundVolumeSfx = CLIP<int>(ConfMan.getInt("sfx_volume"), 0, Audio::Mixer::kMaxChannelVolume);
+ if (ConfMan.hasKey("mute")) {
+ if (ConfMan.getBool("mute")) {
+ soundVolumeMusic = 0;
+ soundVolumeSfx = 0;
+ }
+ }
+
+ // In case a music is being played set the music volume. Set the sfx
+ // volume otherwise. This is safe because in the latter case either
+ // sfx are playing or there is no sound being played at all.
+ if (_soundPlaying != -1) {
+ _mixer->setChannelVolume(_soundHandle, soundVolumeMusic);
+ } else {
+ _mixer->setChannelVolume(_soundHandle, soundVolumeSfx);
+ }
+}
+
+void Player_AD::writeReg(int r, int v) {
+ if (r >= 0 && r < ARRAYSIZE(_registerBackUpTable)) {
+ _registerBackUpTable[r] = v;
+ }
+ _opl2->writeReg(r, v);
+}
+
+uint8 Player_AD::readReg(int r) const {
+ if (r >= 0 && r < ARRAYSIZE(_registerBackUpTable)) {
+ return _registerBackUpTable[r];
+ } else {
+ return 0;
+ }
+}
+
+void Player_AD::setupChannel(const uint channel, const byte *instrOffset) {
+ instrOffset += 2;
+ writeReg(0xC0 + channel, *instrOffset++);
+ setupOperator(_operatorOffsetTable[channel * 2 + 0], instrOffset);
+ setupOperator(_operatorOffsetTable[channel * 2 + 1], instrOffset);
+}
+
+void Player_AD::setupOperator(const uint opr, const byte *&instrOffset) {
+ writeReg(0x20 + opr, *instrOffset++);
+ writeReg(0x40 + opr, *instrOffset++);
+ writeReg(0x60 + opr, *instrOffset++);
+ writeReg(0x80 + opr, *instrOffset++);
+ writeReg(0xE0 + opr, *instrOffset++);
+}
+
+const int Player_AD::_operatorOffsetTable[18] = {
+ 0, 3, 1, 4,
+ 2, 5, 8, 11,
+ 9, 12, 10, 13,
+ 16, 19, 17, 20,
+ 18, 21
+};
+
+// Music
+
+void Player_AD::startMusic() {
+ memset(_instrumentOffset, 0, sizeof(_instrumentOffset));
+ memset(_channelLastEvent, 0, sizeof(_channelLastEvent));
+ memset(_channelFrequency, 0, sizeof(_channelFrequency));
+ memset(_channelB0Reg, 0, sizeof(_channelB0Reg));
+
+ _voiceChannels = 0;
+ uint instruments = _resource[10];
+ for (uint i = 0; i < instruments; ++i) {
+ const int instrIndex = _resource[11 + i] - 1;
+ if (0 <= instrIndex && instrIndex < 16) {
+ _instrumentOffset[instrIndex] = i * 16 + 16 + 3;
+ _voiceChannels |= _resource[_instrumentOffset[instrIndex] + 13];
+ }
+ }
+
+ if (_voiceChannels) {
+ _mdvdrState = 0x20;
+ _voiceChannels = 6;
+ } else {
+ _mdvdrState = 0;
+ _voiceChannels = 9;
+ }
+
+ _curOffset = 0x93;
+ // TODO: is this the same for Loom?
+ _nextEventTimer = 40;
+ _engineMusicTimer = 0;
+ _internalMusicTimer = 0;
+ _musicTimer = 0;
+
+ writeReg(0xBD, _mdvdrState);
+
+ const bool isLoom = (_vm->_game.id == GID_LOOM);
+ _timerLimit = isLoom ? 473 : 256;
+ _musicTicks = _resource[3] * (isLoom ? 2 : 1);
+ _loopFlag = (_resource[4] == 0);
+ _musicLoopStart = READ_LE_UINT16(_resource + 5);
+}
+
+void Player_AD::updateMusic() {
+ _musicTimer += _musicTicks;
+ if (_musicTimer < _timerLimit) {
+ return;
+ }
+ _musicTimer -= _timerLimit;
+
+ ++_internalMusicTimer;
+ if (_internalMusicTimer > 120) {
+ _internalMusicTimer = 0;
+ ++_engineMusicTimer;
+ }
+
+ --_nextEventTimer;
+ if (_nextEventTimer) {
+ return;
+ }
+
+ while (true) {
+ uint command = _resource[_curOffset++];
+ if (command == 0xFF) {
+ // META EVENT
+ // Get the command number.
+ command = _resource[_curOffset++];
+ if (command == 47) {
+ // End of track
+ if (_loopFlag) {
+ // In case the track is looping jump to the start.
+ _curOffset = _musicLoopStart;
+ _nextEventTimer = 0;
+ } else {
+ // Otherwise completely stop playback.
+ stopAllSounds();
+ }
+ return;
+ } else if (command == 88) {
+ // This is proposedly a debug information insertion. The CMS
+ // player code handles this differently, but is still using
+ // the same resources...
+ _curOffset += 5;
+ } else if (command == 81) {
+ // Change tempo. This is used exclusively in Loom.
+ const uint timing = _resource[_curOffset + 2] | (_resource[_curOffset + 1] << 8);
+ _musicTicks = 0x73000 / timing;
+ command = _resource[_curOffset++];
+ _curOffset += command;
+ } else {
+ // In case an unknown meta event occurs just skip over the
+ // data by using the length supplied.
+ command = _resource[_curOffset++];
+ _curOffset += command;
+ }
+ } else {
+ if (command >= 0x90) {
+ // NOTE ON
+ // Extract the channel number and save it in command.
+ command -= 0x90;
+
+ const uint instrOffset = _instrumentOffset[command];
+ if (instrOffset) {
+ if (_resource[instrOffset + 13] != 0) {
+ setupRhythm(_resource[instrOffset + 13], instrOffset);
+ } else {
+ int channel = findFreeChannel();
+ if (channel != -1) {
+ noteOff(channel);
+ setupChannel(channel, instrOffset);
+ _channelLastEvent[channel] = command + 0x90;
+ _channelFrequency[channel] = _resource[_curOffset];
+ setupFrequency(channel, _resource[_curOffset]);
+ }
+ }
+ }
+ } else {
+ // NOTE OFF
+ const uint note = _resource[_curOffset];
+ command += 0x10;
+
+ // Find the output channel which plays the note.
+ uint channel = 0xFF;
+ for (uint i = 0; i < _voiceChannels; ++i) {
+ if (_channelFrequency[i] == note && _channelLastEvent[i] == command) {
+ channel = i;
+ break;
+ }
+ }
+
+ if (channel != 0xFF) {
+ // In case a output channel playing the note was found,
+ // stop it.
+ noteOff(channel);
+ } else {
+ // In case there is no such note this will disable the
+ // rhythm instrument played on the channel.
+ command -= 0x90;
+ const uint instrOffset = _instrumentOffset[command];
+ if (instrOffset && _resource[instrOffset + 13] != 0) {
+ const uint rhythmInstr = _resource[instrOffset + 13];
+ if (rhythmInstr < 6) {
+ _mdvdrState &= _mdvdrTable[rhythmInstr] ^ 0xFF;
+ writeReg(0xBD, _mdvdrState);
+ }
+ }
+ }
+ }
+
+ _curOffset += 2;
+ }
+
+ // In case there is a delay till the next event stop handling.
+ if (_resource[_curOffset] != 0) {
+ break;
+ }
+ ++_curOffset;
+ }
+
+ _nextEventTimer = _resource[_curOffset++];
+ if (_nextEventTimer & 0x80) {
+ _nextEventTimer -= 0x80;
+ _nextEventTimer <<= 7;
+ _nextEventTimer |= _resource[_curOffset++];
+ }
+
+ _nextEventTimer >>= (_vm->_game.id == GID_LOOM) ? 2 : 1;
+ if (!_nextEventTimer) {
+ _nextEventTimer = 1;
+ }
+}
+
+void Player_AD::noteOff(uint channel) {
+ _channelLastEvent[channel] = 0;
+ writeReg(0xB0 + channel, _channelB0Reg[channel] & 0xDF);
+}
+
+int Player_AD::findFreeChannel() {
+ for (uint i = 0; i < _voiceChannels; ++i) {
+ if (!_channelLastEvent[i]) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+void Player_AD::setupFrequency(uint channel, int8 frequency) {
+ frequency -= 31;
+ if (frequency < 0) {
+ frequency = 0;
+ }
+
+ uint octave = 0;
+ while (frequency >= 12) {
+ frequency -= 12;
+ ++octave;
+ }
+
+ const uint noteFrequency = _noteFrequencies[frequency];
+ octave <<= 2;
+ octave |= noteFrequency >> 8;
+ octave |= 0x20;
+ writeReg(0xA0 + channel, noteFrequency & 0xFF);
+ _channelB0Reg[channel] = octave;
+ writeReg(0xB0 + channel, octave);
+}
+
+void Player_AD::setupRhythm(uint rhythmInstr, uint instrOffset) {
+ if (rhythmInstr == 1) {
+ setupChannel(6, instrOffset);
+ writeReg(0xA6, _resource[instrOffset++]);
+ writeReg(0xB6, _resource[instrOffset] & 0xDF);
+ _mdvdrState |= 0x10;
+ writeReg(0xBD, _mdvdrState);
+ } else if (rhythmInstr < 6) {
+ const byte *secondOperatorOffset = _resource + instrOffset + 8;
+ setupOperator(_rhythmOperatorTable[rhythmInstr], secondOperatorOffset);
+ writeReg(0xA0 + _rhythmChannelTable[rhythmInstr], _resource[instrOffset++]);
+ writeReg(0xB0 + _rhythmChannelTable[rhythmInstr], _resource[instrOffset++] & 0xDF);
+ writeReg(0xC0 + _rhythmChannelTable[rhythmInstr], _resource[instrOffset]);
+ _mdvdrState |= _mdvdrTable[rhythmInstr];
+ writeReg(0xBD, _mdvdrState);
+ }
+}
+
+const uint Player_AD::_noteFrequencies[12] = {
+ 0x200, 0x21E, 0x23F, 0x261,
+ 0x285, 0x2AB, 0x2D4, 0x300,
+ 0x32E, 0x35E, 0x390, 0x3C7
+};
+
+const uint Player_AD::_mdvdrTable[6] = {
+ 0x00, 0x10, 0x08, 0x04, 0x02, 0x01
+};
+
+const uint Player_AD::_rhythmOperatorTable[6] = {
+ 0x00, 0x00, 0x14, 0x12, 0x15, 0x11
+};
+
+const uint Player_AD::_rhythmChannelTable[6] = {
+ 0x00, 0x00, 0x07, 0x08, 0x08, 0x07
+};
+
+// SFX
+
+void Player_AD::startSfx() {
+ writeReg(0xBD, 0x00);
+
+ // The second byte of the resource defines the logical channel where
+ // the sound effect should be played.
+ const int startChannel = _resource[1] * 3;
+
+ // Clear the channel.
+ _channels[startChannel + 0].state = 0;
+ _channels[startChannel + 1].state = 0;
+ _channels[startChannel + 2].state = 0;
+
+ clearChannel(startChannel + 0);
+ clearChannel(startChannel + 1);
+ clearChannel(startChannel + 2);
+
+ // Set up the first channel to pick up playback.
+ _channels[startChannel].currentOffset = _channels[startChannel].startOffset = _resource + 2;
+ _channels[startChannel].state = 1;
+
+ // Scan for the start of the other channels and set them up if required.
+ int curChannel = startChannel + 1;
+ const byte *bufferPosition = _resource + 2;
+ uint8 command = 0;
+ while ((command = *bufferPosition) != 0xFF) {
+ switch (command) {
+ case 1:
+ // INSTRUMENT DEFINITION
+ bufferPosition += 15;
+ break;
+
+ case 2:
+ // NOTE DEFINITION
+ bufferPosition += 11;
+ break;
+
+ case 0x80:
+ // LOOP
+ bufferPosition += 1;
+ break;
+
+ default:
+ // START OF CHANNEL
+ bufferPosition += 1;
+ _channels[curChannel].currentOffset = bufferPosition;
+ _channels[curChannel].startOffset = bufferPosition;
+ _channels[curChannel].state = 1;
+ ++curChannel;
+ break;
+ }
+ }
+}
+
+void Player_AD::updateSfx() {
+ if (--_sfxTimer) {
+ return;
+ }
+ _sfxTimer = 4;
+
+ for (int i = 0; i <= 9; ++i) {
+ if (!_channels[i].state) {
+ continue;
+ }
+
+ updateChannel(i);
+ }
+}
+
+void Player_AD::clearChannel(int channel) {
+ writeReg(0xA0 + channel, 0x00);
+ writeReg(0xB0 + channel, 0x00);
+}
+
+void Player_AD::updateChannel(int channel) {
+ if (_channels[channel].state == 1) {
+ parseSlot(channel);
+ } else {
+ updateSlot(channel);
+ }
+}
+
+void Player_AD::parseSlot(int channel) {
+ while (true) {
+ const byte *curOffset = _channels[channel].currentOffset;
+
+ switch (*curOffset) {
+ case 1:
+ // INSTRUMENT DEFINITION
+ ++curOffset;
+ _channels[channel].instrumentData[0] = *(curOffset + 0);
+ _channels[channel].instrumentData[1] = *(curOffset + 2);
+ _channels[channel].instrumentData[2] = *(curOffset + 9);
+ _channels[channel].instrumentData[3] = *(curOffset + 8);
+ _channels[channel].instrumentData[4] = *(curOffset + 4);
+ _channels[channel].instrumentData[5] = *(curOffset + 3);
+ _channels[channel].instrumentData[6] = 0;
+
+ setupChannel(channel, curOffset);
+
+ writeReg(0xA0 + channel, *(curOffset + 0));
+ writeReg(0xB0 + channel, *(curOffset + 1) & 0xDF);
+
+ _channels[channel].currentOffset += 15;
+ break;
+
+ case 2:
+ // NOTE DEFINITION
+ ++curOffset;
+ _channels[channel].state = 2;
+ noteOffOn(channel);
+ parseNote(channel, 0, curOffset);
+ parseNote(channel, 1, curOffset);
+ return;
+
+ case 0x80:
+ // LOOP
+ _channels[channel].currentOffset = _channels[channel].startOffset;
+ break;
+
+ default:
+ // START OF CHANNEL
+ // When we encounter a start of another channel while playback
+ // it means that the current channel is finished. Thus, we will
+ // stop it.
+ clearChannel(channel);
+ _channels[channel].state = 0;
+
+ // If no channel of the sound effect is playing anymore, unlock
+ // the resource.
+ channel /= 3;
+ if (!_channels[channel + 0].state
+ && !_channels[channel + 1].state
+ && !_channels[channel + 2].state) {
+ _vm->_res->unlock(rtSound, _sfxResource[channel]);
+ }
+ return;
+ }
+ }
+}
+
+void Player_AD::updateSlot(int channel) {
+ const byte *curOffset = _channels[channel].currentOffset + 1;
+
+ for (int num = 0; num <= 1; ++num, curOffset += 5) {
+ if (!(*curOffset & 0x80)) {
+ continue;
+ }
+
+ const int note = channel * 2 + num;
+ bool updateNote = false;
+
+ if (_notes[note].state == 2) {
+ if (!--_notes[note].sustainTimer) {
+ updateNote = true;
+ }
+ } else {
+ updateNote = processNoteEnvelope(note, _notes[note].instrumentValue);
+
+ if (_notes[note].bias) {
+ writeRegisterSpecial(note, _notes[note].bias - _notes[note].instrumentValue, *curOffset & 0x07);
+ } else {
+ writeRegisterSpecial(note, _notes[note].instrumentValue, *curOffset & 0x07);
+ }
+ }
+
+ if (updateNote) {
+ if (processNote(note, curOffset)) {
+ if (!(*curOffset & 0x08)) {
+ _channels[channel].currentOffset += 11;
+ _channels[channel].state = 1;
+ continue;
+ } else if (*curOffset & 0x10) {
+ noteOffOn(channel);
+ }
+
+ _notes[note].state = -1;
+ processNote(note, curOffset);
+ }
+ }
+
+ if ((*curOffset & 0x20) && !--_notes[note].playTime) {
+ _channels[channel].currentOffset += 11;
+ _channels[channel].state = 1;
+ }
+ }
+}
+
+void Player_AD::parseNote(int channel, int num, const byte *offset) {
+ if (num) {
+ offset += 5;
+ }
+
+ if (*offset & 0x80) {
+ const int note = channel * 2 + num;
+ _notes[note].state = -1;
+ processNote(note, offset);
+ _notes[note].playTime = 0;
+
+ if (*offset & 0x20) {
+ _notes[note].playTime = (*(offset + 4) >> 4) * 118;
+ _notes[note].playTime += (*(offset + 4) & 0x0F) * 8;
+ }
+ }
+}
+
+bool Player_AD::processNote(int note, const byte *offset) {
+ if (++_notes[note].state == 4) {
+ return true;
+ }
+
+ const int instrumentDataOffset = *offset & 0x07;
+ _notes[note].bias = _noteBiasTable[instrumentDataOffset];
+
+ uint8 instrumentDataValue = 0;
+ if (_notes[note].state == 0) {
+ instrumentDataValue = _channels[note / 2].instrumentData[instrumentDataOffset];
+ }
+
+ uint8 noteInstrumentValue = readRegisterSpecial(note, instrumentDataValue, instrumentDataOffset);
+ if (_notes[note].bias) {
+ noteInstrumentValue = _notes[note].bias - noteInstrumentValue;
+ }
+ _notes[note].instrumentValue = noteInstrumentValue;
+
+ if (_notes[note].state == 2) {
+ _notes[note].sustainTimer = _numStepsTable[*(offset + 3) >> 4];
+
+ if (*offset & 0x40) {
+ _notes[note].sustainTimer = (((getRnd() << 8) * _notes[note].sustainTimer) >> 16) + 1;
+ }
+ } else {
+ int timer1, timer2;
+ if (_notes[note].state == 3) {
+ timer1 = *(offset + 3) & 0x0F;
+ timer2 = 0;
+ } else {
+ timer1 = *(offset + _notes[note].state + 1) >> 4;
+ timer2 = *(offset + _notes[note].state + 1) & 0x0F;
+ }
+
+ int adjustValue = ((_noteAdjustTable[timer2] * _noteAdjustScaleTable[instrumentDataOffset]) >> 16) - noteInstrumentValue;
+ setupNoteEnvelopeState(note, _numStepsTable[timer1], adjustValue);
+ }
+
+ return false;
+}
+
+void Player_AD::noteOffOn(int channel) {
+ const uint8 regValue = readReg(0xB0 | channel);
+ writeReg(0xB0 | channel, regValue & 0xDF);
+ writeReg(0xB0 | channel, regValue | 0x20);
+}
+
+void Player_AD::writeRegisterSpecial(int note, uint8 value, int offset) {
+ if (offset == 6) {
+ return;
+ }
+
+ // Division by 2 extracts the channel number out of the note.
+ note /= 2;
+
+ uint8 regNum;
+ if (_useOperatorTable[offset]) {
+ regNum = _operatorOffsetTable[_channelOperatorOffsetTable[offset] + note * 2];
+ } else {
+ regNum = _channelOffsetTable[note];
+ }
+
+ regNum += _baseRegisterTable[offset];
+
+ uint8 regValue = readReg(regNum) & (~_registerMaskTable[offset]);
+ regValue |= value << _registerShiftTable[offset];
+
+ writeReg(regNum, regValue);
+}
+
+uint8 Player_AD::readRegisterSpecial(int note, uint8 defaultValue, int offset) {
+ if (offset == 6) {
+ return 0;
+ }
+
+ // Division by 2 extracts the channel number out of the note.
+ note /= 2;
+
+ uint8 regNum;
+ if (_useOperatorTable[offset]) {
+ regNum = _operatorOffsetTable[_channelOperatorOffsetTable[offset] + note * 2];
+ } else {
+ regNum = _channelOffsetTable[note];
+ }
+
+ regNum += _baseRegisterTable[offset];
+
+ uint8 regValue;
+ if (defaultValue) {
+ regValue = defaultValue;
+ } else {
+ regValue = readReg(regNum);
+ }
+
+ regValue &= _registerMaskTable[offset];
+ regValue >>= _registerShiftTable[offset];
+
+ return regValue;
+}
+
+void Player_AD::setupNoteEnvelopeState(int note, int steps, int adjust) {
+ _notes[note].preIncrease = 0;
+ if (ABS(adjust) > steps) {
+ _notes[note].preIncrease = 1;
+ _notes[note].adjust = adjust / steps;
+ _notes[note].envelope.stepIncrease = ABS(adjust % steps);
+ } else {
+ _notes[note].adjust = adjust;
+ _notes[note].envelope.stepIncrease = ABS(adjust);
+ }
+
+ _notes[note].envelope.step = steps;
+ _notes[note].envelope.stepCounter = 0;
+ _notes[note].envelope.timer = steps;
+}
+
+bool Player_AD::processNoteEnvelope(int note, int &instrumentValue) {
+ if (_notes[note].preIncrease) {
+ instrumentValue += _notes[note].adjust;
+ }
+
+ _notes[note].envelope.stepCounter += _notes[note].envelope.stepIncrease;
+ if (_notes[note].envelope.stepCounter >= _notes[note].envelope.step) {
+ _notes[note].envelope.stepCounter -= _notes[note].envelope.step;
+
+ if (_notes[note].adjust < 0) {
+ --instrumentValue;
+ } else {
+ ++instrumentValue;
+ }
+ }
+
+ if (--_notes[note].envelope.timer) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+uint8 Player_AD::getRnd() {
+ if (_rndSeed & 1) {
+ _rndSeed >>= 1;
+ _rndSeed ^= 0xB8;
+ } else {
+ _rndSeed >>= 1;
+ }
+
+ return _rndSeed;
+}
+
+const uint Player_AD::_noteBiasTable[7] = {
+ 0x00, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x00
+};
+
+const uint Player_AD::_numStepsTable[16] = {
+ 1, 4, 6, 8,
+ 10, 14, 18, 24,
+ 36, 64, 100, 160,
+ 240, 340, 600, 1200
+};
+
+const uint Player_AD::_noteAdjustScaleTable[7] = {
+ 255, 7, 63, 15, 63, 15, 63
+};
+
+const uint Player_AD::_noteAdjustTable[16] = {
+ 0, 4369, 8738, 13107,
+ 17476, 21845, 26214, 30583,
+ 34952, 39321, 43690, 48059,
+ 52428, 46797, 61166, 65535
+};
+
+const bool Player_AD::_useOperatorTable[7] = {
+ false, false, true, true, true, true, false
+};
+
+const uint Player_AD::_channelOffsetTable[11] = {
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 8, 7
+};
+
+const uint Player_AD::_channelOperatorOffsetTable[7] = {
+ 0, 0, 1, 1, 0, 0, 0
+};
+
+const uint Player_AD::_baseRegisterTable[7] = {
+ 0xA0, 0xC0, 0x40, 0x20, 0x40, 0x20, 0x00
+};
+
+const uint Player_AD::_registerMaskTable[7] = {
+ 0xFF, 0x0E, 0x3F, 0x0F, 0x3F, 0x0F, 0x00
+};
+
+const uint Player_AD::_registerShiftTable[7] = {
+ 0, 1, 0, 0, 0, 0, 0
+};
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_ad.h b/engines/scumm/players/player_ad.h
new file mode 100644
index 0000000000..fbb65fbe24
--- /dev/null
+++ b/engines/scumm/players/player_ad.h
@@ -0,0 +1,190 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_AD_H
+#define SCUMM_PLAYERS_PLAYER_AD_H
+
+#include "scumm/music.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+#include "common/mutex.h"
+
+namespace OPL {
+class OPL;
+}
+
+namespace Scumm {
+
+class ScummEngine;
+
+/**
+ * Sound output for v3/v4 AdLib data.
+ */
+class Player_AD : public MusicEngine, public Audio::AudioStream {
+public:
+ Player_AD(ScummEngine *scumm, Audio::Mixer *mixer);
+ virtual ~Player_AD();
+
+ // MusicEngine API
+ virtual void setMusicVolume(int vol);
+ virtual void startSound(int sound);
+ virtual void stopSound(int sound);
+ virtual void stopAllSounds();
+ virtual int getMusicTimer();
+ virtual int getSoundStatus(int sound) const;
+
+ virtual void saveLoadWithSerializer(Serializer *ser);
+
+ // AudioStream API
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+ virtual bool isStereo() const { return false; }
+ virtual bool endOfData() const { return false; }
+ virtual int getRate() const { return _rate; }
+
+private:
+ ScummEngine *const _vm;
+ Common::Mutex _mutex;
+ Audio::Mixer *const _mixer;
+ const int _rate;
+ Audio::SoundHandle _soundHandle;
+ void setupVolume();
+
+ OPL::OPL *_opl2;
+
+ int _samplesPerCallback;
+ int _samplesPerCallbackRemainder;
+ int _samplesTillCallback;
+ int _samplesTillCallbackRemainder;
+
+ int _soundPlaying;
+ int _engineMusicTimer;
+
+ // AdLib register utilities
+ uint8 _registerBackUpTable[256];
+ void writeReg(int r, int v);
+ uint8 readReg(int r) const;
+
+ // Instrument setup
+ void setupChannel(const uint channel, uint instrOffset) {
+ setupChannel(channel, _resource + instrOffset);
+ }
+ void setupChannel(const uint channel, const byte *instrOffset);
+ void setupOperator(const uint opr, const byte *&instrOffset);
+ static const int _operatorOffsetTable[18];
+
+ // Sound data
+ const byte *_resource;
+
+ // Music handling
+ void startMusic();
+ void updateMusic();
+ void noteOff(uint channel);
+ int findFreeChannel();
+ void setupFrequency(uint channel, int8 frequency);
+ void setupRhythm(uint rhythmInstr, uint instrOffset);
+
+ uint _timerLimit;
+ uint _musicTicks;
+ uint _musicTimer;
+ uint _internalMusicTimer;
+ bool _loopFlag;
+ uint _musicLoopStart;
+ uint _instrumentOffset[16];
+ uint _channelLastEvent[9];
+ uint _channelFrequency[9];
+ uint _channelB0Reg[9];
+
+ uint _mdvdrState;
+ uint _voiceChannels;
+
+ uint _curOffset;
+ uint _nextEventTimer;
+
+ static const uint _noteFrequencies[12];
+ static const uint _mdvdrTable[6];
+ static const uint _rhythmOperatorTable[6];
+ static const uint _rhythmChannelTable[6];
+
+ // SFX handling
+ void startSfx();
+ void updateSfx();
+ void clearChannel(int channel);
+ void updateChannel(int channel);
+ void parseSlot(int channel);
+ void updateSlot(int channel);
+ void parseNote(int channel, int num, const byte *offset);
+ bool processNote(int note, const byte *offset);
+ void noteOffOn(int channel);
+ void writeRegisterSpecial(int note, uint8 value, int offset);
+ uint8 readRegisterSpecial(int note, uint8 defaultValue, int offset);
+ void setupNoteEnvelopeState(int note, int steps, int adjust);
+ bool processNoteEnvelope(int note, int &instrumentValue);
+
+ int _sfxTimer;
+
+ int _sfxResource[3];
+ int _sfxPriority[3];
+
+ struct Channel {
+ int state;
+ const byte *currentOffset;
+ const byte *startOffset;
+ uint8 instrumentData[7];
+ } _channels[11];
+
+ uint8 _rndSeed;
+ uint8 getRnd();
+
+ struct Note {
+ int state;
+ int playTime;
+ int sustainTimer;
+ int instrumentValue;
+ int bias;
+ int preIncrease;
+ int adjust;
+
+ struct Envelope {
+ int stepIncrease;
+ int step;
+ int stepCounter;
+ int timer;
+ } envelope;
+ } _notes[22];
+
+ static const uint _noteBiasTable[7];
+ static const uint _numStepsTable[16];
+ static const uint _noteAdjustScaleTable[7];
+ static const uint _noteAdjustTable[16];
+ static const bool _useOperatorTable[7];
+ static const uint _channelOffsetTable[11];
+ static const uint _channelOperatorOffsetTable[7];
+ static const uint _baseRegisterTable[7];
+ static const uint _registerMaskTable[7];
+ static const uint _registerShiftTable[7];
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_apple2.cpp b/engines/scumm/players/player_apple2.cpp
new file mode 100644
index 0000000000..87b8100f22
--- /dev/null
+++ b/engines/scumm/players/player_apple2.cpp
@@ -0,0 +1,500 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "engines/engine.h"
+#include "scumm/players/player_apple2.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+/************************************
+ * Apple-II sound-resource parsers
+ ************************************/
+
+/*
+ * SoundFunction1: frequency up/down
+ */
+class AppleII_SoundFunction1_FreqUpDown : public AppleII_SoundFunction {
+public:
+ virtual void init(Player_AppleII *player, const byte *params) {
+ _player = player;
+ _delta = params[0];
+ _count = params[1];
+ _interval = params[2];
+ _limit = params[3];
+ _decInterval = (params[4] >= 0x40);
+ }
+
+ virtual bool update() { // D085
+ if (_decInterval) {
+ do {
+ _update(_interval, _count);
+ _interval -= _delta;
+ } while (_interval >= _limit);
+ } else {
+ do {
+ _update(_interval, _count);
+ _interval += _delta;
+ } while (_interval < _limit);
+ }
+ return true;
+ }
+
+private:
+ void _update(int interval /*a*/, int count /*y*/) { // D076
+ assert(interval > 0); // 0 == 256?
+ assert(count > 0); // 0 == 256?
+
+ for (; count >= 0; --count) {
+ _player->speakerToggle();
+ _player->generateSamples(17 + 5 * interval);
+ }
+ }
+
+protected:
+ int _delta;
+ int _count;
+ byte _interval; // must be unsigned byte ("interval < delta" possible)
+ int _limit;
+ bool _decInterval;
+};
+
+/*
+ * SoundFunction2: symmetric wave (~)
+ */
+class AppleII_SoundFunction2_SymmetricWave : public AppleII_SoundFunction {
+public:
+ virtual void init(Player_AppleII *player, const byte *params) {
+ _player = player;
+ _params = params;
+ _pos = 1;
+ }
+
+ virtual bool update() { // D0D6
+ // while (pos = 1; pos < 256; ++pos)
+ if (_pos < 256) {
+ byte interval = _params[_pos];
+ if (interval == 0xFF)
+ return true;
+ _update(interval, _params[0] /*, LD12F=interval*/);
+
+ ++_pos;
+ return false;
+ }
+ return true;
+ }
+
+private:
+ void _update(int interval /*a*/, int count) { // D0EF
+ if (interval == 0xFE) {
+ _player->wait(interval, 10);
+ } else {
+ assert(count > 0); // 0 == 256?
+ assert(interval > 0); // 0 == 256?
+
+ int a = (interval >> 3) + count;
+ for (int y = a; y > 0; --y) {
+ _player->generateSamples(1292 - 5*interval);
+ _player->speakerToggle();
+
+ _player->generateSamples(1287 - 5*interval);
+ _player->speakerToggle();
+ }
+ }
+ }
+
+protected:
+ const byte *_params;
+ int _pos;
+};
+
+/*
+ * SoundFunction3: asymmetric wave (__-)
+ */
+class AppleII_SoundFunction3_AsymmetricWave : public AppleII_SoundFunction {
+public:
+ virtual void init(Player_AppleII *player, const byte *params) {
+ _player = player;
+ _params = params;
+ _pos = 1;
+ }
+
+ virtual bool update() { // D132
+ // while (pos = 1; pos < 256; ++pos)
+ if (_pos < 256) {
+ byte interval = _params[_pos];
+ if (interval == 0xFF)
+ return true;
+ _update(interval, _params[0]);
+
+ ++_pos;
+ return false;
+ }
+ return true;
+ }
+
+private:
+ void _update(int interval /*a*/, int count /*LD12D*/) { // D14B
+ if (interval == 0xFE) {
+ _player->wait(interval, 70);
+ } else {
+ assert(interval > 0); // 0 == 256?
+ assert(count > 0); // 0 == 256?
+
+ for (int y = count; y > 0; --y) {
+ _player->generateSamples(1289 - 5*interval);
+ _player->speakerToggle();
+ }
+ }
+ }
+
+protected:
+ const byte *_params;
+ int _pos;
+};
+
+/*
+ * SoundFunction4: polyphone (2 voices)
+ */
+class AppleII_SoundFunction4_Polyphone : public AppleII_SoundFunction {
+public:
+ virtual void init(Player_AppleII *player, const byte *params) {
+ _player = player;
+ _params = params;
+ _updateRemain1 = 80;
+ _updateRemain2 = 10;
+ _count = 0;
+ }
+
+ virtual bool update() { // D170
+ // while (_params[0] != 0x01)
+ if (_params[0] != 0x01) {
+ if (_count == 0) // prepare next loop
+ nextLoop(_params[0], _params[1], _params[2]);
+ if (loopIteration()) // loop finished -> fetch next parameter set
+ _params += 3;
+ return false;
+ }
+ return true;
+ }
+
+private:
+ /*
+ * prepare for next parameter set loop
+ */
+ void nextLoop(byte param0, byte param1, byte param2) { // LD182
+ _count = (-param2 << 8) | 0x3;
+
+ _bitmask1 = 0x3;
+ _bitmask2 = 0x3;
+
+ _updateInterval2 = param0;
+ if (_updateInterval2 == 0)
+ _bitmask2 = 0x0;
+
+ _updateInterval1 = param1;
+ if (_updateInterval1 == 0) {
+ _bitmask1 = 0x0;
+ if (_bitmask2 != 0) {
+ _bitmask1 = _bitmask2;
+ _bitmask2 = 0;
+ _updateInterval1 = _updateInterval2;
+ }
+ }
+
+ _speakerShiftReg = 0;
+ }
+
+ /*
+ * perform one loop iteration
+ * Returns true if loop finished
+ */
+ bool loopIteration() { // D1A2
+ --_updateRemain1;
+ --_updateRemain2;
+
+ if (_updateRemain2 == 0) {
+ _updateRemain2 = _updateInterval2;
+ // use only first voice's data (bitmask1) if both voices are triggered
+ if (_updateRemain1 != 0) {
+ _speakerShiftReg ^= _bitmask2;
+ }
+ }
+
+ if (_updateRemain1 == 0) {
+ _updateRemain1 = _updateInterval1;
+ _speakerShiftReg ^= _bitmask1;
+ }
+
+ if (_speakerShiftReg & 0x1)
+ _player->speakerToggle();
+ _speakerShiftReg >>= 1;
+ _player->generateSamples(42); /* actually 42.5 */
+
+ ++_count;
+ return (_count == 0);
+ }
+
+protected:
+ const byte *_params;
+
+ byte _updateRemain1;
+ byte _updateRemain2;
+
+ uint16 _count;
+ byte _bitmask1;
+ byte _bitmask2;
+ byte _updateInterval1;
+ byte _updateInterval2;
+ byte _speakerShiftReg;
+};
+
+/*
+ * SoundFunction5: periodic noise
+ */
+class AppleII_SoundFunction5_Noise : public AppleII_SoundFunction {
+public:
+ virtual void init(Player_AppleII *player, const byte *params) {
+ _player = player;
+ _index = 0;
+ _param0 = params[0];
+ assert(_param0 > 0);
+ }
+
+ virtual bool update() { // D222
+ const byte noiseMask[] = {
+ 0x3F, 0x3F, 0x7F, 0x7F, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F
+ };
+
+ // while (i = 0; i < 10; ++i)
+ if (_index < 10) {
+ int count = _param0;
+ do {
+ _update(noise() & noiseMask[_index], 1);
+ --count;
+ } while (count > 0);
+
+ ++_index;
+ return false;
+ }
+
+ return true;
+ }
+
+private:
+ void _update(int interval /*a*/, int count) { // D270
+ assert(count > 0); // 0 == 256?
+ if (interval == 0)
+ interval = 256;
+
+ for (int i = count; i > 0; --i) {
+ _player->generateSamples(10 + 5*interval);
+ _player->speakerToggle();
+
+ _player->generateSamples(5 + 5*interval);
+ _player->speakerToggle();
+ }
+ }
+
+ byte /*a*/ noise() { // D261
+ static int pos = 0; // initial value?
+ byte result = _noiseTable[pos];
+ pos = (pos + 1) % 256;
+ return result;
+ }
+
+protected:
+ int _index;
+ int _param0;
+
+private:
+ static const byte _noiseTable[256];
+};
+
+// LD000[loc] ^ LD00A[loc]
+const byte AppleII_SoundFunction5_Noise::_noiseTable[256] = {
+ 0x65, 0x1b, 0xda, 0x11, 0x61, 0xe5, 0x77, 0x57, 0x92, 0xc8, 0x51, 0x1c, 0xd4, 0x91, 0x62, 0x63,
+ 0x00, 0x38, 0x57, 0xd5, 0x18, 0xd8, 0xdc, 0x40, 0x03, 0x86, 0xd3, 0x2f, 0x10, 0x11, 0xd8, 0x3c,
+ 0xbe, 0x00, 0x19, 0xc5, 0xd2, 0xc3, 0xca, 0x34, 0x00, 0x28, 0xbf, 0xb9, 0x18, 0x20, 0x01, 0xcc,
+ 0xda, 0x08, 0xbc, 0x75, 0x7c, 0xb0, 0x8d, 0xe0, 0x09, 0x18, 0xbf, 0x5d, 0xe9, 0x8c, 0x75, 0x64,
+ 0xe5, 0xb5, 0x5d, 0xe0, 0xb7, 0x7d, 0xe9, 0x8c, 0x55, 0x65, 0xc5, 0xb5, 0x5d, 0xd8, 0x09, 0x0d,
+ 0x64, 0xf0, 0xf0, 0x08, 0x63, 0x03, 0x00, 0x55, 0x35, 0xc0, 0x00, 0x20, 0x74, 0xa5, 0x1e, 0xe3,
+ 0x00, 0x06, 0x3c, 0x52, 0xd1, 0x70, 0xd0, 0x57, 0x02, 0xf0, 0x00, 0xb6, 0xfc, 0x02, 0x11, 0x9a,
+ 0x3b, 0xc8, 0x38, 0xdf, 0x1a, 0xb0, 0xd1, 0xb8, 0xd0, 0x18, 0x8a, 0x4a, 0xea, 0x1b, 0x12, 0x5d,
+ 0x29, 0x58, 0xd8, 0x43, 0xb8, 0x2d, 0xd2, 0x61, 0x10, 0x3c, 0x0c, 0x5d, 0x1b, 0x61, 0x10, 0x3c,
+ 0x0a, 0x5d, 0x1d, 0x61, 0x10, 0x3c, 0x0b, 0x19, 0x88, 0x21, 0xc0, 0x21, 0x07, 0x00, 0x65, 0x62,
+ 0x08, 0xe9, 0x36, 0x40, 0x20, 0x41, 0x06, 0x00, 0x20, 0x00, 0x00, 0xed, 0xa3, 0x00, 0x88, 0x06,
+ 0x98, 0x01, 0x5d, 0x7f, 0x02, 0x1d, 0x78, 0x03, 0x60, 0xcb, 0x3a, 0x01, 0xbd, 0x78, 0x02, 0x5d,
+ 0x7e, 0x03, 0x1d, 0xf5, 0xa6, 0x40, 0x81, 0xb4, 0xd0, 0x8d, 0xd3, 0xd0, 0x6d, 0xd5, 0x61, 0x48,
+ 0x61, 0x4d, 0xd1, 0xc8, 0xb1, 0xd8, 0x69, 0xff, 0x61, 0xd9, 0xed, 0xa0, 0xfe, 0x19, 0x91, 0x37,
+ 0x19, 0x37, 0x00, 0xf1, 0x00, 0x01, 0x1f, 0x00, 0xad, 0xc1, 0x01, 0x01, 0x2e, 0x00, 0x40, 0xc6,
+ 0x7a, 0x9b, 0x95, 0x43, 0xfc, 0x18, 0xd2, 0x9e, 0x2a, 0x5a, 0x4b, 0x2a, 0xb6, 0x87, 0x30, 0x6c
+};
+
+/************************************
+ * Apple-II player
+ ************************************/
+
+Player_AppleII::Player_AppleII(ScummEngine *scumm, Audio::Mixer *mixer)
+ : _mixer(mixer), _vm(scumm), _soundFunc(0) {
+ resetState();
+ setSampleRate(_mixer->getOutputRate());
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_AppleII::~Player_AppleII() {
+ _mixer->stopHandle(_soundHandle);
+ delete _soundFunc;
+}
+
+void Player_AppleII::resetState() {
+ _soundNr = 0;
+ _type = 0;
+ _loop = 0;
+ _params = NULL;
+ _speakerState = 0;
+ delete _soundFunc;
+ _soundFunc = 0;
+ _sampleConverter.reset();
+}
+
+void Player_AppleII::startSound(int nr) {
+ Common::StackLock lock(_mutex);
+
+ byte *data = _vm->getResourceAddress(rtSound, nr);
+ assert(data);
+ byte *ptr1 = data + 4;
+
+ resetState();
+ _soundNr = nr;
+ _type = ptr1[0];
+ _loop = ptr1[1];
+ _params = &ptr1[2];
+
+ switch (_type) {
+ case 0: // empty (nothing to play)
+ resetState();
+ return;
+ case 1:
+ _soundFunc = new AppleII_SoundFunction1_FreqUpDown();
+ break;
+ case 2:
+ _soundFunc = new AppleII_SoundFunction2_SymmetricWave();
+ break;
+ case 3:
+ _soundFunc = new AppleII_SoundFunction3_AsymmetricWave();
+ break;
+ case 4:
+ _soundFunc = new AppleII_SoundFunction4_Polyphone();
+ break;
+ case 5:
+ _soundFunc = new AppleII_SoundFunction5_Noise();
+ break;
+ }
+ _soundFunc->init(this, _params);
+
+ assert(_loop > 0);
+
+ debug(4, "startSound %d: type %d, loop %d",
+ nr, _type, _loop);
+}
+
+bool Player_AppleII::updateSound() {
+ if (!_soundFunc)
+ return false;
+
+ if (_soundFunc->update()) {
+ --_loop;
+ if (_loop <= 0) {
+ delete _soundFunc;
+ _soundFunc = 0;
+ } else {
+ // reset function state on each loop
+ _soundFunc->init(this, _params);
+ }
+ }
+
+ return true;
+}
+
+void Player_AppleII::stopAllSounds() {
+ Common::StackLock lock(_mutex);
+ resetState();
+}
+
+void Player_AppleII::stopSound(int nr) {
+ Common::StackLock lock(_mutex);
+ if (_soundNr == nr) {
+ resetState();
+ }
+}
+
+int Player_AppleII::getSoundStatus(int nr) const {
+ Common::StackLock lock(_mutex);
+ return (_soundNr == nr);
+}
+
+int Player_AppleII::getMusicTimer() {
+ /* Apple-II sounds are synchronous -> no music timer */
+ return 0;
+}
+
+int Player_AppleII::readBuffer(int16 *buffer, const int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ if (!_soundNr)
+ return 0;
+
+ int samplesLeft = numSamples;
+ do {
+ int nSamplesRead = _sampleConverter.readSamples(buffer, samplesLeft);
+ samplesLeft -= nSamplesRead;
+ buffer += nSamplesRead;
+ } while ((samplesLeft > 0) && updateSound());
+
+ // reset state if sound is played completely
+ if (!_soundFunc && (_sampleConverter.availableSize() == 0))
+ resetState();
+
+ return numSamples - samplesLeft;
+}
+
+/************************************
+ * Apple-II sound-resource helpers
+ ************************************/
+
+// toggle speaker on/off
+void Player_AppleII::speakerToggle() {
+ _speakerState ^= 0x1;
+}
+
+void Player_AppleII::generateSamples(int cycles) {
+ _sampleConverter.addCycles(_speakerState, cycles);
+}
+
+void Player_AppleII::wait(int interval, int count /*y*/) {
+ assert(count > 0); // 0 == 256?
+ assert(interval > 0); // 0 == 256?
+ generateSamples(11 + count*(8 + 5 * interval));
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_apple2.h b/engines/scumm/players/player_apple2.h
new file mode 100644
index 0000000000..9930a4f95d
--- /dev/null
+++ b/engines/scumm/players/player_apple2.h
@@ -0,0 +1,297 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_APPLEII_H
+#define SCUMM_PLAYERS_PLAYER_APPLEII_H
+
+#include "common/mutex.h"
+#include "common/scummsys.h"
+#include "common/memstream.h"
+#include "scumm/music.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/softsynth/sid.h"
+
+namespace Scumm {
+
+class ScummEngine;
+
+/*
+ * Optimized for use with periodical read/write phases when the buffer
+ * is filled in a write phase and completely read in a read phase.
+ * The growing strategy is optimized for repeated small (e.g. 2 bytes)
+ * single writes resulting in large buffers
+ * (avg.: 4KB, max: 18KB @ 16bit/22.050kHz (MM sound21)).
+ */
+class SampleBuffer {
+public:
+ SampleBuffer() : _data(0) {
+ clear();
+ }
+
+ ~SampleBuffer() {
+ free(_data);
+ }
+
+ void clear() {
+ free(_data);
+ _data = 0;
+ _capacity = 0;
+ _writePos = 0;
+ _readPos = 0;
+ }
+
+ void ensureFree(uint32 needed) {
+ // if data was read completely, reset read/write pos to front
+ if ((_writePos != 0) && (_writePos == _readPos)) {
+ _writePos = 0;
+ _readPos = 0;
+ }
+
+ // check for enough space at end of buffer
+ uint32 freeEndCnt = _capacity - _writePos;
+ if (needed <= freeEndCnt)
+ return;
+
+ uint32 avail = availableSize();
+
+ // check for enough space at beginning and end of buffer
+ if (needed <= _readPos + freeEndCnt) {
+ // move unread data to front of buffer
+ memmove(_data, _data + _readPos, avail);
+ _writePos = avail;
+ _readPos = 0;
+ } else { // needs a grow
+ byte *old_data = _data;
+ uint32 new_len = avail + needed;
+
+ _capacity = new_len + 2048;
+ _data = (byte *)malloc(_capacity);
+
+ if (old_data) {
+ // copy old unread data to front of new buffer
+ memcpy(_data, old_data + _readPos, avail);
+ free(old_data);
+ _writePos = avail;
+ _readPos = 0;
+ }
+ }
+ }
+
+ uint32 availableSize() const {
+ if (_readPos >= _writePos)
+ return 0;
+ return _writePos - _readPos;
+ }
+
+ uint32 write(const void *dataPtr, uint32 dataSize) {
+ ensureFree(dataSize);
+ memcpy(_data + _writePos, dataPtr, dataSize);
+ _writePos += dataSize;
+ return dataSize;
+ }
+
+ uint32 read(byte *dataPtr, uint32 dataSize) {
+ uint32 avail = availableSize();
+ if (avail == 0)
+ return 0;
+ if (dataSize > avail)
+ dataSize = avail;
+ memcpy(dataPtr, _data + _readPos, dataSize);
+ _readPos += dataSize;
+ return dataSize;
+ }
+
+private:
+ uint32 _writePos;
+ uint32 _readPos;
+ uint32 _capacity;
+ byte *_data;
+};
+
+// CPU_CLOCK according to AppleWin
+static const double APPLEII_CPU_CLOCK = 1020484.5; // ~ 1.02 MHz
+
+/*
+ * Converts the 1-bit speaker state values into audio samples.
+ * This is done by aggregation of the speaker states at each
+ * CPU cycle in a sampling period into an audio sample.
+ */
+class SampleConverter {
+private:
+ void addSampleToBuffer(int sample) {
+ int16 value = sample * _volume / _maxVolume;
+ _buffer.write(&value, sizeof(value));
+ }
+
+public:
+ SampleConverter() :
+ _cyclesPerSampleFP(0),
+ _missingCyclesFP(0),
+ _sampleCyclesSumFP(0),
+ _volume(_maxVolume)
+ {}
+
+ ~SampleConverter() {}
+
+ void reset() {
+ _missingCyclesFP = 0;
+ _sampleCyclesSumFP = 0;
+ _buffer.clear();
+ }
+
+ uint32 availableSize() const {
+ return _buffer.availableSize();
+ }
+
+ void setMusicVolume(int vol) {
+ assert(vol >= 0 && vol <= _maxVolume);
+ _volume = vol;
+ }
+
+ void setSampleRate(int rate) {
+ /* ~46 CPU cycles per sample @ 22.05kHz */
+ _cyclesPerSampleFP = int(APPLEII_CPU_CLOCK * (1 << PREC_SHIFT) / rate);
+ reset();
+ }
+
+ void addCycles(byte level, const int cycles) {
+ /* convert to fixed precision floats */
+ int cyclesFP = cycles << PREC_SHIFT;
+
+ // step 1: if cycles are left from the last call, process them first
+ if (_missingCyclesFP > 0) {
+ int n = (_missingCyclesFP < cyclesFP) ? _missingCyclesFP : cyclesFP;
+ if (level)
+ _sampleCyclesSumFP += n;
+ cyclesFP -= n;
+ _missingCyclesFP -= n;
+ if (_missingCyclesFP == 0) {
+ addSampleToBuffer(2*32767 * _sampleCyclesSumFP / _cyclesPerSampleFP - 32767);
+ } else {
+ return;
+ }
+ }
+
+ _sampleCyclesSumFP = 0;
+
+ // step 2: process blocks of cycles fitting into a whole sample
+ while (cyclesFP >= _cyclesPerSampleFP) {
+ addSampleToBuffer(level ? 32767 : -32767);
+ cyclesFP -= _cyclesPerSampleFP;
+ }
+
+ // step 3: remember cycles left for next call
+ if (cyclesFP > 0) {
+ _missingCyclesFP = _cyclesPerSampleFP - cyclesFP;
+ if (level)
+ _sampleCyclesSumFP = cyclesFP;
+ }
+ }
+
+ uint32 readSamples(void *buffer, int numSamples) {
+ return _buffer.read((byte *)buffer, numSamples * 2) / 2;
+ }
+
+private:
+ static const int PREC_SHIFT = 7;
+
+private:
+ int _cyclesPerSampleFP; /* (fixed precision) */
+ int _missingCyclesFP; /* (fixed precision) */
+ int _sampleCyclesSumFP; /* (fixed precision) */
+ int _volume; /* 0 - 256 */
+ static const int _maxVolume = 256;
+ SampleBuffer _buffer;
+};
+
+class Player_AppleII;
+
+class AppleII_SoundFunction {
+public:
+ AppleII_SoundFunction() {}
+ virtual ~AppleII_SoundFunction() {}
+ virtual void init(Player_AppleII *player, const byte *params) = 0;
+ /* returns true if finished */
+ virtual bool update() = 0;
+protected:
+ Player_AppleII *_player;
+};
+
+class Player_AppleII : public Audio::AudioStream, public MusicEngine {
+public:
+ Player_AppleII(ScummEngine *scumm, Audio::Mixer *mixer);
+ virtual ~Player_AppleII();
+
+ virtual void setMusicVolume(int vol) { _sampleConverter.setMusicVolume(vol); }
+ void setSampleRate(int rate) {
+ _sampleRate = rate;
+ _sampleConverter.setSampleRate(rate);
+ }
+ virtual void startSound(int sound);
+ virtual void stopSound(int sound);
+ virtual void stopAllSounds();
+ virtual int getSoundStatus(int sound) const;
+ virtual int getMusicTimer();
+
+ // AudioStream API
+ int readBuffer(int16 *buffer, const int numSamples);
+ bool isStereo() const { return false; }
+ bool endOfData() const { return false; }
+ int getRate() const { return _sampleRate; }
+
+public:
+ void speakerToggle();
+ void generateSamples(int cycles);
+ void wait(int interval, int count);
+
+private:
+ // sound number
+ int _soundNr;
+ // type of sound
+ int _type;
+ // number of loops left
+ int _loop;
+ // global sound param list
+ const byte *_params;
+ // speaker toggle state (0 / 1)
+ byte _speakerState;
+ // sound function
+ AppleII_SoundFunction *_soundFunc;
+ // cycle to sample converter
+ SampleConverter _sampleConverter;
+
+private:
+ ScummEngine *_vm;
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _soundHandle;
+ int _sampleRate;
+ Common::Mutex _mutex;
+
+private:
+ void resetState();
+ bool updateSound();
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_mac.cpp b/engines/scumm/players/player_mac.cpp
new file mode 100644
index 0000000000..281eec5336
--- /dev/null
+++ b/engines/scumm/players/player_mac.cpp
@@ -0,0 +1,419 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/macresman.h"
+#include "common/translation.h"
+#include "engines/engine.h"
+#include "gui/message.h"
+#include "scumm/players/player_mac.h"
+#include "scumm/resource.h"
+#include "scumm/scumm.h"
+#include "scumm/imuse/imuse.h"
+
+namespace Scumm {
+
+Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds)
+ : _vm(scumm),
+ _mixer(mixer),
+ _sampleRate(_mixer->getOutputRate()),
+ _soundPlaying(-1),
+ _numberOfChannels(numberOfChannels),
+ _channelMask(channelMask),
+ _fadeNoteEnds(fadeNoteEnds) {
+ assert(scumm);
+ assert(mixer);
+}
+
+void Player_Mac::init() {
+ _channel = new Player_Mac::Channel[_numberOfChannels];
+
+ int i;
+
+ for (i = 0; i < _numberOfChannels; i++) {
+ _channel[i]._looped = false;
+ _channel[i]._length = 0;
+ _channel[i]._data = NULL;
+ _channel[i]._pos = 0;
+ _channel[i]._pitchModifier = 0;
+ _channel[i]._velocity = 0;
+ _channel[i]._remaining = 0;
+ _channel[i]._notesLeft = false;
+ _channel[i]._instrument._data = NULL;
+ _channel[i]._instrument._size = 0;
+ _channel[i]._instrument._rate = 0;
+ _channel[i]._instrument._loopStart = 0;
+ _channel[i]._instrument._loopEnd = 0;
+ _channel[i]._instrument._baseFreq = 0;
+ _channel[i]._instrument._pos = 0;
+ _channel[i]._instrument._subPos = 0;
+ }
+
+ _pitchTable[116] = 1664510;
+ _pitchTable[117] = 1763487;
+ _pitchTable[118] = 1868350;
+ _pitchTable[119] = 1979447;
+ _pitchTable[120] = 2097152;
+ _pitchTable[121] = 2221855;
+ _pitchTable[122] = 2353973;
+ _pitchTable[123] = 2493948;
+ _pitchTable[124] = 2642246;
+ _pitchTable[125] = 2799362;
+ _pitchTable[126] = 2965820;
+ _pitchTable[127] = 3142177;
+ for (i = 115; i >= 0; --i) {
+ _pitchTable[i] = _pitchTable[i + 12] / 2;
+ }
+
+ setMusicVolume(255);
+
+ if (!checkMusicAvailable()) {
+ return;
+ }
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_Mac::~Player_Mac() {
+ Common::StackLock lock(_mutex);
+ _mixer->stopHandle(_soundHandle);
+ stopAllSounds_Internal();
+ delete[] _channel;
+}
+
+void Player_Mac::saveLoadWithSerializer(Serializer *ser) {
+ Common::StackLock lock(_mutex);
+ if (ser->getVersion() < VER(94)) {
+ if (_vm->_game.id == GID_MONKEY && ser->isLoading()) {
+ IMuse *dummyImuse = IMuse::create(_vm->_system, NULL, NULL);
+ dummyImuse->save_or_load(ser, _vm, false);
+ delete dummyImuse;
+ }
+ } else {
+ static const SaveLoadEntry musicEntries[] = {
+ MKLINE(Player_Mac, _sampleRate, sleUint32, VER(94)),
+ MKLINE(Player_Mac, _soundPlaying, sleInt16, VER(94)),
+ MKEND()
+ };
+
+ static const SaveLoadEntry channelEntries[] = {
+ MKLINE(Channel, _pos, sleUint16, VER(94)),
+ MKLINE(Channel, _pitchModifier, sleInt32, VER(94)),
+ MKLINE(Channel, _velocity, sleUint8, VER(94)),
+ MKLINE(Channel, _remaining, sleUint32, VER(94)),
+ MKLINE(Channel, _notesLeft, sleUint8, VER(94)),
+ MKEND()
+ };
+
+ static const SaveLoadEntry instrumentEntries[] = {
+ MKLINE(Instrument, _pos, sleUint32, VER(94)),
+ MKLINE(Instrument, _subPos, sleUint32, VER(94)),
+ MKEND()
+ };
+
+ uint32 mixerSampleRate = _sampleRate;
+ int i;
+
+ ser->saveLoadEntries(this, musicEntries);
+
+ if (ser->isLoading() && _soundPlaying != -1) {
+ const byte *ptr = _vm->getResourceAddress(rtSound, _soundPlaying);
+ assert(ptr);
+ loadMusic(ptr);
+ }
+
+ ser->saveLoadArrayOf(_channel, _numberOfChannels, sizeof(Channel), channelEntries);
+ for (i = 0; i < _numberOfChannels; i++) {
+ ser->saveLoadEntries(&_channel[i], instrumentEntries);
+ }
+
+ if (ser->isLoading()) {
+ // If necessary, adjust the channel data to fit the
+ // current sample rate.
+ if (_soundPlaying != -1 && _sampleRate != mixerSampleRate) {
+ double mult = (double)_sampleRate / (double)mixerSampleRate;
+ for (i = 0; i < _numberOfChannels; i++) {
+ _channel[i]._pitchModifier = (int)((double)_channel[i]._pitchModifier * mult);
+ _channel[i]._remaining = (int)((double)_channel[i]._remaining / mult);
+ }
+ }
+ _sampleRate = mixerSampleRate;
+ }
+ }
+}
+
+void Player_Mac::setMusicVolume(int vol) {
+ debug(5, "Player_Mac::setMusicVolume(%d)", vol);
+}
+
+void Player_Mac::stopAllSounds_Internal() {
+ if (_soundPlaying != -1) {
+ _vm->_res->unlock(rtSound, _soundPlaying);
+ }
+ _soundPlaying = -1;
+ for (int i = 0; i < _numberOfChannels; i++) {
+ // The channel data is managed by the resource manager, so
+ // don't delete that.
+ delete[] _channel[i]._instrument._data;
+ _channel[i]._instrument._data = NULL;
+
+ _channel[i]._remaining = 0;
+ _channel[i]._notesLeft = false;
+ }
+}
+
+void Player_Mac::stopAllSounds() {
+ Common::StackLock lock(_mutex);
+ debug(5, "Player_Mac::stopAllSounds()");
+ stopAllSounds_Internal();
+}
+
+void Player_Mac::stopSound(int nr) {
+ Common::StackLock lock(_mutex);
+ debug(5, "Player_Mac::stopSound(%d)", nr);
+
+ if (nr == _soundPlaying) {
+ stopAllSounds();
+ }
+}
+
+void Player_Mac::startSound(int nr) {
+ Common::StackLock lock(_mutex);
+ debug(5, "Player_Mac::startSound(%d)", nr);
+
+ stopAllSounds_Internal();
+
+ const byte *ptr = _vm->getResourceAddress(rtSound, nr);
+ assert(ptr);
+
+ if (!loadMusic(ptr)) {
+ return;
+ }
+
+ _vm->_res->lock(rtSound, nr);
+ _soundPlaying = nr;
+}
+
+bool Player_Mac::Channel::loadInstrument(Common::SeekableReadStream *stream) {
+ uint16 soundType = stream->readUint16BE();
+ if (soundType != 1) {
+ warning("Player_Mac::loadInstrument: Unsupported sound type %d", soundType);
+ return false;
+ }
+ uint16 typeCount = stream->readUint16BE();
+ if (typeCount != 1) {
+ warning("Player_Mac::loadInstrument: Unsupported data type count %d", typeCount);
+ return false;
+ }
+ uint16 dataType = stream->readUint16BE();
+ if (dataType != 5) {
+ warning("Player_Mac::loadInstrument: Unsupported data type %d", dataType);
+ return false;
+ }
+
+ stream->readUint32BE(); // initialization option
+
+ uint16 cmdCount = stream->readUint16BE();
+ if (cmdCount != 1) {
+ warning("Player_Mac::loadInstrument: Unsupported command count %d", cmdCount);
+ return false;
+ }
+ uint16 command = stream->readUint16BE();
+ if (command != 0x8050 && command != 0x8051) {
+ warning("Player_Mac::loadInstrument: Unsupported command 0x%04X", command);
+ return false;
+ }
+
+ stream->readUint16BE(); // 0
+ uint32 soundHeaderOffset = stream->readUint32BE();
+
+ stream->seek(soundHeaderOffset);
+
+ uint32 soundDataOffset = stream->readUint32BE();
+ uint32 size = stream->readUint32BE();
+ uint32 rate = stream->readUint32BE() >> 16;
+ uint32 loopStart = stream->readUint32BE();
+ uint32 loopEnd = stream->readUint32BE();
+ byte encoding = stream->readByte();
+ byte baseFreq = stream->readByte();
+
+ if (encoding != 0) {
+ warning("Player_Mac::loadInstrument: Unsupported encoding %d", encoding);
+ return false;
+ }
+
+ stream->skip(soundDataOffset);
+
+ byte *data = new byte[size];
+ stream->read(data, size);
+
+ _instrument._data = data;
+ _instrument._size = size;
+ _instrument._rate = rate;
+ _instrument._loopStart = loopStart;
+ _instrument._loopEnd = loopEnd;
+ _instrument._baseFreq = baseFreq;
+
+ return true;
+}
+
+int Player_Mac::getMusicTimer() {
+ return 0;
+}
+
+int Player_Mac::getSoundStatus(int nr) const {
+ return _soundPlaying == nr;
+}
+
+uint32 Player_Mac::durationToSamples(uint16 duration) {
+ // The correct formula should be:
+ //
+ // (duration * 473 * _sampleRate) / (4 * 480 * 480)
+ //
+ // But that's likely to cause integer overflow, so we do it in two
+ // steps using bitwise operations to perform
+ // ((duration * 473 * _sampleRate) / 4096) without overflowing,
+ // then divide this by 225
+ // (note that 4 * 480 * 480 == 225 * 4096 == 225 << 12)
+ //
+ // The original code is a bit unclear on if it should be 473 or 437,
+ // but since the comments indicated 473 I'm assuming 437 was a typo.
+ uint32 samples = (duration * _sampleRate);
+ samples = (samples >> 12) * 473 + (((samples & 4095) * 473) >> 12);
+ samples = samples / 225;
+ return samples;
+}
+
+int Player_Mac::noteToPitchModifier(byte note, Instrument *instrument) {
+ if (note > 0) {
+ const int pitchIdx = note + 60 - instrument->_baseFreq;
+ // I don't want to use floating-point arithmetics here, but I
+ // ran into overflow problems with the church music in Monkey
+ // Island. It's only once per note, so it should be ok.
+ double mult = (double)instrument->_rate / (double)_sampleRate;
+ return (int)(mult * _pitchTable[pitchIdx]);
+ } else {
+ return 0;
+ }
+}
+
+int Player_Mac::readBuffer(int16 *data, const int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ memset(data, 0, numSamples * 2);
+ if (_soundPlaying == -1) {
+ return numSamples;
+ }
+
+ bool notesLeft = false;
+
+ for (int i = 0; i < _numberOfChannels; i++) {
+ if (!(_channelMask & (1 << i))) {
+ continue;
+ }
+
+ uint samplesLeft = numSamples;
+ int16 *ptr = data;
+
+ while (samplesLeft > 0) {
+ int generated;
+ if (_channel[i]._remaining == 0) {
+ uint32 samples;
+ int pitchModifier;
+ byte velocity;
+ if (getNextNote(i, samples, pitchModifier, velocity)) {
+ _channel[i]._remaining = samples;
+ _channel[i]._pitchModifier = pitchModifier;
+ _channel[i]._velocity = velocity;
+
+ } else {
+ _channel[i]._pitchModifier = 0;
+ _channel[i]._velocity = 0;
+ _channel[i]._remaining = samplesLeft;
+ }
+ }
+ generated = MIN<uint32>(_channel[i]._remaining, samplesLeft);
+ if (_channel[i]._velocity != 0) {
+ _channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated, _channel[i]._remaining, _fadeNoteEnds);
+ }
+ ptr += generated;
+ samplesLeft -= generated;
+ _channel[i]._remaining -= generated;
+ }
+
+ if (_channel[i]._notesLeft) {
+ notesLeft = true;
+ }
+ }
+
+ if (!notesLeft) {
+ stopAllSounds_Internal();
+ }
+
+ return numSamples;
+}
+
+void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds) {
+ int samplesLeft = numSamples;
+ while (samplesLeft) {
+ _subPos += pitchModifier;
+ while (_subPos >= 0x10000) {
+ _subPos -= 0x10000;
+ _pos++;
+ if (_pos >= _loopEnd) {
+ _pos = _loopStart;
+ }
+ }
+
+ int newSample = (((int16)((_data[_pos] << 8) ^ 0x8000)) * volume) / 255;
+
+ if (fadeNoteEnds) {
+ // Fade out the last 100 samples on each note. Even at
+ // low output sample rates this is just a fraction of a
+ // second, but it gets rid of distracting "pops" at the
+ // end when the sample would otherwise go abruptly from
+ // something to nothing. This was particularly
+ // noticeable on the distaff notes in Loom.
+ //
+ // The reason it's conditional is that Monkey Island
+ // appears to have a "hold current note" command, and
+ // if we fade out the current note in that case we
+ // will actually introduce new "pops".
+
+ remainingSamplesOnNote--;
+ if (remainingSamplesOnNote < 100) {
+ newSample = (newSample * remainingSamplesOnNote) / 100;
+ }
+ }
+
+ int sample = *data + newSample;
+ if (sample > 32767) {
+ sample = 32767;
+ } else if (sample < -32768) {
+ sample = -32768;
+ }
+
+ *data++ = sample;
+ samplesLeft--;
+ }
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_mac.h b/engines/scumm/players/player_mac.h
new file mode 100644
index 0000000000..7f9f42c34e
--- /dev/null
+++ b/engines/scumm/players/player_mac.h
@@ -0,0 +1,133 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_MAC_H
+#define SCUMM_PLAYERS_PLAYER_MAC_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+#include "common/mutex.h"
+#include "scumm/music.h"
+#include "scumm/saveload.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+#define RES_SND MKTAG('s', 'n', 'd', ' ')
+
+class Mixer;
+
+namespace Scumm {
+
+class ScummEngine;
+
+/**
+ * Scumm Macintosh music driver, base class.
+ */
+class Player_Mac : public Audio::AudioStream, public MusicEngine {
+public:
+ Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds);
+ virtual ~Player_Mac();
+
+ void init();
+
+ // MusicEngine API
+ virtual void setMusicVolume(int vol);
+ virtual void startSound(int sound);
+ virtual void stopSound(int sound);
+ virtual void stopAllSounds();
+ virtual int getMusicTimer();
+ virtual int getSoundStatus(int sound) const;
+
+ // AudioStream API
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+ virtual bool isStereo() const { return false; }
+ virtual bool endOfData() const { return false; }
+ virtual int getRate() const { return _sampleRate; }
+
+ virtual void saveLoadWithSerializer(Serializer *ser);
+
+private:
+ Common::Mutex _mutex;
+ Audio::Mixer *const _mixer;
+ Audio::SoundHandle _soundHandle;
+ uint32 _sampleRate;
+ int _soundPlaying;
+
+ void stopAllSounds_Internal();
+
+ struct Instrument {
+ byte *_data;
+ uint32 _size;
+ uint32 _rate;
+ uint32 _loopStart;
+ uint32 _loopEnd;
+ byte _baseFreq;
+
+ uint _pos;
+ uint _subPos;
+
+ void newNote() {
+ _pos = 0;
+ _subPos = 0;
+ }
+
+ void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds);
+ };
+
+ int _pitchTable[128];
+ int _numberOfChannels;
+ int _channelMask;
+ bool _fadeNoteEnds;
+
+ virtual bool checkMusicAvailable() { return false; }
+ virtual bool loadMusic(const byte *ptr) { return false; }
+ virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { return false; }
+
+protected:
+ struct Channel {
+ virtual ~Channel() {}
+
+ Instrument _instrument;
+ bool _looped;
+ uint32 _length;
+ const byte *_data;
+
+ uint _pos;
+ int _pitchModifier;
+ byte _velocity;
+ uint32 _remaining;
+
+ bool _notesLeft;
+
+ bool loadInstrument(Common::SeekableReadStream *stream);
+ };
+
+ ScummEngine *const _vm;
+ Channel *_channel;
+
+ uint32 durationToSamples(uint16 duration);
+ int noteToPitchModifier(byte note, Instrument *instrument);
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_mod.cpp b/engines/scumm/players/player_mod.cpp
new file mode 100644
index 0000000000..abaa8c1fc5
--- /dev/null
+++ b/engines/scumm/players/player_mod.cpp
@@ -0,0 +1,223 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+#include "scumm/players/player_mod.h"
+#include "audio/mixer.h"
+#include "audio/rate.h"
+#include "audio/decoders/raw.h"
+
+namespace Scumm {
+
+Player_MOD::Player_MOD(Audio::Mixer *mixer)
+ : _mixer(mixer), _sampleRate(mixer->getOutputRate()) {
+ int i;
+ _mixamt = 0;
+ _mixpos = 0;
+
+ for (i = 0; i < MOD_MAXCHANS; i++) {
+ _channels[i].id = 0;
+ _channels[i].vol = 0;
+ _channels[i].freq = 0;
+ _channels[i].input = NULL;
+ _channels[i].ctr = 0;
+ _channels[i].pos = 0;
+ }
+
+ _playproc = NULL;
+ _playparam = NULL;
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_MOD::~Player_MOD() {
+ _mixer->stopHandle(_soundHandle);
+ for (int i = 0; i < MOD_MAXCHANS; i++) {
+ if (!_channels[i].id)
+ continue;
+ delete _channels[i].input;
+ }
+}
+
+void Player_MOD::setMusicVolume(int vol) {
+ _maxvol = vol;
+}
+
+void Player_MOD::setUpdateProc(ModUpdateProc *proc, void *param, int freq) {
+ _playproc = proc;
+ _playparam = param;
+ _mixamt = _sampleRate / freq;
+}
+void Player_MOD::clearUpdateProc() {
+ _playproc = NULL;
+ _playparam = NULL;
+ _mixamt = 0;
+}
+
+void Player_MOD::startChannel(int id, void *data, int size, int rate, uint8 vol, int loopStart, int loopEnd, int8 pan) {
+ int i;
+ if (id == 0)
+ error("player_mod - attempted to start channel id 0");
+
+ for (i = 0; i < MOD_MAXCHANS; i++) {
+ if (!_channels[i].id)
+ break;
+ }
+ if (i == MOD_MAXCHANS) {
+ warning("player_mod - too many music channels playing (%i max)",MOD_MAXCHANS);
+ return;
+ }
+ _channels[i].id = id;
+ _channels[i].vol = vol;
+ _channels[i].pan = pan;
+ _channels[i].freq = rate;
+ _channels[i].ctr = 0;
+
+ Audio::SeekableAudioStream *stream = Audio::makeRawStream((const byte *)data, size, rate, 0);
+ if (loopStart != loopEnd) {
+ _channels[i].input = new Audio::SubLoopingAudioStream(stream, 0, Audio::Timestamp(0, loopStart, rate), Audio::Timestamp(0, loopEnd, rate));
+ } else {
+ _channels[i].input = stream;
+ }
+
+ // read the first sample
+ _channels[i].input->readBuffer(&_channels[i].pos, 1);
+}
+
+void Player_MOD::stopChannel(int id) {
+ if (id == 0)
+ error("player_mod - attempted to stop channel id 0");
+ for (int i = 0; i < MOD_MAXCHANS; i++) {
+ if (_channels[i].id == id) {
+ delete _channels[i].input;
+ _channels[i].input = NULL;
+ _channels[i].id = 0;
+ _channels[i].vol = 0;
+ _channels[i].freq = 0;
+ _channels[i].ctr = 0;
+ _channels[i].pos = 0;
+ }
+ }
+}
+void Player_MOD::setChannelVol(int id, uint8 vol) {
+ if (id == 0)
+ error("player_mod - attempted to set volume for channel id 0");
+ for (int i = 0; i < MOD_MAXCHANS; i++) {
+ if (_channels[i].id == id) {
+ _channels[i].vol = vol;
+ break;
+ }
+ }
+}
+
+void Player_MOD::setChannelPan(int id, int8 pan) {
+ if (id == 0)
+ error("player_mod - attempted to set pan for channel id 0");
+ for (int i = 0; i < MOD_MAXCHANS; i++) {
+ if (_channels[i].id == id) {
+ _channels[i].pan = pan;
+ break;
+ }
+ }
+}
+
+void Player_MOD::setChannelFreq(int id, int freq) {
+ if (id == 0)
+ error("player_mod - attempted to set frequency for channel id 0");
+ for (int i = 0; i < MOD_MAXCHANS; i++) {
+ if (_channels[i].id == id) {
+ if (freq > 31400) // this is about as high as WinUAE goes
+ freq = 31400; // can't easily verify on my own Amiga
+ _channels[i].freq = freq;
+ break;
+ }
+ }
+}
+
+void Player_MOD::do_mix(int16 *data, uint len) {
+ int i;
+ int dpos = 0;
+ uint dlen = 0;
+ memset(data, 0, 2 * len * sizeof(int16));
+ while (len) {
+ if (_playproc) {
+ dlen = _mixamt - _mixpos;
+ if (!_mixpos)
+ _playproc(_playparam);
+ if (dlen <= len) {
+ _mixpos = 0;
+ len -= dlen;
+ } else {
+ _mixpos = len;
+ dlen = len;
+ len = 0;
+ }
+ } else {
+ dlen = len;
+ len = 0;
+ }
+ for (i = 0; i < MOD_MAXCHANS; i++) {
+ if (_channels[i].id) {
+ Audio::st_volume_t vol_l = (127 - _channels[i].pan) * _channels[i].vol / 127;
+ Audio::st_volume_t vol_r = (127 + _channels[i].pan) * _channels[i].vol / 127;
+ for (uint j = 0; j < dlen; j++) {
+ // simple linear resample, unbuffered
+ int delta = (uint32)(_channels[i].freq * 0x10000) / _sampleRate;
+ uint16 cfrac = ~_channels[i].ctr & 0xFFFF;
+ if (_channels[i].ctr + delta < 0x10000)
+ cfrac = delta;
+ _channels[i].ctr += delta;
+ int32 cpos = _channels[i].pos * cfrac / 0x10000;
+ while (_channels[i].ctr >= 0x10000) {
+ if (_channels[i].input->readBuffer(&_channels[i].pos, 1) != 1) { // out of data
+ stopChannel(_channels[i].id);
+ goto skipchan; // exit 2 loops at once
+ }
+ _channels[i].ctr -= 0x10000;
+ if (_channels[i].ctr > 0x10000)
+ cpos += _channels[i].pos;
+ else
+ cpos += (int32)(_channels[i].pos * (_channels[i].ctr & 0xFFFF)) / 0x10000;
+ }
+ int16 pos = 0;
+ // if too many samples play in a row, the calculation below will overflow and clip
+ // so try and split it up into pieces it can manage comfortably
+ while (cpos < -0x8000) {
+ pos -= 0x80000000 / delta;
+ cpos += 0x8000;
+ }
+ while (cpos > 0x7FFF) {
+ pos += 0x7FFF0000 / delta;
+ cpos -= 0x7FFF;
+ }
+ pos += cpos * 0x10000 / delta;
+ Audio::clampedAdd(data[(dpos + j) * 2 + 0], pos * vol_l / Audio::Mixer::kMaxMixerVolume);
+ Audio::clampedAdd(data[(dpos + j) * 2 + 1], pos * vol_r / Audio::Mixer::kMaxMixerVolume);
+ }
+ }
+skipchan: ; // channel ran out of data
+ }
+ dpos += dlen;
+ }
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_mod.h b/engines/scumm/players/player_mod.h
new file mode 100644
index 0000000000..d4a5b16fca
--- /dev/null
+++ b/engines/scumm/players/player_mod.h
@@ -0,0 +1,100 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_MOD_H
+#define SCUMM_PLAYERS_PLAYER_MOD_H
+
+#include "scumm/scumm.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+namespace Audio {
+class RateConverter;
+}
+
+namespace Scumm {
+
+/**
+ * Generic Amiga MOD mixer - provides a 60Hz 'update' routine.
+ */
+class Player_MOD : public Audio::AudioStream {
+public:
+ Player_MOD(Audio::Mixer *mixer);
+ virtual ~Player_MOD();
+ virtual void setMusicVolume(int vol);
+
+ virtual void startChannel(int id, void *data, int size, int rate, uint8 vol, int loopStart = 0, int loopEnd = 0, int8 pan = 0);
+ virtual void stopChannel(int id);
+ virtual void setChannelVol(int id, uint8 vol);
+ virtual void setChannelPan(int id, int8 pan);
+ virtual void setChannelFreq(int id, int freq);
+
+ typedef void ModUpdateProc(void *param);
+
+ virtual void setUpdateProc(ModUpdateProc *proc, void *param, int freq);
+ virtual void clearUpdateProc();
+
+ // AudioStream API
+ int readBuffer(int16 *buffer, const int numSamples) {
+ do_mix(buffer, numSamples / 2);
+ return numSamples;
+ }
+ bool isStereo() const { return true; }
+ bool endOfData() const { return false; }
+ int getRate() const { return _sampleRate; }
+
+private:
+ enum {
+ MOD_MAXCHANS = 24
+ };
+
+ struct soundChan {
+ int id;
+ uint8 vol;
+ int8 pan;
+ uint16 freq;
+
+ uint32 ctr;
+ int16 pos;
+ Audio::AudioStream *input;
+ };
+
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _soundHandle;
+
+ uint32 _mixamt;
+ uint32 _mixpos;
+ const int _sampleRate;
+
+ soundChan _channels[MOD_MAXCHANS];
+
+ uint8 _maxvol;
+
+ virtual void do_mix(int16 *buf, uint len);
+
+ ModUpdateProc *_playproc;
+ void *_playparam;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_nes.cpp b/engines/scumm/players/player_nes.cpp
new file mode 100644
index 0000000000..f55f1f9edd
--- /dev/null
+++ b/engines/scumm/players/player_nes.cpp
@@ -0,0 +1,1067 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * aint32 with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef DISABLE_NES_APU
+
+#include "engines/engine.h"
+#include "scumm/players/player_nes.h"
+#include "scumm/scumm.h"
+#include "audio/mixer.h"
+
+namespace Scumm {
+
+static const byte channelMask[4] = {1, 2, 4, 8};
+
+static const uint16 freqTable[64] = {
+ 0x07F0, 0x077E, 0x0712, 0x06AE, 0x064E, 0x05F3, 0x059E, 0x054D,
+ 0x0501, 0x04B9, 0x0475, 0x0435, 0x03F8, 0x03BF, 0x0389, 0x0357,
+ 0x0327, 0x02F9, 0x02CF, 0x02A6, 0x0280, 0x025C, 0x023A, 0x021A,
+ 0x01FC, 0x01DF, 0x01C4, 0x01AB, 0x0193, 0x017C, 0x0167, 0x0152,
+ 0x013F, 0x012D, 0x011C, 0x010C, 0x00FD, 0x00EE, 0x00E1, 0x00D4,
+ 0x00C8, 0x00BD, 0x00B2, 0x00A8, 0x009F, 0x0096, 0x008D, 0x0085,
+ 0x007E, 0x0076, 0x0070, 0x0069, 0x0063, 0x005E, 0x0058, 0x0053,
+ 0x004F, 0x004A, 0x0046, 0x0042, 0x003E, 0x003A, 0x0037, 0x0034
+};
+
+static const byte instChannel[16] = {
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 1, 3, 3, 3
+};
+static const byte startCmd[16] = {
+ 0x05, 0x03, 0x06, 0x08, 0x0B, 0x01, 0x01, 0x1A,
+ 0x16, 0x06, 0x04, 0x17, 0x02, 0x10, 0x0E, 0x0D
+};
+static const byte releaseCmd[16] = {
+ 0x0F, 0x00, 0x00, 0x09, 0x00, 0x14, 0x15, 0x00,
+ 0x00, 0x00, 0x1B, 0x1B, 0x0F, 0x0F, 0x0F, 0x0F
+};
+static const byte nextCmd[28] = {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x17, 0xFF, 0x07, 0xFF,
+ 0xFF, 0x0A, 0x09, 0x0C, 0x00, 0x00, 0x00, 0x00,
+ 0x11, 0x12, 0x11, 0x03, 0xFF, 0xFF, 0x18, 0x00,
+ 0x19, 0x00, 0x00, 0x00
+};
+static const byte nextDelay[28] = {
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00,
+ 0x00, 0x05, 0x08, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00,
+ 0x03, 0x00, 0x00, 0x00
+};
+
+namespace APUe {
+
+static const byte LengthCounts[32] = {
+ 0x0A,0xFE,
+ 0x14,0x02,
+ 0x28,0x04,
+ 0x50,0x06,
+ 0xA0,0x08,
+ 0x3C,0x0A,
+ 0x0E,0x0C,
+ 0x1A,0x0E,
+
+ 0x0C,0x10,
+ 0x18,0x12,
+ 0x30,0x14,
+ 0x60,0x16,
+ 0xC0,0x18,
+ 0x48,0x1A,
+ 0x10,0x1C,
+ 0x20,0x1E
+};
+
+class SoundGen {
+protected:
+ byte wavehold;
+ uint32 freq; // short
+ uint32 CurD;
+
+public:
+ byte Timer;
+ int32 Pos;
+ uint32 Cycles; // short
+
+ inline byte GetTimer() const { return Timer; }
+};
+
+class Square : public SoundGen {
+protected:
+ byte volume, envelope, duty, swpspeed, swpdir, swpstep, swpenab;
+ byte Vol;
+ byte EnvCtr, Envelope, BendCtr;
+ bool Enabled, ValidFreq, Active;
+ bool EnvClk, SwpClk;
+
+ void CheckActive();
+
+public:
+ void Reset();
+ void Write(int Reg, byte Val);
+ void Run();
+ void QuarterFrame();
+ void HalfFrame();
+};
+
+static const int8 Duties[4][8] = {
+ {-4,+4,-4,-4,-4,-4,-4,-4},
+ {-4,+4,+4,-4,-4,-4,-4,-4},
+ {-4,+4,+4,+4,+4,-4,-4,-4},
+ {+4,-4,-4,+4,+4,+4,+4,+4}
+};
+
+void Square::Reset() {
+ memset(this, 0, sizeof(*this));
+ Cycles = 1;
+ EnvCtr = 1;
+ BendCtr = 1;
+}
+
+void Square::CheckActive() {
+ ValidFreq = (freq >= 0x8) && ((swpdir) || !((freq + (freq >> swpstep)) & 0x800));
+ Active = Timer && ValidFreq;
+ Pos = Active ? (Duties[duty][CurD] * Vol) : 0;
+}
+
+void Square::Write(int Reg, byte Val) {
+ switch (Reg) {
+ case 0:
+ volume = Val & 0xF;
+ envelope = Val & 0x10;
+ wavehold = Val & 0x20;
+ duty = (Val >> 6) & 0x3;
+ Vol = envelope ? volume : Envelope;
+ break;
+
+ case 1:
+ swpstep = Val & 0x07;
+ swpdir = Val & 0x08;
+ swpspeed = (Val >> 4) & 0x7;
+ swpenab = Val & 0x80;
+ SwpClk = true;
+ break;
+
+ case 2:
+ freq &= 0x700;
+ freq |= Val;
+ break;
+
+ case 3:
+ freq &= 0xFF;
+ freq |= (Val & 0x7) << 8;
+
+ if (Enabled)
+ Timer = LengthCounts[(Val >> 3) & 0x1F];
+
+ CurD = 0;
+ EnvClk = true;
+ break;
+
+ case 4:
+ Enabled = (Val != 0);
+ if (!Enabled)
+ Timer = 0;
+ break;
+ }
+ CheckActive();
+}
+
+void Square::Run() {
+ Cycles = (freq + 1) << 1;
+ CurD = (CurD + 1) & 0x7;
+
+ if (Active)
+ Pos = Duties[duty][CurD] * Vol;
+}
+
+void Square::QuarterFrame() {
+ if (EnvClk) {
+ EnvClk = false;
+ Envelope = 0xF;
+ EnvCtr = volume + 1;
+ } else if (!--EnvCtr) {
+ EnvCtr = volume + 1;
+
+ if (Envelope)
+ Envelope--;
+ else
+ Envelope = wavehold ? 0xF : 0x0;
+ }
+
+ Vol = envelope ? volume : Envelope;
+ CheckActive();
+}
+
+void Square::HalfFrame() {
+ if (!--BendCtr) {
+ BendCtr = swpspeed + 1;
+
+ if (swpenab && swpstep && ValidFreq) {
+ int sweep = freq >> swpstep;
+ // FIXME: Is -sweep or ~sweep correct???
+ freq += swpdir ? -sweep : sweep;
+ }
+ }
+
+ if (SwpClk) {
+ SwpClk = false;
+ BendCtr = swpspeed + 1;
+ }
+
+ if (Timer && !wavehold)
+ Timer--;
+
+ CheckActive();
+}
+
+
+class Triangle : public SoundGen {
+protected:
+ byte linear;
+ byte LinCtr;
+ bool Enabled, Active;
+ bool LinClk;
+
+ void CheckActive();
+
+public:
+ void Reset();
+ void Write(int Reg, byte Val);
+ void Run();
+ void QuarterFrame();
+ void HalfFrame();
+};
+
+static const int8 TriDuty[32] = {
+ -8,-7,-6,-5,-4,-3,-2,-1,
+ +0,+1,+2,+3,+4,+5,+6,+7,
+ +7,+6,+5,+4,+3,+2,+1,+0,
+ -1,-2,-3,-4,-5,-6,-7,-8
+};
+
+void Triangle::Reset() {
+ memset(this, 0, sizeof(*this));
+ Cycles = 1;
+}
+
+void Triangle::CheckActive() {
+ Active = Timer && LinCtr;
+
+ if (freq < 4)
+ Pos = 0; // beyond hearing range
+ else
+ Pos = TriDuty[CurD] * 8;
+}
+
+void Triangle::Write(int Reg, byte Val) {
+ switch (Reg) {
+ case 0:
+ linear = Val & 0x7F;
+ wavehold = (Val >> 7) & 0x1;
+ break;
+
+ case 2:
+ freq &= 0x700;
+ freq |= Val;
+ break;
+
+ case 3:
+ freq &= 0xFF;
+ freq |= (Val & 0x7) << 8;
+
+ if (Enabled)
+ Timer = LengthCounts[(Val >> 3) & 0x1F];
+
+ LinClk = true;
+ break;
+
+ case 4:
+ Enabled = (Val != 0);
+ if (!Enabled)
+ Timer = 0;
+ break;
+ }
+ CheckActive();
+}
+
+void Triangle::Run() {
+ Cycles = freq + 1;
+
+ if (Active) {
+ CurD++;
+ CurD &= 0x1F;
+
+ if (freq < 4)
+ Pos = 0; // beyond hearing range
+ else
+ Pos = TriDuty[CurD] * 8;
+ }
+}
+
+void Triangle::QuarterFrame() {
+ if (LinClk)
+ LinCtr = linear;
+ else if (LinCtr)
+ LinCtr--;
+
+ if (!wavehold)
+ LinClk = false;
+
+ CheckActive();
+}
+
+void Triangle::HalfFrame() {
+ if (Timer && !wavehold)
+ Timer--;
+
+ CheckActive();
+}
+
+class Noise : public SoundGen {
+protected:
+ byte volume, envelope, datatype;
+ byte Vol;
+ byte EnvCtr, Envelope;
+ bool Enabled;
+ bool EnvClk;
+
+ void CheckActive();
+
+public:
+ void Reset();
+ void Write(int Reg, byte Val);
+ void Run();
+ void QuarterFrame();
+ void HalfFrame();
+};
+
+static const uint32 NoiseFreq[16] = {
+ 0x004,0x008,0x010,0x020,0x040,0x060,0x080,0x0A0,
+ 0x0CA,0x0FE,0x17C,0x1FC,0x2FA,0x3F8,0x7F2,0xFE4
+};
+
+void Noise::Reset() {
+ memset(this, 0, sizeof(*this));
+ CurD = 1;
+ Cycles = 1;
+ EnvCtr = 1;
+
+}
+
+void Noise::Write(int Reg, byte Val) {
+ switch (Reg) {
+ case 0:
+ volume = Val & 0x0F;
+ envelope = Val & 0x10;
+ wavehold = Val & 0x20;
+ Vol = envelope ? volume : Envelope;
+
+ if (Timer)
+ Pos = ((CurD & 0x4000) ? -2 : 2) * Vol;
+ break;
+
+ case 2:
+ freq = Val & 0xF;
+ datatype = Val & 0x80;
+ break;
+
+ case 3:
+ if (Enabled)
+ Timer = LengthCounts[(Val >> 3) & 0x1F];
+
+ EnvClk = true;
+ break;
+
+ case 4:
+ Enabled = (Val != 0);
+ if (!Enabled)
+ Timer = 0;
+ break;
+ }
+}
+
+void Noise::Run() {
+ Cycles = NoiseFreq[freq]; /* no + 1 here */
+
+ if (datatype)
+ CurD = (CurD << 1) | (((CurD >> 14) ^ (CurD >> 8)) & 0x1);
+ else
+ CurD = (CurD << 1) | (((CurD >> 14) ^ (CurD >> 13)) & 0x1);
+
+ if (Timer)
+ Pos = ((CurD & 0x4000) ? -2 : 2) * Vol;
+}
+
+void Noise::QuarterFrame() {
+ if (EnvClk) {
+ EnvClk = false;
+ Envelope = 0xF;
+ EnvCtr = volume + 1;
+ } else if (!--EnvCtr) {
+ EnvCtr = volume + 1;
+
+ if (Envelope)
+ Envelope--;
+ else
+ Envelope = wavehold ? 0xF : 0x0;
+ }
+
+ Vol = envelope ? volume : Envelope;
+
+ if (Timer)
+ Pos = ((CurD & 0x4000) ? -2 : 2) * Vol;
+}
+
+void Noise::HalfFrame() {
+ if (Timer && !wavehold)
+ Timer--;
+}
+
+class APU {
+protected:
+ int BufPos;
+ int SampleRate;
+
+ Square _square0;
+ Square _square1;
+ Triangle _triangle;
+ Noise _noise;
+
+ struct {
+ uint32 Cycles;
+ int Num;
+ } Frame;
+
+public:
+ APU(int rate) : SampleRate(rate) {
+ Reset();
+ }
+
+ void WriteReg(int Addr, byte Val);
+ byte Read4015();
+ void Reset ();
+ int16 GetSample();
+};
+
+void APU::WriteReg(int Addr, byte Val) {
+ switch (Addr) {
+ case 0x000: _square0.Write(0,Val); break;
+ case 0x001: _square0.Write(1,Val); break;
+ case 0x002: _square0.Write(2,Val); break;
+ case 0x003: _square0.Write(3,Val); break;
+ case 0x004: _square1.Write(0,Val); break;
+ case 0x005: _square1.Write(1,Val); break;
+ case 0x006: _square1.Write(2,Val); break;
+ case 0x007: _square1.Write(3,Val); break;
+ case 0x008: _triangle.Write(0,Val); break;
+ case 0x009: _triangle.Write(1,Val); break;
+ case 0x00A: _triangle.Write(2,Val); break;
+ case 0x00B: _triangle.Write(3,Val); break;
+ case 0x00C: _noise.Write(0,Val); break;
+ case 0x00D: _noise.Write(1,Val); break;
+ case 0x00E: _noise.Write(2,Val); break;
+ case 0x00F: _noise.Write(3,Val); break;
+ case 0x015: _square0.Write(4,Val & 0x1);
+ _square1.Write(4,Val & 0x2);
+ _triangle.Write(4,Val & 0x4);
+ _noise.Write(4,Val & 0x8);
+ break;
+ }
+}
+
+byte APU::Read4015() {
+ byte result =
+ (( _square0.GetTimer()) ? 0x01 : 0) |
+ (( _square1.GetTimer()) ? 0x02 : 0) |
+ ((_triangle.GetTimer()) ? 0x04 : 0) |
+ (( _noise.GetTimer()) ? 0x08 : 0);
+ return result;
+}
+
+void APU::Reset () {
+ BufPos = 0;
+
+ _square0.Reset();
+ _square1.Reset();
+ _triangle.Reset();
+ _noise.Reset();
+
+ Frame.Num = 0;
+ Frame.Cycles = 1;
+}
+
+template<class T>
+int step(T &obj, int sampcycles, uint frame_Cycles, int frame_Num) {
+ int samppos = 0;
+ while (sampcycles) {
+ // Compute the maximal amount we can step ahead before triggering
+ // an action (i.e. compute the minimum of sampcycles, frame_Cycles
+ // and obj.Cycles).
+ uint max_step = sampcycles;
+ if (max_step > frame_Cycles)
+ max_step = frame_Cycles;
+ if (max_step > obj.Cycles)
+ max_step = obj.Cycles;
+
+ // During all but the last of these steps, we just add the value of obj.Pos
+ // to samppos -- so we can to that all at once with a simple multiplication:
+ samppos += obj.Pos * (max_step - 1);
+
+ // Now step ahead...
+ sampcycles -= max_step;
+ frame_Cycles -= max_step;
+ obj.Cycles -= max_step;
+
+ if (!frame_Cycles) {
+ frame_Cycles = 7457;
+
+ if (frame_Num < 4) {
+ obj.QuarterFrame();
+
+ if (frame_Num & 1)
+ frame_Cycles++;
+ else
+ obj.HalfFrame();
+
+ frame_Num++;
+ } else
+ frame_Num = 0;
+ }
+
+ if (!obj.Cycles)
+ obj.Run();
+
+ samppos += obj.Pos;
+ }
+
+ return samppos;
+}
+
+int16 APU::GetSample() {
+ int samppos = 0;
+
+ const int sampcycles = 1+(1789773-BufPos-1)/SampleRate;
+ BufPos = BufPos + sampcycles * SampleRate - 1789773;
+
+ samppos += step( _square0, sampcycles, Frame.Cycles, Frame.Num);
+ samppos += step( _square1, sampcycles, Frame.Cycles, Frame.Num);
+ samppos += step(_triangle, sampcycles, Frame.Cycles, Frame.Num);
+ samppos += step( _noise, sampcycles, Frame.Cycles, Frame.Num);
+
+ uint tmp = sampcycles;
+ while (tmp >= Frame.Cycles) {
+ tmp -= Frame.Cycles;
+ Frame.Cycles = 7457;
+
+ if (Frame.Num < 4) {
+ if (Frame.Num & 1)
+ Frame.Cycles++;
+ Frame.Num++;
+ } else
+ Frame.Num = 0;
+ }
+
+ Frame.Cycles -= tmp;
+
+ return (samppos << 6) / sampcycles;
+}
+
+} // End of namespace APUe
+
+Player_NES::Player_NES(ScummEngine *scumm, Audio::Mixer *mixer) {
+ int i;
+ _vm = scumm;
+ _mixer = mixer;
+ _sampleRate = _mixer->getOutputRate();
+ _apu = new APUe::APU(_sampleRate);
+
+ _samples_per_frame = _sampleRate / 60;
+ _current_sample = 0;
+
+ for (i = 0; i < NUMSLOTS; i++) {
+ _slot[i].id = -1;
+ _slot[i].framesleft = 0;
+ _slot[i].type = 0;
+ _slot[i].offset = 0;
+ _slot[i].data = NULL;
+ }
+
+ for (i = 0; i < NUMCHANS; i++) {
+ _mchan[i].command = 0;
+ _mchan[i].framedelay = 0;
+ _mchan[i].pitch = 0;
+ _mchan[i].volume = 0;
+ _mchan[i].voldelta = 0;
+ _mchan[i].envflags = 0;
+ _mchan[i].cmdlock = 0;
+ }
+ isSFXplaying = wasSFXplaying = false;
+
+ auxData1 = auxData2 = NULL;
+ numNotes = 0;
+
+ APU_writeControl(0);
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_NES::~Player_NES() {
+ _mixer->stopHandle(_soundHandle);
+ delete _apu;
+}
+
+void Player_NES::setMusicVolume (int vol) {
+ _maxvol = vol;
+}
+
+int Player_NES::readBuffer(int16 *buffer, const int numSamples) {
+ for (int n = 0; n < numSamples; n++) {
+ buffer[n] = _apu->GetSample() * _maxvol / 255;
+ _current_sample++;
+
+ if (_current_sample == _samples_per_frame) {
+ _current_sample = 0;
+ sound_play();
+ }
+ }
+ return numSamples;
+}
+void Player_NES::stopAllSounds() {
+ for (int i = 0; i < NUMSLOTS; i++) {
+ _slot[i].framesleft = 0;
+ _slot[i].type = 0;
+ _slot[i].id = -1;
+ }
+
+ isSFXplaying = 0;
+ checkSilenceChannels(0);
+}
+
+void Player_NES::stopSound(int nr) {
+ if (nr == -1)
+ return;
+
+ for (int i = 0; i < NUMSLOTS; i++) {
+ if (_slot[i].id != nr)
+ continue;
+
+ isSFXplaying = 0;
+ _slot[i].framesleft = 0;
+ _slot[i].type = 0;
+ _slot[i].id = -1;
+ checkSilenceChannels(i);
+ }
+}
+
+void Player_NES::startSound(int nr) {
+ byte *data = _vm->getResourceAddress(rtSound, nr) + 2;
+ assert(data);
+
+ int soundType = data[1];
+ int chan = data[0];
+
+ if (chan == 4) {
+ if (_slot[2].framesleft)
+ return;
+ chan = 0;
+ }
+
+ if (soundType < _slot[chan].type)
+ return;
+
+ _slot[chan].type = soundType;
+ _slot[chan].id = nr;
+ _slot[chan].data = data;
+ _slot[chan].offset = 2;
+ _slot[chan].framesleft = 1;
+ checkSilenceChannels(chan);
+ if (chan == 2) {
+ numNotes = _slot[chan].data[2];
+ auxData1 = _slot[chan].data + 3;
+ auxData2 = auxData1 + numNotes;
+ _slot[chan].data = auxData2 + numNotes;
+ _slot[chan].offset = 0;
+
+ for (int i = 0; i < NUMCHANS; i++)
+ _mchan[i].cmdlock = 0;
+ }
+}
+
+void Player_NES::checkSilenceChannels(int chan) {
+ for (chan--; chan >= 0; chan--) {
+ if (_slot[chan].framesleft)
+ return;
+ }
+ APU_writeControl(0);
+}
+
+void Player_NES::sound_play() {
+ if (_slot[0].framesleft)
+ playSFX(0);
+ else if (_slot[1].framesleft)
+ playSFX(1);
+
+ playMusic();
+}
+
+void Player_NES::playSFX (int nr) {
+ if (--_slot[nr].framesleft)
+ return;
+
+ while (1) {
+ int a = _slot[nr].data[_slot[nr].offset++];
+ if (a < 16) {
+ a >>= 2;
+ APU_writeControl(APU_readStatus() | channelMask[a]);
+ isSFXplaying = true;
+ APU_writeChannel(a, 0, _slot[nr].data[_slot[nr].offset++]);
+ APU_writeChannel(a, 1, _slot[nr].data[_slot[nr].offset++]);
+ APU_writeChannel(a, 2, _slot[nr].data[_slot[nr].offset++]);
+ APU_writeChannel(a, 3, _slot[nr].data[_slot[nr].offset++]);
+ } else if (a == 0xFE) {
+ _slot[nr].offset = 2;
+ } else if (a == 0xFF) {
+ _slot[nr].id = -1;
+ _slot[nr].type = 0;
+ isSFXplaying = false;
+ APU_writeControl(0);
+
+ if (!nr && _slot[1].framesleft) {
+ _slot[1].framesleft = 1;
+ isSFXplaying = true;
+ }
+ return;
+ } else {
+ _slot[nr].framesleft = _slot[nr].data[_slot[nr].offset++];
+ return;
+ }
+ }
+}
+
+void Player_NES::playMusic() {
+ if (!_slot[2].framesleft)
+ return;
+
+ if (wasSFXplaying && !isSFXplaying)
+ for (int x = 1; x >= 0; x--)
+ if (_mchan[x].cmdlock) {
+ _mchan[x].command = _mchan[x].cmdlock;
+ _mchan[x].framedelay = 1;
+ }
+
+ wasSFXplaying = isSFXplaying;
+ if (!--_slot[2].framesleft) {
+top:
+ int b = _slot[2].data[_slot[2].offset++];
+ if (b == 0xFF) {
+ _slot[2].id = -1;
+ _slot[2].type = 0;
+ b = 0;
+ } else if (b == 0xFE) {
+ _slot[2].offset = 0;
+ goto top;
+ } else {
+ if (b < numNotes) {
+ int inst = auxData1[b];
+ int ch = instChannel[inst];
+ _mchan[ch].pitch = auxData2[b];
+ _mchan[ch].cmdlock = startCmd[inst];
+ _mchan[ch].command = startCmd[inst];
+ _mchan[ch].framedelay = 1;
+ goto top;
+ }
+ b -= numNotes;
+ if (b < 16) {
+ int inst = b;
+ int ch = instChannel[inst];
+ _mchan[ch].cmdlock = 0;
+ _mchan[ch].command = releaseCmd[inst];
+ _mchan[ch].framedelay = 1;
+ goto top;
+ }
+ b -= 16;
+ }
+ _slot[2].framesleft = b;
+ }
+
+ for (int x = NUMCHANS - 1; x >= 0; x--) {
+ if (_slot[0].framesleft || _slot[1].framesleft) {
+ _mchan[x].volume = 0;
+ _mchan[x].framedelay = 0;
+ continue;
+ }
+
+ if (_mchan[x].framedelay && !--_mchan[x].framedelay) {
+ switch (_mchan[x].command) {
+ case 0x00:
+ case 0x13:
+ _mchan[x].voldelta = -10;
+ break;
+
+ case 0x01:
+ case 0x03:
+ case 0x08:
+ case 0x16:
+ _mchan[x].envflags = 0x30;
+ _mchan[x].volume = 0x6F;
+ _mchan[x].voldelta = 0;
+
+ APU_writeChannel(x, 0, 0x00);
+ APU_writeChannel(x, 1, 0x7F);
+ APU_writeControl(APU_readStatus() | channelMask[x]);
+ APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+ APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+ chainCommand(x);
+ break;
+
+ case 0x02:
+ _mchan[x].envflags = 0xB0;
+ _mchan[x].volume = 0x6F;
+ _mchan[x].voldelta = 0;
+
+ APU_writeChannel(x, 0, 0x00);
+ APU_writeChannel(x, 1, 0x84);
+ APU_writeControl(APU_readStatus() | channelMask[x]);
+ APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+ APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+ chainCommand(x);
+ break;
+
+ case 0x04:
+ _mchan[x].envflags = 0x80;
+ _mchan[x].volume = 0x6F;
+ _mchan[x].voldelta = 0;
+
+ APU_writeChannel(x, 0, 0x00);
+ APU_writeChannel(x, 1, 0x7F);
+ APU_writeControl(APU_readStatus() | channelMask[x]);
+ APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+ APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+ chainCommand(x);
+ break;
+
+ case 0x05:
+ _mchan[x].envflags = 0xF0;
+ _mchan[x].volume = 0x6F;
+ _mchan[x].voldelta = -15;
+
+ APU_writeChannel(x, 1, 0x7F);
+ APU_writeControl(APU_readStatus() | channelMask[x]);
+ APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+ APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+ chainCommand(x);
+ break;
+
+ case 0x06:
+ _mchan[x].pitch += 0x18;
+ _mchan[x].envflags = 0x80;
+ _mchan[x].volume = 0x6F;
+ _mchan[x].voldelta = 0;
+
+ APU_writeChannel(x, 0, 0x00);
+ APU_writeChannel(x, 1, 0x7F);
+ APU_writeControl(APU_readStatus() | channelMask[x]);
+ APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+ APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+ chainCommand(x);
+ break;
+
+ case 0x07:
+ APU_writeChannel(x, 2, freqTable[_mchan[x].pitch - 0x0C] & 0xFF);
+ APU_writeChannel(x, 3, freqTable[_mchan[x].pitch - 0x0C] >> 8);
+
+ chainCommand(x);
+ break;
+
+ case 0x09:
+ _mchan[x].voldelta = -2;
+
+ APU_writeChannel(x, 1, 0x7F);
+ APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+ APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+ chainCommand(x);
+ break;
+
+ case 0x0A:
+ APU_writeChannel(x, 1, 0x86);
+ APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+ APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+ chainCommand(x);
+ break;
+
+ case 0x0B: case 0x1A:
+ _mchan[x].envflags = 0x70;
+ _mchan[x].volume = 0x6F;
+ _mchan[x].voldelta = 0;
+
+ APU_writeChannel(x, 0, 0x00);
+ APU_writeChannel(x, 1, 0x7F);
+ APU_writeControl(APU_readStatus() | channelMask[x]);
+ APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+ APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+ chainCommand(x);
+ break;
+
+ case 0x0C:
+ _mchan[x].envflags = 0xB0;
+
+ chainCommand(x);
+ break;
+
+ case 0x0D:
+ _mchan[x].envflags = 0x30;
+ _mchan[x].volume = 0x5F;
+ _mchan[x].voldelta = -22;
+
+ APU_writeChannel(x, 0, 0x00);
+ APU_writeControl(APU_readStatus() | channelMask[x]);
+ APU_writeChannel(x, 2, _mchan[x].pitch & 0xF);
+ APU_writeChannel(x, 3, 0xFF);
+
+ chainCommand(x);
+ break;
+
+ case 0x0E:
+ case 0x10:
+ _mchan[x].envflags = 0x30;
+ _mchan[x].volume = 0x5F;
+ _mchan[x].voldelta = -6;
+
+ APU_writeChannel(x, 0, 0x00);
+ APU_writeControl(APU_readStatus() | channelMask[x]);
+ APU_writeChannel(x, 2, _mchan[x].pitch & 0xF);
+ APU_writeChannel(x, 3, 0xFF);
+
+ chainCommand(x);
+ break;
+
+ case 0x0F:
+ chainCommand(x);
+ break;
+
+ case 0x11:
+ APU_writeChannel(x, 2, _mchan[x].pitch & 0xF);
+ APU_writeChannel(x, 3, 0xFF);
+
+ chainCommand(x);
+ break;
+
+ case 0x12:
+ APU_writeChannel(x, 2, (_mchan[x].pitch + 3) & 0xF);
+ APU_writeChannel(x, 3, 0xFF);
+
+ chainCommand(x);
+ break;
+
+ case 0x14:
+ _mchan[x].voldelta = -12;
+
+ APU_writeChannel(x, 1, 0x8C);
+
+ chainCommand(x);
+ break;
+
+ case 0x15:
+ _mchan[x].voldelta = -12;
+
+ APU_writeChannel(x, 1, 0x84);
+
+ chainCommand(x);
+ break;
+
+ case 0x17:
+ _mchan[x].pitch += 0x0C;
+ _mchan[x].envflags = 0x80;
+ _mchan[x].volume = 0x6F;
+ _mchan[x].voldelta = 0;
+
+ APU_writeChannel(x, 0, 0x00);
+ APU_writeChannel(x, 1, 0x7F);
+ APU_writeControl(APU_readStatus() | channelMask[x]);
+ APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+ APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+ chainCommand(x);
+ break;
+
+ case 0x18:
+ _mchan[x].envflags = 0x70;
+
+ chainCommand(x);
+ break;
+
+ case 0x19:
+ _mchan[x].envflags = 0xB0;
+
+ chainCommand(x);
+ break;
+
+ case 0x1B:
+ _mchan[x].envflags = 0x00;
+ _mchan[x].voldelta = -10;
+ break;
+ }
+ }
+
+ _mchan[x].volume += _mchan[x].voldelta;
+
+ if (_mchan[x].volume < 0)
+ _mchan[x].volume = 0;
+ if (_mchan[x].volume > MAXVOLUME)
+ _mchan[x].volume = MAXVOLUME;
+
+ APU_writeChannel(x, 0, (_mchan[x].volume >> 3) | _mchan[x].envflags);
+ }
+}
+
+void Player_NES::chainCommand(int c) {
+ int i = _mchan[c].command;
+ _mchan[c].command = nextCmd[i];
+ _mchan[c].framedelay = nextDelay[i];
+}
+
+int Player_NES::getSoundStatus(int nr) const {
+ for (int i = 0; i < NUMSLOTS; i++)
+ if (_slot[i].id == nr)
+ return 1;
+ return 0;
+}
+
+void Player_NES::APU_writeChannel(int chan, int offset, byte value) {
+ _apu->WriteReg(0x000 + 4 * chan + offset, value);
+}
+void Player_NES::APU_writeControl(byte value) {
+ _apu->WriteReg(0x015, value);
+}
+byte Player_NES::APU_readStatus() {
+ return _apu->Read4015();
+}
+
+} // End of namespace Scumm
+
+#endif // DISABLE_NES_APU
diff --git a/engines/scumm/players/player_nes.h b/engines/scumm/players/player_nes.h
new file mode 100644
index 0000000000..f0b3e79aad
--- /dev/null
+++ b/engines/scumm/players/player_nes.h
@@ -0,0 +1,114 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_NES_H
+#define SCUMM_PLAYERS_PLAYER_NES_H
+
+#include "common/scummsys.h"
+#include "scumm/music.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+namespace Scumm {
+
+class ScummEngine;
+namespace APUe {
+class APU;
+}
+
+static const int MAXVOLUME = 0x7F;
+static const int NUMSLOTS = 3;
+static const int NUMCHANS = 4;
+
+/**
+ * Scumm NES sound/music driver.
+ */
+class Player_NES : public Audio::AudioStream, public MusicEngine {
+public:
+ Player_NES(ScummEngine *scumm, Audio::Mixer *mixer);
+ virtual ~Player_NES();
+
+ virtual void setMusicVolume(int vol);
+ virtual void startSound(int sound);
+ virtual void stopSound(int sound);
+ virtual void stopAllSounds();
+ virtual int getSoundStatus(int sound) const;
+
+ // AudioStream API
+ int readBuffer(int16 *buffer, const int numSamples);
+ bool isStereo() const { return false; }
+ bool endOfData() const { return false; }
+ int getRate() const { return _sampleRate; }
+
+private:
+
+ void sound_play();
+ void playSFX(int nr);
+ void playMusic();
+ byte fetchSoundByte(int nr);
+ void chainCommand(int chan);
+ void checkSilenceChannels(int chan);
+
+ void APU_writeChannel(int chan, int offset, byte value);
+ void APU_writeControl(byte value);
+ byte APU_readStatus();
+
+ ScummEngine *_vm;
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _soundHandle;
+ APUe::APU *_apu;
+ int _sampleRate;
+ int _samples_per_frame;
+ int _current_sample;
+ int _maxvol;
+
+ struct slot {
+ int framesleft;
+ int id;
+ int type;
+ byte *data;
+ int offset;
+ } _slot[NUMSLOTS];
+
+ struct mchan {
+ int command;
+ int framedelay;
+ int pitch;
+ int volume;
+ int voldelta;
+ int envflags;
+ int cmdlock;
+ } _mchan[NUMCHANS];
+
+ bool isSFXplaying, wasSFXplaying;
+
+ byte *dataStart;
+ int numNotes;
+ byte *auxData1;
+ byte *auxData2;
+
+ byte *soundptr;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_pce.cpp b/engines/scumm/players/player_pce.cpp
new file mode 100644
index 0000000000..6d6e2fcde5
--- /dev/null
+++ b/engines/scumm/players/player_pce.cpp
@@ -0,0 +1,756 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ * The PSG_HuC6280 class is based on the HuC6280 sound chip emulator
+ * by Charles MacDonald (E-mail: cgfm2@hotmail.com, WWW: http://cgfm2.emuviews.com)
+ * The implementation used here was taken from MESS (http://www.mess.org/)
+ * the Multiple Emulator Super System (sound/c6280.c).
+ * LFO and noise channel support have been removed (not used by Loom PCE).
+ */
+
+#include <math.h>
+#include "scumm/players/player_pce.h"
+#include "common/endian.h"
+
+// PCE sound engine is only used by Loom, which requires 16bit color support
+#ifdef USE_RGB_COLOR
+
+namespace Scumm {
+
+// CPU and PSG use the same base clock but with a different divider
+const double MASTER_CLOCK = 21477270.0; // ~21.48 MHz
+const double CPU_CLOCK = MASTER_CLOCK / 3; // ~7.16 MHz
+const double PSG_CLOCK = MASTER_CLOCK / 6; // ~3.58 MHz
+const double TIMER_CLOCK = CPU_CLOCK / 1024; // ~6.9 kHz
+
+// The PSG update routine is originally triggered by the timer IRQ (not by VSYNC)
+// approx. 120 times per second (TIML=0x39). But as just every second call is used
+// to update the PSG we will call the update routine approx. 60 times per second.
+const double UPDATE_FREQ = TIMER_CLOCK / (57 + 1) / 2; // ~60 Hz
+
+// $AFA5
+static const byte wave_table[7][32] = {
+ { // sine
+ 0x10, 0x19, 0x1C, 0x1D, 0x1E, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1E, 0x1D, 0x1C, 0x19,
+ 0x10, 0x05, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x05,
+ }, { // mw-shaped
+ 0x10, 0x1C, 0x1D, 0x1E, 0x1E, 0x1F, 0x1F, 0x1E, 0x1C, 0x1E, 0x1F, 0x1F, 0x1E, 0x1E, 0x1D, 0x1C,
+ 0x10, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0x01, 0x03, 0x01, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03,
+ }, { // square
+ 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ }, { // triangle
+ 0x10, 0x0C, 0x08, 0x04, 0x01, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C, 0x1F, 0x1C, 0x18, 0x14,
+ 0x10, 0x0C, 0x08, 0x04, 0x01, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C, 0x1F, 0x1C, 0x18, 0x14,
+ }, { // saw-tooth
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ }, { // sigmoid
+ 0x07, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x1F, 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19,
+ 0x08, 0x06, 0x05, 0x03, 0x02, 0x01, 0x00, 0x00, 0x0F, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x16,
+ }, { // MW-shaped
+ 0x1F, 0x1E, 0x1D, 0x1D, 0x1C, 0x1A, 0x17, 0x0F, 0x0F, 0x17, 0x1A, 0x1C, 0x1D, 0x1D, 0x1E, 0x1F,
+ 0x00, 0x01, 0x02, 0x02, 0x03, 0x05, 0x08, 0x0F, 0x0F, 0x08, 0x05, 0x03, 0x02, 0x02, 0x01, 0x00
+ }
+};
+
+// AEBC
+static const int control_offsets[14] = {
+ 0, 7, 20, 33, 46, 56, 75, 88, 116, 126, 136, 152, 165, 181
+};
+
+// AED8
+static const byte control_data[205] = {
+ /* 0*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0xFF,
+ /* 7*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x01, 0x00, 0xF0, 0x3A, 0x00, 0xFD, 0xFF,
+ /* 20*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x01, 0x00, 0xF0, 0x1D, 0x00, 0xF8, 0xFF,
+ /* 33*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x02, 0x00, 0xF0, 0x1E, 0x00, 0xFC, 0xFF,
+ /* 46*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x07, 0x00, 0xE0, 0xFF,
+ /* 56*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x01, 0x00, 0xD8, 0xF0, 0x00, 0xD8, 0x01, 0x00, 0x00, 0x04, 0x00, 0xF0, 0xFF,
+ /* 75*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x03, 0x00, 0xF8, 0x6E, 0x00, 0xFF, 0xFF,
+ /* 88*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x08, 0x00, 0xF0, 0xF0, 0x00, 0xD0, 0x01, 0x00, 0x00, 0x05, 0x00, 0xF0, 0xF0, 0x00, 0xB8, 0xE6, 0x80, 0xFF, 0xE6, 0x80, 0xFF, 0xFF,
+ /*116*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x05, 0x00, 0xD0, 0xFF,
+ /*126*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x04, 0x00, 0xF8, 0xFF,
+ /*136*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x03, 0x00, 0xF4, 0xE6, 0xC0, 0xFF, 0xE6, 0xC0, 0xFF, 0xFF,
+ /*152*/ 0xF0, 0x00, 0xD0, 0x01, 0x00, 0x00, 0x02, 0x00, 0x10, 0x0E, 0x00, 0xFE, 0xFF,
+ /*165*/ 0xF0, 0x00, 0xA8, 0x01, 0x00, 0x00, 0x18, 0x00, 0x02, 0xE6, 0x80, 0xFE, 0xE6, 0xC0, 0xFF, 0xFF,
+ /*181*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x02, 0x00, 0xF8, 0x02, 0x00, 0x00, 0x02, 0x00, 0xF0, 0x01, 0x00, 0xF8, 0x02, 0x00, 0x08, 0xF1, 0x99, 0xAF
+};
+
+static const uint16 lookup_table[87] = {
+ 0x0D40, 0x0C80, 0x0BC0, 0x0B20, 0x0A80, 0x09E0, 0x0940, 0x08C0,
+ 0x0840, 0x07E0, 0x0760, 0x0700, 0x06A0, 0x0640, 0x05E0, 0x0590,
+ 0x0540, 0x04F0, 0x04A0, 0x0460, 0x0420, 0x03F0, 0x03B0, 0x0380,
+ 0x0350, 0x0320, 0x02F0, 0x02C8, 0x02A0, 0x0278, 0x0250, 0x0230,
+ 0x0210, 0x01F8, 0x01D8, 0x01C0, 0x01A8, 0x0190, 0x0178, 0x0164,
+ 0x0150, 0x013C, 0x0128, 0x0118, 0x0108, 0x00FC, 0x00EC, 0x00E0,
+ 0x00D4, 0x00C8, 0x00BC, 0x00B2, 0x00A8, 0x009E, 0x0094, 0x008C,
+ 0x0084, 0x007E, 0x0076, 0x0070, 0x006A, 0x0064, 0x005E, 0x0059,
+ 0x0054, 0x004F, 0x004A, 0x0046, 0x0042, 0x003F, 0x003B, 0x0038,
+ 0x0035, 0x0032, 0x0030, 0x002D, 0x002A, 0x0028, 0x0026, 0x0024,
+ 0x0022, 0x0020, 0x001E, 0x001C, 0x001B, 0x8E82, 0xB500
+};
+
+// B27B
+static const uint16 freq_offset[3] = {
+ 0, 2, 9
+};
+
+static const uint16 freq_table[] = {
+ 0x0000, 0x0800,
+ 0xFFB0, 0xFFD1, 0xFFE8, 0xFFF1, 0x0005, 0x0000, 0x0800,
+ 0xFF9C, 0xFFD8, 0x0000, 0x000F, 0x0005, 0x0000, 0x0800
+};
+
+static const int sound_table[13] = {
+ 0, 2, 3, 4, 5, 6, 7, 8, 9, 11, 1, 10, 11
+};
+
+// 0xAE12
+// Note:
+// - offsets relative to data_table
+// - byte one of each sound was always 0x3F (= use all channels) -> removed from table
+static const uint16 sounds[13][6] = {
+ { 481, 481, 481, 481, 481, 481 },
+ { 395, 408, 467, 480, 480, 480 },
+ { 85, 96, 109, 109, 109, 109 },
+ { 110, 121, 134, 134, 134, 134 },
+ { 135, 146, 159, 159, 159, 159 },
+ { 160, 171, 184, 184, 184, 184 },
+ { 185, 196, 209, 209, 209, 209 },
+ { 210, 221, 234, 234, 234, 234 },
+ { 235, 246, 259, 259, 259, 259 },
+ { 260, 271, 284, 284, 284, 284 },
+ { 285, 298, 311, 324, 335, 348 },
+ { 349, 360, 361, 362, 373, 384 },
+ { 0, 84, 84, 84, 84, 84 } // unused
+};
+
+// 0xB2A1
+static const byte data_table[482] = {
+ /* 0*/ 0xE2, 0x0A, 0xE1, 0x0D, 0xE6, 0xED, 0xE0, 0x0F, 0xE2, 0x00, 0xE1, 0x00,
+ 0xF2, 0xF2, 0xB2, 0xE1, 0x01, 0xF2, 0xF2, 0xB2, 0xE1, 0x02, 0xF2, 0xF2,
+ 0xB2, 0xE1, 0x03, 0xF2, 0xF2, 0xB2, 0xE1, 0x04, 0xF2, 0xF2, 0xB2, 0xE1,
+ 0x05, 0xF2, 0xF2, 0xB2, 0xE1, 0x06, 0xF2, 0xF2, 0xB2, 0xE1, 0x07, 0xF2,
+ 0xF2, 0xB2, 0xE1, 0x08, 0xF2, 0xF2, 0xB2, 0xE1, 0x09, 0xF2, 0xF2, 0xB2,
+ 0xE1, 0x0A, 0xF2, 0xF2, 0xB2, 0xE1, 0x0B, 0xF2, 0xF2, 0xB2, 0xE1, 0x0C,
+ 0xF2, 0xF2, 0xB2, 0xE1, 0x0D, 0xF2, 0xF2, 0xB2, 0xFF, 0xD1, 0x03, 0xF3,
+ /* 84*/ 0xFF,
+
+ /* 85*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x07, 0xFF,
+ /* 96*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x07, 0xFF,
+ /*109*/ 0xFF,
+
+ /*110*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x27, 0xFF,
+ /*121*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x27, 0xFF,
+ /*134*/ 0xFF,
+
+ /*135*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x47, 0xFF,
+ /*146*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x47, 0xFF,
+ /*159*/ 0xFF,
+
+ /*160*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x57, 0xFF,
+ /*171*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x57, 0xFF,
+ /*184*/ 0xFF,
+
+ /*185*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x77, 0xFF,
+ /*196*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x77, 0xFF,
+ /*209*/ 0xFF,
+
+ /*210*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x97, 0xFF,
+ /*221*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x97, 0xFF,
+ /*234*/ 0xFF,
+
+ /*235*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0xB7, 0xFF,
+ /*246*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0xB7, 0xFF,
+ /*259*/ 0xFF,
+
+ /*260*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0x07, 0xFF,
+ /*271*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD5, 0xF0, 0x0C, 0x07, 0xFF,
+ /*284*/ 0xFF,
+
+ /*285*/ 0xE2, 0x0B, 0xE1, 0x04, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x14, 0x0E, 0xFF,
+ /*298*/ 0xE2, 0x0B, 0xE1, 0x04, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x32, 0x1E, 0xFF,
+ /*311*/ 0xE2, 0x0B, 0xE1, 0x0B, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x50, 0x1E, 0xFF,
+ /*324*/ 0xE2, 0x0B, 0xE1, 0x0B, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0x0E, 0xFF,
+ /*335*/ 0xE2, 0x0B, 0xE1, 0x02, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x0A, 0x0E, 0xFF,
+ /*348*/ 0xFF,
+
+ /*349*/ 0xE2, 0x03, 0xE1, 0x01, 0xE6, 0xED, 0xE0, 0x06, 0xD6, 0x17, 0xFF,
+ /*360*/ 0xFF,
+ /*361*/ 0xFF,
+ /*362*/ 0xE2, 0x04, 0xE1, 0x04, 0xE6, 0xED, 0xE0, 0x06, 0xD5, 0xA7, 0xFF,
+ /*373*/ 0xE2, 0x03, 0xE1, 0x06, 0xE6, 0xED, 0xE0, 0x06, 0xD6, 0x37, 0xFF,
+ /*384*/ 0xE2, 0x04, 0xE1, 0x06, 0xE6, 0xED, 0xE0, 0x06, 0xD3, 0x87, 0xFF,
+
+ /*395*/ 0xE2, 0x0C, 0xE1, 0x00, 0xE0, 0x04, 0xE6, 0xED, 0xD4, 0x0B, 0xE8, 0x0B, 0xFF,
+ /*408*/ 0xE2, 0x0C, 0xE1, 0x03, 0xE0, 0x04, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x00,
+ 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00,
+ 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00,
+ 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00,
+ 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xFF,
+ /*467*/ 0xE2, 0x0C, 0xE1, 0x00, 0xE0, 0x04, 0xE6, 0xED, 0xD4, 0x1B, 0xE8, 0x1B, 0xFF,
+ /*480*/ 0xFF,
+
+ /*481*/ 0xFF
+};
+
+
+/*
+ * PSG_HuC6280
+ */
+
+class PSG_HuC6280 {
+private:
+ typedef struct {
+ uint16 frequency;
+ uint8 control;
+ uint8 balance;
+ uint8 waveform[32];
+ uint8 index;
+ int16 dda;
+ uint32 counter;
+ } channel_t;
+
+ double _clock;
+ double _rate;
+ uint8 _select;
+ uint8 _balance;
+ channel_t _channel[8];
+ int16 _volumeTable[32];
+ uint32 _noiseFreqTable[32];
+ uint32 _waveFreqTable[4096];
+
+public:
+ void init();
+ void reset();
+ void write(int offset, byte data);
+ void update(int16* samples, int sampleCnt);
+
+ PSG_HuC6280(double clock, double samplerate);
+};
+
+PSG_HuC6280::PSG_HuC6280(double clock, double samplerate) {
+ _clock = clock;
+ _rate = samplerate;
+
+ // Initialize PSG_HuC6280 emulator
+ init();
+}
+
+void PSG_HuC6280::init() {
+ int i;
+ double step;
+
+ // Loudest volume level for table
+ double level = 65535.0 / 6.0 / 32.0;
+
+ // Clear context
+ reset();
+
+ // Make waveform frequency table
+ for (i = 0; i < 4096; i++) {
+ step = ((_clock / _rate) * 4096) / (i+1);
+ _waveFreqTable[(1 + i) & 0xFFF] = (uint32)step;
+ }
+
+ // Make noise frequency table
+ for (i = 0; i < 32; i++) {
+ step = ((_clock / _rate) * 32) / (i+1);
+ _noiseFreqTable[i] = (uint32)step;
+ }
+
+ // Make volume table
+ // PSG_HuC6280 has 48dB volume range spread over 32 steps
+ step = 48.0 / 32.0;
+ for (i = 0; i < 31; i++) {
+ _volumeTable[i] = (uint16)level;
+ level /= pow(10.0, step / 20.0);
+ }
+ _volumeTable[31] = 0;
+}
+
+void PSG_HuC6280::reset() {
+ _select = 0;
+ _balance = 0xFF;
+ memset(_channel, 0, sizeof(_channel));
+ memset(_volumeTable, 0, sizeof(_volumeTable));
+ memset(_noiseFreqTable, 0, sizeof(_noiseFreqTable));
+ memset(_waveFreqTable, 0, sizeof(_waveFreqTable));
+}
+
+void PSG_HuC6280::write(int offset, byte data) {
+ channel_t *chan = &_channel[_select];
+
+ switch(offset & 0x0F) {
+ case 0x00: // Channel select
+ _select = data & 0x07;
+ break;
+
+ case 0x01: // Global balance
+ _balance = data;
+ break;
+
+ case 0x02: // Channel frequency (LSB)
+ chan->frequency = (chan->frequency & 0x0F00) | data;
+ chan->frequency &= 0x0FFF;
+ break;
+
+ case 0x03: // Channel frequency (MSB)
+ chan->frequency = (chan->frequency & 0x00FF) | (data << 8);
+ chan->frequency &= 0x0FFF;
+ break;
+
+ case 0x04: // Channel control (key-on, DDA mode, volume)
+ // 1-to-0 transition of DDA bit resets waveform index
+ if ((chan->control & 0x40) && ((data & 0x40) == 0)) {
+ chan->index = 0;
+ }
+ chan->control = data;
+ break;
+
+ case 0x05: // Channel balance
+ chan->balance = data;
+ break;
+
+ case 0x06: // Channel waveform data
+ switch(chan->control & 0xC0) {
+ case 0x00:
+ chan->waveform[chan->index & 0x1F] = data & 0x1F;
+ chan->index = (chan->index + 1) & 0x1F;
+ break;
+
+ case 0x40:
+ break;
+
+ case 0x80:
+ chan->waveform[chan->index & 0x1F] = data & 0x1F;
+ chan->index = (chan->index + 1) & 0x1F;
+ break;
+
+ case 0xC0:
+ chan->dda = data & 0x1F;
+ break;
+ }
+
+ break;
+
+ case 0x07: // Noise control (enable, frequency)
+ case 0x08: // LFO frequency
+ case 0x09: // LFO control (enable, mode)
+ break;
+
+ default:
+ break;
+ }
+}
+
+void PSG_HuC6280::update(int16* samples, int sampleCnt) {
+ static const int scale_tab[] = {
+ 0x00, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
+ 0x10, 0x13, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F
+ };
+ int ch;
+ int i;
+
+ int lmal = (_balance >> 4) & 0x0F;
+ int rmal = (_balance >> 0) & 0x0F;
+ int vll, vlr;
+
+ lmal = scale_tab[lmal];
+ rmal = scale_tab[rmal];
+
+ // Clear buffer
+ memset(samples, 0, 2 * sampleCnt * sizeof(int16));
+
+ for (ch = 0; ch < 6; ch++) {
+ // Only look at enabled channels
+ if (_channel[ch].control & 0x80) {
+ int lal = (_channel[ch].balance >> 4) & 0x0F;
+ int ral = (_channel[ch].balance >> 0) & 0x0F;
+ int al = _channel[ch].control & 0x1F;
+
+ lal = scale_tab[lal];
+ ral = scale_tab[ral];
+
+ // Calculate volume just as the patent says
+ vll = (0x1F - lal) + (0x1F - al) + (0x1F - lmal);
+ if (vll > 0x1F) vll = 0x1F;
+
+ vlr = (0x1F - ral) + (0x1F - al) + (0x1F - rmal);
+ if (vlr > 0x1F) vlr = 0x1F;
+
+ vll = _volumeTable[vll];
+ vlr = _volumeTable[vlr];
+
+ // Check channel mode
+ if (_channel[ch].control & 0x40) {
+ /* DDA mode */
+ for (i = 0; i < sampleCnt; i++) {
+ samples[2*i] += (int16)(vll * (_channel[ch].dda - 16));
+ samples[2*i + 1] += (int16)(vlr * (_channel[ch].dda - 16));
+ }
+ } else {
+ /* Waveform mode */
+ uint32 step = _waveFreqTable[_channel[ch].frequency];
+ for (i = 0; i < sampleCnt; i += 1) {
+ int offset;
+ int16 data;
+ offset = (_channel[ch].counter >> 12) & 0x1F;
+ _channel[ch].counter += step;
+ _channel[ch].counter &= 0x1FFFF;
+ data = _channel[ch].waveform[offset];
+ samples[2*i] += (int16)(vll * (data - 16));
+ samples[2*i + 1] += (int16)(vlr * (data - 16));
+ }
+ }
+ }
+ }
+}
+
+
+/*
+ * Player_PCE
+ */
+
+void Player_PCE::PSG_Write(int reg, byte data) {
+ _psg->write(reg, data);
+}
+
+void Player_PCE::setupWaveform(byte bank) {
+ const byte *ptr = wave_table[bank];
+ PSG_Write(4, 0x40);
+ PSG_Write(4, 0x00);
+ for (int i = 0; i < 32; ++i) {
+ PSG_Write(6, ptr[i]);
+ }
+}
+
+// A541
+void Player_PCE::procA541(channel_t *channel) {
+ channel->soundDataPtr = NULL;
+ channel->controlVecShort10 = 0;
+
+ channel->controlVecShort03 = 0;
+ channel->controlVecShort06 = 0;
+ channel->controlVec8 = 0;
+ channel->controlVec9 = 0;
+ channel->controlVec10 = 0;
+ channel->soundUpdateCounter = 0;
+ channel->controlVec18 = 0;
+ channel->controlVec19 = 0;
+ channel->controlVec23 = false;
+ channel->controlVec24 = false;
+ channel->controlVec21 = 0;
+
+ channel->waveformCtrl = 0x80;
+}
+
+// A592
+void Player_PCE::startSound(int sound) {
+ channel_t *channel;
+ const uint16 *ptr = sounds[sound_table[sound]];
+
+ for (int i = 0; i < 6; ++i) {
+ channel = &channels[i];
+ procA541(channel);
+
+ channel->controlVec24 = true;
+ channel->waveformCtrl = 0;
+ channel->controlVec0 = 0;
+ channel->controlVec19 = 0;
+ channel->controlVec18 = 0;
+ channel->soundDataPtr = &data_table[*ptr++];
+ }
+}
+
+// A64B
+void Player_PCE::updateSound() {
+ for (int i = 0; i < 12; i++) {
+ channel_t *channel = &channels[i];
+ bool cond = true;
+ if (i < 6) {
+ channel->controlVec21 ^= 0xFF;
+ if (!channel->controlVec21)
+ cond = false;
+ }
+ if (cond) {
+ processSoundData(channel);
+ procAB7F(channel);
+ procAC24(channel);
+ channel->controlVec11 = (channel->controlVecShort10 >> 11) | 0x80;
+ channel->balance = channel->balance2;
+ }
+ }
+
+ for (int i = 0; i < 6; ++i) {
+ procA731(&channels[i]);
+ }
+}
+
+int Player_PCE::readBuffer(int16 *buffer, const int numSamples) {
+ int sampleCopyCnt;
+ int samplesLeft = numSamples;
+
+ Common::StackLock lock(_mutex);
+
+ while (true) {
+ // copy samples to output buffer
+ sampleCopyCnt = (samplesLeft < _sampleBufferCnt) ? samplesLeft : _sampleBufferCnt;
+ if (sampleCopyCnt > 0) {
+ memcpy(buffer, _sampleBuffer, sampleCopyCnt * sizeof(int16));
+ buffer += sampleCopyCnt;
+ samplesLeft -= sampleCopyCnt;
+ _sampleBufferCnt -= sampleCopyCnt;
+ }
+
+ if (samplesLeft == 0)
+ break;
+
+ // retrieve samples for one timer period
+ updateSound();
+ _psg->update(_sampleBuffer, _samplesPerPeriod / 2);
+ _sampleBufferCnt = _samplesPerPeriod;
+ }
+
+ // copy remaining samples to the front of the buffer
+ if (_sampleBufferCnt > 0) {
+ memmove(&_sampleBuffer[0],
+ &_sampleBuffer[_samplesPerPeriod - _sampleBufferCnt],
+ _sampleBufferCnt * sizeof(int16));
+ }
+
+ return numSamples;
+}
+
+void Player_PCE::procA731(channel_t *channel) {
+ PSG_Write(0, channel->id);
+ PSG_Write(2, channel->freq & 0xFF);
+ PSG_Write(3, (channel->freq >> 8) & 0xFF);
+
+ int tmp = channel->controlVec11;
+ if ((channel->controlVec11 & 0xC0) == 0x80) {
+ tmp = channel->controlVec11 & 0x1F;
+ if (tmp != 0) {
+ tmp -= channel->controlVec0;
+ if (tmp >= 0) {
+ tmp |= 0x80;
+ } else {
+ tmp = 0;
+ }
+ }
+ }
+
+ PSG_Write(5, channel->balance);
+ if ((channel->waveformCtrl & 0x80) == 0) {
+ channel->waveformCtrl |= 0x80;
+ PSG_Write(0, channel->id);
+ setupWaveform(channel->waveformCtrl & 0x7F);
+ }
+
+ PSG_Write(4, tmp);
+}
+
+// A793
+void Player_PCE::processSoundData(channel_t *channel) {
+ channel->soundUpdateCounter--;
+ if (channel->soundUpdateCounter > 0) {
+ return;
+ }
+
+ while (true) {
+ const byte *ptr = channel->soundDataPtr;
+ byte value = (ptr ? *ptr++ : 0xFF);
+ if (value < 0xD0) {
+ int mult = (value & 0x0F) + 1;
+ channel->soundUpdateCounter = mult * channel->controlVec1;
+ value >>= 4;
+ procAA62(channel, value);
+ channel->soundDataPtr = ptr;
+ return;
+ }
+
+ // jump_table (A7F7)
+ switch (value - 0xD0) {
+ case 0: /*A85A*/
+ case 1: /*A85D*/
+ case 2: /*A861*/
+ case 3: /*A865*/
+ case 4: /*A869*/
+ case 5: /*A86D*/
+ case 6: /*A871*/
+ channel->controlVec2 = (value - 0xD0) * 12;
+ break;
+ case 11: /*A8A8*/
+ channel->controlVecShort06 = (int8)*ptr++;
+ break;
+ case 16: /*A8C2*/
+ channel->controlVec1 = *ptr++;
+ break;
+ case 17: /*A8CA*/
+ channel->waveformCtrl = *ptr++;
+ break;
+ case 18: /*A8D2*/
+ channel->controlVec10 = *ptr++;
+ break;
+ case 22: /*A8F2*/
+ value = *ptr;
+ channel->balance = value;
+ channel->balance2 = value;
+ ptr++;
+ break;
+ case 24: /*A905*/
+ channel->controlVec23 = true;
+ break;
+ case 32: /*A921*/
+ ptr++;
+ break;
+ case 47:
+ channel->controlVec24 = false;
+ channel->controlVec10 &= 0x7F;
+ channel->controlVecShort10 &= 0x00FF;
+ return;
+ default:
+ // unused -> ignore
+ break;
+ }
+
+ channel->soundDataPtr = ptr;
+ }
+}
+
+void Player_PCE::procAA62(channel_t *channel, int a) {
+ procACEA(channel, a);
+ if (channel->controlVec23) {
+ channel->controlVec23 = false;
+ return;
+ }
+
+ channel->controlVec18 = 0;
+
+ channel->controlVec10 |= 0x80;
+ int y = channel->controlVec10 & 0x7F;
+ channel->controlBufferPos = &control_data[control_offsets[y]];
+ channel->controlVec5 = 0;
+}
+
+void Player_PCE::procAB7F(channel_t *channel) {
+ uint16 freqValue = channel->controlVecShort02;
+ channel->controlVecShort02 += channel->controlVecShort03;
+
+ int pos = freq_offset[channel->controlVec19] + channel->controlVec18;
+ freqValue += freq_table[pos];
+ if (freq_table[pos + 1] != 0x0800) {
+ channel->controlVec18++;
+ }
+ freqValue += channel->controlVecShort06;
+
+ channel->freq = freqValue;
+}
+
+void Player_PCE::procAC24(channel_t *channel) {
+ if ((channel->controlVec10 & 0x80) == 0)
+ return;
+
+ if (channel->controlVec5 == 0) {
+ const byte *ctrlPtr = channel->controlBufferPos;
+ byte value = *ctrlPtr++;
+ while (value >= 0xF0) {
+ if (value == 0xF0) {
+ channel->controlVecShort10 = READ_LE_UINT16(ctrlPtr);
+ ctrlPtr += 2;
+ } else if (value == 0xFF) {
+ channel->controlVec10 &= 0x7F;
+ return;
+ } else {
+ // unused
+ }
+ value = *ctrlPtr++;
+ }
+ channel->controlVec5 = value;
+ channel->controlVecShort09 = READ_LE_UINT16(ctrlPtr);
+ ctrlPtr += 2;
+ channel->controlBufferPos = ctrlPtr;
+ }
+
+ channel->controlVecShort10 += channel->controlVecShort09;
+ channel->controlVec5--;
+}
+
+void Player_PCE::procACEA(channel_t *channel, int a) {
+ int x = a +
+ channel->controlVec2 +
+ channel->controlVec8 +
+ channel->controlVec9;
+ channel->controlVecShort02 = lookup_table[x];
+}
+
+Player_PCE::Player_PCE(ScummEngine *scumm, Audio::Mixer *mixer) {
+ for (int i = 0; i < 12; ++i) {
+ memset(&channels[i], 0, sizeof(channel_t));
+ channels[i].id = i;
+ }
+
+ _mixer = mixer;
+ _sampleRate = _mixer->getOutputRate();
+ _vm = scumm;
+
+ _samplesPerPeriod = 2 * (int)(_sampleRate / UPDATE_FREQ);
+ _sampleBuffer = new int16[_samplesPerPeriod];
+ _sampleBufferCnt = 0;
+
+ _psg = new PSG_HuC6280(PSG_CLOCK, _sampleRate);
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_PCE::~Player_PCE() {
+ _mixer->stopHandle(_soundHandle);
+ delete[] _sampleBuffer;
+ delete _psg;
+}
+
+void Player_PCE::stopSound(int nr) {
+ // TODO: implement
+}
+
+void Player_PCE::stopAllSounds() {
+ // TODO: implement
+}
+
+int Player_PCE::getSoundStatus(int nr) const {
+ // TODO: status for each sound
+ for (int i = 0; i < 6; ++i) {
+ if (channels[i].controlVec24)
+ return 1;
+ }
+ return 0;
+}
+
+int Player_PCE::getMusicTimer() {
+ return 0;
+}
+
+} // End of namespace Scumm
+
+#endif // USE_RGB_COLOR
diff --git a/engines/scumm/players/player_pce.h b/engines/scumm/players/player_pce.h
new file mode 100644
index 0000000000..ca2eddf58c
--- /dev/null
+++ b/engines/scumm/players/player_pce.h
@@ -0,0 +1,133 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_PCE_H
+#define SCUMM_PLAYERS_PLAYER_PCE_H
+
+#include "common/scummsys.h"
+#include "common/mutex.h"
+#include "scumm/music.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+// PCE sound engine is only used by Loom, which requires 16bit color support
+#ifdef USE_RGB_COLOR
+
+namespace Scumm {
+
+class ScummEngine;
+class PSG_HuC6280;
+
+class Player_PCE : public Audio::AudioStream, public MusicEngine {
+private:
+ struct channel_t {
+ int id;
+
+ byte controlVec0;
+ byte controlVec1;
+ byte controlVec2;
+ byte controlVec5;
+ byte balance;
+ byte balance2;
+ byte controlVec8;
+ byte controlVec9;
+ byte controlVec10;
+ byte controlVec11;
+ int16 soundUpdateCounter;
+ byte controlVec18;
+ byte controlVec19;
+ byte waveformCtrl;
+ byte controlVec21;
+ bool controlVec23;
+ bool controlVec24;
+
+ uint16 controlVecShort02;
+ uint16 controlVecShort03;
+ int16 controlVecShort06;
+ uint16 freq;
+ uint16 controlVecShort09;
+ uint16 controlVecShort10;
+
+ const byte* soundDataPtr;
+ const byte* controlBufferPos;
+ };
+
+public:
+ Player_PCE(ScummEngine *scumm, Audio::Mixer *mixer);
+ virtual ~Player_PCE();
+
+ virtual void setMusicVolume(int vol) { _maxvol = vol; }
+ virtual void startSound(int sound);
+ virtual void stopSound(int sound);
+ virtual void stopAllSounds();
+ virtual int getSoundStatus(int sound) const;
+ virtual int getMusicTimer();
+
+ // AudioStream API
+ int readBuffer(int16 *buffer, const int numSamples);
+ bool isStereo() const { return true; }
+ bool endOfData() const { return false; }
+ int getRate() const { return _sampleRate; }
+
+private:
+ ScummEngine *_vm;
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _soundHandle;
+ int _sampleRate;
+ int _maxvol;
+
+private:
+ PSG_HuC6280 *_psg;
+ channel_t channels[12];
+ Common::Mutex _mutex;
+
+ // number of samples per timer period
+ int _samplesPerPeriod;
+ int16* _sampleBuffer;
+ int _sampleBufferCnt;
+
+ void init();
+ bool isPlaying();
+
+ void PSG_Write(int reg, byte data);
+
+ void setupWaveform(byte bank);
+ void procA541(channel_t *channel);
+ void updateSound();
+ void procA731(channel_t *channel);
+ void processSoundData(channel_t *channel);
+ void procA9F3(int x);
+ void procAA62(channel_t *channel, int a);
+ uint16 procAAF6(int x);
+ void procAB7F(channel_t *channel);
+ void procAC24(channel_t *channel);
+ void procACEA(channel_t *channel, int a);
+ void procAD21(int a, int x);
+ void procAD29(int value);
+ void procAD3D(int a, int x);
+};
+
+} // End of namespace Scumm
+
+#endif // USE_RGB_COLOR
+
+#endif
diff --git a/engines/scumm/players/player_sid.cpp b/engines/scumm/players/player_sid.cpp
new file mode 100644
index 0000000000..1b97ad16d4
--- /dev/null
+++ b/engines/scumm/players/player_sid.cpp
@@ -0,0 +1,1384 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef DISABLE_SID
+
+#include "engines/engine.h"
+#include "scumm/players/player_sid.h"
+#include "scumm/scumm.h"
+#include "audio/mixer.h"
+
+namespace Scumm {
+
+/*
+ * The player's update() routine is called once per (NTSC/PAL) frame as it is
+ * called by the VIC Rasterline interrupt handler which is in turn called
+ * approx. 50 (PAL) or 60 (NTSC) times per second.
+ * The SCUMM V0/V1 music playback routines or sound data have not been adjusted
+ * to PAL systems. As a consequence, music is played audibly (-16%) slower
+ * on PAL systems.
+ * In addition, the SID oscillator frequency depends on the video clock too.
+ * As SCUMM games use an NTSC frequency table for both NTSC and PAL versions
+ * all tone frequencies on PAL systems are slightly (-4%) lower than on NTSC ones.
+ *
+ * For more info on the SID chip see:
+ * - http://www.dopeconnection.net/C64_SID.htm (German)
+ * For more info on the VIC chip see:
+ * - http://www.htu.tugraz.at/~herwig/c64/man-vic.php (German)
+ * - http://www.c64-wiki.de/index.php/VIC (German)
+ */
+
+struct TimingProps {
+ double clockFreq;
+ int cyclesPerFrame;
+};
+
+static const TimingProps timingProps[2] = {
+ { 17734472.0 / 18, 312 * 63 }, // PAL: 312*63 cycles/frame @ 985248 Hz (~50Hz)
+ { 14318180.0 / 14, 263 * 65 } // NTSC: 263*65 cycles/frame @ 1022727 Hz (~60Hz)
+};
+
+static const uint8 BITMASK[7] = {
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40
+};
+static const uint8 BITMASK_INV[7] = {
+ 0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF
+};
+
+static const int SID_REG_OFFSET[7] = {
+ 0, 7, 14, 21, 2, 9, 16
+};
+
+// NTSC frequency table (also used for PAL versions).
+// FREQ_TBL[i] = tone_freq[i] * 2^24 / clockFreq
+static const uint16 FREQ_TBL[97] = {
+ 0x0000, 0x010C, 0x011C, 0x012D, 0x013E, 0x0151, 0x0166, 0x017B,
+ 0x0191, 0x01A9, 0x01C3, 0x01DD, 0x01FA, 0x0218, 0x0238, 0x025A,
+ 0x027D, 0x02A3, 0x02CC, 0x02F6, 0x0323, 0x0353, 0x0386, 0x03BB,
+ 0x03F4, 0x0430, 0x0470, 0x04B4, 0x04FB, 0x0547, 0x0598, 0x05ED,
+ 0x0647, 0x06A7, 0x070C, 0x0777, 0x07E9, 0x0861, 0x08E1, 0x0968,
+ 0x09F7, 0x0A8F, 0x0B30, 0x0BDA, 0x0C8F, 0x0D4E, 0x0E18, 0x0EEF,
+ 0x0FD2, 0x10C3, 0x11C3, 0x12D1, 0x13EF, 0x151F, 0x1660, 0x17B5,
+ 0x191E, 0x1A9C, 0x1C31, 0x1DDF, 0x1FA5, 0x2187, 0x2386, 0x25A2,
+ 0x27DF, 0x2A3E, 0x2CC1, 0x2F6B, 0x323C, 0x3539, 0x3863, 0x3BBE,
+ 0x3F4B, 0x430F, 0x470C, 0x4B45, 0x4FBF, 0x547D, 0x5983, 0x5ED6,
+ 0x6479, 0x6A73, 0x70C7, 0x777C, 0x7E97, 0x861E, 0x8E18, 0x968B,
+ 0x9F7E, 0xA8FA, 0xB306, 0xBDAC, 0xC8F3, 0xD4E6, 0xE18F, 0xEEF8,
+ 0xFD2E
+};
+
+static const int SONG_CHANNEL_OFFSET[3] = { 6, 8, 10 };
+static const int RES_ID_CHANNEL[3] = { 3, 4, 5 };
+
+#define LOBYTE_(a) ((a) & 0xFF)
+#define HIBYTE_(a) (((a) >> 8) & 0xFF)
+
+#define GETBIT(var, pos) ((var) & (1<<(pos)))
+
+void Player_SID::handleMusicBuffer() { // $33cd
+ int channel = 2;
+ while (channel >= 0) {
+ if ((statusBits1A & BITMASK[channel]) == 0 ||
+ (busyChannelBits & BITMASK[channel]) != 0) {
+ --channel;
+ continue;
+ }
+
+ if (setupSongFileData() == 1)
+ return;
+
+ uint8* l_chanFileDataPtr = chanFileData[channel];
+
+ uint16 l_freq = 0;
+ bool l_keepFreq = false;
+
+ int y = 0;
+ uint8 curByte = l_chanFileDataPtr[y++];
+
+ // freq or 0/0xFF
+ if (curByte == 0) {
+ func_3674(channel);
+ if (!isMusicPlaying)
+ return;
+ continue;
+ } else if (curByte == 0xFF) {
+ l_keepFreq = true;
+ } else {
+ l_freq = FREQ_TBL[curByte];
+ }
+
+ uint8 local1 = 0;
+ curByte = l_chanFileDataPtr[y++];
+ bool isLastCmdByte = (curByte & 0x80) != 0;
+ uint16 curStepSum = stepTbl[curByte & 0x7f];
+
+ for (int i = 0; !isLastCmdByte && (i < 2); ++i) {
+ curByte = l_chanFileDataPtr[y++];
+ isLastCmdByte = (curByte & 0x80) != 0;
+ if (curByte & 0x40) {
+ // note: bit used in zak theme (95) only (not used/handled in MM)
+ _music_timer = curByte & 0x3f;
+ } else {
+ local1 = curByte & 0x3f;
+ }
+ }
+
+ chanFileData[channel] += y;
+ chanDataOffset[channel] += y;
+
+ uint8 *l_chanBuf = getResource(RES_ID_CHANNEL[channel]);
+
+ if (local1 != 0) {
+ // TODO: signed or unsigned?
+ uint16 offset = READ_LE_UINT16(&actSongFileData[local1*2 + 12]);
+ l_chanFileDataPtr = actSongFileData + offset;
+
+ // next five bytes: freqDelta, attack, sustain and phase bit
+ for (int i = 0; i < 5; ++i) {
+ l_chanBuf[15 + i] = l_chanFileDataPtr[i];
+ }
+ phaseBit[channel] = l_chanFileDataPtr[4];
+
+ for (int i = 0; i < 17; ++i) {
+ l_chanBuf[25 + i] = l_chanFileDataPtr[5 + i];
+ }
+ }
+
+ if (l_keepFreq) {
+ if (!releasePhase[channel]) {
+ l_chanBuf[10] &= 0xfe; // release phase
+ }
+ releasePhase[channel] = true;
+ } else {
+ if (releasePhase[channel]) {
+ l_chanBuf[19] = phaseBit[channel];
+ l_chanBuf[10] |= 0x01; // attack phase
+ }
+ l_chanBuf[11] = LOBYTE_(l_freq);
+ l_chanBuf[12] = HIBYTE_(l_freq);
+ releasePhase[channel] = false;
+ }
+
+ // set counter value for frequency update (freqDeltaCounter)
+ l_chanBuf[13] = LOBYTE_(curStepSum);
+ l_chanBuf[14] = HIBYTE_(curStepSum);
+
+ _soundQueue[channel] = RES_ID_CHANNEL[channel];
+ processSongData(channel);
+ _soundQueue[channel+4] = RES_ID_CHANNEL[channel];
+ processSongData(channel+4);
+ --channel;
+ }
+}
+
+int Player_SID::setupSongFileData() { // $36cb
+ // no song playing
+ // TODO: remove (never NULL)
+ if (_music == NULL) {
+ for (int i = 2; i >= 0; --i) {
+ if (songChannelBits & BITMASK[i]) {
+ func_3674(i);
+ }
+ }
+ return 1;
+ }
+
+ // no new song
+ songFileOrChanBufData = _music;
+ if (_music == actSongFileData) {
+ return 0;
+ }
+
+ // new song selected
+ actSongFileData = _music;
+ for (int i = 0; i < 3; ++i) {
+ chanFileData[i] = _music + chanDataOffset[i];
+ }
+
+ return -1;
+}
+
+//x:0..2
+void Player_SID::func_3674(int channel) { // $3674
+ statusBits1B &= BITMASK_INV[channel];
+ if (statusBits1B == 0) {
+ isMusicPlaying = false;
+ unlockCodeLocation();
+ safeUnlockResource(resID_song);
+ for (int i = 0; i < 3; ++i) {
+ safeUnlockResource(RES_ID_CHANNEL[i]);
+ }
+ }
+
+ chanPrio[channel] = 2;
+
+ statusBits1A &= BITMASK_INV[channel];
+ phaseBit[channel] = 0;
+
+ func_4F45(channel);
+}
+
+void Player_SID::resetPlayerState() { // $48f7
+ for (int i = 6; i >= 0; --i)
+ releaseChannel(i);
+
+ isMusicPlaying = false;
+ unlockCodeLocation(); // does nothing
+ statusBits1B = 0;
+ statusBits1A = 0;
+ freeChannelCount = 3;
+ swapPrepared = false;
+ filterSwapped = false;
+ pulseWidthSwapped = false;
+ //var5163 = 0;
+}
+
+void Player_SID::resetSID() { // $48D8
+ SIDReg24 = 0x0f;
+
+ SID_Write( 4, 0);
+ SID_Write(11, 0);
+ SID_Write(18, 0);
+ SID_Write(23, 0);
+ SID_Write(21, 0);
+ SID_Write(22, 0);
+ SID_Write(24, SIDReg24);
+
+ resetPlayerState();
+}
+
+void Player_SID::update() { // $481B
+ if (initializing)
+ return;
+
+ if (_soundInQueue) {
+ for (int i = 6; i >= 0; --i) {
+ if (_soundQueue[i] != -1)
+ processSongData(i);
+ }
+ _soundInQueue = false;
+ }
+
+ // no sound
+ if (busyChannelBits == 0)
+ return;
+
+ for (int i = 6; i >= 0; --i) {
+ if (busyChannelBits & BITMASK[i]) {
+ updateFreq(i);
+ }
+ }
+
+ // seems to be used for background (prio=1?) sounds.
+ // If a bg sound cannot be played because all SID
+ // voices are used by higher priority sounds, the
+ // bg sound's state is updated here so it will be at
+ // the correct state when a voice is available again.
+ if (swapPrepared) {
+ swapVars(0, 0);
+ swapVarLoaded = true;
+ updateFreq(0);
+ swapVars(0, 0);
+ if (pulseWidthSwapped) {
+ swapVars(4, 1);
+ updateFreq(4);
+ swapVars(4, 1);
+ }
+ swapVarLoaded = false;
+ }
+
+ for (int i = 6; i >= 0; --i) {
+ if (busyChannelBits & BITMASK[i])
+ setSIDWaveCtrlReg(i);
+ };
+
+ if (isMusicPlaying) {
+ handleMusicBuffer();
+ }
+
+ return;
+}
+
+// channel: 0..6
+void Player_SID::processSongData(int channel) { // $4939
+ // always: _soundQueue[channel] != -1
+ // -> channelMap[channel] != -1
+ channelMap[channel] = _soundQueue[channel];
+ _soundQueue[channel] = -1;
+ songPosUpdateCounter[channel] = 0;
+
+ isVoiceChannel = (channel < 3);
+
+ songFileOrChanBufOffset[channel] = vec6[channel];
+
+ setupSongPtr(channel);
+
+ //vec5[channel] = songFileOrChanBufData; // not used
+
+ if (songFileOrChanBufData == NULL) { // chanBuf (4C1C)
+ /*
+ // TODO: do we need this?
+ LOBYTE_(vec20[channel]) = 0;
+ LOBYTE_(songPosPtr[channel]) = LOBYTE_(songFileOrChanBufOffset[channel]);
+ */
+ releaseResourceUnk(channel);
+ return;
+ }
+
+ vec20[channel] = songFileOrChanBufData; // chanBuf (4C1C)
+ songPosPtr[channel] = songFileOrChanBufData + songFileOrChanBufOffset[channel]; // chanBuf (4C1C)
+ uint8* ptr1 = songPosPtr[channel];
+
+ int y = -1;
+ if (channel < 4) {
+ ++y;
+ if (channel == 3) {
+ readSetSIDFilterAndProps(&y, ptr1);
+ } else if (statusBits1A & BITMASK[channel]) {
+ ++y;
+ } else { // channel = 0/1/2
+ waveCtrlReg[channel] = ptr1[y];
+
+ ++y;
+ if (ptr1[y] & 0x0f) {
+ // filter on for voice channel
+ SIDReg23 |= BITMASK[channel];
+ } else {
+ // filter off for voice channel
+ SIDReg23 &= BITMASK_INV[channel];
+ }
+ SID_Write(23, SIDReg23);
+ }
+ }
+
+ saveSongPos(y, channel);
+ busyChannelBits |= BITMASK[channel];
+ readSongChunk(channel);
+}
+
+void Player_SID::readSetSIDFilterAndProps(int *offset, uint8* dataPtr) { // $49e7
+ SIDReg23 |= dataPtr[*offset];
+ SID_Write(23, SIDReg23);
+ ++*offset;
+ SIDReg24 = dataPtr[*offset];
+ SID_Write(24, SIDReg24);
+}
+
+void Player_SID::saveSongPos(int y, int channel) {
+ ++y;
+ songPosPtr[channel] += y;
+ songFileOrChanBufOffset[channel] += y;
+}
+
+// channel: 0..6
+void Player_SID::updateFreq(int channel) {
+ isVoiceChannel = (channel < 3);
+
+ --freqDeltaCounter[channel];
+ if (freqDeltaCounter[channel] < 0) {
+ readSongChunk(channel);
+ } else {
+ freqReg[channel] += freqDelta[channel];
+ }
+ setSIDFreqAS(channel);
+}
+
+void Player_SID::resetFreqDelta(int channel) {
+ freqDeltaCounter[channel] = 0;
+ freqDelta[channel] = 0;
+}
+
+void Player_SID::readSongChunk(int channel) { // $4a6b
+ while (true) {
+ if (setupSongPtr(channel) == 1) {
+ // do something with code resource
+ releaseResourceUnk(1);
+ return;
+ }
+
+ uint8* ptr1 = songPosPtr[channel];
+
+ //curChannelActive = true;
+
+ uint8 l_cmdByte = ptr1[0];
+ if (l_cmdByte == 0) {
+ //curChannelActive = false;
+ songPosUpdateCounter[channel] = 0;
+
+ var481A = -1;
+ releaseChannel(channel);
+ return;
+ }
+
+ //vec19[channel] = l_cmdByte;
+
+ // attack (1) / release (0) phase
+ if (isVoiceChannel) {
+ if (GETBIT(l_cmdByte, 0))
+ waveCtrlReg[channel] |= 0x01; // start attack phase
+ else
+ waveCtrlReg[channel] &= 0xfe; // start release phase
+ }
+
+ // channel finished bit
+ if (GETBIT(l_cmdByte, 1)) {
+ var481A = -1;
+ releaseChannel(channel);
+ return;
+ }
+
+ int y = 0;
+
+ // frequency
+ if (GETBIT(l_cmdByte, 2)) {
+ y += 2;
+ freqReg[channel] = READ_LE_UINT16(&ptr1[y-1]);
+ if (!GETBIT(l_cmdByte, 6)) {
+ y += 2;
+ freqDeltaCounter[channel] = READ_LE_UINT16(&ptr1[y-1]);
+ y += 2;
+ freqDelta[channel] = READ_LE_UINT16(&ptr1[y-1]);
+ } else {
+ resetFreqDelta(channel);
+ }
+ } else {
+ resetFreqDelta(channel);
+ }
+
+ // attack / release
+ if (isVoiceChannel && GETBIT(l_cmdByte, 3)) {
+ // start release phase
+ waveCtrlReg[channel] &= 0xfe;
+ setSIDWaveCtrlReg(channel);
+
+ ++y;
+ attackReg[channel] = ptr1[y];
+ ++y;
+ sustainReg[channel] = ptr1[y];
+
+ // set attack (1) or release (0) phase
+ waveCtrlReg[channel] |= (l_cmdByte & 0x01);
+ }
+
+ if (GETBIT(l_cmdByte, 4)) {
+ ++y;
+ uint8 curByte = ptr1[y];
+
+ // pulse width
+ if (isVoiceChannel && GETBIT(curByte, 0)) {
+ int reg = SID_REG_OFFSET[channel+4];
+
+ y += 2;
+ SID_Write(reg, ptr1[y-1]);
+ SID_Write(reg+1, ptr1[y]);
+ }
+
+ if (GETBIT(curByte, 1)) {
+ ++y;
+ readSetSIDFilterAndProps(&y, ptr1);
+
+ y += 2;
+ SID_Write(21, ptr1[y-1]);
+ SID_Write(22, ptr1[y]);
+ }
+
+ if (GETBIT(curByte, 2)) {
+ resetFreqDelta(channel);
+
+ y += 2;
+ freqDeltaCounter[channel] = READ_LE_UINT16(&ptr1[y-1]);
+ }
+ }
+
+ // set waveform (?)
+ if (GETBIT(l_cmdByte, 5)) {
+ ++y;
+ waveCtrlReg[channel] = (waveCtrlReg[channel] & 0x0f) | ptr1[y];
+ }
+
+ // song position
+ if (GETBIT(l_cmdByte, 7)) {
+ if (songPosUpdateCounter[channel] == 1) {
+ y += 2;
+ --songPosUpdateCounter[channel];
+ saveSongPos(y, channel);
+ } else {
+ // looping / skipping / ...
+ ++y;
+ songPosPtr[channel] -= ptr1[y];
+ songFileOrChanBufOffset[channel] -= ptr1[y];
+
+ ++y;
+ if (songPosUpdateCounter[channel] == 0) {
+ songPosUpdateCounter[channel] = ptr1[y];
+ } else {
+ --songPosUpdateCounter[channel];
+ }
+ }
+ } else {
+ saveSongPos(y, channel);
+ return;
+ }
+ }
+}
+
+/**
+ * Sets frequency, attack and sustain register
+ */
+void Player_SID::setSIDFreqAS(int channel) { // $4be6
+ if (swapVarLoaded)
+ return;
+ int reg = SID_REG_OFFSET[channel];
+ SID_Write(reg, LOBYTE_(freqReg[channel])); // freq/pulseWidth voice 1/2/3
+ SID_Write(reg+1, HIBYTE_(freqReg[channel]));
+ if (channel < 3) {
+ SID_Write(reg+5, attackReg[channel]); // attack
+ SID_Write(reg+6, sustainReg[channel]); // sustain
+ }
+}
+
+void Player_SID::setSIDWaveCtrlReg(int channel) { // $4C0D
+ if (channel < 3) {
+ int reg = SID_REG_OFFSET[channel];
+ SID_Write(reg+4, waveCtrlReg[channel]);
+ }
+}
+
+// channel: 0..6
+int Player_SID::setupSongPtr(int channel) { // $4C1C
+ //resID:5,4,3,songid
+ int resID = channelMap[channel];
+
+ // TODO: when does this happen, only if resID == 0?
+ if (getResource(resID) == NULL) {
+ releaseResourceUnk(resID);
+ if (resID == bgSoundResID) {
+ bgSoundResID = 0;
+ bgSoundActive = false;
+ swapPrepared = false;
+ pulseWidthSwapped = false;
+ }
+ return 1;
+ }
+
+ songFileOrChanBufData = getResource(resID); // chanBuf (4C1C)
+ if (songFileOrChanBufData == vec20[channel]) {
+ return 0;
+ } else {
+ vec20[channel] = songFileOrChanBufData;
+ songPosPtr[channel] = songFileOrChanBufData + songFileOrChanBufOffset[channel];
+ return -1;
+ }
+}
+
+// ignore: no effect
+// chanResIndex: 3,4,5 or 58
+void Player_SID::unlockResource(int chanResIndex) { // $4CDA
+ if ((resStatus[chanResIndex] & 0x7F) != 0)
+ --resStatus[chanResIndex];
+}
+
+void Player_SID::countFreeChannels() { // $4f26
+ freeChannelCount = 0;
+ for (int i = 0; i < 3; ++i) {
+ if (GETBIT(usedChannelBits, i) == 0)
+ ++freeChannelCount;
+ }
+}
+
+void Player_SID::func_4F45(int channel) { // $4F45
+ if (swapVarLoaded) {
+ if (channel == 0) {
+ swapPrepared = false;
+ resetSwapVars();
+ }
+ pulseWidthSwapped = false;
+ } else {
+ if (channel == 3) {
+ filterUsed = false;
+ }
+
+ if (chanPrio[channel] == 1) {
+ if (var481A == 1)
+ prepareSwapVars(channel);
+ else if (channel < 3)
+ clearSIDWaveform(channel);
+ } else if (channel < 3 && bgSoundActive && swapPrepared &&
+ !(filterSwapped && filterUsed))
+ {
+ busyChannelBits |= BITMASK[channel];
+ useSwapVars(channel);
+ waveCtrlReg[channel] |= 0x01;
+ setSIDWaveCtrlReg(channel);
+
+ safeUnlockResource(channelMap[channel]);
+ return;
+ }
+
+ chanPrio[channel] = 0;
+ usedChannelBits &= BITMASK_INV[channel];
+ countFreeChannels();
+ }
+
+ int resIndex = channelMap[channel];
+ channelMap[channel] = 0;
+ safeUnlockResource(resIndex);
+}
+
+// chanResIndex: 3,4,5 or 58
+void Player_SID::safeUnlockResource(int resIndex) { // $4FEA
+ if (!isMusicPlaying) {
+ unlockResource(resIndex);
+ }
+}
+
+void Player_SID::releaseResource(int resIndex) { // $5031
+ releaseResChannels(resIndex);
+ if (resIndex == bgSoundResID && var481A == -1) {
+ safeUnlockResource(resIndex);
+
+ bgSoundResID = 0;
+ bgSoundActive = false;
+ swapPrepared = false;
+ pulseWidthSwapped = false;
+
+ resetSwapVars();
+ }
+}
+
+void Player_SID::releaseResChannels(int resIndex) { // $5070
+ for (int i = 3; i >= 0; --i) {
+ if (resIndex == channelMap[i]) {
+ releaseChannel(i);
+ }
+ }
+}
+
+void Player_SID::stopSound_intern(int soundResID) { // $5093
+ for (int i = 0; i < 7; ++i) {
+ if (soundResID == _soundQueue[i]) {
+ _soundQueue[i] = -1;
+ }
+ }
+ var481A = -1;
+ releaseResource(soundResID);
+}
+
+void Player_SID::stopMusic_intern() { // $4CAA
+ statusBits1B = 0;
+ isMusicPlaying = false;
+
+ if (resID_song != 0) {
+ unlockResource(resID_song);
+ }
+
+ chanPrio[0] = 2;
+ chanPrio[1] = 2;
+ chanPrio[2] = 2;
+
+ statusBits1A = 0;
+ phaseBit[0] = 0;
+ phaseBit[1] = 0;
+ phaseBit[2] = 0;
+}
+
+void Player_SID::releaseResourceUnk(int resIndex) { // $50A4
+ var481A = -1;
+ releaseResource(resIndex);
+}
+
+// a: 0..6
+void Player_SID::releaseChannel(int channel) {
+ stopChannel(channel);
+ if (channel >= 4) {
+ return;
+ }
+ if (channel < 3) {
+ SIDReg23Stuff = SIDReg23;
+ clearSIDWaveform(channel);
+ }
+ func_4F45(channel);
+ if (channel >= 3) {
+ return;
+ }
+ if ((SIDReg23 != SIDReg23Stuff) &&
+ (SIDReg23 & 0x07) == 0)
+ {
+ if (filterUsed) {
+ func_4F45(3);
+ stopChannel(3);
+ }
+ }
+
+ stopChannel(channel + 4);
+}
+
+void Player_SID::clearSIDWaveform(int channel) {
+ if (!isMusicPlaying && var481A == -1) {
+ waveCtrlReg[channel] &= 0x0e;
+ setSIDWaveCtrlReg(channel);
+ }
+}
+
+void Player_SID::stopChannel(int channel) {
+ songPosUpdateCounter[channel] = 0;
+ // clear "channel" bit
+ busyChannelBits &= BITMASK_INV[channel];
+ if (channel >= 4) {
+ // pulsewidth = 0
+ channelMap[channel] = 0;
+ }
+}
+
+// channel: 0..6, swapIndex: 0..2
+void Player_SID::swapVars(int channel, int swapIndex) { // $51a5
+ if (channel < 3) {
+ SWAP(attackReg[channel], swapAttack[swapIndex]);
+ SWAP(sustainReg[channel], swapSustain[swapIndex]);
+ }
+ //SWAP(vec5[channel], swapVec5[swapIndex]); // not used
+ //SWAP(vec19[channel], swapVec19[swapIndex]); // not used
+
+ SWAP(chanPrio[channel], swapSongPrio[swapIndex]);
+ SWAP(channelMap[channel], swapVec479C[swapIndex]);
+ SWAP(songPosUpdateCounter[channel], swapSongPosUpdateCounter[swapIndex]);
+ SWAP(waveCtrlReg[channel], swapWaveCtrlReg[swapIndex]);
+ SWAP(songPosPtr[channel], swapSongPosPtr[swapIndex]);
+ SWAP(freqReg[channel], swapFreqReg[swapIndex]);
+ SWAP(freqDeltaCounter[channel], swapVec11[swapIndex]);
+ SWAP(freqDelta[channel], swapVec10[swapIndex]);
+ SWAP(vec20[channel], swapVec20[swapIndex]);
+ SWAP(songFileOrChanBufOffset[channel], swapVec8[swapIndex]);
+}
+
+void Player_SID::resetSwapVars() { // $52d0
+ for (int i = 0; i < 2; ++i) {
+ swapAttack[i] = 0;
+ swapSustain[i] = 0;
+ }
+ for (int i = 0; i < 3; ++i) {
+ swapVec5[i] = 0;
+ swapSongPrio[i] = 0;
+ swapVec479C[i] = 0;
+ swapVec19[i] = 0;
+ swapSongPosUpdateCounter[i] = 0;
+ swapWaveCtrlReg[i] = 0;
+ swapSongPosPtr[i] = 0;
+ swapFreqReg[i] = 0;
+ swapVec11[i] = 0;
+ swapVec10[i] = 0;
+ swapVec20[i] = 0;
+ swapVec8[i] = 0;
+ }
+}
+
+void Player_SID::prepareSwapVars(int channel) { // $52E5
+ if (channel >= 4)
+ return;
+
+ if (channel < 3) {
+ if (!keepSwapVars) {
+ resetSwapVars();
+ }
+ swapVars(channel, 0);
+ if (busyChannelBits & BITMASK[channel+4]) {
+ swapVars(channel+4, 1);
+ pulseWidthSwapped = true;
+ }
+ } else if (channel == 3) {
+ SIDReg24_HiNibble = SIDReg24 & 0x70;
+ resetSwapVars();
+ keepSwapVars = true;
+ swapVars(3, 2);
+ filterSwapped = true;
+ }
+ swapPrepared = true;
+}
+
+void Player_SID::useSwapVars(int channel) { // $5342
+ if (channel >= 3)
+ return;
+
+ swapVars(channel, 0);
+ setSIDFreqAS(channel);
+ if (pulseWidthSwapped) {
+ swapVars(channel+4, 1);
+ setSIDFreqAS(channel+4);
+ }
+ if (filterSwapped) {
+ swapVars(3, 2);
+
+ // resonating filter freq. or voice-to-filter mapping?
+ SIDReg23 = (SIDReg23Stuff & 0xf0) | BITMASK[channel];
+ SID_Write(23, SIDReg23);
+
+ // filter props
+ SIDReg24 = (SIDReg24 & 0x0f) | SIDReg24_HiNibble;
+ SID_Write(24, SIDReg24);
+
+ // filter freq.
+ SID_Write(21, LOBYTE_(freqReg[3]));
+ SID_Write(22, HIBYTE_(freqReg[3]));
+ } else {
+ SIDReg23 = SIDReg23Stuff & BITMASK_INV[channel];
+ SID_Write(23, SIDReg23);
+ }
+
+ swapPrepared = false;
+ pulseWidthSwapped = false;
+ keepSwapVars = false;
+ SIDReg24_HiNibble = 0;
+ filterSwapped = false;
+}
+
+// ignore: no effect
+// resIndex: 3,4,5 or 58
+void Player_SID::lockResource(int resIndex) { // $4ff4
+ if (!isMusicPlaying)
+ ++resStatus[resIndex];
+}
+
+void Player_SID::reserveChannel(int channel, uint8 prioValue, int chanResIndex) { // $4ffe
+ if (channel == 3) {
+ filterUsed = true;
+ } else if (channel < 3) {
+ usedChannelBits |= BITMASK[channel];
+ countFreeChannels();
+ }
+
+ chanPrio[channel] = prioValue;
+ lockResource(chanResIndex);
+}
+
+// ignore: no effect
+void Player_SID::unlockCodeLocation() { // $513e
+ resStatus[1] &= 0x80;
+ resStatus[2] &= 0x80;
+}
+
+// ignore: no effect
+void Player_SID::lockCodeLocation() { // $514f
+ resStatus[1] |= 0x01;
+ resStatus[2] |= 0x01;
+}
+
+void Player_SID::initMusic(int songResIndex) { // $7de6
+ unlockResource(resID_song);
+
+ resID_song = songResIndex;
+ _music = getResource(resID_song);
+ if (_music == NULL) {
+ return;
+ }
+
+ // song base address
+ uint8* songFileDataPtr = _music;
+ actSongFileData = _music;
+
+ initializing = true;
+ _soundInQueue = false;
+ isMusicPlaying = false;
+
+ unlockCodeLocation();
+ resetPlayerState();
+
+ lockResource(resID_song);
+ buildStepTbl(songFileDataPtr[5]);
+
+ // fetch sound
+ songChannelBits = songFileDataPtr[4];
+ for (int i = 2; i >= 0; --i) {
+ if ((songChannelBits & BITMASK[i]) != 0) {
+ func_7eae(i, songFileDataPtr);
+ }
+ }
+
+ isMusicPlaying = true;
+ lockCodeLocation();
+
+ SIDReg23 &= 0xf0;
+ SID_Write(23, SIDReg23);
+
+ handleMusicBuffer();
+
+ initializing = false;
+ _soundInQueue = true;
+}
+
+// params:
+// channel: channel 0..2
+void Player_SID::func_7eae(int channel, uint8* songFileDataPtr) {
+ int pos = SONG_CHANNEL_OFFSET[channel];
+ chanDataOffset[channel] = READ_LE_UINT16(&songFileDataPtr[pos]);
+ chanFileData[channel] = songFileDataPtr + chanDataOffset[channel];
+
+ //vec5[channel+4] = vec5[channel] = CHANNEL_BUFFER_ADDR[RES_ID_CHANNEL[channel]]; // not used
+ vec6[channel+4] = 0x0019;
+ vec6[channel] = 0x0008;
+
+ func_819b(channel);
+
+ waveCtrlReg[channel] = 0;
+}
+
+void Player_SID::func_819b(int channel) {
+ reserveChannel(channel, 127, RES_ID_CHANNEL[channel]);
+
+ statusBits1B |= BITMASK[channel];
+ statusBits1A |= BITMASK[channel];
+}
+
+void Player_SID::buildStepTbl(int step) { // $82B4
+ stepTbl[0] = 0;
+ stepTbl[1] = step - 2;
+ for (int i = 2; i < 33; ++i) {
+ stepTbl[i] = stepTbl[i-1] + step;
+ }
+}
+
+int Player_SID::reserveSoundFilter(uint8 value, uint8 chanResIndex) { // $4ED0
+ int channel = 3;
+ reserveChannel(channel, value, chanResIndex);
+ return channel;
+}
+
+int Player_SID::reserveSoundVoice(uint8 value, uint8 chanResIndex) { // $4EB8
+ for (int i = 2; i >= 0; --i) {
+ if ((usedChannelBits & BITMASK[i]) == 0) {
+ reserveChannel(i, value, chanResIndex);
+ return i;
+ }
+ }
+ return 0;
+}
+
+void Player_SID::findLessPrioChannels(uint8 soundPrio) { // $4ED8
+ minChanPrio = 127;
+
+ chansWithLowerPrioCount = 0;
+ for (int i = 2; i >= 0; --i) {
+ if (usedChannelBits & BITMASK[i]) {
+ if (chanPrio[i] < soundPrio)
+ ++chansWithLowerPrioCount;
+ if (chanPrio[i] < minChanPrio) {
+ minChanPrio = chanPrio[i];
+ minChanPrioIndex = i;
+ }
+ }
+ }
+
+ if (chansWithLowerPrioCount == 0)
+ return;
+
+ if (soundPrio >= chanPrio[3]) {
+ actFilterHasLowerPrio = true;
+ } else {
+ /* TODO: is this really a no-op?
+ if (minChanPrioIndex < chanPrio[3])
+ minChanPrioIndex = minChanPrioIndex;
+ */
+
+ actFilterHasLowerPrio = false;
+ }
+}
+
+void Player_SID::releaseResourceBySound(int resID) { // $5088
+ var481A = 1;
+ releaseResource(resID);
+}
+
+void Player_SID::readVec6Data(int x, int *offset, uint8 *songFilePtr, int chanResID) { // $4E99
+ //vec5[x] = songFilePtr;
+ vec6[x] = songFilePtr[*offset];
+ *offset += 2;
+ _soundQueue[x] = chanResID;
+}
+
+int Player_SID::initSound(int soundResID) { // $4D0A
+ initializing = true;
+
+ if (isMusicPlaying && (statusBits1A & 0x07) == 0x07) {
+ initializing = false;
+ return -2;
+ }
+
+ uint8 *songFilePtr = getResource(soundResID);
+ if (songFilePtr == NULL) {
+ initializing = false;
+ return 1;
+ }
+
+ uint8 soundPrio = songFilePtr[4];
+ // for (mostly but not always looped) background sounds
+ if (soundPrio == 1) {
+ bgSoundResID = soundResID;
+ bgSoundActive = true;
+ }
+
+ uint8 requestedChannels = 0;
+ if ((songFilePtr[5] & 0x40) == 0) {
+ ++requestedChannels;
+ if (songFilePtr[5] & 0x02)
+ ++requestedChannels;
+ if (songFilePtr[5] & 0x08)
+ ++requestedChannels;
+ }
+
+ bool filterNeeded = (songFilePtr[5] & 0x20) != 0;
+ bool filterBlocked = (filterUsed && filterNeeded);
+ if (filterBlocked || (freeChannelCount < requestedChannels)) {
+ findLessPrioChannels(soundPrio);
+
+ if ((freeChannelCount + chansWithLowerPrioCount < requestedChannels) ||
+ (filterBlocked && !actFilterHasLowerPrio)) {
+ initializing = false;
+ return -1;
+ }
+
+ if (filterBlocked) {
+ if (soundPrio < chanPrio[3]) {
+ initializing = false;
+ return -1;
+ }
+
+ uint8 l_resID = channelMap[3];
+ releaseResourceBySound(l_resID);
+ }
+
+ while ((freeChannelCount < requestedChannels) || (filterNeeded && filterUsed)) {
+ findLessPrioChannels(soundPrio);
+ if (minChanPrio >= soundPrio) {
+ initializing = false;
+ return -1;
+ }
+
+ uint8 l_resID = channelMap[minChanPrioIndex];
+ releaseResourceBySound(l_resID);
+ }
+ }
+
+ int x;
+ uint8 soundByte5 = songFilePtr[5];
+ if (soundByte5 & 0x40)
+ x = reserveSoundFilter(soundPrio, soundResID);
+ else
+ x = reserveSoundVoice(soundPrio, soundResID);
+
+ uint8 var4CF3 = x;
+ int y = 6;
+ if (soundByte5 & 0x01) {
+ x += 4;
+ readVec6Data(x, &y, songFilePtr, soundResID);
+ }
+ if (soundByte5 & 0x02) {
+ x = reserveSoundVoice(soundPrio, soundResID);
+ readVec6Data(x, &y, songFilePtr, soundResID);
+ }
+ if (soundByte5 & 0x04) {
+ x += 4;
+ readVec6Data(x, &y, songFilePtr, soundResID);
+ }
+ if (soundByte5 & 0x08) {
+ x = reserveSoundVoice(soundPrio, soundResID);
+ readVec6Data(x, &y, songFilePtr, soundResID);
+ }
+ if (soundByte5 & 0x10) {
+ x += 4;
+ readVec6Data(x, &y, songFilePtr, soundResID);
+ }
+ if (soundByte5 & 0x20) {
+ x = reserveSoundFilter(soundPrio, soundResID);
+ readVec6Data(x, &y, songFilePtr, soundResID);
+ }
+
+ //vec5[var4CF3] = songFilePtr;
+ vec6[var4CF3] = y;
+ _soundQueue[var4CF3] = soundResID;
+
+ initializing = false;
+ _soundInQueue = true;
+
+ return soundResID;
+}
+
+void Player_SID::unused1() { // $50AF
+ var481A = -1;
+ if (bgSoundResID != 0) {
+ releaseResourceUnk(bgSoundResID);
+ }
+}
+
+///////////////////////////
+///////////////////////////
+
+#define ZEROMEM(a) memset(a, 0, sizeof(a))
+
+Player_SID::Player_SID(ScummEngine *scumm, Audio::Mixer *mixer) {
+ /*
+ * clear memory
+ */
+
+ resID_song = 0;
+ statusBits1A = 0;
+ statusBits1B = 0;
+ busyChannelBits = 0;
+ SIDReg23 = 0;
+ SIDReg23Stuff = 0;
+ SIDReg24 = 0;
+ bgSoundResID = 0;
+ freeChannelCount = 0;
+ usedChannelBits = 0;
+ var481A = 0;
+ songChannelBits = 0;
+ //var5163 = 0;
+ SIDReg24_HiNibble = 0;
+ chansWithLowerPrioCount = 0;
+ minChanPrio = 0;
+ minChanPrioIndex = 0;
+
+ _music = NULL;
+ songFileOrChanBufData = NULL;
+ actSongFileData = NULL;
+
+ initializing = false;
+ _soundInQueue = false;
+ isVoiceChannel = false;
+ isMusicPlaying = false;
+ swapVarLoaded = false;
+ bgSoundActive = false;
+ filterUsed = false;
+ pulseWidthSwapped = false;
+ swapPrepared = false;
+ filterSwapped = false;
+ keepSwapVars = false;
+ actFilterHasLowerPrio = false;
+
+ ZEROMEM(chanFileData);
+ ZEROMEM(chanDataOffset);
+ ZEROMEM(songPosPtr);
+ ZEROMEM(freqReg);
+ ZEROMEM(vec6);
+ ZEROMEM(songFileOrChanBufOffset);
+ ZEROMEM(freqDelta);
+ ZEROMEM(freqDeltaCounter);
+ ZEROMEM(swapSongPosPtr);
+ ZEROMEM(swapVec5);
+ ZEROMEM(swapVec8);
+ ZEROMEM(swapVec10);
+ ZEROMEM(swapFreqReg);
+ ZEROMEM(swapVec11);
+ ZEROMEM(vec20);
+ ZEROMEM(swapVec20);
+ ZEROMEM(resStatus);
+ ZEROMEM(attackReg);
+ ZEROMEM(sustainReg);
+ ZEROMEM(phaseBit);
+ ZEROMEM(releasePhase);
+ ZEROMEM(_soundQueue);
+ ZEROMEM(channelMap);
+ ZEROMEM(songPosUpdateCounter);
+ ZEROMEM(chanPrio);
+ ZEROMEM(waveCtrlReg);
+ ZEROMEM(swapAttack);
+ ZEROMEM(swapSustain);
+ ZEROMEM(swapSongPrio);
+ ZEROMEM(swapVec479C);
+ ZEROMEM(swapVec19);
+ ZEROMEM(swapSongPosUpdateCounter);
+ ZEROMEM(swapWaveCtrlReg);
+ ZEROMEM(stepTbl);
+
+ /*
+ * initialize data
+ */
+
+ const uint8 chanBuffer_const[3][45] = {
+ {
+ 0x00,0x00,0x00,0x00,0x7f,0x01,0x19,0x00,
+ 0x00,0x00,0x2d,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0xf0,0x40,0x10,0x04,0x00,0x00,
+ 0x00,0x04,0x27,0x03,0xff,0xff,0x01,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00
+ },
+ {
+ 0x00,0x00,0x00,0x00,0x7f,0x01,0x19,0x00,
+ 0x00,0x00,0x2d,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0xf0,0x20,0x10,0x04,0x00,0x00,
+ 0x00,0x04,0x27,0x03,0xff,0xff,0x02,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00
+ },
+ {
+ 0x00,0x00,0x00,0x00,0x7f,0x01,0x19,0x00,
+ 0x00,0x00,0x2d,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0xf0,0x20,0x10,0x04,0x00,0x00,
+ 0x00,0x04,0x27,0x03,0xff,0xff,0x02,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00
+ }
+ };
+ memcpy(chanBuffer, chanBuffer_const, sizeof(chanBuffer_const));
+
+ for (int i = 0; i < 7; ++i) {
+ _soundQueue[i] = -1;
+ };
+
+ _music_timer = 0;
+
+ _mixer = mixer;
+ _sampleRate = _mixer->getOutputRate();
+ _vm = scumm;
+
+ // sound speed is slightly different on NTSC and PAL machines
+ // as the SID clock depends on the frame rate.
+ // ScummVM does not distinguish between NTSC and PAL targets
+ // so we use the NTSC timing here as the music was composed for
+ // NTSC systems (music on PAL systems is slower).
+ _videoSystem = NTSC;
+ _cpuCyclesLeft = 0;
+
+ initSID();
+ resetSID();
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_SID::~Player_SID() {
+ _mixer->stopHandle(_soundHandle);
+ delete _sid;
+}
+
+uint8 *Player_SID::getResource(int resID) {
+ switch (resID) {
+ case 0:
+ return NULL;
+ case 3:
+ case 4:
+ case 5:
+ return chanBuffer[resID-3];
+ default:
+ return _vm->getResourceAddress(rtSound, resID);
+ }
+}
+
+int Player_SID::readBuffer(int16 *buffer, const int numSamples) {
+ int samplesLeft = numSamples;
+
+ Common::StackLock lock(_mutex);
+
+ while (samplesLeft > 0) {
+ // update SID status after each frame
+ if (_cpuCyclesLeft <= 0) {
+ update();
+ _cpuCyclesLeft = timingProps[_videoSystem].cyclesPerFrame;
+ }
+ // fetch samples
+ int sampleCount = _sid->updateClock(_cpuCyclesLeft, (short *)buffer, samplesLeft);
+ samplesLeft -= sampleCount;
+ buffer += sampleCount;
+ }
+
+ return numSamples;
+}
+
+void Player_SID::SID_Write(int reg, uint8 data) {
+ _sid->write(reg, data);
+}
+
+void Player_SID::initSID() {
+ _sid = new Resid::SID();
+ _sid->set_sampling_parameters(
+ timingProps[_videoSystem].clockFreq,
+ _sampleRate);
+ _sid->enable_filter(true);
+
+ _sid->reset();
+ // Synchronize the waveform generators (must occur after reset)
+ _sid->write( 4, 0x08);
+ _sid->write(11, 0x08);
+ _sid->write(18, 0x08);
+ _sid->write( 4, 0x00);
+ _sid->write(11, 0x00);
+ _sid->write(18, 0x00);
+}
+
+void Player_SID::startSound(int nr) {
+ byte *data = _vm->getResourceAddress(rtSound, nr);
+ assert(data);
+
+ // WORKAROUND:
+ // sound[4] contains either a song prio or a music channel usage byte.
+ // As music channel usage is always 0x07 for all music files and
+ // prio 7 is never used in any sound file use this byte for auto-detection.
+ bool isMusic = (data[4] == 0x07);
+
+ Common::StackLock lock(_mutex);
+
+ if (isMusic) {
+ initMusic(nr);
+ } else {
+ stopSound_intern(nr);
+ initSound(nr);
+ }
+}
+
+void Player_SID::stopSound(int nr) {
+ if (nr == -1)
+ return;
+
+ Common::StackLock lock(_mutex);
+ stopSound_intern(nr);
+}
+
+void Player_SID::stopAllSounds() {
+ Common::StackLock lock(_mutex);
+ resetPlayerState();
+}
+
+int Player_SID::getSoundStatus(int nr) const {
+ int result = 0;
+
+ //Common::StackLock lock(_mutex);
+
+ if (resID_song == nr && isMusicPlaying) {
+ result = 1;
+ }
+
+ for (int i = 0; (i < 4) && (result == 0); ++i) {
+ if (nr == _soundQueue[i] || nr == channelMap[i]) {
+ result = 1;
+ }
+ }
+
+ return result;
+}
+
+int Player_SID::getMusicTimer() {
+ int result = _music_timer;
+ _music_timer = 0;
+ return result;
+}
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_sid.h b/engines/scumm/players/player_sid.h
new file mode 100644
index 0000000000..a48ec793cd
--- /dev/null
+++ b/engines/scumm/players/player_sid.h
@@ -0,0 +1,276 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_SID_H
+#define SCUMM_PLAYERS_PLAYER_SID_H
+
+#include "common/mutex.h"
+#include "common/scummsys.h"
+#include "scumm/music.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/softsynth/sid.h"
+
+namespace Scumm {
+
+// the "channel" parameters seem to be in fact SID register
+// offsets. Should be replaced.
+enum sid_reg_t {
+ FREQ_VOICE1,
+ FREQ_VOICE2,
+ FREQ_VOICE3,
+ FREQ_FILTER,
+ PULSE_VOICE1,
+ PULSE_VOICE2,
+ PULSE_VOICE3
+};
+
+enum VideoStandard {
+ PAL,
+ NTSC
+};
+
+class ScummEngine;
+
+class Player_SID : public Audio::AudioStream, public MusicEngine {
+public:
+ Player_SID(ScummEngine *scumm, Audio::Mixer *mixer);
+ virtual ~Player_SID();
+
+ virtual void setMusicVolume(int vol) { _maxvol = vol; }
+ virtual void startSound(int sound);
+ virtual void stopSound(int sound);
+ virtual void stopAllSounds();
+ virtual int getSoundStatus(int sound) const;
+ virtual int getMusicTimer();
+
+ // AudioStream API
+ int readBuffer(int16 *buffer, const int numSamples);
+ bool isStereo() const { return false; }
+ bool endOfData() const { return false; }
+ int getRate() const { return _sampleRate; }
+
+private:
+ Resid::SID *_sid;
+ void SID_Write(int reg, uint8 data);
+ void initSID();
+ uint8 *getResource(int resID);
+
+ // number of cpu cycles until next frame update
+ Resid::cycle_count _cpuCyclesLeft;
+
+ ScummEngine *_vm;
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _soundHandle;
+ int _sampleRate;
+ int _maxvol;
+ Common::Mutex _mutex;
+
+ VideoStandard _videoSystem;
+
+ int _music_timer;
+ uint8* _music;
+
+private:
+ void initMusic(int songResIndex); // $7de6
+ int initSound(int soundResID); // $4D0A
+ void stopSound_intern(int soundResID); // $5093
+ void stopMusic_intern(); // $4CAA
+
+ void resetSID(); // $48D8
+ void update(); // $481B
+ void handleMusicBuffer();
+ int setupSongFileData(); // $36cb
+ void func_3674(int channel); // $3674
+ void resetPlayerState(); // $48f7
+ void processSongData(int channel); // $4939
+ void readSetSIDFilterAndProps(int *offset, uint8* dataPtr); // $49e7
+ void saveSongPos(int y, int channel);
+ void updateFreq(int channel);
+ void resetFreqDelta(int channel);
+ void readSongChunk(int channel); // $4a6b
+ void setSIDFreqAS(int channel); // $4be6
+ void setSIDWaveCtrlReg(int channel); // $4C0D
+ int setupSongPtr(int channel); // $4C1C
+ void unlockResource(int chanResIndex); // $4CDA
+ void countFreeChannels(); // $4f26
+ void func_4F45(int channel); // $4F45
+ void safeUnlockResource(int resIndex); // $4FEA
+ void releaseResource(int resIndex); // $5031
+ void releaseResChannels(int resIndex); // $5070
+ void releaseResourceUnk(int resIndex); // $50A4
+ void releaseChannel(int channel);
+ void clearSIDWaveform(int channel);
+ void stopChannel(int channel);
+ void swapVars(int channel, int swapIndex); // $51a5
+ void resetSwapVars(); // $52d0
+ void prepareSwapVars(int channel); // $52E5
+ void useSwapVars(int channel); // $5342
+ void lockResource(int resIndex); // $4ff4
+ void reserveChannel(int channel, uint8 prioValue, int chanResIndex); // $4ffe
+ void unlockCodeLocation(); // $513e
+ void lockCodeLocation(); // $514f
+ void func_7eae(int channel, uint8* songFileDataPtr); // $7eae
+ void func_819b(int channel); // $819b
+ void buildStepTbl(int step); // $82B4
+ int reserveSoundFilter(uint8 value, uint8 chanResIndex); // $4ED0
+ int reserveSoundVoice(uint8 value, uint8 chanResIndex); // $4EB8
+ void findLessPrioChannels(uint8 soundPrio); // $4ED8
+ void releaseResourceBySound(int resID); // $5088
+ void readVec6Data(int x, int *offset, uint8 *songFilePtr, int chanResID); // $4E99
+
+ void unused1(); // $50AF
+
+ uint8 chanBuffer[3][45];
+
+ int resID_song;
+
+ // statusBits1A/1B are always equal
+ uint8 statusBits1A;
+ uint8 statusBits1B;
+
+ uint8 busyChannelBits;
+
+ uint8 SIDReg23;
+ uint8 SIDReg23Stuff;
+ uint8 SIDReg24;
+
+ uint8* chanFileData[3];
+ uint16 chanDataOffset[3];
+ uint8* songPosPtr[7];
+
+ // 0..2: freq value voice1/2/3
+ // 3: filter freq
+ // 4..6: pulse width
+ uint16 freqReg[7];
+
+ // start offset[i] for songFileOrChanBufData to obtain songPosPtr[i]
+ // vec6[0..2] = 0x0008;
+ // vec6[4..6] = 0x0019;
+ uint16 vec6[7];
+
+ // current offset[i] for songFileOrChanBufData to obtain songPosPtr[i] (starts with vec6[i], increased later)
+ uint16 songFileOrChanBufOffset[7];
+
+ uint16 freqDelta[7];
+ int freqDeltaCounter[7];
+ uint8* swapSongPosPtr[3];
+ uint8* swapVec5[3];
+ uint16 swapVec8[3];
+ uint16 swapVec10[3];
+ uint16 swapFreqReg[3];
+ int swapVec11[3];
+
+ // never read
+ //uint8* vec5[7];
+ // never read
+ //uint8 vec19[7];
+ // never read (needed by scumm engine?)
+ //bool curChannelActive;
+
+ uint8* vec20[7];
+
+ uint8* swapVec20[3];
+
+ // resource status (never read)
+ // bit7: some flag
+ // bit6..0: counter (use-count?), maybe just bit0 as flag (used/unused?)
+ uint8 resStatus[70];
+
+ uint8* songFileOrChanBufData;
+ uint8* actSongFileData;
+
+ uint16 stepTbl[33];
+
+ bool initializing;
+ bool _soundInQueue;
+ bool isVoiceChannel;
+
+ bool isMusicPlaying;
+ bool swapVarLoaded;
+ bool bgSoundActive;
+ bool filterUsed;
+
+ uint8 bgSoundResID;
+ uint8 freeChannelCount;
+
+ // seems to be used for managing the three voices
+ // bit[0..2]: 0 -> unused, 1 -> already in use
+ uint8 usedChannelBits;
+ uint8 attackReg[3];
+ uint8 sustainReg[3];
+
+ // -1/0/1
+ int var481A;
+
+ // bit-array: 00000cba
+ // a/b/c: channel1/2/3
+ uint8 songChannelBits;
+
+ bool pulseWidthSwapped;
+ bool swapPrepared;
+
+ // never read
+ //uint8 var5163;
+
+ bool filterSwapped;
+ uint8 SIDReg24_HiNibble;
+ bool keepSwapVars;
+
+ uint8 phaseBit[3];
+ bool releasePhase[3];
+
+ // values: a resID or -1
+ // resIDs: 3, 4, 5 or song-number
+ int _soundQueue[7];
+
+ // values: a resID or 0
+ // resIDs: 3, 4, 5 or song-number
+ int channelMap[7];
+
+ uint8 songPosUpdateCounter[7];
+
+ // priortity of channel contents
+ // MM: 1: lowest .. 120: highest (1,2,A,64,6E,73,78)
+ // Zak: -???: lowest .. 120: highest (5,32,64,65,66,6E,78, A5,A6,AF,D7)
+ uint8 chanPrio[7];
+
+ // only [0..2] used?
+ uint8 waveCtrlReg[7];
+
+ uint8 swapAttack[2];
+ uint8 swapSustain[2];
+ uint8 swapSongPrio[3];
+ int swapVec479C[3];
+ uint8 swapVec19[3];
+ uint8 swapSongPosUpdateCounter[3];
+ uint8 swapWaveCtrlReg[3];
+
+ bool actFilterHasLowerPrio;
+ uint8 chansWithLowerPrioCount;
+ uint8 minChanPrio;
+ uint8 minChanPrioIndex;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_towns.cpp b/engines/scumm/players/player_towns.cpp
new file mode 100644
index 0000000000..acbdbc7fb6
--- /dev/null
+++ b/engines/scumm/players/player_towns.cpp
@@ -0,0 +1,753 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+#include "scumm/sound.h"
+#include "scumm/players/player_towns.h"
+
+namespace Scumm {
+
+Player_Towns::Player_Towns(ScummEngine *vm, bool isVersion2) : _vm(vm), _v2(isVersion2), _intf(0), _numSoundMax(isVersion2 ? 256 : 200), _unkFlags(0x33) {
+ memset(_pcmCurrentSound, 0, sizeof(_pcmCurrentSound));
+}
+
+void Player_Towns::setSfxVolume(int vol) {
+ if (!_intf)
+ return;
+ _intf->setSoundEffectVolume(vol);
+}
+
+int Player_Towns::getSoundStatus(int sound) const {
+ if (!_intf)
+ return 0;
+ for (int i = 1; i < 9; i++) {
+ if (_pcmCurrentSound[i].index == sound)
+ return _intf->callback(40, 0x3f + i) ? 1 : 0;
+ }
+ return 0;
+}
+
+void Player_Towns::saveLoadWithSerializer(Serializer *ser) {
+ static const SaveLoadEntry pcmEntries[] = {
+ MKLINE(PcmCurrentSound, index, sleInt16, VER(81)),
+ MKLINE(PcmCurrentSound, chan, sleInt16, VER(81)),
+ MKLINE(PcmCurrentSound, note, sleUint8, VER(81)),
+ MKLINE(PcmCurrentSound, velo, sleUint8, VER(81)),
+ MKLINE(PcmCurrentSound, pan, sleUint8, VER(81)),
+ MKLINE(PcmCurrentSound, paused, sleUint8, VER(81)),
+ MKLINE(PcmCurrentSound, looping, sleUint8, VER(81)),
+ MKLINE(PcmCurrentSound, priority, sleUint32, VER(81)),
+ MKEND()
+ };
+
+ for (int i = 1; i < 9; i++) {
+ if (!_pcmCurrentSound[i].index)
+ continue;
+
+ if (_intf->callback(40, i + 0x3f))
+ continue;
+
+ _intf->callback(39, i + 0x3f);
+
+ _pcmCurrentSound[i].index = 0;
+ }
+
+ ser->saveLoadArrayOf(_pcmCurrentSound, 9, sizeof(PcmCurrentSound), pcmEntries);
+}
+
+void Player_Towns::restoreAfterLoad() {
+ Common::Array<uint16> restoredSounds;
+
+ for (int i = 1; i < 9; i++) {
+ if (!_pcmCurrentSound[i].index || _pcmCurrentSound[i].index == 0xffff)
+ continue;
+
+ // Don't restart multichannel sounds more than once
+ if (Common::find(restoredSounds.begin(), restoredSounds.end(), _pcmCurrentSound[i].index) != restoredSounds.end())
+ continue;
+
+ if (!_v2)
+ restoredSounds.push_back(_pcmCurrentSound[i].index);
+
+ uint8 *ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index);
+ if (!ptr)
+ continue;
+
+ if (_vm->_game.version != 3)
+ ptr += 2;
+
+ if (ptr[13])
+ continue;
+
+ playPcmTrack(_pcmCurrentSound[i].index, ptr + 6, _pcmCurrentSound[i].velo, _pcmCurrentSound[i].pan, _pcmCurrentSound[i].note, _pcmCurrentSound[i].priority);
+ }
+}
+
+void Player_Towns::playPcmTrack(int sound, const uint8 *data, int velo, int pan, int note, int priority) {
+ if (!_intf)
+ return;
+
+ const uint8 *sfxData = data + 16;
+
+ int numChan = _v2 ? 1 : data[14];
+ for (int i = 0; i < numChan; i++) {
+ int chan = allocatePcmChannel(sound, i, priority);
+ if (!chan)
+ return;
+
+ _intf->callback(70, _unkFlags);
+ _intf->callback(3, chan + 0x3f, pan);
+ _intf->callback(37, chan + 0x3f, note, velo, sfxData);
+
+ _pcmCurrentSound[chan].note = note;
+ _pcmCurrentSound[chan].velo = velo;
+ _pcmCurrentSound[chan].pan = pan;
+ _pcmCurrentSound[chan].paused = 0;
+ _pcmCurrentSound[chan].looping = READ_LE_UINT32(&sfxData[20]) ? 1 : 0;
+
+ sfxData += (READ_LE_UINT32(&sfxData[12]) + 32);
+ }
+}
+
+void Player_Towns::stopPcmTrack(int sound) {
+ if (!_intf)
+ return;
+
+ for (int i = 1; i < 9; i++) {
+ if (sound == _pcmCurrentSound[i].index || !sound) {
+ _intf->callback(39, i + 0x3f);
+ _pcmCurrentSound[i].index = 0;
+ }
+ }
+}
+
+int Player_Towns::allocatePcmChannel(int sound, int sfxChanRelIndex, uint32 priority) {
+ if (!_intf)
+ return 0;
+
+ int chan = 0;
+
+ if (_v2 && priority > 255) {
+ chan = 8;
+ if (_intf->callback(40, 0x47))
+ _intf->callback(39, 0x47);
+ } else {
+ for (int i = 8; i; i--) {
+ if (!_pcmCurrentSound[i].index) {
+ chan = i;
+ continue;
+ }
+
+ if (_intf->callback(40, i + 0x3f))
+ continue;
+
+ chan = i;
+ if (_pcmCurrentSound[chan].index == 0xffff)
+ _intf->callback(39, chan + 0x3f);
+ else
+ _vm->_sound->stopSound(_pcmCurrentSound[chan].index);
+ }
+
+ if (!chan) {
+ for (int i = 1; i < 9; i++) {
+ if (priority >= _pcmCurrentSound[i].priority)
+ chan = i;
+ }
+ if (_pcmCurrentSound[chan].index == 0xffff)
+ _intf->callback(39, chan + 0x3f);
+ else
+ _vm->_sound->stopSound(_pcmCurrentSound[chan].index);
+ }
+ }
+
+ if (chan) {
+ _pcmCurrentSound[chan].index = sound;
+ _pcmCurrentSound[chan].chan = sfxChanRelIndex;
+ _pcmCurrentSound[chan].priority = priority;
+ }
+
+ return chan;
+}
+
+Player_Towns_v1::Player_Towns_v1(ScummEngine *vm, Audio::Mixer *mixer) : Player_Towns(vm, false) {
+ _soundOverride = 0;
+ _cdaCurrentSound = _eupCurrentSound = _cdaNumLoops = 0;
+ _cdaForceRestart = 0;
+ _cdaVolLeft = _cdaVolRight = 0;
+
+ _eupVolLeft = _eupVolRight = 0;
+ _eupLooping = false;
+
+ if (_vm->_game.version == 3) {
+ _soundOverride = new SoundOvrParameters[_numSoundMax];
+ memset(_soundOverride, 0, _numSoundMax * sizeof(SoundOvrParameters));
+ }
+
+ _driver = new TownsEuphonyDriver(mixer);
+}
+
+Player_Towns_v1::~Player_Towns_v1() {
+ delete _driver;
+ delete[] _soundOverride;
+}
+
+bool Player_Towns_v1::init() {
+ if (!_driver)
+ return false;
+
+ if (!_driver->init())
+ return false;
+
+ _driver->reserveSoundEffectChannels(8);
+ _intf = _driver->intf();
+
+ // Treat all 6 fm channels and all 8 pcm channels as sound effect channels
+ // since music seems to exist as CD audio only in the games which use this
+ // MusicEngine implementation.
+ _intf->setSoundEffectChanMask(-1);
+
+ setVolumeCD(255, 255);
+
+ return true;
+}
+
+void Player_Towns_v1::setMusicVolume(int vol) {
+ _driver->setMusicVolume(vol);
+}
+
+void Player_Towns_v1::startSound(int sound) {
+ uint8 *ptr = _vm->getResourceAddress(rtSound, sound);
+ if (_vm->_game.version != 3)
+ ptr += 2;
+
+ int type = ptr[13];
+
+ if (type == 0) {
+ uint8 velocity = 0;
+ uint8 note = 0;
+
+ if (_vm->_game.version == 3) {
+ velocity = (_soundOverride[sound].vLeft + _soundOverride[sound].vRight);
+ note = _soundOverride[sound].note;
+ }
+
+ velocity = velocity ? velocity >> 2 : ptr[14] >> 1;
+ uint16 len = READ_LE_UINT16(ptr) + 2;
+ playPcmTrack(sound, ptr + 6, velocity, 64, note ? note : (len > 50 ? ptr[50] : 60), READ_LE_UINT16(ptr + 10));
+
+ } else if (type == 1) {
+ playEuphonyTrack(sound, ptr + 6);
+
+ } else if (type == 2) {
+ playCdaTrack(sound, ptr + 6);
+ }
+
+ if (_vm->_game.version == 3)
+ _soundOverride[sound].vLeft = _soundOverride[sound].vRight = _soundOverride[sound].note = 0;
+}
+
+void Player_Towns_v1::stopSound(int sound) {
+ if (sound == 0 || sound == _cdaCurrentSound) {
+ _cdaCurrentSound = 0;
+ _vm->_sound->stopCD();
+ _vm->_sound->stopCDTimer();
+ }
+
+ if (sound != 0 && sound == _eupCurrentSound) {
+ _eupCurrentSound = 0;
+ _eupLooping = false;
+ _driver->stopParser();
+ }
+
+ stopPcmTrack(sound);
+}
+
+void Player_Towns_v1::stopAllSounds() {
+ _cdaCurrentSound = 0;
+ _vm->_sound->stopCD();
+ _vm->_sound->stopCDTimer();
+
+ _eupCurrentSound = 0;
+ _eupLooping = false;
+ _driver->stopParser();
+
+ stopPcmTrack(0);
+}
+
+int Player_Towns_v1::getSoundStatus(int sound) const {
+ if (sound == _cdaCurrentSound)
+ return _vm->_sound->pollCD();
+ if (sound == _eupCurrentSound)
+ return _driver->parserIsPlaying() ? 1 : 0;
+ return Player_Towns::getSoundStatus(sound);
+}
+
+int32 Player_Towns_v1::doCommand(int numargs, int args[]) {
+ int32 res = 0;
+
+ switch (args[0]) {
+ case 2:
+ _driver->intf()->callback(73, 0);
+ break;
+
+ case 3:
+ restartLoopingSounds();
+ break;
+
+ case 8:
+ startSound(args[1]);
+ break;
+
+ case 9:
+ _vm->_sound->stopSound(args[1]);
+ break;
+
+ case 11:
+ stopPcmTrack(0);
+ break;
+
+ case 14:
+ startSoundEx(args[1], args[2], args[3], args[4]);
+ break;
+
+ case 15:
+ stopSoundSuspendLooping(args[1]);
+ break;
+
+ default:
+ warning("Player_Towns_v1::doCommand: Unknown command %d", args[0]);
+ break;
+ }
+
+ return res;
+}
+
+void Player_Towns_v1::setVolumeCD(int left, int right) {
+ _cdaVolLeft = left & 0xff;
+ _cdaVolRight = right & 0xff;
+ _driver->setOutputVolume(1, left >> 1, right >> 1);
+}
+
+void Player_Towns_v1::setSoundVolume(int sound, int left, int right) {
+ if (_soundOverride && sound > 0 && sound < _numSoundMax) {
+ _soundOverride[sound].vLeft = left;
+ _soundOverride[sound].vRight = right;
+ }
+}
+
+void Player_Towns_v1::setSoundNote(int sound, int note) {
+ if (_soundOverride && sound > 0 && sound < _numSoundMax)
+ _soundOverride[sound].note = note;
+}
+
+void Player_Towns_v1::saveLoadWithSerializer(Serializer *ser) {
+ _cdaCurrentSoundTemp = (_vm->_sound->pollCD() && _cdaNumLoops > 1) ? _cdaCurrentSound & 0xff : 0;
+ _cdaNumLoopsTemp = _cdaNumLoops & 0xff;
+
+ static const SaveLoadEntry cdEntries[] = {
+ MKLINE(Player_Towns_v1, _cdaCurrentSoundTemp, sleUint8, VER(81)),
+ MKLINE(Player_Towns_v1, _cdaNumLoopsTemp, sleUint8, VER(81)),
+ MKLINE(Player_Towns_v1, _cdaVolLeft, sleUint8, VER(81)),
+ MKLINE(Player_Towns_v1, _cdaVolRight, sleUint8, VER(81)),
+ MKEND()
+ };
+
+ ser->saveLoadEntries(this, cdEntries);
+
+ if (!_eupLooping && !_driver->parserIsPlaying())
+ _eupCurrentSound = 0;
+
+ static const SaveLoadEntry eupEntries[] = {
+ MKLINE(Player_Towns_v1, _eupCurrentSound, sleUint8, VER(81)),
+ MKLINE(Player_Towns_v1, _eupLooping, sleUint8, VER(81)),
+ MKLINE(Player_Towns_v1, _eupVolLeft, sleUint8, VER(81)),
+ MKLINE(Player_Towns_v1, _eupVolRight, sleUint8, VER(81)),
+ MKEND()
+ };
+
+ ser->saveLoadEntries(this, eupEntries);
+
+ Player_Towns::saveLoadWithSerializer(ser);
+}
+
+void Player_Towns_v1::restoreAfterLoad() {
+ setVolumeCD(_cdaVolLeft, _cdaVolRight);
+
+ if (_cdaCurrentSoundTemp) {
+ uint8 *ptr = _vm->getResourceAddress(rtSound, _cdaCurrentSoundTemp) + 6;
+ if (_vm->_game.version != 3)
+ ptr += 2;
+
+ if (ptr[7] == 2) {
+ playCdaTrack(_cdaCurrentSoundTemp, ptr, true);
+ _cdaCurrentSound = _cdaCurrentSoundTemp;
+ _cdaNumLoops = _cdaNumLoopsTemp;
+ }
+ }
+
+ if (_eupCurrentSound) {
+ uint8 *ptr = _vm->getResourceAddress(rtSound, _eupCurrentSound) + 6;
+ if (_vm->_game.version != 3)
+ ptr += 2;
+
+ if (ptr[7] == 1) {
+ setSoundVolume(_eupCurrentSound, _eupVolLeft, _eupVolRight);
+ playEuphonyTrack(_eupCurrentSound, ptr);
+ }
+ }
+
+ Player_Towns::restoreAfterLoad();
+}
+
+void Player_Towns_v1::restartLoopingSounds() {
+ if (_cdaNumLoops && !_cdaForceRestart)
+ _cdaForceRestart = 1;
+
+ for (int i = 1; i < 9; i++) {
+ if (!_pcmCurrentSound[i].paused)
+ continue;
+
+ _pcmCurrentSound[i].paused = 0;
+
+ uint8 *ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index);
+ if (!ptr)
+ continue;
+ ptr += 24;
+
+ int c = 1;
+ while (_pcmCurrentSound[i].chan != c) {
+ ptr = ptr + READ_LE_UINT32(&ptr[12]) + 32;
+ c++;
+ }
+
+ _driver->playSoundEffect(i + 0x3f, _pcmCurrentSound[i].note, _pcmCurrentSound[i].velo, ptr);
+ }
+
+ _driver->intf()->callback(73, 1);
+}
+
+void Player_Towns_v1::startSoundEx(int sound, int velo, int pan, int note) {
+ uint8 *ptr = _vm->getResourceAddress(rtSound, sound) + 2;
+
+ if (pan > 99)
+ pan = 99;
+
+ velo = velo ? (velo * ptr[14] + 50) / 100 : ptr[14];
+ velo = CLIP(velo, 1, 255);
+ uint16 pri = READ_LE_UINT16(ptr + 10);
+
+ if (ptr[13] == 0) {
+ velo >>= 1;
+
+ if (!velo)
+ velo = 1;
+
+ pan = pan ? (((pan << 7) - pan) + 50) / 100 : 64;
+
+ playPcmTrack(sound, ptr + 6, velo ? velo : ptr[14] >> 1, pan, note ? note : ptr[50], pri);
+
+ } else if (ptr[13] == 2) {
+ int volLeft = velo;
+ int volRight = velo;
+
+ if (pan < 50)
+ volRight = ((pan * 2 + 1) * velo + 50) / 100;
+ else if (pan > 50)
+ volLeft = (((99 - pan) * 2 + 1) * velo + 50) / 100;
+
+ setVolumeCD(volLeft, volRight);
+
+ if (!_cdaForceRestart && sound == _cdaCurrentSound)
+ return;
+
+ playCdaTrack(sound, ptr + 6, true);
+ }
+}
+
+void Player_Towns_v1::stopSoundSuspendLooping(int sound) {
+ if (!sound) {
+ return;
+ } else if (sound == _cdaCurrentSound) {
+ if (_cdaNumLoops && _cdaForceRestart)
+ _cdaForceRestart = 1;
+ } else {
+ for (int i = 1; i < 9; i++) {
+ if (sound == _pcmCurrentSound[i].index) {
+ if (!_driver->soundEffectIsPlaying(i + 0x3f))
+ continue;
+ _driver->stopSoundEffect(i + 0x3f);
+ if (_pcmCurrentSound[i].looping)
+ _pcmCurrentSound[i].paused = 1;
+ else
+ _pcmCurrentSound[i].index = 0;
+ }
+ }
+ }
+}
+
+void Player_Towns_v1::playEuphonyTrack(int sound, const uint8 *data) {
+ const uint8 *pos = data + 16;
+ const uint8 *src = pos + data[14] * 48;
+ const uint8 *trackData = src + 150;
+
+ for (int i = 0; i < 32; i++)
+ _driver->configChan_enable(i, *src++);
+ for (int i = 0; i < 32; i++)
+ _driver->configChan_setMode(i, 0xff);
+ for (int i = 0; i < 32; i++)
+ _driver->configChan_remap(i, *src++);
+ for (int i = 0; i < 32; i++)
+ _driver->configChan_adjustVolume(i, *src++);
+ for (int i = 0; i < 32; i++)
+ _driver->configChan_setTranspose(i, *src++);
+
+ src += 8;
+ for (int i = 0; i < 6; i++)
+ _driver->assignChannel(i, *src++);
+
+ for (int i = 0; i < data[14]; i++) {
+ _driver->loadInstrument(i, i, pos + i * 48);
+ _driver->intf()->callback(4, i, i);
+ }
+
+ _eupVolLeft = _soundOverride[sound].vLeft;
+ _eupVolRight = _soundOverride[sound].vRight;
+ int lvl = _soundOverride[sound].vLeft + _soundOverride[sound].vRight;
+ if (!lvl)
+ lvl = data[8] + data[9];
+ lvl >>= 2;
+
+ for (int i = 0; i < 6; i++)
+ _driver->chanVolume(i, lvl);
+
+ uint32 trackSize = READ_LE_UINT32(src);
+ src += 4;
+ uint8 startTick = *src++;
+
+ _driver->setMusicTempo(*src++);
+ _driver->startMusicTrack(trackData, trackSize, startTick);
+
+ _eupLooping = (*src != 1) ? 1 : 0;
+ _driver->setMusicLoop(_eupLooping != 0);
+ _driver->continueParsing();
+ _eupCurrentSound = sound;
+}
+
+void Player_Towns_v1::playCdaTrack(int sound, const uint8 *data, bool skipTrackVelo) {
+ const uint8 *ptr = data;
+
+ if (!sound)
+ return;
+
+ if (!skipTrackVelo) {
+ if (_vm->_game.version == 3) {
+ if (_soundOverride[sound].vLeft + _soundOverride[sound].vRight)
+ setVolumeCD(_soundOverride[sound].vLeft, _soundOverride[sound].vRight);
+ else
+ setVolumeCD(ptr[8], ptr[9]);
+ } else {
+ setVolumeCD(ptr[8], ptr[9]);
+ }
+ }
+
+ if (sound == _cdaCurrentSound && _vm->_sound->pollCD() == 1)
+ return;
+
+ ptr += 16;
+
+ int track = ptr[0];
+ _cdaNumLoops = ptr[1];
+ int start = (ptr[2] * 60 + ptr[3]) * 75 + ptr[4];
+ int end = (ptr[5] * 60 + ptr[6]) * 75 + ptr[7];
+
+ _vm->_sound->playCDTrack(track, _cdaNumLoops == 0xff ? -1 : _cdaNumLoops, start, end <= start ? 0 : end - start);
+ _cdaForceRestart = 0;
+ _cdaCurrentSound = sound;
+}
+
+Player_Towns_v2::Player_Towns_v2(ScummEngine *vm, Audio::Mixer *mixer, IMuse *imuse, bool disposeIMuse) : Player_Towns(vm, true), _imuse(imuse), _imuseDispose(disposeIMuse), _sblData(0) {
+ _soundOverride = new SoundOvrParameters[_numSoundMax];
+ memset(_soundOverride, 0, _numSoundMax * sizeof(SoundOvrParameters));
+ _intf = new TownsAudioInterface(mixer, 0);
+}
+
+Player_Towns_v2::~Player_Towns_v2() {
+ delete _intf;
+ _intf = 0;
+
+ if (_imuseDispose)
+ delete _imuse;
+
+ delete[] _sblData;
+ delete[] _soundOverride;
+}
+
+bool Player_Towns_v2::init() {
+ if (!_intf)
+ return false;
+
+ if (!_intf->init())
+ return false;
+
+ _intf->callback(33, 8);
+ _intf->setSoundEffectChanMask(~0x3f);
+
+ return true;
+}
+
+void Player_Towns_v2::setMusicVolume(int vol) {
+ _imuse->setMusicVolume(vol);
+}
+
+int Player_Towns_v2::getSoundStatus(int sound) const {
+ if (_soundOverride[sound].type == 7)
+ return Player_Towns::getSoundStatus(sound);
+ return _imuse->getSoundStatus(sound);
+}
+
+void Player_Towns_v2::startSound(int sound) {
+ uint8 *ptr = _vm->getResourceAddress(rtSound, sound);
+
+ if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S')) {
+ _soundOverride[sound].type = 7;
+ uint8 velo = _soundOverride[sound].velo ? _soundOverride[sound].velo - 1: (ptr[10] + ptr[11] + 1) >> 1;
+ uint8 pan = _soundOverride[sound].pan ? _soundOverride[sound].pan - 1 : 64;
+ uint8 pri = ptr[9];
+ _soundOverride[sound].velo = _soundOverride[sound].pan = 0;
+ playPcmTrack(sound, ptr + 8, velo, pan, ptr[52], pri);
+
+ } else if (READ_BE_UINT32(ptr) == MKTAG('S','B','L',' ')) {
+ _soundOverride[sound].type = 5;
+ playVocTrack(ptr + 27);
+
+ } else {
+ _soundOverride[sound].type = 3;
+ _imuse->startSound(sound);
+ }
+}
+
+void Player_Towns_v2::stopSound(int sound) {
+ if (_soundOverride[sound].type == 7) {
+ stopPcmTrack(sound);
+ } else {
+ _imuse->stopSound(sound);
+ }
+}
+
+void Player_Towns_v2::stopAllSounds() {
+ stopPcmTrack(0);
+ _imuse->stopAllSounds();
+}
+
+int32 Player_Towns_v2::doCommand(int numargs, int args[]) {
+ int32 res = -1;
+ uint8 *ptr = 0;
+
+ switch (args[0]) {
+ case 8:
+ startSound(args[1]);
+ res = 0;
+ break;
+
+ case 9:
+ case 15:
+ stopSound(args[1]);
+ res = 0;
+ break;
+
+ case 11:
+ stopPcmTrack(0);
+ break;
+
+ case 13:
+ res = getSoundStatus(args[1]);
+ break;
+
+ case 258:
+ if (_soundOverride[args[1]].type == 0) {
+ ptr = _vm->getResourceAddress(rtSound, args[1]);
+ if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S'))
+ _soundOverride[args[1]].type = 7;
+ }
+ if (_soundOverride[args[1]].type == 7) {
+ _soundOverride[args[1]].velo = args[2] + 1;
+ res = 0;
+ }
+ break;
+
+ case 259:
+ if (_soundOverride[args[1]].type == 0) {
+ ptr = _vm->getResourceAddress(rtSound, args[1]);
+ if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S'))
+ _soundOverride[args[1]].type = 7;
+ }
+ if (_soundOverride[args[1]].type == 7) {
+ _soundOverride[args[1]].pan = 64 - CLIP<int>(args[2], -63, 63);
+ res = 0;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (res == -1)
+ return _imuse->doCommand(numargs, args);
+
+ return res;
+}
+
+void Player_Towns_v2::saveLoadWithSerializer(Serializer *ser) {
+ if (ser->getVersion() >= 83)
+ Player_Towns::saveLoadWithSerializer(ser);
+}
+
+void Player_Towns_v2::playVocTrack(const uint8 *data) {
+ static const uint8 header[] = {
+ 0x54, 0x61, 0x6C, 0x6B, 0x69, 0x65, 0x20, 0x20,
+ 0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x36, 0x04, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00
+ };
+
+ uint32 len = (READ_LE_UINT32(data) >> 8) - 2;
+
+ int chan = allocatePcmChannel(0xffff, 0, 0x1000);
+ if (!chan)
+ return;
+
+ delete[] _sblData;
+ _sblData = new uint8[len + 32];
+
+ memcpy(_sblData, header, 32);
+ WRITE_LE_UINT32(_sblData + 12, len);
+
+ const uint8 *src = data + 6;
+ uint8 *dst = _sblData + 32;
+ for (uint32 i = 0; i < len; i++)
+ *dst++ = *src & 0x80 ? (*src++ & 0x7f) : -*src++;
+
+ _intf->callback(37, 0x3f + chan, 60, 127, _sblData);
+ _pcmCurrentSound[chan].paused = 0;
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_towns.h b/engines/scumm/players/player_towns.h
new file mode 100644
index 0000000000..2369b7da5f
--- /dev/null
+++ b/engines/scumm/players/player_towns.h
@@ -0,0 +1,180 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_TOWNS_H
+#define SCUMM_PLAYERS_PLAYER_TOWNS_H
+
+#include "scumm/scumm.h"
+#include "scumm/imuse/imuse.h"
+#include "audio/softsynth/fmtowns_pc98/towns_euphony.h"
+#include "audio/softsynth/fmtowns_pc98/towns_midi.h"
+
+namespace Scumm {
+
+class Player_Towns : public MusicEngine {
+public:
+ Player_Towns(ScummEngine *vm, bool isVersion2);
+ virtual ~Player_Towns() {}
+
+ virtual bool init() = 0;
+
+ void setSfxVolume(int vol);
+
+ int getSoundStatus(int sound) const;
+
+ virtual int32 doCommand(int numargs, int args[]) = 0;
+
+ virtual void saveLoadWithSerializer(Serializer *ser);
+ virtual void restoreAfterLoad();
+
+ // version 1 specific
+ virtual int getCurrentCdaSound() { return 0; }
+ virtual int getCurrentCdaVolume() { return 0; }
+ virtual void setVolumeCD(int left, int right) {}
+ virtual void setSoundVolume(int sound, int left, int right) {}
+ virtual void setSoundNote(int sound, int note) {}
+
+protected:
+ void playPcmTrack(int sound, const uint8 *data, int velo = 0, int pan = 64, int note = 0, int priority = 0);
+ void stopPcmTrack(int sound);
+
+ int allocatePcmChannel(int sound, int sfxChanRelIndex, uint32 priority);
+
+ struct PcmCurrentSound {
+ uint16 index;
+ uint16 chan;
+ uint8 note;
+ uint8 velo;
+ uint8 pan;
+ uint8 paused;
+ uint8 looping;
+ uint32 priority;
+ } _pcmCurrentSound[9];
+
+ uint8 _unkFlags;
+
+ TownsAudioInterface *_intf;
+ ScummEngine *_vm;
+
+ const int _numSoundMax;
+ const bool _v2;
+};
+
+class Player_Towns_v1 : public Player_Towns {
+public:
+ Player_Towns_v1(ScummEngine *vm, Audio::Mixer *mixer);
+ ~Player_Towns_v1();
+
+ bool init();
+
+ void setMusicVolume(int vol);
+ void startSound(int sound);
+ void stopSound(int sound);
+ void stopAllSounds();
+
+ int getSoundStatus(int sound) const;
+ int getCurrentCdaSound() { return _cdaCurrentSound; }
+ int getCurrentCdaVolume() { return (_cdaVolLeft + _cdaVolRight + 1) >> 1; }
+
+ int32 doCommand(int numargs, int args[]);
+
+ void setVolumeCD(int left, int right);
+ void setSoundVolume(int sound, int left, int right);
+ void setSoundNote(int sound, int note);
+
+ void saveLoadWithSerializer(Serializer *ser);
+ void restoreAfterLoad();
+
+ TownsEuphonyDriver *driver() { return _driver; }
+
+private:
+ void restartLoopingSounds();
+ void startSoundEx(int sound, int velo, int pan, int note);
+ void stopSoundSuspendLooping(int sound);
+
+ void playEuphonyTrack(int sound, const uint8 *data);
+ void playCdaTrack(int sound, const uint8 *data, bool skipTrackVelo = false);
+
+ struct SoundOvrParameters {
+ uint8 vLeft;
+ uint8 vRight;
+ uint8 note;
+ };
+
+ SoundOvrParameters *_soundOverride;
+
+ uint8 _cdaVolLeft;
+ uint8 _cdaVolRight;
+
+ uint8 _eupCurrentSound;
+ uint8 _eupLooping;
+ uint8 _eupVolLeft;
+ uint8 _eupVolRight;
+
+ uint8 _cdaCurrentSound;
+ uint8 _cdaNumLoops;
+ uint8 _cdaForceRestart;
+
+ uint8 _cdaCurrentSoundTemp;
+ uint8 _cdaNumLoopsTemp;
+
+ TownsEuphonyDriver *_driver;
+};
+
+class Player_Towns_v2 : public Player_Towns {
+public:
+ Player_Towns_v2(ScummEngine *vm, Audio::Mixer *mixer, IMuse *imuse, bool disposeIMuse);
+ ~Player_Towns_v2();
+
+ bool init();
+
+ void setMusicVolume(int vol);
+
+ int getSoundStatus(int sound) const;
+ void startSound(int sound);
+ void stopSound(int sound);
+ void stopAllSounds();
+
+ int32 doCommand(int numargs, int args[]);
+
+ void saveLoadWithSerializer(Serializer *ser);
+
+private:
+ void playVocTrack(const uint8 *data);
+
+ struct SoundOvrParameters {
+ uint8 velo;
+ uint8 pan;
+ uint8 type;
+ };
+
+ SoundOvrParameters *_soundOverride;
+
+ uint8 *_sblData;
+
+ IMuse *_imuse;
+ const bool _imuseDispose;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_v1.cpp b/engines/scumm/players/player_v1.cpp
new file mode 100644
index 0000000000..0fa1ee9361
--- /dev/null
+++ b/engines/scumm/players/player_v1.cpp
@@ -0,0 +1,607 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+#include "engines/engine.h"
+#include "scumm/players/player_v1.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+#define FB_WNOISE 0x12000 /* feedback for white noise */
+#define FB_PNOISE 0x08000 /* feedback for periodic noise */
+
+#define TIMER_BASE_FREQ 1193000
+#define FIXP_SHIFT 16
+
+Player_V1::Player_V1(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr)
+ : Player_V2(scumm, mixer, pcjr) {
+ // Initialize channel code
+ for (int i = 0; i < 4; ++i)
+ clear_channel(i);
+
+ _mplex_step = (_sampleRate << FIXP_SHIFT) / 1193000;
+ _next_chunk = _repeat_chunk = 0;
+ _forced_level = 0;
+ _random_lsr = 0;
+}
+
+Player_V1::~Player_V1() {
+}
+
+void Player_V1::chainSound(int nr, byte *data) {
+ uint i;
+ for (i = 0; i < 4; ++i)
+ clear_channel(i);
+
+ _current_nr = nr;
+ _current_data = data;
+ _repeat_chunk = _next_chunk = data + (_pcjr ? 2 : 4);
+
+ debug(4, "Chaining new sound %d", nr);
+ if (_pcjr)
+ parsePCjrChunk();
+ else
+ parseSpeakerChunk();
+}
+
+void Player_V1::startSound(int nr) {
+ Common::StackLock lock(_mutex);
+
+ byte *data = _vm->getResourceAddress(rtSound, nr);
+ assert(data);
+
+ int offset = _pcjr ? READ_LE_UINT16(data+4) : 6;
+ int cprio = _current_data ? *(_current_data) & 0x7f : 0;
+ int prio = *(data + offset) & 0x7f;
+ int restartable = *(data + offset) & 0x80;
+
+ debug(4, "startSound %d: prio %d%s, cprio %d",
+ nr, prio, restartable ? " restartable" : "", cprio);
+
+ if (!_current_nr || cprio <= prio) {
+ if (_current_data && (*(_current_data) & 0x80)) {
+ _next_nr = _current_nr;
+ _next_data = _current_data;
+ }
+
+ chainSound(nr, data + offset);
+ }
+}
+
+void Player_V1::stopAllSounds() {
+ Common::StackLock lock(_mutex);
+
+ for (int i = 0; i < 4; i++)
+ clear_channel(i);
+ _repeat_chunk = _next_chunk = 0;
+ _next_nr = _current_nr = 0;
+ _next_data = _current_data = 0;
+}
+
+void Player_V1::stopSound(int nr) {
+ Common::StackLock lock(_mutex);
+
+ if (_next_nr == nr) {
+ _next_nr = 0;
+ _next_data = 0;
+ }
+ if (_current_nr == nr) {
+ for (int i = 0; i < 4; i++) {
+ clear_channel(i);
+ }
+ _repeat_chunk = _next_chunk = 0;
+ _current_nr = 0;
+ _current_data = 0;
+ chainNextSound();
+ }
+}
+
+void Player_V1::clear_channel(int i) {
+ _channels[i].freq = 0;
+ _channels[i].volume = 15;
+}
+
+int Player_V1::getMusicTimer() {
+ /* Do V1 games have a music timer? */
+ return 0;
+}
+
+void Player_V1::parseSpeakerChunk() {
+ set_mplex(3000);
+ _forced_level = 0;
+
+ parse_again:
+ _chunk_type = READ_LE_UINT16(_next_chunk);
+ debug(6, "parseSpeakerChunk: sound %d, offset %lx, chunk %x",
+ _current_nr, (long)(_next_chunk - _current_data), _chunk_type);
+
+ _next_chunk += 2;
+ switch (_chunk_type) {
+ case 0xffff:
+ _current_nr = 0;
+ _current_data = 0;
+ _channels[0].freq = 0;
+ _next_chunk = 0;
+ chainNextSound();
+ break;
+ case 0xfffe:
+ _repeat_chunk = _next_chunk;
+ goto parse_again;
+
+ case 0xfffd:
+ _next_chunk = _repeat_chunk;
+ goto parse_again;
+
+ case 0xfffc:
+ /* handle reset. We don't need this do we? */
+ goto parse_again;
+
+ case 0:
+ _time_left = 1;
+ set_mplex(READ_LE_UINT16(_next_chunk));
+ _next_chunk += 2;
+ break;
+ case 1:
+ set_mplex(READ_LE_UINT16(_next_chunk));
+ _start = READ_LE_UINT16(_next_chunk + 2);
+ _end = READ_LE_UINT16(_next_chunk + 4);
+ _delta = (int16) READ_LE_UINT16(_next_chunk + 6);
+ _repeat_ctr = READ_LE_UINT16(_next_chunk + 8);
+ _channels[0].freq = _start;
+ _next_chunk += 10;
+ debug(6, "chunk 1: mplex %d, freq %d -> %d step %d x %d",
+ _mplex, _start, _end, _delta, _repeat_ctr);
+ break;
+ case 2:
+ _start = READ_LE_UINT16(_next_chunk);
+ _end = READ_LE_UINT16(_next_chunk + 2);
+ _delta = (int16) READ_LE_UINT16(_next_chunk + 4);
+ _channels[0].freq = 0;
+ _next_chunk += 6;
+ _forced_level = -1;
+ debug(6, "chunk 2: %d -> %d step %d",
+ _start, _end, _delta);
+ break;
+ case 3:
+ _start = READ_LE_UINT16(_next_chunk);
+ _end = READ_LE_UINT16(_next_chunk + 2);
+ _delta = (int16) READ_LE_UINT16(_next_chunk + 4);
+ _channels[0].freq = 0;
+ _next_chunk += 6;
+ _forced_level = -1;
+ debug(6, "chunk 3: %d -> %d step %d",
+ _start, _end, _delta);
+ break;
+ }
+}
+
+void Player_V1::nextSpeakerCmd() {
+ uint16 lsr;
+ switch (_chunk_type) {
+ case 0:
+ if (--_time_left)
+ return;
+ _time_left = READ_LE_UINT16(_next_chunk);
+ _next_chunk += 2;
+ if (_time_left == 0xfffb) {
+ /* handle 0xfffb?? */
+ _time_left = READ_LE_UINT16(_next_chunk);
+ _next_chunk += 2;
+ }
+ debug(7, "nextSpeakerCmd: chunk %d, offset %4lx: notelen %d",
+ _chunk_type, (long)(_next_chunk - 2 - _current_data), _time_left);
+ if (_time_left == 0) {
+ parseSpeakerChunk();
+ } else {
+ _channels[0].freq = READ_LE_UINT16(_next_chunk);
+ _next_chunk += 2;
+ debug(7, "freq_current: %d", _channels[0].freq);
+ }
+ break;
+
+ case 1:
+ _channels[0].freq = (_channels[0].freq + _delta) & 0xffff;
+ if (_channels[0].freq == _end) {
+ if (!--_repeat_ctr) {
+ parseSpeakerChunk();
+ return;
+ }
+ _channels[0].freq = _start;
+ }
+ break;
+
+ case 2:
+ _start = (_start + _delta) & 0xffff;
+ if (_start == _end) {
+ parseSpeakerChunk();
+ return;
+ }
+ set_mplex(_start);
+ _forced_level = -_forced_level;
+ break;
+ case 3:
+ _start = (_start + _delta) & 0xffff;
+ if (_start == _end) {
+ parseSpeakerChunk();
+ return;
+ }
+ lsr = _random_lsr + 0x9248;
+ lsr = (lsr >> 3) | (lsr << 13);
+ _random_lsr = lsr;
+ set_mplex((_start & lsr) | 0x180);
+ _forced_level = -_forced_level;
+ break;
+ }
+}
+
+void Player_V1::parsePCjrChunk() {
+ uint tmp;
+ uint i;
+
+ set_mplex(3000);
+ _forced_level = 0;
+
+parse_again:
+
+ _chunk_type = READ_LE_UINT16(_next_chunk);
+ debug(6, "parsePCjrChunk: sound %d, offset %4lx, chunk %x",
+ _current_nr, (long)(_next_chunk - _current_data), _chunk_type);
+
+ _next_chunk += 2;
+ switch (_chunk_type) {
+ case 0xffff:
+ for (i = 0; i < 4; ++i)
+ clear_channel(i);
+ _current_nr = 0;
+ _current_data = 0;
+ _repeat_chunk = _next_chunk = 0;
+ chainNextSound();
+ break;
+
+ case 0xfffe:
+ _repeat_chunk = _next_chunk;
+ goto parse_again;
+
+ case 0xfffd:
+ _next_chunk = _repeat_chunk;
+ goto parse_again;
+
+ case 0xfffc:
+ /* handle reset. We don't need this do we? */
+ goto parse_again;
+
+ case 0:
+ set_mplex(READ_LE_UINT16(_next_chunk));
+ _next_chunk += 2;
+ for (i = 0; i < 4; i++) {
+ tmp = READ_LE_UINT16(_next_chunk);
+ _next_chunk += 2;
+ if (tmp == 0xffff) {
+ _channels[i].cmd_ptr = 0;
+ continue;
+ }
+ _channels[i].attack = READ_LE_UINT16(_current_data + tmp);
+ _channels[i].decay = READ_LE_UINT16(_current_data + tmp + 2);
+ _channels[i].level = READ_LE_UINT16(_current_data + tmp + 4);
+ _channels[i].sustain_1 = READ_LE_UINT16(_current_data + tmp + 6);
+ _channels[i].sustain_2 = READ_LE_UINT16(_current_data + tmp + 8);
+ _channels[i].notelen = 1;
+ _channels[i].volume = 15;
+ _channels[i].cmd_ptr = _current_data + tmp + 10;
+ }
+ break;
+
+ case 1:
+ set_mplex(READ_LE_UINT16(_next_chunk));
+ tmp = READ_LE_UINT16(_next_chunk + 2);
+ _channels[0].cmd_ptr = tmp != 0xffff ? _current_data + tmp : NULL;
+ tmp = READ_LE_UINT16(_next_chunk + 4);
+ _start = READ_LE_UINT16(_next_chunk + 6);
+ _delta = (int16) READ_LE_UINT16(_next_chunk + 8);
+ _time_left = READ_LE_UINT16(_next_chunk + 10);
+ _next_chunk += 12;
+ if (tmp >= 0xe0) {
+ _channels[3].freq = tmp & 0xf;
+ _value_ptr = &_channels[3].volume;
+ } else {
+ assert(!(tmp & 0x10));
+ tmp = (tmp & 0x60) >> 5;
+ _value_ptr = &_channels[tmp].freq;
+ _channels[tmp].volume = 0;
+ }
+ *_value_ptr = _start;
+ if (_channels[0].cmd_ptr) {
+ tmp = READ_LE_UINT16(_channels[0].cmd_ptr);
+ _start_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 2);
+ _delta_2 = (int16) READ_LE_UINT16(_channels[0].cmd_ptr + 4);
+ _time_left_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 6);
+ _channels[0].cmd_ptr += 8;
+ if (_value_ptr == &_channels[3].volume) {
+ tmp = (tmp & 0x70) >> 4;
+ if (tmp & 1)
+ _value_ptr_2 = &_channels[tmp >> 1].volume;
+ else
+ _value_ptr_2 = &_channels[tmp >> 1].freq;
+ } else {
+ assert(!(tmp & 0x10));
+ tmp = (tmp & 0x60) >> 5;
+ _value_ptr_2 = &_channels[tmp].freq;
+ _channels[tmp].volume = 0;
+ }
+ *_value_ptr_2 = _start_2;
+ }
+ debug(6, "chunk 1: %lu: %d step %d for %d, %lu: %d step %d for %d",
+ (long)(_value_ptr - (uint *)_channels), _start, _delta, _time_left,
+ (long)(_value_ptr_2 - (uint *)_channels), _start_2, _delta_2, _time_left_2);
+ break;
+
+ case 2:
+ _start = READ_LE_UINT16(_next_chunk);
+ _end = READ_LE_UINT16(_next_chunk + 2);
+ _delta = (int16) READ_LE_UINT16(_next_chunk + 4);
+ _channels[0].freq = 0;
+ _next_chunk += 6;
+ _forced_level = -1;
+ debug(6, "chunk 2: %d -> %d step %d",
+ _start, _end, _delta);
+ break;
+ case 3:
+ set_mplex(READ_LE_UINT16(_next_chunk));
+ tmp = READ_LE_UINT16(_next_chunk + 2);
+ assert((tmp & 0xf0) == 0xe0);
+ _channels[3].freq = tmp & 0xf;
+ if ((tmp & 3) == 3) {
+ _next_chunk += 2;
+ _channels[2].freq = READ_LE_UINT16(_next_chunk + 2);
+ }
+ _channels[3].volume = READ_LE_UINT16(_next_chunk + 4);
+ _repeat_ctr = READ_LE_UINT16(_next_chunk + 6);
+ _delta = (int16) READ_LE_UINT16(_next_chunk + 8);
+ _next_chunk += 10;
+ break;
+ }
+}
+
+void Player_V1::nextPCjrCmd() {
+ uint i;
+ int dummy;
+ switch (_chunk_type) {
+ case 0:
+ for (i = 0; i < 4; i++) {
+ if (!_channels[i].cmd_ptr)
+ continue;
+ if (!--_channels[i].notelen) {
+ dummy = READ_LE_UINT16(_channels[i].cmd_ptr);
+ if (dummy >= 0xfffe) {
+ if (dummy == 0xfffe)
+ _next_chunk = _current_data + 2;
+ parsePCjrChunk();
+ return;
+ }
+ _channels[i].notelen = 4 * dummy;
+ dummy = READ_LE_UINT16(_channels[i].cmd_ptr + 2);
+ if (dummy == 0) {
+ _channels[i].hull_counter = 4;
+ _channels[i].sustctr = _channels[i].sustain_2;
+ } else {
+ _channels[i].hull_counter = 1;
+ _channels[i].freq = dummy;
+ }
+ debug(7, "chunk 0: channel %d play %d for %d",
+ i, dummy, _channels[i].notelen);
+ _channels[i].cmd_ptr += 4;
+ }
+
+
+ switch (_channels[i].hull_counter) {
+ case 1:
+ _channels[i].volume -= _channels[i].attack;
+ if ((int) _channels[i].volume <= 0) {
+ _channels[i].volume = 0;
+ _channels[i].hull_counter++;
+ }
+ break;
+ case 2:
+ _channels[i].volume += _channels[i].decay;
+ if (_channels[i].volume >= _channels[i].level) {
+ _channels[i].volume = _channels[i].level;
+ _channels[i].hull_counter++;
+ }
+ break;
+ case 4:
+ if (--_channels[i].sustctr < 0) {
+ _channels[i].sustctr = _channels[i].sustain_2;
+ _channels[i].volume += _channels[i].sustain_1;
+ if ((int) _channels[i].volume >= 15) {
+ _channels[i].volume = 15;
+ _channels[i].hull_counter++;
+ }
+ }
+ break;
+ }
+ }
+ break;
+
+ case 1:
+ _start += _delta;
+ *_value_ptr = _start;
+ if (!--_time_left) {
+ _start = READ_LE_UINT16(_next_chunk);
+ _next_chunk += 2;
+ if (_start == 0xffff) {
+ parsePCjrChunk();
+ return;
+ }
+ _delta = (int16) READ_LE_UINT16(_next_chunk);
+ _time_left = READ_LE_UINT16(_next_chunk + 2);
+ _next_chunk += 4;
+ *_value_ptr = _start;
+ }
+
+ if (_channels[0].cmd_ptr) {
+ _start_2 += _delta_2;
+ *_value_ptr_2 = _start_2;
+ if (!--_time_left_2) {
+ _start_2 = READ_LE_UINT16(_channels[0].cmd_ptr);
+ if (_start_2 == 0xffff) {
+ _next_chunk = _channels[0].cmd_ptr + 2;
+ parsePCjrChunk();
+ return;
+ }
+ _delta_2 = (int16) READ_LE_UINT16(_channels[0].cmd_ptr + 2);
+ _time_left_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 4);
+ _channels[0].cmd_ptr += 6;
+ }
+ }
+ break;
+
+ case 2:
+ _start += _delta;
+ if (_start == _end) {
+ parsePCjrChunk();
+ return;
+ }
+ set_mplex(_start);
+ debug(7, "chunk 2: mplex %d curve %d", _start, _forced_level);
+ _forced_level = -_forced_level;
+ break;
+ case 3:
+ dummy = _channels[3].volume + _delta;
+ if (dummy >= 15) {
+ _channels[3].volume = 15;
+ } else if (dummy <= 0) {
+ _channels[3].volume = 0;
+ } else {
+ _channels[3].volume = dummy;
+ break;
+ }
+
+ if (!--_repeat_ctr) {
+ parsePCjrChunk();
+ return;
+ }
+ _delta = READ_LE_UINT16(_next_chunk);
+ _next_chunk += 2;
+ break;
+ }
+}
+
+void Player_V1::set_mplex(uint mplex) {
+ if (mplex == 0)
+ mplex = 65536;
+ _mplex = mplex;
+ _tick_len = _mplex_step * mplex;
+}
+
+void Player_V1::nextTick() {
+ if (_next_chunk) {
+ if (_pcjr)
+ nextPCjrCmd();
+ else
+ nextSpeakerCmd();
+ }
+}
+
+void Player_V1::generateSpkSamples(int16 *data, uint len) {
+ uint i;
+
+ memset(data, 0, 2 * sizeof(int16) * len);
+ if (_channels[0].freq == 0) {
+ if (_forced_level) {
+ int sample = _forced_level * _volumetable[0];
+ for (i = 0; i < len; i++)
+ data[2*i] = data[2*i+1] = sample;
+ debug(9, "speaker: %8x: forced one", _tick_len);
+ } else if (!_level) {
+ return;
+ }
+ } else {
+ squareGenerator(0, _channels[0].freq, 0, 0, data, len);
+ debug(9, "speaker: %8x: freq %d %.1f", _tick_len,
+ _channels[0].freq, 1193000.0 / _channels[0].freq);
+ }
+ lowPassFilter(data, len);
+}
+
+void Player_V1::generatePCjrSamples(int16 *data, uint len) {
+ uint i, j;
+ uint freq, vol;
+ bool hasdata = false;
+
+ memset(data, 0, 2 * sizeof(int16) * len);
+
+ if (_forced_level) {
+ int sample = _forced_level * _volumetable[0];
+ for (i = 0; i < len; i++)
+ data[2*i] = data[2*i+1] = sample;
+ hasdata = true;
+ debug(9, "channel[4]: %8x: forced one", _tick_len);
+ }
+
+ for (i = 1; i < 3; i++) {
+ freq = _channels[i].freq;
+ if (freq) {
+ for (j = 0; j < i; j++) {
+ if (freq == _channels[j].freq) {
+ /* HACK: this channel is playing at
+ * the same frequency as another.
+ * Synchronize it to the same phase to
+ * prevent interference.
+ */
+ _timer_count[i] = _timer_count[j];
+ _timer_output ^= (1 << i) &
+ (_timer_output ^ _timer_output << (i - j));
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < 4; i++) {
+ freq = _channels[i].freq;
+ vol = _channels[i].volume;
+ if (!_volumetable[_channels[i].volume]) {
+ _timer_count[i] -= len << FIXP_SHIFT;
+ if (_timer_count[i] < 0)
+ _timer_count[i] = 0;
+ } else if (i < 3) {
+ hasdata = true;
+ squareGenerator(i, freq, vol, 0, data, len);
+ debug(9, "channel[%d]: %8x: freq %d %.1f ; volume %d",
+ i, _tick_len, freq, 111860.0 / freq, vol);
+ } else {
+ int noiseFB = (freq & 4) ? FB_WNOISE : FB_PNOISE;
+ int n = (freq & 3);
+
+ freq = (n == 3) ? 2 * (_channels[2].freq) : 1 << (5 + n);
+ hasdata = true;
+ squareGenerator(i, freq, vol, noiseFB, data, len);
+ debug(9, "channel[%d]: %x: noise freq %d %.1f ; volume %d",
+ i, _tick_len, freq, 111860.0 / freq, vol);
+ }
+ }
+
+ if (_level || hasdata)
+ lowPassFilter(data, len);
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_v1.h b/engines/scumm/players/player_v1.h
new file mode 100644
index 0000000000..ccd24c39df
--- /dev/null
+++ b/engines/scumm/players/player_v1.h
@@ -0,0 +1,98 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_V1_H
+#define SCUMM_PLAYERS_PLAYER_V1_H
+
+#include "scumm/players/player_v2.h"
+
+namespace Scumm {
+
+/**
+ * Scumm V1 PC-Speaker player.
+ */
+class Player_V1 : public Player_V2 {
+public:
+ Player_V1(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr);
+ ~Player_V1();
+
+ virtual void startSound(int sound);
+ virtual void stopSound(int sound);
+ virtual void stopAllSounds();
+ virtual int getMusicTimer();
+
+protected:
+ virtual void nextTick();
+ virtual void clear_channel(int i);
+ virtual void chainSound(int nr, byte *data);
+
+ virtual void generateSpkSamples(int16 *data, uint len);
+ virtual void generatePCjrSamples(int16 *data, uint len);
+
+ void restartSound();
+
+ void set_mplex(uint mplex);
+ void parseSpeakerChunk();
+ void nextSpeakerCmd();
+ void parsePCjrChunk();
+ void nextPCjrCmd();
+
+private:
+ struct channel_data_v1 {
+ uint freq;
+ uint volume;
+ byte *cmd_ptr;
+ uint notelen;
+ uint hull_counter;
+ uint attack;
+ uint decay;
+ uint level;
+ uint sustain_1;
+ uint sustain_2;
+ int sustctr;
+ };
+
+ channel_data_v1 _channels[4];
+
+ byte *_next_chunk;
+ byte *_repeat_chunk;
+ uint _chunk_type;
+ uint _mplex_step;
+ uint _mplex;
+ uint _repeat_ctr;
+ uint _freq_current;
+ int _forced_level;
+ uint16 _random_lsr;
+ uint *_value_ptr;
+ uint _time_left;
+ uint _start;
+ uint _end;
+ int _delta;
+ uint *_value_ptr_2;
+ uint _time_left_2;
+ uint _start_2;
+ int _delta_2;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_v2.cpp b/engines/scumm/players/player_v2.cpp
new file mode 100644
index 0000000000..2429af2d8c
--- /dev/null
+++ b/engines/scumm/players/player_v2.cpp
@@ -0,0 +1,327 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/players/player_v2.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+#define SPK_DECAY 0xa000 /* Depends on sample rate */
+#define PCJR_DECAY 0xa000 /* Depends on sample rate */
+
+#define NG_PRESET 0x0f35 /* noise generator preset */
+#define FB_WNOISE 0x12000 /* feedback for white noise */
+#define FB_PNOISE 0x08000 /* feedback for periodic noise */
+
+
+Player_V2::Player_V2(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr)
+ : Player_V2Base(scumm, mixer, pcjr) {
+
+ int i;
+
+ // Initialize square generator
+ _level = 0;
+
+ _RNG = NG_PRESET;
+
+ _pcjr = pcjr;
+
+ if (_pcjr) {
+ _decay = PCJR_DECAY;
+ _update_step = (_sampleRate << FIXP_SHIFT) / (111860 * 2);
+ } else {
+ _decay = SPK_DECAY;
+ _update_step = (_sampleRate << FIXP_SHIFT) / (1193000 * 2);
+ }
+
+ // Adapt _decay to sample rate. It must be squared when
+ // sample rate doubles.
+ for (i = 0; (_sampleRate << i) < 30000; i++)
+ _decay = _decay * _decay / 65536;
+
+ _timer_output = 0;
+ for (i = 0; i < 4; i++)
+ _timer_count[i] = 0;
+
+ setMusicVolume(255);
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_V2::~Player_V2() {
+ Common::StackLock lock(_mutex);
+ _mixer->stopHandle(_soundHandle);
+}
+
+void Player_V2::setMusicVolume (int vol) {
+ if (vol > 255)
+ vol = 255;
+
+ /* scale to int16, FIXME: find best value */
+ double out = vol * 128 / 3;
+
+ /* build volume table (2dB per step) */
+ for (int i = 0; i < 15; i++) {
+ /* limit volume to avoid clipping */
+ if (out > 0xffff)
+ _volumetable[i] = 0xffff;
+ else
+ _volumetable[i] = (int) out;
+
+ out /= 1.258925412; /* = 10 ^ (2/20) = 2dB */
+ }
+ _volumetable[15] = 0;
+}
+
+void Player_V2::stopAllSounds() {
+ Common::StackLock lock(_mutex);
+
+ for (int i = 0; i < 4; i++) {
+ clear_channel(i);
+ }
+ _next_nr = _current_nr = 0;
+ _next_data = _current_data = 0;
+}
+
+void Player_V2::stopSound(int nr) {
+ Common::StackLock lock(_mutex);
+
+ if (_next_nr == nr) {
+ _next_nr = 0;
+ _next_data = 0;
+ }
+ if (_current_nr == nr) {
+ for (int i = 0; i < 4; i++) {
+ clear_channel(i);
+ }
+ _current_nr = 0;
+ _current_data = 0;
+ chainNextSound();
+ }
+}
+
+void Player_V2::startSound(int nr) {
+ Common::StackLock lock(_mutex);
+
+ byte *data = _vm->getResourceAddress(rtSound, nr);
+ assert(data);
+
+ int cprio = _current_data ? *(_current_data + _header_len) : 0;
+ int prio = *(data + _header_len);
+ int nprio = _next_data ? *(_next_data + _header_len) : 0;
+
+ int restartable = *(data + _header_len + 1);
+
+ if (!_current_nr || cprio <= prio) {
+ int tnr = _current_nr;
+ int tprio = cprio;
+ byte *tdata = _current_data;
+
+ chainSound(nr, data);
+ nr = tnr;
+ prio = tprio;
+ data = tdata;
+ restartable = data ? *(data + _header_len + 1) : 0;
+ }
+
+ if (!_current_nr) {
+ nr = 0;
+ _next_nr = 0;
+ _next_data = 0;
+ }
+
+ if (nr != _current_nr
+ && restartable
+ && (!_next_nr
+ || nprio <= prio)) {
+
+ _next_nr = nr;
+ _next_data = data;
+ }
+}
+
+int Player_V2::getSoundStatus(int nr) const {
+ return _current_nr == nr || _next_nr == nr;
+}
+
+int Player_V2::readBuffer(int16 *data, const int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ uint step;
+ uint len = numSamples / 2;
+
+ do {
+ if (!(_next_tick >> FIXP_SHIFT)) {
+ _next_tick += _tick_len;
+ nextTick();
+ }
+
+ step = len;
+ if (step > (_next_tick >> FIXP_SHIFT))
+ step = (_next_tick >> FIXP_SHIFT);
+ if (_pcjr)
+ generatePCjrSamples(data, step);
+ else
+ generateSpkSamples(data, step);
+ data += 2 * step;
+ _next_tick -= step << FIXP_SHIFT;
+ } while (len -= step);
+
+ return numSamples;
+}
+
+void Player_V2::lowPassFilter(int16 *sample, uint len) {
+ for (uint i = 0; i < len; i++) {
+ _level = (int) (_level * _decay
+ + sample[0] * (0x10000 - _decay)) >> 16;
+ sample[0] = sample[1] = _level;
+ sample += 2;
+ }
+}
+
+void Player_V2::squareGenerator(int channel, int freq, int vol,
+ int noiseFeedback, int16 *sample, uint len) {
+ int32 period = _update_step * freq;
+ int32 nsample;
+ if (period == 0)
+ period = _update_step;
+
+ for (uint i = 0; i < len; i++) {
+ uint32 duration = 0;
+
+ if (_timer_output & (1 << channel))
+ duration += _timer_count[channel];
+
+ _timer_count[channel] -= (1 << FIXP_SHIFT);
+ while (_timer_count[channel] <= 0) {
+
+ if (noiseFeedback) {
+ if (_RNG & 1) {
+ _RNG ^= noiseFeedback;
+ _timer_output ^= (1 << channel);
+ }
+ _RNG >>= 1;
+ } else {
+ _timer_output ^= (1 << channel);
+ }
+
+ if (_timer_output & (1 << channel))
+ duration += period;
+
+ _timer_count[channel] += period;
+ }
+
+ if (_timer_output & (1 << channel))
+ duration -= _timer_count[channel];
+
+ nsample = *sample +
+ (((int32) (duration - (1 << (FIXP_SHIFT - 1)))
+ * (int32) _volumetable[vol]) >> FIXP_SHIFT);
+ /* overflow: clip value */
+ if (nsample > 0x7fff)
+ nsample = 0x7fff;
+ if (nsample < -0x8000)
+ nsample = -0x8000;
+ *sample = nsample;
+ // The following write isn't necessary, because the lowPassFilter does it for us
+ //sample[1] = sample[0];
+ sample += 2;
+ }
+}
+
+void Player_V2::generateSpkSamples(int16 *data, uint len) {
+ int winning_channel = -1;
+ for (int i = 0; i < 4; i++) {
+ if (winning_channel == -1
+ && _channels[i].d.volume
+ && _channels[i].d.time_left) {
+ winning_channel = i;
+ }
+ }
+
+ memset(data, 0, 2 * sizeof(int16) * len);
+ if (winning_channel != -1) {
+ squareGenerator(0, _channels[winning_channel].d.freq, 0,
+ 0, data, len);
+ } else if (_level == 0)
+ /* shortcut: no sound is being played. */
+ return;
+
+ lowPassFilter(data, len);
+}
+
+void Player_V2::generatePCjrSamples(int16 *data, uint len) {
+ int i, j;
+ int freq, vol;
+
+ memset(data, 0, 2 * sizeof(int16) * len);
+ bool hasdata = false;
+
+ for (i = 1; i < 3; i++) {
+ freq = _channels[i].d.freq >> 6;
+ if (_channels[i].d.volume && _channels[i].d.time_left) {
+ for (j = 0; j < i; j++) {
+ if (_channels[j].d.volume
+ && _channels[j].d.time_left
+ && freq == (_channels[j].d.freq >> 6)) {
+ /* HACK: this channel is playing at
+ * the same frequency as another.
+ * Synchronize it to the same phase to
+ * prevent interference.
+ */
+ _timer_count[i] = _timer_count[j];
+ _timer_output ^= (1 << i) &
+ (_timer_output ^ _timer_output << (i - j));
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < 4; i++) {
+ freq = _channels[i].d.freq >> 6;
+ vol = (65535 - _channels[i].d.volume) >> 12;
+ if (!_channels[i].d.volume || !_channels[i].d.time_left) {
+ _timer_count[i] -= len << FIXP_SHIFT;
+ if (_timer_count[i] < 0)
+ _timer_count[i] = 0;
+ } else if (i < 3) {
+ hasdata = true;
+ squareGenerator(i, freq, vol, 0, data, len);
+ } else {
+ int noiseFB = (freq & 4) ? FB_WNOISE : FB_PNOISE;
+ int n = (freq & 3);
+
+ freq = (n == 3) ? 2 * (_channels[2].d.freq>>6) : 1 << (5 + n);
+ hasdata = true;
+ squareGenerator(i, freq, vol, noiseFB, data, len);
+ }
+#if 0
+ debug(9, "channel[%d]: freq %d %.1f ; volume %d",
+ i, freq, 111860.0 / freq, vol);
+#endif
+ }
+
+ if (_level || hasdata)
+ lowPassFilter(data, len);
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_v2.h b/engines/scumm/players/player_v2.h
new file mode 100644
index 0000000000..33878ff08b
--- /dev/null
+++ b/engines/scumm/players/player_v2.h
@@ -0,0 +1,72 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_V2_H
+#define SCUMM_PLAYERS_PLAYER_V2_H
+
+#include "scumm/players/player_v2base.h"
+
+namespace Scumm {
+
+/**
+ * Scumm V2 PC-Speaker MIDI driver.
+ * This simulates the pc speaker sound, which is driven by the 8253 (square
+ * wave generator) and a low-band filter.
+ */
+class Player_V2 : public Player_V2Base {
+public:
+ Player_V2(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr);
+ virtual ~Player_V2();
+
+ // MusicEngine API
+ virtual void setMusicVolume(int vol);
+ virtual void startSound(int sound);
+ virtual void stopSound(int sound);
+ virtual void stopAllSounds();
+// virtual int getMusicTimer();
+ virtual int getSoundStatus(int sound) const;
+
+ // AudioStream API
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+
+protected:
+ unsigned int _update_step;
+ unsigned int _decay;
+ int _level;
+ unsigned int _RNG;
+ unsigned int _volumetable[16];
+
+ int _timer_count[4];
+ int _timer_output;
+
+protected:
+ virtual void generateSpkSamples(int16 *data, uint len);
+ virtual void generatePCjrSamples(int16 *data, uint len);
+
+ void lowPassFilter(int16 *data, uint len);
+ void squareGenerator(int channel, int freq, int vol,
+ int noiseFeedback, int16 *sample, uint len);
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_v2a.cpp b/engines/scumm/players/player_v2a.cpp
new file mode 100644
index 0000000000..aeccb8b7cb
--- /dev/null
+++ b/engines/scumm/players/player_v2a.cpp
@@ -0,0 +1,1954 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "engines/engine.h"
+#include "scumm/players/player_v2a.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+#define BASE_FREQUENCY 3579545
+
+static uint32 CRCtable[256];
+
+
+static void InitCRC() {
+ const uint32 poly = 0xEDB88320;
+ int i, j;
+ uint32 n;
+
+ for (i = 0; i < 256; i++) {
+ n = i;
+ for (j = 0; j < 8; j++)
+ n = (n & 1) ? ((n >> 1) ^ poly) : (n >> 1);
+ CRCtable[i] = n;
+ }
+}
+
+static uint32 GetCRC(byte *data, int len) {
+ uint32 CRC = 0xFFFFFFFF;
+ int i;
+ for (i = 0; i < len; i++)
+ CRC = (CRC >> 8) ^ CRCtable[(CRC ^ data[i]) & 0xFF];
+ return CRC ^ 0xFFFFFFFF;
+}
+
+class V2A_Sound {
+public:
+ V2A_Sound() : _id(0), _mod(NULL) { }
+ virtual ~V2A_Sound() {}
+ virtual void start(Player_MOD *mod, int id, const byte *data) = 0;
+ virtual bool update() = 0;
+ virtual void stop() = 0;
+protected:
+ int _id;
+ Player_MOD *_mod;
+};
+
+// unsupported sound effect, print warning message to console
+class V2A_Sound_Unsupported : public V2A_Sound {
+public:
+ V2A_Sound_Unsupported() { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ warning("player_v2a - sound %i not supported", id);
+ }
+ virtual bool update() { return false; }
+ virtual void stop() { }
+};
+
+// template, automatically stops all channels when a sound is silenced
+template<int numChan>
+class V2A_Sound_Base : public V2A_Sound {
+public:
+ V2A_Sound_Base() : _offset(0), _size(0), _data(0) { }
+ V2A_Sound_Base(uint16 offset, uint16 size) : _offset(offset), _size(size), _data(0) { }
+ virtual void stop() {
+ assert(_id);
+ for (int i = 0; i < numChan; i++)
+ _mod->stopChannel(_id | (i << 8));
+ _id = 0;
+ free(_data);
+ _data = 0;
+ }
+protected:
+ const uint16 _offset;
+ const uint16 _size;
+
+ char *_data;
+};
+
+// plays a music track
+class V2A_Sound_Music : public V2A_Sound {
+public:
+ V2A_Sound_Music(uint16 instoff, uint16 voloff, uint16 chan1off, uint16 chan2off, uint16 chan3off, uint16 chan4off, uint16 sampoff, bool looped) :
+ _instoff(instoff), _voloff(voloff), _chan1off(chan1off), _chan2off(chan2off), _chan3off(chan3off), _chan4off(chan4off), _sampoff(sampoff), _looped(looped) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+
+ _data = (char *)malloc(READ_LE_UINT16(data));
+ memcpy(_data, data, READ_LE_UINT16(data));
+
+ _chan[0].dataptr_i = _chan1off;
+ _chan[1].dataptr_i = _chan2off;
+ _chan[2].dataptr_i = _chan3off;
+ _chan[3].dataptr_i = _chan4off;
+ for (int i = 0; i < 4; i++) {
+ _chan[i].dataptr = _chan[i].dataptr_i;
+ _chan[i].volbase = 0;
+ _chan[i].volptr = 0;
+ _chan[i].chan = 0;
+ _chan[i].dur = 0;
+ _chan[i].ticks = 0;
+ }
+ update();
+ }
+ virtual bool update() {
+ assert(_id);
+ int i, j = 0;
+ for (i = 0; i < 4; i++) {
+ if (_chan[i].dur) {
+ if (!--_chan[i].dur) {
+ _mod->stopChannel(_id | (_chan[i].chan << 8));
+ } else {
+ _mod->setChannelVol(_id | (_chan[i].chan << 8),
+ READ_BE_UINT16(_data + _chan[i].volbase + (_chan[i].volptr++ << 1)));
+ if (_chan[i].volptr == 0) {
+ _mod->stopChannel(_id | (_chan[i].chan << 8));
+ _chan[i].dur = 0;
+ }
+ }
+ }
+ if (!_chan[i].dataptr) {
+ j++;
+ continue;
+ }
+ if (READ_BE_UINT16(_data + _chan[i].dataptr) <= _chan[i].ticks) {
+ if (READ_BE_UINT16(_data + _chan[i].dataptr + 2) == 0xFFFF) {
+ if (_looped) {
+ _chan[i].dataptr = _chan[i].dataptr_i;
+ _chan[i].ticks = 0;
+ if (READ_BE_UINT16(_data + _chan[i].dataptr) > 0) {
+ _chan[i].ticks++;
+ continue;
+ }
+ } else {
+ _chan[i].dataptr = 0;
+ j++;
+ continue;
+ }
+ }
+ int freq = BASE_FREQUENCY / READ_BE_UINT16(_data + _chan[i].dataptr + 2);
+ int inst = READ_BE_UINT16(_data + _chan[i].dataptr + 8);
+ _chan[i].volbase = _voloff + (READ_BE_UINT16(_data + _instoff + (inst << 5)) << 9);
+ _chan[i].volptr = 0;
+ _chan[i].chan = READ_BE_UINT16(_data + _chan[i].dataptr + 6) & 0x3;
+
+ if (_chan[i].dur) // if there's something playing, stop it
+ _mod->stopChannel(_id | (_chan[i].chan << 8));
+
+ _chan[i].dur = READ_BE_UINT16(_data + _chan[i].dataptr + 4);
+
+ int vol = READ_BE_UINT16(_data + _chan[i].volbase + (_chan[i].volptr++ << 1));
+
+ int pan;
+ if ((_chan[i].chan == 0) || (_chan[i].chan == 3))
+ pan = -127;
+ else
+ pan = 127;
+ int offset = READ_BE_UINT16(_data + _instoff + (inst << 5) + 0x14);
+ int len = READ_BE_UINT16(_data + _instoff + (inst << 5) + 0x18);
+ int loopoffset = READ_BE_UINT16(_data + _instoff + (inst << 5) + 0x16);
+ int looplen = READ_BE_UINT16(_data + _instoff + (inst << 5) + 0x10);
+
+ int size = len + looplen;
+ char *data = (char *)malloc(size);
+ memcpy(data, _data + _sampoff + offset, len);
+ memcpy(data + len, _data + _sampoff + loopoffset, looplen);
+
+ _mod->startChannel(_id | (_chan[i].chan << 8), data, size, freq, vol, len, looplen + len, pan);
+ _chan[i].dataptr += 16;
+ }
+ _chan[i].ticks++;
+ }
+ if (j == 4)
+ return false;
+ return true;
+ }
+ virtual void stop() {
+ assert(_id);
+ for (int i = 0; i < 4; i++) {
+ if (_chan[i].dur)
+ _mod->stopChannel(_id | (_chan[i].chan << 8));
+ }
+ free(_data);
+ _id = 0;
+ }
+private:
+ const uint16 _instoff;
+ const uint16 _voloff;
+ const uint16 _chan1off;
+ const uint16 _chan2off;
+ const uint16 _chan3off;
+ const uint16 _chan4off;
+ const uint16 _sampoff;
+ const bool _looped;
+
+ char *_data;
+ struct tchan {
+ uint16 dataptr_i;
+ uint16 dataptr;
+ uint16 volbase;
+ uint8 volptr;
+ uint16 chan;
+ uint16 dur;
+ uint16 ticks;
+ } _chan[4];
+};
+
+// plays a single waveform
+class V2A_Sound_Single : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_Single(uint16 offset, uint16 size, uint16 freq, uint8 vol) :
+ V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ char *tmp_data = (char *)malloc(_size);
+ memcpy(tmp_data, data + _offset, _size);
+ int vol = (_vol << 2) | (_vol >> 4);
+ _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, vol, 0, 0);
+ _ticks = 1 + (60 * _size * _freq) / BASE_FREQUENCY;
+ }
+ virtual bool update() {
+ assert(_id);
+ _ticks--;
+ if (!_ticks) {
+ return false;
+ }
+ return true;
+ }
+private:
+ const uint16 _freq;
+ const uint8 _vol;
+
+ int _ticks;
+};
+
+// plays a single looped waveform
+class V2A_Sound_SingleLooped : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_SingleLooped(uint16 offset, uint16 size, uint16 freq, uint8 vol, uint16 loopoffset, uint16 loopsize) :
+ V2A_Sound_Base<1>(offset, size), _loopoffset(loopoffset), _loopsize(loopsize), _freq(freq), _vol(vol) { }
+ V2A_Sound_SingleLooped(uint16 offset, uint16 size, uint16 freq, uint8 vol) :
+ V2A_Sound_Base<1>(offset, size), _loopoffset(0), _loopsize(size), _freq(freq), _vol(vol) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ char *tmp_data = (char *)malloc(_size);
+ memcpy(tmp_data, data + _offset, _size);
+ int vol = (_vol << 2) | (_vol >> 4);
+ _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, vol, _loopoffset, _loopoffset + _loopsize);
+ }
+ virtual bool update() {
+ assert(_id);
+ return true;
+ }
+private:
+ const uint16 _loopoffset;
+ const uint16 _loopsize;
+ const uint16 _freq;
+ const uint8 _vol;
+};
+
+// plays two looped waveforms
+class V2A_Sound_MultiLooped : public V2A_Sound_Base<2> {
+public:
+ V2A_Sound_MultiLooped(uint16 offset, uint16 size, uint16 freq1, uint8 vol1, uint16 freq2, uint8 vol2) :
+ V2A_Sound_Base<2>(offset, size), _freq1(freq1), _vol1(vol1), _freq2(freq2), _vol2(vol2) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ char *tmp_data1 = (char *)malloc(_size);
+ char *tmp_data2 = (char *)malloc(_size);
+ memcpy(tmp_data1, data + _offset, _size);
+ memcpy(tmp_data2, data + _offset, _size);
+ int vol1 = (_vol1 << 1) | (_vol1 >> 5);
+ int vol2 = (_vol2 << 1) | (_vol2 >> 5);
+ _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, vol1, 0, _size, -127);
+ _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, vol2, 0, _size, 127);
+ }
+ virtual bool update() {
+ assert(_id);
+ return true;
+ }
+private:
+ const uint16 _freq1;
+ const uint8 _vol1;
+ const uint16 _freq2;
+ const uint8 _vol2;
+};
+
+// plays two looped waveforms for a fixed number of frames
+class V2A_Sound_MultiLoopedDuration : public V2A_Sound_MultiLooped {
+public:
+ V2A_Sound_MultiLoopedDuration(uint16 offset, uint16 size, uint16 freq1, uint8 vol1, uint16 freq2, uint8 vol2, uint16 numframes) :
+ V2A_Sound_MultiLooped(offset, size, freq1, vol1, freq2, vol2), _duration(numframes) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ V2A_Sound_MultiLooped::start(mod, id, data);
+ _ticks = 0;
+ }
+ virtual bool update() {
+ assert(_id);
+ _ticks++;
+ if (_ticks >= _duration)
+ return false;
+ return true;
+ }
+private:
+ const uint16 _duration;
+
+ int _ticks;
+};
+
+// plays a single looped waveform which starts at one frequency and bends to another frequency, where it remains until stopped
+class V2A_Sound_SingleLoopedPitchbend : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_SingleLoopedPitchbend(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint8 vol, uint8 step) :
+ V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2), _vol(vol), _step(step) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ char *tmp_data = (char *)malloc(_size);
+ memcpy(tmp_data, data + _offset, _size);
+ int vol = (_vol << 2) | (_vol >> 4);
+ _curfreq = _freq1;
+ _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, vol, 0, _size);
+ }
+ virtual bool update() {
+ assert(_id);
+ if (_freq1 < _freq2) {
+ _curfreq += _step;
+ if (_curfreq > _freq2)
+ _curfreq = _freq2;
+ else
+ _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+ } else {
+ _curfreq -= _step;
+ if (_curfreq < _freq2)
+ _curfreq = _freq2;
+ else
+ _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+ }
+ return true;
+ }
+private:
+ const uint16 _freq1;
+ const uint16 _freq2;
+ const uint8 _vol;
+ const uint16 _step;
+
+ uint16 _curfreq;
+};
+
+// plays a single looped waveform starting at a specific frequency/volume, dropping in frequency and fading volume to zero
+// used when Maniac Mansion explodes
+class V2A_Sound_Special_Maniac69 : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_Special_Maniac69(uint16 offset, uint16 size, uint16 freq, uint8 vol) :
+ V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ char *tmp_data = (char *)malloc(_size);
+ memcpy(tmp_data, data + _offset, _size);
+ _curvol = (_vol << 3) | (_vol >> 3);
+ _curfreq = _freq;
+ _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, _curvol >> 1, 0, _size);
+ }
+ virtual bool update() {
+ assert(_id);
+ _curfreq += 2;
+ _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+ _curvol--;
+ if (_curvol == 0)
+ return false;
+ _mod->setChannelVol(_id, _curvol >> 1);
+ return true;
+ }
+private:
+ const uint16 _freq;
+ const uint8 _vol;
+
+ uint16 _curfreq;
+ uint16 _curvol;
+};
+
+// plays a single looped waveform, fading the volume from zero to maximum at one rate, then back to zero at another rate
+// used when a microwave oven goes 'Ding'
+class V2A_Sound_Special_ManiacDing : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_Special_ManiacDing(uint16 offset, uint16 size, uint16 freq, uint8 fadeinrate, uint8 fadeoutrate) :
+ V2A_Sound_Base<1>(offset, size), _freq(freq), _fade1(fadeinrate), _fade2(fadeoutrate) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ char *tmp_data = (char *)malloc(_size);
+ memcpy(tmp_data, data + _offset, _size);
+ _curvol = 1;
+ _dir = 0;
+ _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, _curvol, 0, _size);
+ }
+ virtual bool update() {
+ assert(_id);
+ if (_dir == 0) {
+ _curvol += _fade1;
+ if (_curvol > 0x3F) {
+ _curvol = 0x3F;
+ _dir = 1;
+ }
+ } else {
+ _curvol -= _fade2;
+ if (_curvol < 1)
+ return false;
+ }
+ _mod->setChannelVol(_id, (_curvol << 2) | (_curvol >> 4));
+ return true;
+ }
+private:
+ const uint16 _freq;
+ const uint16 _fade1;
+ const uint16 _fade2;
+
+ int _curvol;
+ int _dir;
+};
+
+// plays two looped waveforms, fading the volume from zero to maximum at one rate, then back to zero at another rate
+// used in Zak McKracken for several stereo 'Ding' sounds
+class V2A_Sound_Special_ZakStereoDing : public V2A_Sound_Base<2> {
+public:
+ V2A_Sound_Special_ZakStereoDing(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint8 fadeinrate, uint8 fadeoutrate) :
+ V2A_Sound_Base<2>(offset, size), _freq1(freq1), _freq2(freq2), _fade1(fadeinrate), _fade2(fadeoutrate) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ char *tmp_data1 = (char *)malloc(_size);
+ char *tmp_data2 = (char *)malloc(_size);
+ memcpy(tmp_data1, data + _offset, _size);
+ memcpy(tmp_data2, data + _offset, _size);
+ _curvol = 1;
+ _dir = 0;
+ _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, 1, 0, _size, -127);
+ _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, 1, 0, _size, 127);
+ }
+ virtual bool update() {
+ assert(_id);
+ if (_dir == 0) {
+ _curvol += _fade1;
+ if (_curvol > 0x3F) {
+ _curvol = 0x3F;
+ _dir = 1;
+ }
+ } else {
+ _curvol -= _fade2;
+ if (_curvol < 1)
+ return false;
+ }
+ _mod->setChannelVol(_id | 0x000, (_curvol << 1) | (_curvol >> 5));
+ _mod->setChannelVol(_id | 0x100, (_curvol << 1) | (_curvol >> 5));
+ return true;
+ }
+private:
+ const uint16 _freq1;
+ const uint16 _freq2;
+ const uint16 _fade1;
+ const uint16 _fade2;
+
+ int _curvol;
+ int _dir;
+};
+
+// plays a single looped waveform, starting at one frequency and at full volume, bending down to another frequency, and then fading volume to zero
+// used in Maniac Mansion for the tentacle sounds
+class V2A_Sound_Special_ManiacTentacle : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_Special_ManiacTentacle(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint16 step) :
+ V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2), _step(step) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ char *tmp_data = (char *)malloc(_size);
+ memcpy(tmp_data, data + _offset, _size);
+ _curfreq = _freq1;
+ _curvol = 0x3F;
+ _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, (_curvol << 2) | (_curvol >> 4), 0, _size);
+ }
+ virtual bool update() {
+ assert(_id);
+ if (_curfreq > _freq2)
+ _curvol = 0x3F + _freq2 - _curfreq;
+ if (_curvol < 1)
+ return false;
+ _curfreq += _step;
+ _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+ _mod->setChannelVol(_id, (_curvol << 2) | (_curvol >> 4));
+ return true;
+ }
+private:
+ const uint16 _freq1;
+ const uint16 _freq2;
+ const uint16 _step;
+
+ uint16 _curfreq;
+ int _curvol;
+};
+
+// plays a single looped waveform, starting at one frequency, bending down to another frequency, and then back up to the original frequency
+// used for electronic noises
+class V2A_Sound_Special_Maniac59 : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_Special_Maniac59(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint16 step, uint8 vol) :
+ V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2), _step(step), _vol(vol) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ char *tmp_data = (char *)malloc(_size);
+ memcpy(tmp_data, data + _offset, _size);
+ int vol = (_vol << 2) | (_vol >> 4);
+ _curfreq = _freq1;
+ _dir = 2;
+ _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, vol, 0, _size);
+ }
+ virtual bool update() {
+ assert(_id);
+ if (_dir == 2) {
+ _curfreq += _step;
+ if (_curfreq > _freq2) {
+ _curfreq = _freq2;
+ _dir = 1;
+ }
+ _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+ } else if (_dir == 1) {
+ _curfreq -= _step;
+ if (_curfreq < _freq1) {
+ _curfreq = _freq1;
+ _dir = 0;
+ }
+ _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+ }
+ return true;
+ }
+private:
+ const uint16 _freq1;
+ const uint16 _freq2;
+ const uint16 _step;
+ const uint8 _vol;
+
+ uint16 _curfreq;
+ int _dir;
+};
+
+// plays a single looped waveform, simultaneously bending the frequency downward and slowly fading volume to zero
+// don't remember where this one is used
+class V2A_Sound_Special_Maniac61 : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_Special_Maniac61(uint16 offset, uint16 size, uint16 freq1, uint16 freq2) :
+ V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ char *tmp_data = (char *)malloc(_size);
+ memcpy(tmp_data, data + _offset, _size);
+ _curfreq = _freq1;
+ _curvol = 0x3F;
+ _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, (_curvol << 2) | (_curvol >> 4), 0, _size);
+ }
+ virtual bool update() {
+ assert(_id);
+ _curfreq++;
+ if (!(_curfreq & 3))
+ _curvol--;
+ if ((_curfreq == _freq2) || (_curvol == 0))
+ return false;
+ _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+ _mod->setChannelVol(_id, (_curvol << 2) | (_curvol >> 4));
+ return true;
+ }
+private:
+ const uint16 _freq1;
+ const uint16 _freq2;
+
+ uint16 _curfreq;
+ uint8 _curvol;
+};
+
+// intermittently plays two looped waveforms for a specific duration
+// used for ringing telephones
+class V2A_Sound_Special_ManiacPhone : public V2A_Sound_Base<2> {
+public:
+ V2A_Sound_Special_ManiacPhone(uint16 offset, uint16 size, uint16 freq1, uint8 vol1, uint16 freq2, uint8 vol2, uint16 numframes, uint8 playwidth, uint8 loopwidth) :
+ V2A_Sound_Base<2>(offset, size), _freq1(freq1), _vol1(vol1), _freq2(freq2), _vol2(vol2), _duration(numframes), _playwidth(playwidth), _loopwidth(loopwidth) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ _data = (char *)malloc(READ_LE_UINT16(data));
+ memcpy(_data, data, READ_LE_UINT16(data));
+ soundon();
+ _ticks = 0;
+ _loop = 0;
+ }
+ virtual bool update() {
+ assert(_id);
+ if (_loop == _playwidth) {
+ _mod->stopChannel(_id | 0x000);
+ _mod->stopChannel(_id | 0x100);
+ }
+ if (_loop == _loopwidth) {
+ _loop = 0;
+ soundon();
+ }
+ _loop++;
+ _ticks++;
+ if (_ticks >= _duration)
+ return false;
+ return true;
+ }
+private:
+ const uint16 _freq1;
+ const uint8 _vol1;
+ const uint16 _freq2;
+ const uint8 _vol2;
+ const uint16 _duration;
+ const uint8 _playwidth;
+ const uint8 _loopwidth;
+
+ int _ticks;
+ int _loop;
+
+ void soundon() {
+ char *tmp_data1 = (char *)malloc(_size);
+ char *tmp_data2 = (char *)malloc(_size);
+ memcpy(tmp_data1, _data + _offset, _size);
+ memcpy(tmp_data2, _data + _offset, _size);
+ int vol1 = (_vol1 << 1) | (_vol1 >> 5);
+ int vol2 = (_vol2 << 1) | (_vol2 >> 5);
+ _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, vol1, 0, _size, -127);
+ _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, vol2, 0, _size, 127);
+ }
+};
+
+// intermittently plays a single waveform for a specified duration
+// used when applying a wrench to a pipe
+class V2A_Sound_Special_Maniac46 : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_Special_Maniac46(uint16 offset, uint16 size, uint16 freq, uint8 vol, uint8 loopwidth, uint8 numloops) :
+ V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol), _loopwidth(loopwidth), _numloops(numloops) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ _data = (char *)malloc(READ_LE_UINT16(data));
+ memcpy(_data, data, READ_LE_UINT16(data));
+ soundon();
+ _loop = 0;
+ _loopctr = 0;
+ }
+ virtual bool update() {
+ assert(_id);
+ _loop++;
+ if (_loop == _loopwidth) {
+ _loop = 0;
+ _loopctr++;
+ if (_loopctr == _numloops)
+ return false;
+ _mod->stopChannel(_id);
+ soundon();
+ }
+ return true;
+ }
+private:
+ const uint16 _freq;
+ const uint8 _vol;
+ const uint8 _loopwidth;
+ const uint8 _numloops;
+
+ int _loop;
+ int _loopctr;
+
+ void soundon() {
+ char *tmp_data = (char *)malloc(_size);
+ memcpy(tmp_data, _data + _offset, _size);
+ _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, (_vol << 2) | (_vol >> 4), 0, 0);
+ }
+};
+
+// plays a single waveform at irregular intervals for a specified number of frames, possibly looped
+// used for typewriter noises, as well as tapping on the bus in Zak McKracken
+class V2A_Sound_Special_ManiacTypewriter : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_Special_ManiacTypewriter(uint16 offset, uint16 size, uint16 freq, uint8 vol, uint8 numdurs, const uint8 *durations, bool looped) :
+ V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol), _numdurs(numdurs), _durations(durations), _looped(looped) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ _data = (char *)malloc(READ_LE_UINT16(data));
+ memcpy(_data, data, READ_LE_UINT16(data));
+ soundon();
+ _curdur = 0;
+ _ticks = _durations[_curdur++];
+ }
+ virtual bool update() {
+ assert(_id);
+ _ticks--;
+ if (!_ticks) {
+ if (_curdur == _numdurs) {
+ if (_looped)
+ _curdur = 0;
+ else
+ return false;
+ }
+ _mod->stopChannel(_id);
+ soundon();
+ _ticks = _durations[_curdur++];
+ }
+ return true;
+ }
+private:
+ const uint16 _freq;
+ const uint8 _vol;
+ const uint8 _numdurs;
+ const uint8 *_durations;
+ const bool _looped;
+
+ int _ticks;
+ int _curdur;
+
+ void soundon() {
+ char *tmp_data = (char *)malloc(_size);
+ memcpy(tmp_data, _data + _offset, _size);
+ _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, (_vol << 2) | (_vol >> 4), 0, 0);
+ }
+};
+
+// plays two looped waveforms pitch bending up at various predefined rates
+// used for some sort of siren-like noise in Maniac Mansion
+class V2A_Sound_Special_Maniac44 : public V2A_Sound_Base<2> {
+public:
+ V2A_Sound_Special_Maniac44(uint16 offset1, uint16 size1, uint16 offset2, uint16 size2, uint16 freq1, uint16 freq2, uint8 vol) :
+ _offset1(offset1), _size1(size1), _offset2(offset2), _size2(size2), _freq1(freq1), _freq2(freq2), _vol(vol) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ _data = (char *)malloc(READ_LE_UINT16(data));
+ memcpy(_data, data, READ_LE_UINT16(data));
+
+ _loopnum = 1;
+ _step = 2;
+ _curfreq = _freq1;
+
+ soundon(_data + _offset1, _size1);
+ }
+ virtual bool update() {
+ assert(_id);
+ _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _curfreq);
+ _mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / (_curfreq + 3));
+ _curfreq -= _step;
+ if (_loopnum == 7) {
+ if ((BASE_FREQUENCY / _curfreq) >= 65536)
+ return false;
+ else
+ return true;
+ }
+ if (_curfreq >= _freq2)
+ return true;
+ const char steps[8] = {0, 2, 2, 3, 4, 8, 15, 2};
+ _curfreq = _freq1;
+ _step = steps[++_loopnum];
+ if (_loopnum == 7) {
+ _mod->stopChannel(_id | 0x000);
+ _mod->stopChannel(_id | 0x100);
+ soundon(_data + _offset2, _size2);
+ }
+ return true;
+ }
+private:
+ const uint16 _offset1;
+ const uint16 _size1;
+ const uint16 _offset2;
+ const uint16 _size2;
+ const uint16 _freq1;
+ const uint16 _freq2;
+ const uint8 _vol;
+
+ int _curfreq;
+ uint16 _loopnum;
+ uint16 _step;
+
+ void soundon(const char *data, int size) {
+ char *tmp_data1 = (char *)malloc(size);
+ char *tmp_data2 = (char *)malloc(size);
+ memcpy(tmp_data1, data, size);
+ memcpy(tmp_data2, data, size);
+ int vol = (_vol << 1) | (_vol >> 5);
+ _mod->startChannel(_id | 0x000, tmp_data1, size, BASE_FREQUENCY / _curfreq, vol, 0, size, -127);
+ _mod->startChannel(_id | 0x100, tmp_data2, size, BASE_FREQUENCY / (_curfreq + 3), vol, 0, size, 127);
+ }
+};
+
+// plays 4 looped waveforms, each at modulating frequencies
+// used for the siren noise in Maniac Mansion
+class V2A_Sound_Special_Maniac32 : public V2A_Sound_Base<4> {
+public:
+ V2A_Sound_Special_Maniac32(uint16 offset1, uint16 size1, uint16 offset2, uint16 size2, uint8 vol) :
+ _offset1(offset1), _size1(size1), _offset2(offset2), _size2(size2), _vol(vol) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+
+ _freq1 = 0x02D0;
+ _step1 = -0x000A;
+ _freq2 = 0x0122;
+ _step2 = 0x000A;
+ _freq3 = 0x02BC;
+ _step3 = -0x0005;
+ _freq4 = 0x010E;
+ _step4 = 0x0007;
+
+ char *tmp_data1 = (char *)malloc(_size1);
+ char *tmp_data2 = (char *)malloc(_size2);
+ char *tmp_data3 = (char *)malloc(_size1);
+ char *tmp_data4 = (char *)malloc(_size2);
+ memcpy(tmp_data1, data + _offset1, _size1);
+ memcpy(tmp_data2, data + _offset2, _size2);
+ memcpy(tmp_data3, data + _offset1, _size1);
+ memcpy(tmp_data4, data + _offset2, _size2);
+ _mod->startChannel(_id | 0x000, tmp_data1, _size1, BASE_FREQUENCY / _freq1, _vol, 0, _size1, -127);
+ _mod->startChannel(_id | 0x100, tmp_data2, _size2, BASE_FREQUENCY / _freq2, _vol, 0, _size2, 127);
+ _mod->startChannel(_id | 0x200, tmp_data3, _size1, BASE_FREQUENCY / _freq3, _vol, 0, _size1, 127);
+ _mod->startChannel(_id | 0x300, tmp_data4, _size2, BASE_FREQUENCY / _freq4, _vol, 0, _size2, -127);
+ }
+ virtual bool update() {
+ assert(_id);
+ updatefreq(_freq1, _step1, 0x00AA, 0x00FA);
+ updatefreq(_freq2, _step2, 0x019A, 0x03B6);
+ updatefreq(_freq3, _step3, 0x00AA, 0x00FA);
+ updatefreq(_freq4, _step4, 0x019A, 0x03B6);
+ _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _freq1);
+ _mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / _freq2);
+ _mod->setChannelFreq(_id | 0x200, BASE_FREQUENCY / _freq3);
+ _mod->setChannelFreq(_id | 0x300, BASE_FREQUENCY / _freq4);
+ return true;
+ }
+private:
+ const uint16 _offset1;
+ const uint16 _size1;
+ const uint16 _offset2;
+ const uint16 _size2;
+ const uint8 _vol;
+
+ uint16 _freq1;
+ int16 _step1;
+ uint16 _freq2;
+ int16 _step2;
+ uint16 _freq3;
+ int16 _step3;
+ uint16 _freq4;
+ int16 _step4;
+
+ void updatefreq(uint16 &freq, int16 &step, uint16 min, uint16 max) {
+ freq += step;
+ if (freq <= min) {
+ freq = min;
+ step = -step;
+ }
+ if (freq >= max) {
+ freq = max;
+ step = -step;
+ }
+ }
+};
+
+// plays 4 looped waveforms
+// used in the white crystal chamber
+class V2A_Sound_Special_Zak70 : public V2A_Sound_Base<4> {
+public:
+ V2A_Sound_Special_Zak70(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint16 freq3, uint16 freq4, uint8 vol) :
+ V2A_Sound_Base<4>(offset, size), _freq1(freq1), _freq2(freq2), _freq3(freq3), _freq4(freq4), _vol(vol) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+
+ char *tmp_data1 = (char *)malloc(_size);
+ char *tmp_data2 = (char *)malloc(_size);
+ char *tmp_data3 = (char *)malloc(_size);
+ char *tmp_data4 = (char *)malloc(_size);
+ memcpy(tmp_data1, data + _offset, _size);
+ memcpy(tmp_data2, data + _offset, _size);
+ memcpy(tmp_data3, data + _offset, _size);
+ memcpy(tmp_data4, data + _offset, _size);
+ _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, _vol, 0, _size, -127);
+ _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, _vol, 0, _size, 127);
+ _mod->startChannel(_id | 0x200, tmp_data3, _size, BASE_FREQUENCY / _freq3, _vol, 0, _size, 127);
+ _mod->startChannel(_id | 0x300, tmp_data4, _size, BASE_FREQUENCY / _freq4, _vol, 0, _size, -127);
+ }
+ virtual bool update() {
+ assert(_id);
+ return true;
+ }
+protected:
+ const uint16 _freq1;
+ const uint16 _freq2;
+ const uint16 _freq3;
+ const uint16 _freq4;
+ const uint8 _vol;
+};
+
+// plays 4 looped waveforms and fades volume to zero after a specific delay
+// used when the Mindbender disappears
+class V2A_Sound_Special_Zak101 : public V2A_Sound_Special_Zak70 {
+public:
+ V2A_Sound_Special_Zak101(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint16 freq3, uint16 freq4, uint8 vol, uint16 dur) :
+ V2A_Sound_Special_Zak70(offset, size, freq1, freq2, freq3, freq4, vol), _dur(dur) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ V2A_Sound_Special_Zak70::start(mod, id, data);
+ _ticks = _dur;
+ }
+ virtual bool update() {
+ assert(_id);
+ if (!--_ticks)
+ return false;
+ if (_ticks < _vol) {
+ _mod->setChannelVol(_id | 0x000, _ticks);
+ _mod->setChannelVol(_id | 0x100, _ticks);
+ _mod->setChannelVol(_id | 0x200, _ticks);
+ _mod->setChannelVol(_id | 0x300, _ticks);
+ }
+ return true;
+ }
+private:
+ const uint16 _dur;
+
+ int _ticks;
+};
+
+// plays a single looped waveform and slowly fades volume to zero
+// used when refilling oxygen
+class V2A_Sound_Special_Zak37 : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_Special_Zak37(uint16 offset, uint16 size, uint16 freq, uint8 vol) :
+ V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ char *tmp_data = (char *)malloc(_size);
+ memcpy(tmp_data, data + _offset, _size);
+ _curvol = _vol << 2;
+ _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, _curvol, 0, _size);
+ }
+ virtual bool update() {
+ assert(_id);
+ if (!--_curvol)
+ return false;
+ _mod->setChannelVol(_id, _curvol);
+ return true;
+ }
+private:
+ const uint16 _freq;
+ const uint8 _vol;
+
+ int _curvol;
+};
+
+// plays a single looped waveform, slowly bending from one frequency to another and then slowly fading volume from max to zero
+// used in Zak for airplane taking off and landing
+class V2A_Sound_Special_ZakAirplane : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_Special_ZakAirplane(uint16 offset, uint16 size, uint16 freq1, uint16 freq2) :
+ V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ char *tmp_data = (char *)malloc(_size);
+ memcpy(tmp_data, data + _offset, _size);
+ _curfreq = _freq1;
+ _curvol = 0x3F;
+ _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, (_curvol << 2) | (_curvol >> 4), 0, _size);
+ _ticks = 0;
+ }
+ virtual bool update() {
+ assert(_id);
+ _ticks++;
+ if (_ticks < 4)
+ return true;
+ _ticks = 0;
+ if (_curfreq == _freq2) {
+ _curvol--;
+ if (_curvol == 0)
+ return false;
+ _mod->setChannelVol(_id, (_curvol << 2) | (_curvol >> 4));
+ } else {
+ if (_freq1 < _freq2)
+ _curfreq++;
+ else
+ _curfreq--;
+ _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+ }
+ return true;
+ }
+private:
+ const uint16 _freq1;
+ const uint16 _freq2;
+
+ uint16 _curfreq;
+ int _curvol;
+ int _ticks;
+};
+
+// plays 4 looped waveforms, starting at specific frequencies and bending at different rates while fading volume to zero
+// used when the white crystal machine turns off
+class V2A_Sound_Special_Zak71 : public V2A_Sound_Base<4> {
+public:
+ V2A_Sound_Special_Zak71(uint16 offset, uint16 size) :
+ _offset(offset), _size(size) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+
+ _freq1 = 0x00C8;
+ _freq2 = 0x0190;
+ _freq3 = 0x0320;
+ _freq4 = 0x0640;
+ _vol = 0x78;
+
+ char *tmp_data1 = (char *)malloc(_size);
+ char *tmp_data2 = (char *)malloc(_size);
+ char *tmp_data3 = (char *)malloc(_size);
+ char *tmp_data4 = (char *)malloc(_size);
+ memcpy(tmp_data1, data + _offset, _size);
+ memcpy(tmp_data2, data + _offset, _size);
+ memcpy(tmp_data3, data + _offset, _size);
+ memcpy(tmp_data4, data + _offset, _size);
+ _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, MIN((_vol >> 1) + 3, 0x32), 0, _size, -127);
+ _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, MIN((_vol >> 1) + 3, 0x32), 0, _size, 127);
+ _mod->startChannel(_id | 0x200, tmp_data3, _size, BASE_FREQUENCY / _freq3, MIN((_vol >> 1) + 3, 0x32), 0, _size, 127);
+ _mod->startChannel(_id | 0x300, tmp_data4, _size, BASE_FREQUENCY / _freq4, MIN((_vol >> 1) + 3, 0x32), 0, _size, -127);
+ }
+ virtual bool update() {
+ assert(_id);
+ _freq1 += 0x14;
+ _freq2 += 0x1E;
+ _freq3 += 0x32;
+ _freq4 += 0x50;
+ _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _freq1);
+ _mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / _freq2);
+ _mod->setChannelFreq(_id | 0x200, BASE_FREQUENCY / _freq3);
+ _mod->setChannelFreq(_id | 0x300, BASE_FREQUENCY / _freq4);
+ _vol--;
+ if (_vol == 0)
+ return false;
+ _mod->setChannelVol(_id | 0x000, MIN((_vol >> 1) + 3, 0x32));
+ _mod->setChannelVol(_id | 0x100, MIN((_vol >> 1) + 3, 0x32));
+ _mod->setChannelVol(_id | 0x200, MIN((_vol >> 1) + 3, 0x32));
+ _mod->setChannelVol(_id | 0x300, MIN((_vol >> 1) + 3, 0x32));
+ return true;
+ }
+private:
+ const uint16 _offset;
+ const uint16 _size;
+
+ uint16 _freq1;
+ uint16 _freq2;
+ uint16 _freq3;
+ uint16 _freq4;
+ uint8 _vol;
+};
+
+// plays a single looped waveform, bending the frequency upward at a varying rate
+// used when the Skolarian device activates
+class V2A_Sound_Special_Zak99 : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_Special_Zak99(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint8 vol) :
+ V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2), _vol(vol) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ char *tmp_data = (char *)malloc(_size);
+ memcpy(tmp_data, data + _offset, _size);
+ _curfreq = _freq1;
+ _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, (_vol << 2) | (_vol >> 4), 0, _size);
+ _bendrate = 8;
+ _bendctr = 100;
+ _holdctr = 30;
+ }
+ virtual bool update() {
+ assert(_id);
+ if (_curfreq >= _freq2) {
+ _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+ _curfreq -= _bendrate;
+ if (--_bendctr)
+ return true;
+ _bendrate--;
+ if (_bendrate < 2)
+ _bendrate = 2;
+ } else {
+ if (!--_holdctr)
+ return false;
+ }
+ return true;
+ }
+private:
+ const uint16 _freq1;
+ const uint16 _freq2;
+ const uint16 _vol;
+
+ uint16 _curfreq;
+ uint16 _bendrate;
+ uint16 _bendctr;
+ uint16 _holdctr;
+};
+
+// plays one waveform, then switches to a different looped waveform and slowly fades volume to zero
+// used when depressurizing the hostel
+class V2A_Sound_Special_Zak54 : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_Special_Zak54(uint16 offset1, uint16 size1, uint16 offset2, uint16 size2, uint16 freq) :
+ _offset1(offset1), _size1(size1), _offset2(offset2), _size2(size2), _freq(freq) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ _data = (char *)malloc(READ_LE_UINT16(data));
+ memcpy(_data, data, READ_LE_UINT16(data));
+ char *tmp_data = (char *)malloc(_size1);
+ memcpy(tmp_data, data + _offset1, _size1);
+ _vol = 0xFC;
+ _mod->startChannel(_id, tmp_data, _size1, BASE_FREQUENCY / _freq, _vol, 0, _size1);
+ _loop = _size1 * _freq * 60 / BASE_FREQUENCY;
+ }
+ virtual bool update() {
+ assert(_id);
+ if (!_loop) {
+ _vol--;
+ if (_vol)
+ _mod->setChannelVol(_id, _vol);
+ else
+ return false;
+ } else if (!--_loop) {
+ _mod->stopChannel(_id);
+ char *tmp_data = (char *)malloc(_size2);
+ memcpy(tmp_data, _data + _offset2, _size2);
+ _mod->startChannel(_id, tmp_data, _size2, BASE_FREQUENCY / _freq, _vol, 0, _size2);
+ }
+ return true;
+ }
+
+private:
+ const uint16 _offset1;
+ const uint16 _offset2;
+ const uint16 _size1;
+ const uint16 _size2;
+ const uint16 _freq;
+
+ int _vol;
+ int _loop;
+};
+
+// plays 2 looped waveforms at different frequencies, pulsing at different frequencies and ramping the volume up and down once
+// used when abducted at the Bermuda Triangle
+class V2A_Sound_Special_Zak110 : public V2A_Sound_Base<2> {
+public:
+ V2A_Sound_Special_Zak110(uint16 offset1, uint16 size1, uint16 offset2, uint16 size2, uint16 freq1, uint16 freq2) :
+ _offset1(offset1), _size1(size1), _offset2(offset2), _size2(size2), _freq1(freq1), _freq2(freq2) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ _data = (char *)malloc(READ_LE_UINT16(data));
+ memcpy(_data, data, READ_LE_UINT16(data));
+
+ _loopnum = 0;
+ _vol = 0x1500;
+ _beepcount = 0;
+ }
+ virtual bool update() {
+ char *tmp_data;
+ assert(_id);
+
+ int vol = (((_vol >> 7) & 0x7E) | ((_vol >> 15) & 0x01));
+ _beepcount++;
+
+ switch (_beepcount & 0x3) {
+ case 0:
+ _mod->stopChannel(_id | 0x000);
+ break;
+ case 1:
+ tmp_data = (char *)malloc(_size1);
+ memcpy(tmp_data, _data + _offset1, _size1);
+ _mod->startChannel(_id | 0x000, tmp_data, _size1, BASE_FREQUENCY / _freq1, vol, 0, _size1, -127);
+ break;
+ default:
+ _mod->setChannelVol(_id | 0x000, vol);
+ break;
+ }
+
+ switch (_beepcount & 0x7) {
+ case 0:
+ _mod->stopChannel(_id | 0x100);
+ break;
+ case 1:
+ tmp_data = (char *)malloc(_size2);
+ memcpy(tmp_data, _data + _offset2, _size2);
+ _mod->startChannel(_id | 0x100, tmp_data, _size2, BASE_FREQUENCY / _freq2, vol, 0, _size2, 127);
+ break;
+ default:
+ _mod->setChannelVol(_id | 0x100, vol);
+ break;
+ }
+
+ if (_loopnum == 0) {
+ _vol += 0x80;
+ if (_vol == 0x4000) {
+ _vol = 0x3F00;
+ _loopnum = 1;
+ }
+ } else if (_loopnum == 1) {
+ _vol -= 0x20;
+ if (_vol == 0x2000)
+ _loopnum = 2;
+ }
+ return true;
+ }
+private:
+ const uint16 _offset1;
+ const uint16 _size1;
+ const uint16 _offset2;
+ const uint16 _size2;
+ const uint16 _freq1;
+ const uint16 _freq2;
+
+ uint16 _loopnum;
+ uint16 _vol;
+ uint16 _beepcount;
+};
+
+// plays a stereo siren, sweeping up and down quickly several times before sweeping up slowly, stopping, and then going silent
+// door orb sound in the Mars Face
+class V2A_Sound_Special_Zak32 : public V2A_Sound_Base<2> {
+public:
+ V2A_Sound_Special_Zak32(uint16 offset1, uint16 offset2, uint16 size1, uint16 size2) :
+ _offset1(offset1), _offset2(offset2), _size1(size1), _size2(size2) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ _data = (char *)malloc(READ_LE_UINT16(data));
+ memcpy(_data, data, READ_LE_UINT16(data));
+
+ _loopnum = 1;
+ _freqmod = -4;
+ _freq = 0x00C8;
+
+ char *tmp_data1 = (char *)malloc(_size1);
+ char *tmp_data2 = (char *)malloc(_size1);
+ memcpy(tmp_data1, _data + _offset1, _size1);
+ memcpy(tmp_data2, _data + _offset1, _size1);
+ _mod->startChannel(_id | 0x000, tmp_data1, _size1, BASE_FREQUENCY / _freq, 0x7F, 0, _size1, -127);
+ _mod->startChannel(_id | 0x100, tmp_data2, _size1, BASE_FREQUENCY / (_freq + 3), 0x7F, 0, _size1, 127);
+ }
+ virtual bool update() {
+ assert(_id);
+
+ if (_loopnum < 7) {
+ _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _freq);
+ _mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / (_freq + 3));
+ _freq += _freqmod;
+ if (_freq <= 0x80)
+ _freqmod = -_freqmod;
+ else if (_freq >= 0xC8) {
+ _freqmod = -_freqmod;
+ _loopnum++;
+ if (_loopnum == 7) {
+ _freq = 0x00C8;
+ _freqmod = 2;
+ }
+ }
+ return true;
+ } else {
+ if (_loopnum == 7) {
+ _mod->stopChannel(_id | 0x000);
+ _mod->stopChannel(_id | 0x100);
+
+ char *tmp_data1 = (char *)malloc(_size2);
+ char *tmp_data2 = (char *)malloc(_size2);
+ memcpy(tmp_data1, _data + _offset2, _size2);
+ memcpy(tmp_data2, _data + _offset2, _size2);
+ _mod->startChannel(_id | 0x000, tmp_data1, _size2, BASE_FREQUENCY / (_freq), 0x7F, 0, _size2, -127);
+ _mod->startChannel(_id | 0x100, tmp_data2, _size2, BASE_FREQUENCY / (_freq + 3), 0x7F, 0, _size2, 127);
+ _loopnum++;
+ } else {
+ _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _freq);
+ _mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / (_freq + 3));
+ }
+ _freq -= _freqmod;
+ if (_freq > 0)
+ return true;
+ else
+ return false;
+ }
+ }
+private:
+ const uint16 _offset1;
+ const uint16 _offset2;
+ const uint16 _size1;
+ const uint16 _size2;
+
+ uint16 _loopnum;
+ int16 _freqmod;
+ uint16 _freq;
+};
+
+// plays a looped waveform, increasing frequency and reducing volume once the frequency reaches a certain point
+// probably used for some sort of vehicle sound
+class V2A_Sound_Special_Zak52 : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_Special_Zak52(uint16 offset, uint16 size) :
+ _offset(offset), _size(size) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ _data = (char *)malloc(READ_LE_UINT16(data));
+ memcpy(_data, data, READ_LE_UINT16(data));
+
+ _curfreq = 0x0312;
+
+ char *tmp_data = (char *)malloc(_size);
+ memcpy(tmp_data, _data + _offset, _size);
+ _mod->startChannel(_id | 0x000, tmp_data, _size, BASE_FREQUENCY / _curfreq, 0xFF, 0, _size, -127);
+ }
+ virtual bool update() {
+ assert(_id);
+ int vol = (_curfreq - 0xC8) >> 3;
+ if (vol > 0x3F)
+ vol = 0x3F;
+ vol = (vol << 2) | (vol >> 4);
+ _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _curfreq);
+ _mod->setChannelVol(_id | 0x000, vol);
+ _curfreq--;
+ if (_curfreq >= 0x107)
+ return true;
+ else
+ return false;
+ }
+private:
+ const uint16 _offset;
+ const uint16 _size;
+
+ uint16 _curfreq;
+};
+
+// plays a looped waveform, sweeping the frequency up while modulating it (alternating which channel updates) and fading volume out
+// used when teleporting out with the yellow crystal
+class V2A_Sound_Special_Zak61 : public V2A_Sound_Base<2> {
+public:
+ V2A_Sound_Special_Zak61(uint16 offset, uint16 size) :
+ _offset(offset), _size(size) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ _data = (char *)malloc(READ_LE_UINT16(data));
+ memcpy(_data, data, READ_LE_UINT16(data));
+
+ _loop = 1;
+ _curfreq = 0x01F4;
+
+ char *tmp_data1 = (char *)malloc(_size);
+ char *tmp_data2 = (char *)malloc(_size);
+ memcpy(tmp_data1, _data + _offset, _size);
+ memcpy(tmp_data2, _data + _offset, _size);
+ _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _curfreq, 0x7F, 0, _size, -127);
+ // start 2nd channel silent
+ _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _curfreq, 0, 0, _size, 127);
+ }
+ virtual bool update() {
+ assert(_id);
+ int freq = (_loop << 4) + _curfreq;
+ int vol = freq - 0x76;
+ if (vol > 0x3F)
+ vol = 0x3F;
+ vol = (vol << 1) | (vol >> 5);
+ switch (_loop) {
+ case 0:
+ _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / freq);
+ _mod->setChannelVol(_id | 0x000, vol);
+ break;
+ case 1:
+ _mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / freq);
+ _mod->setChannelVol(_id | 0x100, vol);
+ break;
+ }
+ _loop = (_loop + 1) & 3;
+ if (!_loop) {
+ _curfreq -= 4;
+ if (_curfreq <= 0x80)
+ return false;
+ }
+ return true;
+ }
+private:
+ const uint16 _offset;
+ const uint16 _size;
+
+ uint16 _loop;
+ uint16 _curfreq;
+};
+
+// just like Zak61, but sweeps frequency in the other direction
+// used when teleporting in with the yellow crystal
+class V2A_Sound_Special_Zak62 : public V2A_Sound_Base<2> {
+public:
+ V2A_Sound_Special_Zak62(uint16 offset, uint16 size) :
+ _offset(offset), _size(size) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ _data = (char *)malloc(READ_LE_UINT16(data));
+ memcpy(_data, data, READ_LE_UINT16(data));
+
+ _loop = 1;
+ _curfreq = 0x0080;
+
+ char *tmp_data1 = (char *)malloc(_size);
+ char *tmp_data2 = (char *)malloc(_size);
+ memcpy(tmp_data1, _data + _offset, _size);
+ memcpy(tmp_data2, _data + _offset, _size);
+ _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _curfreq, 0x7F, 0, _size, -127);
+ // start 2nd channel silent
+ _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _curfreq, 0, 0, _size, 127);
+ }
+ virtual bool update() {
+ assert(_id);
+ int freq = (_loop << 4) + _curfreq;
+ int vol = 0x0200 - freq;
+ if (vol > 0x3F)
+ vol = 0x3F;
+ vol = (vol << 1) | (vol >> 5);
+ switch (_loop) {
+ case 0:
+ _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / freq);
+ _mod->setChannelVol(_id | 0x000, vol);
+ break;
+ case 1:
+ _mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / freq);
+ _mod->setChannelVol(_id | 0x100, vol);
+ break;
+ }
+ _loop = (_loop + 1) & 3;
+ if (!_loop) {
+ _curfreq += 4;
+ if (_curfreq >= 0x01F4)
+ return false;
+ }
+ return true;
+ }
+private:
+ const uint16 _offset;
+ const uint16 _size;
+
+ uint16 _loop;
+ uint16 _curfreq;
+};
+
+// plays a series of double-looped sounds at varying frequencies and delays, very specialized
+// Guardian of the Sphinx, perhaps?
+class V2A_Sound_Special_Zak82 : public V2A_Sound_Base<4> {
+public:
+ V2A_Sound_Special_Zak82(uint16 offset, uint16 size) :
+ _offset(offset), _size(size) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ _data = (char *)malloc(READ_LE_UINT16(data));
+ memcpy(_data, data, READ_LE_UINT16(data));
+
+ // Wait values were to insure playing an integral number of loops on each sample
+ // and have been adjusted to reflect the actual duration spent playing
+ _loop = 0;
+ _playctr = 240;
+ _wait1 = 76; // was 39, extended to loop twice
+ _wait2 = 10000;
+ _wait3 = 10000;
+ _wait4 = 10000;
+
+ int size = 2000;
+ int offset = _offset;
+ assert(offset + size <= _offset + _size);
+ char *tmp_data = (char *)malloc(size);
+ memcpy(tmp_data, _data + offset, size);
+ _mod->startChannel(_id | 0x000, tmp_data, size, BASE_FREQUENCY / 0x0479, 0xFF, 0, size);
+ }
+ virtual bool update() {
+ assert(_id);
+ char *tmp_data1, *tmp_data2;
+ int size, offset = _offset;
+
+ if (!--_wait1) {
+ _wait1 = 10000;
+ _mod->stopChannel(_id | 0x000);
+ } else if (!--_wait2) {
+ _wait2 = 10000;
+ _mod->stopChannel(_id | 0x000);
+ } else if (!--_wait3) {
+ _wait3 = 10000;
+ _mod->stopChannel(_id | 0x200);
+ } else if (!--_wait4) {
+ _wait4 = 10000;
+ _mod->stopChannel(_id | 0x100);
+ _mod->stopChannel(_id | 0x300);
+ }
+ if (--_playctr)
+ return true;
+
+ switch (++_loop) {
+ case 1:
+ size = 6300;
+ offset += 0x07D0;
+ assert(offset + size <= _offset + _size);
+ tmp_data1 = (char *)malloc(size);
+ memcpy(tmp_data1, _data + offset, size);
+ _mod->startChannel(_id | 0x000, tmp_data1, size, BASE_FREQUENCY / 0x0479, 0x7F, 0, size, -127);
+ _wait2 = 241; // was 120, extended to loop twice
+ _playctr = 10;
+ break;
+ case 2:
+ size = 6292;
+ offset += 0x206C;
+ assert(offset + size <= _offset + _size);
+ tmp_data1 = (char *)malloc(size);
+ memcpy(tmp_data1, _data + offset, size);
+ _mod->startChannel(_id | 0x200, tmp_data1, size, BASE_FREQUENCY / 0x0384, 0x7F, 0, size, 127);
+ _wait3 = 189; // was 94, extended to loop twice
+ _playctr = 20;
+ break;
+ case 3:
+ size = 6300;
+ offset += 0x07D0;
+ assert(offset + size <= _offset + _size);
+ tmp_data1 = (char *)malloc(size);
+ tmp_data2 = (char *)malloc(size);
+ memcpy(tmp_data1, _data + offset, size);
+ memcpy(tmp_data2, _data + offset, size);
+ _mod->startChannel(_id | 0x100, tmp_data1, size, BASE_FREQUENCY / 0x01E0, 0x7F, 0, size, 127);
+ _mod->startChannel(_id | 0x300, tmp_data2, size, BASE_FREQUENCY / 0x01E0, 0x7F, 0, size, -127);
+ _wait4 = 101; // was 50, extended to loop twice
+ _playctr = 120;
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+private:
+ const uint16 _offset;
+ const uint16 _size;
+
+ uint16 _loop;
+ uint16 _playctr;
+ uint16 _wait1;
+ uint16 _wait2;
+ uint16 _wait3;
+ uint16 _wait4;
+};
+
+// plays a "ding" (volume 0-max-0) followed by a sound sample, a pause, then loops again
+// Mars Tram about to depart
+class V2A_Sound_Special_Zak86 : public V2A_Sound_Base<1> {
+public:
+ V2A_Sound_Special_Zak86(uint16 offset, uint16 size) :
+ _offset(offset), _size(size) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ _data = (char *)malloc(READ_LE_UINT16(data));
+ memcpy(_data, data, READ_LE_UINT16(data));
+
+ _mode = 0;
+ _vol = 0;
+ _volmod = 16;
+
+ int size = 32;
+ int offset = _offset + 0x2B8E;
+ assert(offset + size <= _offset + _size);
+ char *tmp_data = (char *)malloc(size);
+ memcpy(tmp_data, _data + offset, size);
+ _mod->startChannel(_id | 0x000, tmp_data, size, BASE_FREQUENCY / 0x0096, 0, 0, size, 0);
+ }
+ virtual bool update() {
+ assert(_id);
+ int size, offset;
+ char *tmp_data;
+
+ switch (_mode) {
+ case 0:
+ _mod->setChannelVol(_id | 0x000, (_vol << 2) | (_vol >> 4));
+ if (_vol + _volmod > 0) {
+ _vol += _volmod;
+ if (_vol > 0x3F) {
+ _vol = 0x3F;
+ _volmod = -4;
+ }
+ return true;
+ }
+ _mod->stopChannel(_id | 0x000);
+ _mode = 1;
+
+ size = 0x2B8E;
+ offset = _offset;
+ assert(offset + size <= _offset + _size);
+ tmp_data = (char *)malloc(size);
+ memcpy(tmp_data, _data + offset, size);
+ _mod->startChannel(_id | 0x000, tmp_data, size, BASE_FREQUENCY / 0x0152, 0x3F);
+ _volmod = 100;
+ break;
+ case 1:
+ if (!--_volmod) {
+ size = 32;
+ offset = _offset + 0x2B8E;
+ assert(offset + size <= _offset + _size);
+ tmp_data = (char *)malloc(size);
+ memcpy(tmp_data, _data + offset, size);
+ _mod->startChannel(_id | 0x000, tmp_data, size, BASE_FREQUENCY / 0x0096, 0, 0, size, 0);
+ _mode = 0;
+ _vol = 0;
+ _volmod = 16;
+ }
+ break;
+ }
+ return true;
+ }
+private:
+ const uint16 _offset;
+ const uint16 _size;
+
+ uint16 _mode;
+ uint16 _vol;
+ int16 _volmod;
+};
+
+// modulates volume on 4 samples, frequency on only 2 of them
+// Skolarian device pedestal activated without any parts
+class V2A_Sound_Special_Zak98 : public V2A_Sound_Base<4> {
+public:
+ V2A_Sound_Special_Zak98(uint16 offset, uint16 size) :
+ _offset(offset), _size(size) { }
+ virtual void start(Player_MOD *mod, int id, const byte *data) {
+ _mod = mod;
+ _id = id;
+ _data = (char *)malloc(READ_LE_UINT16(data));
+ memcpy(_data, data, READ_LE_UINT16(data));
+
+ _freq[0] = 0x1E0;
+ _freq[1] = 0x3E8;
+ _freq[2] = 0x200;
+ _freq[3] = 0x408;
+ _vol[0] = 0x3F;
+ _vol[1] = 0x3F;
+ _vol[2] = 0x3F;
+ _vol[3] = 0x3F;
+ _freqmod = 4;
+ _volmod[0] = -2;
+ _volmod[1] = -1;
+
+ char *tmp_data1 = (char *)malloc(_size);
+ char *tmp_data2 = (char *)malloc(_size);
+ char *tmp_data3 = (char *)malloc(_size);
+ char *tmp_data4 = (char *)malloc(_size);
+ memcpy(tmp_data1, _data + _offset, _size);
+ memcpy(tmp_data2, _data + _offset, _size);
+ memcpy(tmp_data3, _data + _offset, _size);
+ memcpy(tmp_data4, _data + _offset, _size);
+ _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq[0], _vol[0], 0, _size, -127);
+ _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq[1], _vol[1], 0, _size, 127);
+ _mod->startChannel(_id | 0x200, tmp_data3, _size, BASE_FREQUENCY / _freq[2], _vol[2], 0, _size, 127);
+ _mod->startChannel(_id | 0x300, tmp_data4, _size, BASE_FREQUENCY / _freq[3], _vol[3], 0, _size, -127);
+ }
+ virtual bool update() {
+ assert(_id);
+ const uint16 _minvol[2] = {0x2E, 0x32};
+ int i;
+ for (i = 0; i < 4; i++) {
+ _mod->setChannelFreq(_id | (i << 8), BASE_FREQUENCY / _freq[i]);
+ _mod->setChannelVol(_id | (i << 8), _vol[i]);
+ }
+ for (i = 0; i < 2; i++) {
+ _vol[i] += _volmod[i];
+ if (_vol[i] > 0x3F) {
+ _vol[i] = 0x3F;
+ _volmod[i] = -_volmod[i];
+ } else if (_vol[i] < _minvol[i]) {
+ _vol[i] = _minvol[i];
+ _volmod[i] = -_volmod[i];
+ }
+ _vol[i + 2] = _vol[i];
+ }
+ _freq[0] += _freqmod;
+ if (_freq[0] > 0x2BC) {
+ _freq[0] = 0x2BC;
+ _freqmod = -_freqmod;
+ } else if (_freq[0] < 0x1E0) {
+ _freq[0] = 0x1E0;
+ _freqmod = -_freqmod;
+ }
+ _freq[2] = _freq[0] + 0x20;
+ return true;
+ }
+private:
+ const uint16 _offset;
+ const uint16 _size;
+
+ uint16 _freq[4];
+ uint16 _vol[4];
+ int16 _freqmod;
+ int16 _volmod[2];
+};
+
+#define CRCToSound(CRC, SOUND) \
+ if (crc == CRC) \
+ return new SOUND
+
+static V2A_Sound *findSound(unsigned long crc) {
+ CRCToSound(0x8FAB08C4, V2A_Sound_SingleLooped(0x006C, 0x2B58, 0x016E, 0x3F)); // Maniac 17
+ CRCToSound(0xB673160A, V2A_Sound_SingleLooped(0x006C, 0x1E78, 0x01C2, 0x1E)); // Maniac 38
+ CRCToSound(0x4DB1D0B2, V2A_Sound_MultiLooped(0x0072, 0x1BC8, 0x023D, 0x3F, 0x0224, 0x3F)); // Maniac 20
+ CRCToSound(0x754D75EF, V2A_Sound_Single(0x0076, 0x0738, 0x01FC, 0x3F)); // Maniac 10
+ CRCToSound(0x6E3454AF, V2A_Sound_Single(0x0076, 0x050A, 0x017C, 0x3F)); // Maniac 12
+ CRCToSound(0x92F0BBB6, V2A_Sound_Single(0x0076, 0x3288, 0x012E, 0x3F)); // Maniac 41
+ CRCToSound(0xE1B13982, V2A_Sound_MultiLoopedDuration(0x0078, 0x0040, 0x007C, 0x3F, 0x007B, 0x3F, 0x001E)); // Maniac 21
+ CRCToSound(0x288B16CF, V2A_Sound_MultiLoopedDuration(0x007A, 0x0040, 0x007C, 0x3F, 0x007B, 0x3F, 0x000A)); // Maniac 11
+ CRCToSound(0xA7565268, V2A_Sound_MultiLoopedDuration(0x007A, 0x0040, 0x00F8, 0x3F, 0x00F7, 0x3F, 0x000A)); // Maniac 19
+ CRCToSound(0x7D419BFC, V2A_Sound_MultiLoopedDuration(0x007E, 0x0040, 0x012C, 0x3F, 0x0149, 0x3F, 0x001E)); // Maniac 22
+ CRCToSound(0x1B52280C, V2A_Sound_Single(0x0098, 0x0A58, 0x007F, 0x32)); // Maniac 6
+ CRCToSound(0x38D4A810, V2A_Sound_Single(0x0098, 0x2F3C, 0x0258, 0x32)); // Maniac 7
+ CRCToSound(0x09F98FC2, V2A_Sound_Single(0x0098, 0x0A56, 0x012C, 0x32)); // Maniac 16
+ CRCToSound(0x90440A65, V2A_Sound_Single(0x0098, 0x0208, 0x0078, 0x28)); // Maniac 28
+ CRCToSound(0x985C76EF, V2A_Sound_Single(0x0098, 0x0D6E, 0x00C8, 0x32)); // Maniac 30
+ CRCToSound(0x76156137, V2A_Sound_Single(0x0098, 0x2610, 0x017C, 0x39)); // Maniac 39
+ CRCToSound(0x5D95F88C, V2A_Sound_Single(0x0098, 0x0A58, 0x007F, 0x1E)); // Maniac 65
+ CRCToSound(0x92D704EA, V2A_Sound_SingleLooped(0x009C, 0x29BC, 0x012C, 0x3F, 0x1BD4, 0x0DE8)); // Maniac 15
+ CRCToSound(0x92F5513C, V2A_Sound_Single(0x009E, 0x0DD4, 0x01F4, 0x3F)); // Maniac 13
+ CRCToSound(0xCC2F3B5A, V2A_Sound_Single(0x009E, 0x00DE, 0x01AC, 0x3F)); // Maniac 43
+ CRCToSound(0x153207D3, V2A_Sound_Single(0x009E, 0x0E06, 0x02A8, 0x3F)); // Maniac 67
+ CRCToSound(0xC4F370CE, V2A_Sound_Single(0x00AE, 0x0330, 0x01AC, 0x3F)); // Maniac 8
+ CRCToSound(0x928C4BAC, V2A_Sound_Single(0x00AE, 0x08D6, 0x01AC, 0x3F)); // Maniac 9
+ CRCToSound(0x62D5B11F, V2A_Sound_Single(0x00AE, 0x165C, 0x01CB, 0x3F)); // Maniac 27
+ CRCToSound(0x3AB22CB5, V2A_Sound_Single(0x00AE, 0x294E, 0x012A, 0x3F)); // Maniac 62
+ CRCToSound(0x2D70BBE9, V2A_Sound_SingleLoopedPitchbend(0x00B4, 0x1702, 0x03E8, 0x0190, 0x3F, 5)); // Maniac 64
+ CRCToSound(0xFA4C1B1C, V2A_Sound_Special_Maniac69(0x00B2, 0x1702, 0x0190, 0x3F)); // Maniac 69
+ CRCToSound(0x19D50D67, V2A_Sound_Special_ManiacDing(0x00B6, 0x0020, 0x00C8, 16, 2)); // Maniac 14
+ CRCToSound(0x3E6FBE15, V2A_Sound_Special_ManiacTentacle(0x00B2, 0x0010, 0x007C, 0x016D, 1)); // Maniac 25
+ CRCToSound(0x5305753C, V2A_Sound_Special_ManiacTentacle(0x00B2, 0x0010, 0x007C, 0x016D, 7)); // Maniac 36
+ CRCToSound(0x28895106, V2A_Sound_Special_Maniac59(0x00C0, 0x00FE, 0x00E9, 0x0111, 4, 0x0A)); // Maniac 59
+ CRCToSound(0xB641ACF6, V2A_Sound_Special_Maniac61(0x00C8, 0x0100, 0x00C8, 0x01C2)); // Maniac 61
+ CRCToSound(0xE1A91583, V2A_Sound_Special_ManiacPhone(0x00D0, 0x0040, 0x007C, 0x3F, 0x007B, 0x3F, 0x3C, 5, 6)); // Maniac 23
+ CRCToSound(0x64816ED5, V2A_Sound_Special_ManiacPhone(0x00D0, 0x0040, 0x00BE, 0x37, 0x00BD, 0x37, 0x3C, 5, 6)); // Maniac 24
+ CRCToSound(0x639D72C2, V2A_Sound_Special_Maniac46(0x00D0, 0x10A4, 0x0080, 0x3F, 0x28, 3)); // Maniac 46
+ CRCToSound(0xE8826D92, V2A_Sound_Special_ManiacTypewriter(0x00EC, 0x025A, 0x023C, 0x3F, 8, (const uint8 *)"\x20\x41\x04\x21\x08\x10\x13\x07", true)); // Maniac 45
+ CRCToSound(0xEDFF3D41, V2A_Sound_Single(0x00F8, 0x2ADE, 0x01F8, 0x3F)); // Maniac 42 (this should echo, but it's barely noticeable and I don't feel like doing it)
+ CRCToSound(0x15606D06, V2A_Sound_Special_Maniac32(0x0148, 0x0020, 0x0168, 0x0020, 0x3F)); // Maniac 32
+ CRCToSound(0x753EAFE3, V2A_Sound_Special_Maniac44(0x017C, 0x0010, 0x018C, 0x0020, 0x00C8, 0x0080, 0x3F)); // Maniac 44
+ CRCToSound(0xB1AB065C, V2A_Sound_Music(0x0032, 0x00B2, 0x08B2, 0x1222, 0x1A52, 0x23C2, 0x3074, false)); // Maniac 50
+ CRCToSound(0x091F5D9C, V2A_Sound_Music(0x0032, 0x0132, 0x0932, 0x1802, 0x23D2, 0x3EA2, 0x4F04, false)); // Maniac 58
+
+ CRCToSound(0x8E2C8AB3, V2A_Sound_SingleLooped(0x005C, 0x0F26, 0x0168, 0x3C)); // Zak 41
+ CRCToSound(0x3792071F, V2A_Sound_SingleLooped(0x0060, 0x1A18, 0x06A4, 0x3F)); // Zak 88
+ CRCToSound(0xF192EDE9, V2A_Sound_SingleLooped(0x0062, 0x0054, 0x01FC, 0x1E)); // Zak 68
+ CRCToSound(0xC43B0245, V2A_Sound_Special_Zak70(0x006C, 0x166E, 0x00C8, 0x0190, 0x0320, 0x0640, 0x32)); // Zak 70
+ CRCToSound(0xCEB51670, V2A_Sound_SingleLooped(0x00AC, 0x26DC, 0x012C, 0x3F)); // Zak 42
+ CRCToSound(0x10347B51, V2A_Sound_SingleLooped(0x006C, 0x00E0, 0x0594, 0x3F)); // Zak 18
+ CRCToSound(0x9D2FADC0, V2A_Sound_MultiLooped(0x0072, 0x1FC8, 0x016A, 0x3F, 0x01CE, 0x3F)); // Zak 80
+ CRCToSound(0xFAD2C676, V2A_Sound_MultiLooped(0x0076, 0x0010, 0x0080, 0x3F, 0x0090, 0x3B)); // Zak 40
+ CRCToSound(0x01508B48, V2A_Sound_Single(0x0076, 0x0D8C, 0x017C, 0x3F)); // Zak 90
+ CRCToSound(0x9C18DC46, V2A_Sound_Single(0x0076, 0x0D8C, 0x015E, 0x3F)); // Zak 91
+ CRCToSound(0xF98F7EAC, V2A_Sound_Single(0x0076, 0x0D8C, 0x0140, 0x3F)); // Zak 92
+ CRCToSound(0xC925FBEF, V2A_Sound_MultiLoopedDuration(0x0080, 0x0010, 0x0080, 0x3F, 0x0090, 0x3B, 0x0168)); // Zak 53
+ CRCToSound(0xCAB35257, V2A_Sound_Special_Zak101(0x00DA, 0x425C, 0x023C, 0x08F0, 0x0640, 0x0478, 0x3F, 0x012C)); // Zak 101
+ CRCToSound(0xA31FE4FD, V2A_Sound_Single(0x0094, 0x036A, 0x00E1, 0x3F)); // Zak 97
+ CRCToSound(0x0A1AE0F5, V2A_Sound_Single(0x009E, 0x0876, 0x0168, 0x3F)); // Zak 5
+ CRCToSound(0xD01A66CB, V2A_Sound_Single(0x009E, 0x04A8, 0x0168, 0x3F)); // Zak 47
+ CRCToSound(0x5497B912, V2A_Sound_Single(0x009E, 0x0198, 0x01F4, 0x3F)); // Zak 39
+ CRCToSound(0x2B50362F, V2A_Sound_Single(0x009E, 0x09B6, 0x023D, 0x3F)); // Zak 67
+ CRCToSound(0x7BFB6E72, V2A_Sound_Single(0x009E, 0x0D14, 0x0078, 0x3F)); // Zak 69
+ CRCToSound(0xB803A792, V2A_Sound_Single(0x009E, 0x2302, 0x02BC, 0x3F)); // Zak 78
+ CRCToSound(0x7AB82E39, V2A_Sound_SingleLooped(0x00A0, 0x2A3C, 0x016E, 0x3F, 0x1018, 0x1A24)); // Zak 100
+ CRCToSound(0x28057CEC, V2A_Sound_Single(0x0098, 0x0FEC, 0x0140, 0x32)); // Zak 63
+ CRCToSound(0x1180A2FC, V2A_Sound_Single(0x0098, 0x0F06, 0x0190, 0x32)); // Zak 64
+ CRCToSound(0x12616755, V2A_Sound_Single(0x0098, 0x14C8, 0x023C, 0x14)); // Zak 9
+ CRCToSound(0x642723AA, V2A_Sound_Special_Zak37(0x00A2, 0x1702, 0x01F4, 0x3F)); // Zak 37
+ CRCToSound(0xDEE56848, V2A_Sound_Single(0x009A, 0x0F86, 0x0100, 0x3F)); // Zak 93
+ CRCToSound(0xF9BE27B8, V2A_Sound_Special_Zak37(0x011C, 0x1704, 0x0228, 0x3F)); // Zak 113
+ CRCToSound(0xC73487B2, V2A_Sound_Single(0x00B0, 0x18BA, 0x0478, 0x3F)); // Zak 81
+ CRCToSound(0x32D8F925, V2A_Sound_Single(0x00B0, 0x2E46, 0x00F0, 0x3F)); // Zak 94
+ CRCToSound(0x988C83A5, V2A_Sound_Single(0x00B0, 0x0DE0, 0x025B, 0x3F)); // Zak 106
+ CRCToSound(0x8F1E3B3D, V2A_Sound_Single(0x00B0, 0x05FE, 0x04E2, 0x3F)); // Zak 107
+ CRCToSound(0x0A2A7646, V2A_Sound_Single(0x00B0, 0x36FE, 0x016E, 0x3F)); // Zak 43
+ CRCToSound(0x6F1FC435, V2A_Sound_Single(0x00B0, 0x2808, 0x044C, 0x3F)); // Zak 108
+ CRCToSound(0x870EFC29, V2A_Sound_SingleLoopedPitchbend(0x00BA, 0x0100, 0x03E8, 0x00C8, 0x3F, 3)); // Zak 55
+ CRCToSound(0xED773699, V2A_Sound_Special_ManiacDing(0x00B4, 0x0020, 0x012C, 8, 4)); // Zak 3
+ CRCToSound(0x0BF59774, V2A_Sound_Special_ZakStereoDing(0x00BE, 0x0020, 0x00F8, 0x00F7, 8, 1)); // Zak 72
+ CRCToSound(0x656FFEDE, V2A_Sound_Special_ZakStereoDing(0x00BE, 0x0020, 0x00C4, 0x00C3, 8, 1)); // Zak 73
+ CRCToSound(0xFC4D41E5, V2A_Sound_Special_ZakStereoDing(0x00BE, 0x0020, 0x00A5, 0x00A4, 8, 1)); // Zak 74
+ CRCToSound(0xC0DD2089, V2A_Sound_Special_ZakStereoDing(0x00BE, 0x0020, 0x009C, 0x009B, 8, 1)); // Zak 75
+ CRCToSound(0x627DFD92, V2A_Sound_Special_ZakStereoDing(0x00BE, 0x0020, 0x008B, 0x008A, 8, 1)); // Zak 76
+ CRCToSound(0x703E05C1, V2A_Sound_Special_ZakStereoDing(0x00BE, 0x0020, 0x007C, 0x007B, 8, 1)); // Zak 77
+ CRCToSound(0xB0F77006, V2A_Sound_Special_Zak52(0x00B0, 0x01BC)); // Zak 52
+ CRCToSound(0x5AE9D6A7, V2A_Sound_Special_ZakAirplane(0x00CA, 0x22A4, 0x0113, 0x0227)); // Zak 109
+ CRCToSound(0xABE0D3B0, V2A_Sound_Special_ZakAirplane(0x00CE, 0x22A4, 0x0227, 0x0113)); // Zak 105
+ CRCToSound(0x788CC749, V2A_Sound_Special_Zak71(0x00C8, 0x0B37)); // Zak 71
+ CRCToSound(0x2E2AB1FA, V2A_Sound_Special_Zak99(0x00D4, 0x04F0, 0x0FE3, 0x0080, 0x3F)); // Zak 99
+ CRCToSound(0x1304CF20, V2A_Sound_Special_ManiacTypewriter(0x00DC, 0x0624, 0x023C, 0x3C, 2, (const uint8 *)"\x14\x11", false)); // Zak 79
+ CRCToSound(0xAE68ED91, V2A_Sound_Special_Zak54(0x00D4, 0x1A25, 0x1E1E, 0x0B80, 0x01F4)); // Zak 54
+ CRCToSound(0xA4F40F97, V2A_Sound_Special_Zak61(0x00E4, 0x0020)); // Zak 61
+ CRCToSound(0x348F85CE, V2A_Sound_Special_Zak62(0x00E4, 0x0020)); // Zak 62
+ CRCToSound(0xD473AB86, V2A_Sound_Special_ManiacTypewriter(0x0122, 0x03E8, 0x00BE, 0x3F, 7, (const uint8 *)"\x0F\x0B\x04\x0F\x1E\x0F\x66", false)); // Zak 46
+ CRCToSound(0x84A0BA90, V2A_Sound_Special_Zak110(0x0126, 0x0040, 0x0136, 0x0080, 0x007C, 0x0087)); // Zak 110
+ CRCToSound(0x92680D9F, V2A_Sound_Special_Zak32(0x0140, 0x0150, 0x0010, 0x0010)); // Zak 32
+ CRCToSound(0xABFFDB02, V2A_Sound_Special_Zak86(0x01A2, 0x2BAE)); // Zak 86
+ CRCToSound(0x41045447, V2A_Sound_Special_Zak98(0x017A, 0x0020)); // Zak 98
+ CRCToSound(0xC8EEBD34, V2A_Sound_Special_Zak82(0x01A6, 0x3900)); // Zak 82
+ CRCToSound(0x42F9469F, V2A_Sound_Music(0x05F6, 0x0636, 0x0456, 0x0516, 0x05D6, 0x05E6, 0x0A36, true)); // Zak 96
+ CRCToSound(0x038BBD78, V2A_Sound_Music(0x054E, 0x05CE, 0x044E, 0x04BE, 0x052E, 0x053E, 0x0BCE, true)); // Zak 85
+ CRCToSound(0x06FFADC5, V2A_Sound_Music(0x0626, 0x0686, 0x0446, 0x04F6, 0x0606, 0x0616, 0x0C86, true)); // Zak 87
+ CRCToSound(0xCE20ECF0, V2A_Sound_Music(0x0636, 0x0696, 0x0446, 0x0576, 0x0616, 0x0626, 0x0E96, true)); // Zak 114
+ CRCToSound(0xBDA01BB6, V2A_Sound_Music(0x0678, 0x06B8, 0x0458, 0x0648, 0x0658, 0x0668, 0x0EB8, false)); // Zak 33
+ CRCToSound(0x59976529, V2A_Sound_Music(0x088E, 0x092E, 0x048E, 0x05EE, 0x074E, 0x07EE, 0x112E, true)); // Zak 49
+ CRCToSound(0xED1EED02, V2A_Sound_Music(0x08D0, 0x0950, 0x0440, 0x07E0, 0x08B0, 0x08C0, 0x1350, false)); // Zak 112
+ CRCToSound(0x5A16C037, V2A_Sound_Music(0x634A, 0x64CA, 0x049A, 0x18FA, 0x398A, 0x511A, 0x6CCA, false)); // Zak 95
+ return NULL;
+}
+
+Player_V2A::Player_V2A(ScummEngine *scumm, Audio::Mixer *mixer) {
+ int i;
+ _vm = scumm;
+
+ InitCRC();
+
+ for (i = 0; i < V2A_MAXSLOTS; i++) {
+ _slot[i].id = 0;
+ _slot[i].sound = NULL;
+ }
+
+ _mod = new Player_MOD(mixer);
+ _mod->setUpdateProc(update_proc, this, 60);
+}
+
+Player_V2A::~Player_V2A() {
+ delete _mod;
+}
+
+void Player_V2A::setMusicVolume(int vol) {
+ _mod->setMusicVolume(vol);
+}
+
+int Player_V2A::getSoundSlot(int id) const {
+ int i;
+ for (i = 0; i < V2A_MAXSLOTS; i++) {
+ if (_slot[i].id == id)
+ break;
+ }
+ if (i == V2A_MAXSLOTS) {
+ if (id == 0)
+ warning("player_v2a - out of sound slots");
+ return -1;
+ }
+ return i;
+}
+
+void Player_V2A::stopAllSounds() {
+ for (int i = 0; i < V2A_MAXSLOTS; i++) {
+ if (!_slot[i].id)
+ continue;
+ _slot[i].sound->stop();
+ delete _slot[i].sound;
+ _slot[i].sound = NULL;
+ _slot[i].id = 0;
+ }
+}
+
+void Player_V2A::stopSound(int nr) {
+ int i;
+ if (nr == 0)
+ return;
+ i = getSoundSlot(nr);
+ if (i == -1)
+ return;
+ _slot[i].sound->stop();
+ delete _slot[i].sound;
+ _slot[i].sound = NULL;
+ _slot[i].id = 0;
+}
+
+void Player_V2A::startSound(int nr) {
+ assert(_vm);
+ byte *data = _vm->getResourceAddress(rtSound, nr);
+ assert(data);
+ uint32 crc = GetCRC(data + 0x0A, READ_BE_UINT16(data + 0x08));
+ V2A_Sound *snd = findSound(crc);
+ if (snd == NULL) {
+ warning("player_v2a - sound %i not recognized yet (crc %08X)", nr, crc);
+ return;
+ }
+ stopSound(nr);
+ int i = getSoundSlot();
+ if (i == -1) {
+ delete snd;
+ return;
+ }
+ _slot[i].id = nr;
+ _slot[i].sound = snd;
+ _slot[i].sound->start(_mod, nr, data);
+}
+
+void Player_V2A::update_proc(void *param) {
+ ((Player_V2A *)param)->updateSound();
+}
+
+void Player_V2A::updateSound() {
+ int i;
+ for (i = 0; i < V2A_MAXSLOTS; i++) {
+ if ((_slot[i].id) && (!_slot[i].sound->update())) {
+ _slot[i].sound->stop();
+ delete _slot[i].sound;
+ _slot[i].sound = NULL;
+ _slot[i].id = 0;
+ }
+ }
+}
+
+int Player_V2A::getMusicTimer() {
+ return 0; // FIXME - need to keep track of playing music resources
+}
+
+int Player_V2A::getSoundStatus(int nr) const {
+ for (int i = 0; i < V2A_MAXSLOTS; i++) {
+ if (_slot[i].id == nr)
+ return 1;
+ }
+ return 0;
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_v2a.h b/engines/scumm/players/player_v2a.h
new file mode 100644
index 0000000000..12193635f2
--- /dev/null
+++ b/engines/scumm/players/player_v2a.h
@@ -0,0 +1,73 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_V2A_H
+#define SCUMM_PLAYERS_PLAYER_V2A_H
+
+#include "common/scummsys.h"
+#include "scumm/music.h"
+#include "scumm/players/player_mod.h"
+
+class Mixer;
+
+namespace Scumm {
+
+class ScummEngine;
+class V2A_Sound;
+
+/**
+ * Scumm V2 Amiga sound/music driver.
+ */
+class Player_V2A : public MusicEngine {
+public:
+ Player_V2A(ScummEngine *scumm, Audio::Mixer *mixer);
+ virtual ~Player_V2A();
+
+ virtual void setMusicVolume(int vol);
+ virtual void startSound(int sound);
+ virtual void stopSound(int sound);
+ virtual void stopAllSounds();
+ virtual int getMusicTimer();
+ virtual int getSoundStatus(int sound) const;
+
+private:
+ enum {
+ V2A_MAXSLOTS = 8
+ };
+
+ struct soundSlot {
+ int id;
+ V2A_Sound *sound;
+ };
+
+ ScummEngine *_vm;
+ Player_MOD *_mod;
+ soundSlot _slot[V2A_MAXSLOTS];
+
+ int getSoundSlot(int id = 0) const;
+ static void update_proc(void *param);
+ void updateSound();
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_v2base.cpp b/engines/scumm/players/player_v2base.cpp
new file mode 100644
index 0000000000..75f1518989
--- /dev/null
+++ b/engines/scumm/players/player_v2base.cpp
@@ -0,0 +1,654 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/players/player_v2base.h"
+#include "scumm/scumm.h"
+
+#define FREQ_HZ 236 // Don't change!
+
+#define MAX_OUTPUT 0x7fff
+
+namespace Scumm {
+
+const uint8 note_lengths[] = {
+ 0,
+ 0, 0, 2,
+ 0, 3, 4,
+ 5, 6, 8,
+ 9, 12, 16,
+ 18, 24, 32,
+ 36, 48, 64,
+ 72, 96
+};
+
+static const uint16 hull_offsets[] = {
+ 0, 12, 24, 36, 48, 60,
+ 72, 88, 104, 120, 136, 256,
+ 152, 164, 180
+};
+
+static const int16 hulls[] = {
+ // hull 0
+ 3, -1, 0, 0, 0, 0, 0, 0,
+ 0, -1, 0, 0,
+ // hull 1 (staccato)
+ 3, -1, 0, 32, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+ // hull 2 (legato)
+ 3, -1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ // hull 3 (staccatissimo)
+ 3, -1, 0, 2, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+ // hull 4
+ 3, -1, 0, 6, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+ // hull 5
+ 3, -1, 0, 16, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+ // hull 6
+ (int16) 60000, -1, -1000, 20, 0, 0, 0, 0,
+ (int16) 40000, -1, -5000, 5, 0, -1, 0, 0,
+ // hull 7
+ (int16) 50000, -1, 0, 8, 30000, -1, 0, 0,
+ 28000, -1, -5000, 5, 0, -1, 0, 0,
+ // hull 8
+ (int16) 60000, -1, -2000, 16, 0, 0, 0, 0,
+ 28000, -1, -6000, 5, 0, -1, 0, 0,
+ // hull 9
+ (int16) 55000, -1, 0, 8, (int16) 35000, -1, 0, 0,
+ (int16) 40000, -1, -2000, 10, 0, -1, 0, 0,
+ // hull 10
+ (int16) 60000, -1, 0, 4, -2000, 8, 0, 0,
+ (int16) 40000, -1, -6000, 5, 0, -1, 0, 0,
+ // hull 12
+ 0, -1, 150, 340, -150, 340, 0, -1,
+ 0, -1, 0, 0,
+ // hull 13 == 164
+ 20000, -1, 4000, 7, 1000, 15, 0, 0,
+ (int16) 35000, -1, -2000, 15, 0, -1, 0, 0,
+
+ // hull 14 == 180
+ (int16) 35000, -1, 500, 20, 0, 0, 0, 0,
+ (int16) 45000, -1, -500, 60, 0, -1, 0, 0,
+
+ // hull misc = 196
+ (int16) 44000, -1, -4400, 10, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+
+ (int16) 53000, -1, -5300, 10, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+
+ (int16) 63000, -1, -6300, 10, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+
+ (int16) 44000, -1, -1375, 32, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+
+ (int16) 53000, -1, -1656, 32, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+
+ // hull 11 == 256
+ (int16) 63000, -1, -1968, 32, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+
+ (int16) 44000, -1, - 733, 60, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+
+ (int16) 53000, -1, - 883, 60, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+
+ (int16) 63000, -1, -1050, 60, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+
+ (int16) 44000, -1, - 488, 90, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+
+ (int16) 53000, -1, - 588, 90, 0, -1, 0, 0,
+ 0, -1, 0, 0,
+
+ (int16) 63000, -1, - 700, 90, 0, -1, 0, 0,
+ 0, -1, 0, 0
+};
+
+static const uint16 freqmod_lengths[] = {
+ 0x1000, 0x1000, 0x20, 0x2000, 0x1000
+};
+
+static const uint16 freqmod_offsets[] = {
+ 0, 0x100, 0x200, 0x302, 0x202
+};
+
+static const int8 freqmod_table[0x502] = {
+ 0, 3, 6, 9, 12, 15, 18, 21,
+ 24, 27, 30, 33, 36, 39, 42, 45,
+ 48, 51, 54, 57, 59, 62, 65, 67,
+ 70, 73, 75, 78, 80, 82, 85, 87,
+ 89, 91, 94, 96, 98, 100, 102, 103,
+ 105, 107, 108, 110, 112, 113, 114, 116,
+ 117, 118, 119, 120, 121, 122, 123, 123,
+ 124, 125, 125, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 125, 125,
+ 124, 123, 123, 122, 121, 120, 119, 118,
+ 117, 116, 114, 113, 112, 110, 108, 107,
+ 105, 103, 102, 100, 98, 96, 94, 91,
+ 89, 87, 85, 82, 80, 78, 75, 73,
+ 70, 67, 65, 62, 59, 57, 54, 51,
+ 48, 45, 42, 39, 36, 33, 30, 27,
+ 24, 21, 18, 15, 12, 9, 6, 3,
+ 0, -3, -6, -9, -12, -15, -18, -21,
+ -24, -27, -30, -33, -36, -39, -42, -45,
+ -48, -51, -54, -57, -59, -62, -65, -67,
+ -70, -73, -75, -78, -80, -82, -85, -87,
+ -89, -91, -94, -96, -98,-100,-102,-103,
+ -105,-107,-108,-110,-112,-113,-114,-116,
+ -117,-118,-119,-120,-121,-122,-123,-123,
+ -124,-125,-125,-126,-126,-126,-126,-126,
+ -126,-126,-126,-126,-126,-126,-125,-125,
+ -124,-123,-123,-122,-121,-120,-119,-118,
+ -117,-116,-114,-113,-112,-110,-108,-107,
+ -105,-103,-102,-100, -98, -96, -94, -91,
+ -89, -87, -85, -82, -80, -78, -75, -73,
+ -70, -67, -65, -62, -59, -57, -54, -51,
+ -48, -45, -42, -39, -36, -33, -30, -27,
+ -24, -21, -18, -15, -12, -9, -6, -3,
+
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55,
+ 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71,
+ 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87,
+ 88, 89, 90, 91, 92, 93, 94, 95,
+ 96, 97, 98, 99, 100, 101, 102, 103,
+ 104, 105, 106, 107, 108, 109, 110, 111,
+ 112, 113, 114, 115, 116, 117, 118, 119,
+ 120, 121, 122, 123, 124, 125, 126, 127,
+ -128,-127,-126,-125,-124,-123,-122,-121,
+ -120,-119,-118,-117,-116,-115,-114,-113,
+ -112,-111,-110,-109,-108,-107,-106,-105,
+ -104,-103,-102,-101,-100, -99, -98, -97,
+ -96, -95, -94, -93, -92, -91, -90, -89,
+ -88, -87, -86, -85, -84, -83, -82, -81,
+ -80, -79, -78, -77, -76, -75, -74, -73,
+ -72, -71, -70, -69, -68, -67, -66, -65,
+ -64, -63, -62, -61, -60, -59, -58, -57,
+ -56, -55, -54, -53, -52, -51, -50, -49,
+ -48, -47, -46, -45, -44, -43, -42, -41,
+ -40, -39, -38, -37, -36, -35, -34, -33,
+ -32, -31, -30, -29, -28, -27, -26, -25,
+ -24, -23, -22, -21, -20, -19, -18, -17,
+ -16, -15, -14, -13, -12, -11, -10, -9,
+ -8, -7, -6, -5, -4, -3, -2, -1,
+
+ -120, 120,
+
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ -120,-120,-120,-120,-120,-120,-120,-120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+
+ 41, 35, -66,-124, -31, 108, -42, -82,
+ 82,-112, 73, -15, -15, -69, -23, -21,
+ -77, -90, -37, 60,-121, 12, 62,-103,
+ 36, 94, 13, 28, 6, -73, 71, -34,
+ -77, 18, 77, -56, 67, -69,-117, -90,
+ 31, 3, 90, 125, 9, 56, 37, 31,
+ 93, -44, -53, -4,-106, -11, 69, 59,
+ 19, 13,-119, 10, 28, -37, -82, 50,
+ 32,-102, 80, -18, 64, 120, 54, -3,
+ 18, 73, 50, -10, -98, 125, 73, -36,
+ -83, 79, 20, -14, 68, 64, 102, -48,
+ 107, -60, 48, -73, 50, 59, -95, 34,
+ -10, 34,-111, -99, -31,-117, 31, -38,
+ -80, -54,-103, 2, -71, 114, -99, 73,
+ 44,-128, 126, -59,-103, -43, -23,-128,
+ -78, -22, -55, -52, 83, -65, 103, -42,
+ -65, 20, -42, 126, 45, -36,-114, 102,
+ -125, -17, 87, 73, 97, -1, 105,-113,
+ 97, -51, -47, 30, -99,-100, 22, 114,
+ 114, -26, 29, -16,-124, 79, 74, 119,
+ 2, -41, -24, 57, 44, 83, -53, -55,
+ 18, 30, 51, 116, -98, 12, -12, -43,
+ -44, -97, -44, -92, 89, 126, 53, -49,
+ 50, 34, -12, -52, -49, -45,-112, 45,
+ 72, -45,-113, 117, -26, -39, 29, 42,
+ -27, -64, -9, 43, 120,-127,-121, 68,
+ 14, 95, 80, 0, -44, 97,-115, -66,
+ 123, 5, 21, 7, 59, 51,-126, 31,
+ 24, 112,-110, -38, 100, 84, -50, -79,
+ -123, 62, 105, 21, -8, 70, 106, 4,
+ -106, 115, 14, -39, 22, 47, 103, 104,
+ -44, -9, 74, 74, -48, 87, 104, 118,
+ -6, 22, -69, 17, -83, -82, 36,-120,
+ 121, -2, 82, -37, 37, 67, -27, 60,
+ -12, 69, -45, -40, 40, -50, 11, -11,
+ -59, 96, 89, 61,-105, 39,-118, 89,
+ 118, 45, -48, -62, -55, -51, 104, -44,
+ 73, 106, 121, 37, 8, 97, 64, 20,
+ -79, 59, 106, -91, 17, 40, -63,-116,
+ -42, -87, 11,-121,-105,-116, 47, -15,
+ 21, 29,-102,-107, -63,-101, -31, -64,
+ 126, -23, -88,-102, -89,-122, -62, -75,
+ 84, -65,-102, -25, -39, 35, -47, 85,
+ -112, 56, 40, -47, -39, 108, -95, 102,
+ 94, 78, -31, 48,-100, -2, -39, 113,
+ -97, -30, -91, -30, 12,-101, -76, 71,
+ 101, 56, 42, 70,-119, -87,-126, 121,
+ 122, 118, 120, -62, 99, -79, 38, -33,
+ -38, 41, 109, 62, 98, -32,-106, 18,
+ 52, -65, 57, -90, 63,-119, 94, -15,
+ 109, 14, -29, 108, 40, -95, 30, 32,
+ 29, -53, -62, 3, 63, 65, 7,-124,
+ 15, 20, 5, 101, 27, 40, 97, -55,
+ -59, -25, 44,-114, 70, 54, 8, -36,
+ -13, -88,-115, -2, -66, -14, -21, 113,
+ -1, -96, -48, 59, 117, 6,-116, 126,
+ -121, 120, 115, 77, -48, -66,-126, -66,
+ -37, -62, 70, 65, 43,-116, -6, 48,
+ 127, 112, -16, -89, 84,-122, 50,-107,
+ -86, 91, 104, 19, 11, -26, -4, -11,
+ -54, -66, 125, -97,-119,-118, 65, 27,
+ -3, -72, 79, 104, -10, 114, 123, 20,
+ -103, -51, -45, 13, -16, 68, 58, -76,
+ -90, 102, 83, 51, 11, -53, -95, 16
+};
+
+static const uint16 spk_freq_table[12] = {
+ 36484, 34436, 32503, 30679, 28957, 27332,
+ 25798, 24350, 22983, 21693, 20476, 19326
+};
+
+static const uint16 pcjr_freq_table[12] = {
+ 65472, 61760, 58304, 55040, 52032, 49024,
+ 46272, 43648, 41216, 38912, 36736, 34624
+};
+
+
+Player_V2Base::Player_V2Base(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr)
+ : _vm(scumm),
+ _mixer(mixer),
+ _pcjr(pcjr),
+ _sampleRate(_mixer->getOutputRate()) {
+
+ _isV3Game = (scumm->_game.version >= 3);
+
+ _header_len = (scumm->_game.features & GF_OLD_BUNDLE) ? 4 : 6;
+
+ // Initialize sound queue
+ _current_nr = _next_nr = 0;
+ _current_data = _next_data = 0;
+
+ // Initialize channel code
+ for (int i = 0; i < 4; ++i)
+ clear_channel(i);
+
+ _next_tick = 0;
+ _tick_len = (_sampleRate << FIXP_SHIFT) / FREQ_HZ;
+
+ // Initialize V3 music timer
+ _music_timer_ctr = _music_timer = 0;
+ _ticks_per_music_timer = 65535;
+
+ if (_pcjr) {
+ _freqs_table = pcjr_freq_table;
+ } else {
+ _freqs_table = spk_freq_table;
+ }
+}
+
+Player_V2Base::~Player_V2Base() {
+}
+
+void Player_V2Base::chainSound(int nr, byte *data) {
+ int offset = _header_len + (_pcjr ? 10 : 2);
+
+ _current_nr = nr;
+ _current_data = data;
+
+ for (int i = 0; i < 4; i++) {
+ clear_channel(i);
+
+ _channels[i].d.music_script_nr = nr;
+ if (data) {
+ _channels[i].d.next_cmd = READ_LE_UINT16(data + offset + 2 * i);
+ if (_channels[i].d.next_cmd) {
+ _channels[i].d.time_left = 1;
+ }
+ }
+ }
+ _music_timer = 0;
+}
+
+void Player_V2Base::chainNextSound() {
+ if (_next_nr) {
+ chainSound(_next_nr, _next_data);
+ _next_nr = 0;
+ _next_data = 0;
+ }
+}
+
+// TODO: Merge stopAllSounds, stopSound() and startSound(), using some overriding
+// perhaps? Player_V2CMS's implementations start like that of Player_V2
+// but then add some MIDI related stuff.
+
+void Player_V2Base::clear_channel(int i) {
+ ChannelInfo *channel = &_channels[i];
+ memset(channel, 0, sizeof(ChannelInfo));
+}
+
+int Player_V2Base::getMusicTimer() {
+ if (_isV3Game)
+ return _music_timer;
+ else
+ return _channels[0].d.music_timer;
+}
+
+void Player_V2Base::execute_cmd(ChannelInfo *channel) {
+ uint16 value;
+ int16 offset;
+ uint8 *script_ptr;
+ ChannelInfo * current_channel;
+ ChannelInfo * dest_channel;
+
+ current_channel = channel;
+
+ if (channel->d.next_cmd == 0)
+ goto check_stopped;
+ script_ptr = &_current_data[channel->d.next_cmd];
+
+ for (;;) {
+ uint8 opcode = *script_ptr++;
+ if (opcode >= 0xf8) {
+ switch (opcode) {
+ case 0xf8: // set hull curve
+ debug(7, "channels[%d]: hull curve %2d",
+ (uint)(channel - _channels), *script_ptr);
+ channel->d.hull_curve = hull_offsets[*script_ptr / 2];
+ script_ptr++;
+ break;
+
+ case 0xf9: // set freqmod curve
+ debug(7, "channels[%d]: freqmod curve %2d",
+ (uint)(channel - _channels), *script_ptr);
+ channel->d.freqmod_table = freqmod_offsets[*script_ptr / 4];
+ channel->d.freqmod_modulo = freqmod_lengths[*script_ptr / 4];
+ script_ptr++;
+ break;
+
+ case 0xfd: // clear other channel
+ value = READ_LE_UINT16 (script_ptr) / sizeof (ChannelInfo);
+ debug(7, "clear channel %d", value);
+ script_ptr += 2;
+ // In Indy3, when traveling to Venice a command is
+ // issued to clear channel 4. So we introduce a 4th
+ // channel, which is never used. All OOB accesses are
+ // mapped to this channel.
+ //
+ // The original game had room for 8 channels, but only
+ // channels 0-3 are read, changes to other channels
+ // had no effect.
+ if (value >= ARRAYSIZE (_channels))
+ value = 4;
+ channel = &_channels[value];
+ // fall through
+
+ case 0xfa: // clear current channel
+ if (opcode == 0xfa)
+ debug(7, "clear channel");
+ channel->d.next_cmd = 0;
+ channel->d.base_freq = 0;
+ channel->d.freq_delta = 0;
+ channel->d.freq = 0;
+ channel->d.volume = 0;
+ channel->d.volume_delta = 0;
+ channel->d.inter_note_pause = 0;
+ channel->d.transpose = 0;
+ channel->d.hull_curve = 0;
+ channel->d.hull_offset = 0;
+ channel->d.hull_counter = 0;
+ channel->d.freqmod_table = 0;
+ channel->d.freqmod_offset = 0;
+ channel->d.freqmod_incr = 0;
+ channel->d.freqmod_multiplier = 0;
+ channel->d.freqmod_modulo = 0;
+ break;
+
+ case 0xfb: // ret from subroutine
+ debug(7, "ret from sub");
+ script_ptr = _retaddr;
+ break;
+
+ case 0xfc: // call subroutine
+ offset = READ_LE_UINT16 (script_ptr);
+ debug(7, "subroutine %d", offset);
+ script_ptr += 2;
+ _retaddr = script_ptr;
+ script_ptr = _current_data + offset;
+ break;
+
+ case 0xfe: // loop music
+ opcode = *script_ptr++;
+ offset = READ_LE_UINT16 (script_ptr);
+ script_ptr += 2;
+ debug(7, "loop if %d to %d", opcode, offset);
+ if (!channel->array[opcode / 2] || --channel->array[opcode/2])
+ script_ptr += offset;
+ break;
+
+ case 0xff: // set parameter
+ opcode = *script_ptr++;
+ value = READ_LE_UINT16 (script_ptr);
+ channel->array[opcode / 2] = value;
+ debug(7, "channels[%d]: set param %2d = %5d",
+ (uint)(channel - _channels), opcode, value);
+ script_ptr += 2;
+ if (opcode == 14) {
+ /* tempo var */
+ _ticks_per_music_timer = 125;
+ }
+ if (opcode == 0)
+ goto end;
+ break;
+ }
+ } else { // opcode < 0xf8
+ for (;;) {
+ int16 note, octave;
+ int is_last_note;
+ dest_channel = &_channels[(opcode >> 5) & 3];
+
+ if (!(opcode & 0x80)) {
+
+ int tempo = channel->d.tempo;
+ if (!tempo)
+ tempo = 1;
+ channel->d.time_left = tempo * note_lengths[opcode & 0x1f];
+
+ note = *script_ptr++;
+ is_last_note = note & 0x80;
+ note &= 0x7f;
+ if (note == 0x7f) {
+ debug(8, "channels[%d]: pause %d",
+ (uint)(channel - _channels), channel->d.time_left);
+ goto end;
+ }
+ } else {
+
+ channel->d.time_left = ((opcode & 7) << 8) | *script_ptr++;
+
+ if ((opcode & 0x10)) {
+ debug(8, "channels[%d]: pause %d",
+ (uint)(channel - _channels), channel->d.time_left);
+ goto end;
+ }
+
+ is_last_note = 0;
+ note = (*script_ptr++) & 0x7f;
+ }
+
+ debug(8, "channels[%d]: @%04x note: %3d+%d len: %2d hull: %d mod: %d/%d/%d %s",
+ (uint)(dest_channel - channel), script_ptr ? (uint)(script_ptr - _current_data - 2) : 0,
+ note, (signed short) dest_channel->d.transpose, channel->d.time_left,
+ dest_channel->d.hull_curve, dest_channel->d.freqmod_table,
+ dest_channel->d.freqmod_incr,dest_channel->d.freqmod_multiplier,
+ is_last_note ? "last":"");
+
+ uint16 myfreq;
+ dest_channel->d.time_left = channel->d.time_left;
+ dest_channel->d.note_length =
+ channel->d.time_left - dest_channel->d.inter_note_pause;
+ note += dest_channel->d.transpose;
+ while (note < 0)
+ note += 12;
+ octave = note / 12;
+ note = note % 12;
+ dest_channel->d.hull_offset = 0;
+ dest_channel->d.hull_counter = 1;
+ if (_pcjr && dest_channel == &_channels[3]) {
+ dest_channel->d.hull_curve = 196 + note * 12;
+ myfreq = 384 - 64 * octave;
+ } else {
+ myfreq = _freqs_table[note] >> octave;
+ }
+ dest_channel->d.freq = dest_channel->d.base_freq = myfreq;
+ if (is_last_note)
+ goto end;
+ opcode = *script_ptr++;
+ }
+ }
+ }
+
+end:
+ channel = current_channel;
+ if (channel->d.time_left) {
+ channel->d.next_cmd = script_ptr - _current_data;
+ return;
+ }
+
+ channel->d.next_cmd = 0;
+
+check_stopped:
+ int i;
+ for (i = 0; i < 4; i++) {
+ if (_channels[i].d.time_left)
+ return;
+ }
+
+ _current_nr = 0;
+ _current_data = 0;
+ chainNextSound();
+}
+
+void Player_V2Base::next_freqs(ChannelInfo *channel) {
+ channel->d.volume += channel->d.volume_delta;
+ channel->d.base_freq += channel->d.freq_delta;
+
+ channel->d.freqmod_offset += channel->d.freqmod_incr;
+ if (channel->d.freqmod_offset > channel->d.freqmod_modulo)
+ channel->d.freqmod_offset -= channel->d.freqmod_modulo;
+
+ channel->d.freq =
+ (int) (freqmod_table[channel->d.freqmod_table + (channel->d.freqmod_offset >> 4)])
+ * (int) channel->d.freqmod_multiplier / 256
+ + channel->d.base_freq;
+
+ debug(9, "Freq: %d/%d, %d/%d/%d*%d %d",
+ channel->d.base_freq, (int16)channel->d.freq_delta,
+ channel->d.freqmod_table, channel->d.freqmod_offset,
+ channel->d.freqmod_incr, channel->d.freqmod_multiplier,
+ channel->d.freq);
+
+ if (channel->d.note_length && !--channel->d.note_length) {
+ channel->d.hull_offset = 16;
+ channel->d.hull_counter = 1;
+ }
+
+ if (!--channel->d.time_left) {
+ execute_cmd(channel);
+ }
+
+ if (channel->d.hull_counter && !--channel->d.hull_counter) {
+ for (;;) {
+ const int16 *hull_ptr = hulls
+ + channel->d.hull_curve + channel->d.hull_offset / 2;
+ if (hull_ptr[1] == -1) {
+ channel->d.volume = hull_ptr[0];
+ if (hull_ptr[0] == 0)
+ channel->d.volume_delta = 0;
+ channel->d.hull_offset += 4;
+ } else {
+ channel->d.volume_delta = hull_ptr[0];
+ channel->d.hull_counter = hull_ptr[1];
+ channel->d.hull_offset += 4;
+ break;
+ }
+ }
+ }
+}
+
+void Player_V2Base::nextTick() {
+ for (int i = 0; i < 4; i++) {
+ if (!_channels[i].d.time_left)
+ continue;
+ next_freqs(&_channels[i]);
+ }
+ if (_music_timer_ctr++ >= _ticks_per_music_timer) {
+ _music_timer_ctr = 0;
+ _music_timer++;
+ }
+}
+
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_v2base.h b/engines/scumm/players/player_v2base.h
new file mode 100644
index 0000000000..32bde95ddc
--- /dev/null
+++ b/engines/scumm/players/player_v2base.h
@@ -0,0 +1,145 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_V2BASE_H
+#define SCUMM_PLAYERS_PLAYER_V2BASE_H
+
+#include "common/scummsys.h"
+#include "common/mutex.h"
+#include "scumm/music.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+namespace Scumm {
+
+class ScummEngine;
+
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+struct channel_data {
+ uint16 time_left; // 00
+ uint16 next_cmd; // 02
+ uint16 base_freq; // 04
+ uint16 freq_delta; // 06
+ uint16 freq; // 08
+ uint16 volume; // 10
+ uint16 volume_delta; // 12
+ uint16 tempo; // 14
+ uint16 inter_note_pause; // 16
+ uint16 transpose; // 18
+ uint16 note_length; // 20
+ uint16 hull_curve; // 22
+ uint16 hull_offset; // 24
+ uint16 hull_counter; // 26
+ uint16 freqmod_table; // 28
+ uint16 freqmod_offset; // 30
+ uint16 freqmod_incr; // 32
+ uint16 freqmod_multiplier; // 34
+ uint16 freqmod_modulo; // 36
+ uint16 unknown[4]; // 38 - 44
+ uint16 music_timer; // 46
+ uint16 music_script_nr; // 48
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+/**
+ * Common base class for Player_V2 and Player_V2CMS.
+ */
+class Player_V2Base : public Audio::AudioStream, public MusicEngine {
+public:
+ Player_V2Base(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr);
+ virtual ~Player_V2Base();
+
+ // MusicEngine API
+// virtual void setMusicVolume(int vol);
+// virtual void startSound(int sound);
+// virtual void stopSound(int sound);
+// virtual void stopAllSounds();
+ virtual int getMusicTimer();
+// virtual int getSoundStatus(int sound) const;
+
+ // AudioStream API
+/*
+ int readBuffer(int16 *buffer, const int numSamples) {
+ do_mix(buffer, numSamples / 2);
+ return numSamples;
+ }
+*/
+ virtual bool isStereo() const { return true; }
+ virtual bool endOfData() const { return false; }
+ virtual int getRate() const { return _sampleRate; }
+
+protected:
+ enum {
+ FIXP_SHIFT = 16
+ };
+
+ bool _isV3Game;
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _soundHandle;
+ ScummEngine *_vm;
+
+ bool _pcjr;
+ int _header_len;
+
+ const uint32 _sampleRate;
+ uint32 _next_tick;
+ uint32 _tick_len;
+
+ int _current_nr;
+ byte *_current_data;
+ int _next_nr;
+ byte *_next_data;
+ byte *_retaddr;
+
+ Common::Mutex _mutex;
+
+ union ChannelInfo {
+ channel_data d;
+ uint16 array[sizeof(channel_data)/2];
+ };
+
+ ChannelInfo _channels[5];
+
+private:
+ int _music_timer;
+ int _music_timer_ctr;
+ int _ticks_per_music_timer;
+
+ const uint16 *_freqs_table;
+
+protected:
+ virtual void nextTick();
+ virtual void clear_channel(int i);
+ virtual void chainSound(int nr, byte *data);
+ virtual void chainNextSound();
+
+ void execute_cmd(ChannelInfo *channel);
+ void next_freqs(ChannelInfo *channel);
+};
+
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_v2cms.cpp b/engines/scumm/players/player_v2cms.cpp
new file mode 100644
index 0000000000..8e903bf9d2
--- /dev/null
+++ b/engines/scumm/players/player_v2cms.cpp
@@ -0,0 +1,787 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/players/player_v2cms.h"
+#include "scumm/scumm.h"
+#include "audio/mididrv.h"
+#include "audio/mixer.h"
+#include "audio/softsynth/cms.h"
+
+namespace Scumm {
+
+Player_V2CMS::Player_V2CMS(ScummEngine *scumm, Audio::Mixer *mixer)
+ : Player_V2Base(scumm, mixer, true), _cmsVoicesBase(), _cmsVoices(),
+ _cmsChips(), _midiDelay(0), _octaveMask(0), _looping(0), _tempo(0),
+ _tempoSum(0), _midiData(0), _midiSongBegin(0), _musicTimer(0),
+ _musicTimerTicks(0), _voiceTimer(0), _loadedMidiSong(0),
+ _outputTableReady(0), _midiChannel(), _midiChannelUse() {
+ setMusicVolume(255);
+
+ memset(_sfxFreq, 0xFF, sizeof(_sfxFreq));
+ memset(_sfxAmpl, 0x00, sizeof(_sfxAmpl));
+ memset(_sfxOctave, 0x66, sizeof(_sfxOctave));
+
+ _cmsVoices[0].amplitudeOutput = &_cmsChips[0].ampl[0];
+ _cmsVoices[0].freqOutput = &_cmsChips[0].freq[0];
+ _cmsVoices[0].octaveOutput = &_cmsChips[0].octave[0];
+ _cmsVoices[1].amplitudeOutput = &_cmsChips[0].ampl[1];
+ _cmsVoices[1].freqOutput = &_cmsChips[0].freq[1];
+ _cmsVoices[1].octaveOutput = &_cmsChips[0].octave[0];
+ _cmsVoices[2].amplitudeOutput = &_cmsChips[0].ampl[2];
+ _cmsVoices[2].freqOutput = &_cmsChips[0].freq[2];
+ _cmsVoices[2].octaveOutput = &_cmsChips[0].octave[1];
+ _cmsVoices[3].amplitudeOutput = &_cmsChips[0].ampl[3];
+ _cmsVoices[3].freqOutput = &_cmsChips[0].freq[3];
+ _cmsVoices[3].octaveOutput = &_cmsChips[0].octave[1];
+ _cmsVoices[4].amplitudeOutput = &_cmsChips[1].ampl[0];
+ _cmsVoices[4].freqOutput = &_cmsChips[1].freq[0];
+ _cmsVoices[4].octaveOutput = &_cmsChips[1].octave[0];
+ _cmsVoices[5].amplitudeOutput = &_cmsChips[1].ampl[1];
+ _cmsVoices[5].freqOutput = &_cmsChips[1].freq[1];
+ _cmsVoices[5].octaveOutput = &_cmsChips[1].octave[0];
+ _cmsVoices[6].amplitudeOutput = &_cmsChips[1].ampl[2];
+ _cmsVoices[6].freqOutput = &_cmsChips[1].freq[2];
+ _cmsVoices[6].octaveOutput = &_cmsChips[1].octave[1];
+ _cmsVoices[7].amplitudeOutput = &_cmsChips[1].ampl[3];
+ _cmsVoices[7].freqOutput = &_cmsChips[1].freq[3];
+ _cmsVoices[7].octaveOutput = &_cmsChips[1].octave[1];
+
+ // inits the CMS Emulator like in the original
+ _cmsEmu = new CMSEmulator(_sampleRate);
+ for (int i = 0, cmsPort = 0x220; i < 2; cmsPort += 2, ++i) {
+ for (int off = 0; off < 13; ++off) {
+ _cmsEmu->portWrite(cmsPort+1, _cmsInitData[off*2]);
+ _cmsEmu->portWrite(cmsPort, _cmsInitData[off*2+1]);
+ }
+ }
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_V2CMS::~Player_V2CMS() {
+ Common::StackLock lock(_mutex);
+
+ _mixer->stopHandle(_soundHandle);
+ delete _cmsEmu;
+}
+
+void Player_V2CMS::setMusicVolume(int vol) {
+}
+
+int Player_V2CMS::getMusicTimer() {
+ return _midiData ? _musicTimer : Player_V2Base::getMusicTimer();
+}
+
+void Player_V2CMS::stopAllSounds() {
+ Common::StackLock lock(_mutex);
+
+ for (int i = 0; i < 4; i++) {
+ clear_channel(i);
+ }
+ _next_nr = _current_nr = 0;
+ _next_data = _current_data = 0;
+ _midiData = 0;
+ _midiSongBegin = 0;
+ _midiDelay = 0;
+ _musicTimer = _musicTimerTicks = 0;
+ offAllChannels();
+}
+
+void Player_V2CMS::stopSound(int nr) {
+ Common::StackLock lock(_mutex);
+
+ if (_next_nr == nr) {
+ _next_nr = 0;
+ _next_data = 0;
+ }
+ if (_current_nr == nr) {
+ for (int i = 0; i < 4; i++) {
+ clear_channel(i);
+ }
+ _current_nr = 0;
+ _current_data = 0;
+ chainNextSound();
+ }
+ if (_loadedMidiSong == nr) {
+ _midiData = 0;
+ _midiSongBegin = 0;
+ _midiDelay = 0;
+ offAllChannels();
+ }
+}
+
+void Player_V2CMS::startSound(int nr) {
+ Common::StackLock lock(_mutex);
+
+ byte *data = _vm->getResourceAddress(rtSound, nr);
+ assert(data);
+
+ if (data[6] == 0x80) {
+ _musicTimer = _musicTimerTicks = 0;
+ loadMidiData(data, nr);
+ } else {
+ int cprio = _current_data ? *(_current_data + _header_len) : 0;
+ int prio = *(data + _header_len);
+ int nprio = _next_data ? *(_next_data + _header_len) : 0;
+
+ int restartable = *(data + _header_len + 1);
+
+ if (!_current_nr || cprio <= prio) {
+ int tnr = _current_nr;
+ int tprio = cprio;
+ byte *tdata = _current_data;
+
+ chainSound(nr, data);
+ nr = tnr;
+ prio = tprio;
+ data = tdata;
+ restartable = data ? *(data + _header_len + 1) : 0;
+ }
+
+ if (!_current_nr) {
+ nr = 0;
+ _next_nr = 0;
+ _next_data = 0;
+ }
+
+ if (nr != _current_nr
+ && restartable
+ && (!_next_nr
+ || nprio <= prio)) {
+
+ _next_nr = nr;
+ _next_data = data;
+ }
+ }
+}
+
+void Player_V2CMS::loadMidiData(byte *data, int sound) {
+ memset(_midiChannelUse, 0, sizeof(_midiChannelUse));
+ memset(_midiChannel, 0, sizeof(_midiChannel));
+
+ _tempo = data[7];
+ _looping = data[8];
+
+ byte channels = data[14];
+ byte curChannel = 0;
+ byte *voice2 = data + 23;
+
+ for (; channels != 0; ++curChannel, --channels, voice2 += 16) {
+ if (*(data + 15 + curChannel)) {
+ byte channel = *(data + 15 + curChannel) - 1;
+ _midiChannelUse[channel] = 1;
+
+ Voice *voiceDef = &_cmsVoicesBase[channel];
+
+ byte attackDecay = voice2[10];
+ voiceDef->attack = _attackRate[attackDecay >> 4];
+ voiceDef->decay = _decayRate[attackDecay & 0x0F];
+ byte sustainRelease = voice2[11];
+ voiceDef->sustain = _sustainRate[sustainRelease >> 4];
+ voiceDef->release = _releaseRate[sustainRelease & 0x0F];
+
+ if (voice2[3] & 0x40) {
+ voiceDef->vibrato = 0x0301;
+ if (voice2[13] & 0x40) {
+ voiceDef->vibrato = 0x0601;
+ }
+ } else {
+ voiceDef->vibrato = 0;
+ }
+
+ if (voice2[8] & 0x80) {
+ voiceDef->vibrato2 = 0x0506;
+ if (voice2[13] & 0x80) {
+ voiceDef->vibrato2 = 0x050C;
+ }
+ } else {
+ voiceDef->vibrato2 = 0;
+ }
+
+ if ((voice2[8] & 0x0F) > 1) {
+ voiceDef->octadd = 0x01;
+ } else {
+ voiceDef->octadd = 0x00;
+ }
+ }
+ }
+
+ for (int i = 0; i < 8; ++i) {
+ _cmsVoices[i].chanNumber = 0xFF;
+ _cmsVoices[i].curVolume = 0;
+ _cmsVoices[i].nextVoice = 0;
+ }
+
+ _midiDelay = 0;
+ memset(_cmsChips, 0, sizeof(MusicChip)*2);
+ _midiData = data + 151;
+ _midiSongBegin = _midiData + data[9];
+
+ _loadedMidiSong = sound;
+}
+
+int Player_V2CMS::getSoundStatus(int nr) const {
+ return _current_nr == nr || _next_nr == nr || _loadedMidiSong == nr;
+}
+
+void Player_V2CMS::processMidiData() {
+ byte *currentData = _midiData;
+ byte command = 0x00;
+ int16 temp = 0;
+
+ ++_musicTimerTicks;
+ if (_musicTimerTicks > 60) {
+ _musicTimerTicks = 0;
+ ++_musicTimer;
+ }
+
+ if (!_midiDelay) {
+ while (true) {
+ if ((command = *currentData++) == 0xFF) {
+ if ((command = *currentData++) == 0x2F) {
+ if (_looping == 0) {
+ currentData = _midiData = _midiSongBegin;
+ continue;
+ }
+ _midiData = _midiSongBegin = 0;
+ _midiDelay = 0;
+ _loadedMidiSong = 0;
+ offAllChannels();
+ return;
+ } else {
+ if (command == 0x58) {
+ currentData += 6;
+ }
+ }
+ } else {
+ _lastMidiCommand = command;
+ if (command < 0x90) {
+ clearNote(currentData);
+ } else {
+ playNote(currentData);
+ }
+ }
+
+ temp = command = *currentData++;
+ if (command & 0x80) {
+ temp = (command & 0x7F) << 8;
+ command = *currentData++;
+ temp |= (command << 1);
+ temp >>= 1;
+ }
+ temp >>= 1;
+ int lastBit = temp & 1;
+ temp >>= 1;
+ temp += lastBit;
+
+ if (temp)
+ break;
+ }
+ _midiData = currentData;
+ _midiDelay = temp;
+ }
+
+ --_midiDelay;
+ if (_midiDelay < 0)
+ _midiDelay = 0;
+
+ return;
+}
+
+int Player_V2CMS::readBuffer(int16 *buffer, const int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ uint step = 1;
+ int len = numSamples / 2;
+
+ // maybe this needs a complete rewrite
+ do {
+ if (!(_next_tick >> FIXP_SHIFT)) {
+ if (_midiData) {
+ --_voiceTimer;
+ if (!(_voiceTimer & 0x01))
+ playVoice();
+
+ int newTempoSum = _tempo + _tempoSum;
+ _tempoSum = newTempoSum & 0xFF;
+ if (newTempoSum > 0xFF)
+ processMidiData();
+ } else {
+ nextTick();
+ play();
+ }
+ _next_tick += _tick_len;
+ }
+
+ step = len;
+ if (step > (_next_tick >> FIXP_SHIFT))
+ step = (_next_tick >> FIXP_SHIFT);
+ _cmsEmu->readBuffer(buffer, step);
+ buffer += 2 * step;
+ _next_tick -= step << FIXP_SHIFT;
+ } while (len -= step);
+
+ return numSamples;
+}
+
+void Player_V2CMS::playVoice() {
+ if (_outputTableReady) {
+ playMusicChips(_cmsChips);
+ _outputTableReady = 0;
+ }
+
+ _octaveMask = 0xF0;
+ Voice2 *voice = 0;
+ for (int i = 0; i < 8; ++i) {
+ voice = &_cmsVoices[i];
+ _octaveMask = ~_octaveMask;
+
+ if (voice->chanNumber != 0xFF) {
+ processChannel(voice);
+ } else {
+ if (!voice->curVolume) {
+ *(voice->amplitudeOutput) = 0;
+ }
+
+ int volume = voice->curVolume - voice->releaseRate;
+ if (volume < 0)
+ volume = 0;
+
+ voice->curVolume = volume;
+ *(voice->amplitudeOutput) = ((volume >> 4) | (volume & 0xF0)) & voice->channel;
+ ++_outputTableReady;
+ }
+ }
+}
+
+void Player_V2CMS::processChannel(Voice2 *channel) {
+ ++_outputTableReady;
+ switch (channel->nextProcessState) {
+ case Voice2::kEnvelopeAttack:
+ processAttack(channel);
+ break;
+
+ case Voice2::kEnvelopeDecay:
+ processDecay(channel);
+ break;
+
+ case Voice2::kEnvelopeSustain:
+ processSustain(channel);
+ break;
+
+ case Voice2::kEnvelopeRelease:
+ processRelease(channel);
+ break;
+ }
+}
+
+void Player_V2CMS::processRelease(Voice2 *channel) {
+ int newVolume = channel->curVolume - channel->releaseRate;
+ if (newVolume < 0)
+ newVolume = 0;
+
+ channel->curVolume = newVolume;
+ processVibrato(channel);
+}
+
+void Player_V2CMS::processAttack(Voice2 *channel) {
+ int newVolume = channel->curVolume + channel->attackRate;
+ if (newVolume > channel->maxAmpl) {
+ channel->curVolume = channel->maxAmpl;
+ channel->nextProcessState = Voice2::kEnvelopeDecay;
+ } else {
+ channel->curVolume = newVolume;
+ }
+
+ processVibrato(channel);
+}
+
+void Player_V2CMS::processDecay(Voice2 *channel) {
+ int newVolume = channel->curVolume - channel->decayRate;
+ if (newVolume <= channel->sustainRate) {
+ channel->curVolume = channel->sustainRate;
+ channel->nextProcessState = Voice2::kEnvelopeSustain;
+ } else {
+ channel->curVolume = newVolume;
+ }
+
+ processVibrato(channel);
+}
+
+void Player_V2CMS::processSustain(Voice2 *channel) {
+ if (channel->unkVibratoRate) {
+ int16 volume = channel->curVolume + channel->unkRate;
+ if (volume & 0xFF00) {
+ volume = int8(volume >> 8);
+ volume = -volume;
+ }
+
+ channel->curVolume = volume;
+ --channel->unkCount;
+ if (!channel->unkCount) {
+ channel->unkRate = -channel->unkRate;
+ channel->unkCount = (channel->unkVibratoDepth & 0x0F) << 1;
+ }
+ }
+ processVibrato(channel);
+}
+
+void Player_V2CMS::processVibrato(Voice2 *channel) {
+ if (channel->vibratoRate) {
+ int16 temp = channel->curFreq + channel->curVibratoRate;
+ channel->curOctave += (temp & 0xFF00) >> 8;
+ channel->curFreq = temp & 0xFF;
+
+ --channel->curVibratoUnk;
+ if (!channel->curVibratoUnk) {
+ channel->curVibratoRate = -channel->curVibratoRate;
+ channel->curVibratoUnk = (channel->vibratoDepth & 0x0F) << 1;
+ }
+ }
+
+ byte *output = channel->amplitudeOutput;
+ *output = ((channel->curVolume >> 4) | (channel->curVolume & 0xF0)) & channel->channel;
+ output = channel->freqOutput;
+ *output = channel->curFreq;
+ output = channel->octaveOutput;
+ *output = (((channel->curOctave << 4) | (channel->curOctave & 0x0F)) & _octaveMask) | ((~_octaveMask) & *output);
+}
+
+void Player_V2CMS::offAllChannels() {
+ for (int cmsPort = 0x220, i = 0; i < 2; cmsPort += 2, ++i) {
+ for (int off = 1; off <= 10; ++off) {
+ _cmsEmu->portWrite(cmsPort+1, _cmsInitData[off*2]);
+ _cmsEmu->portWrite(cmsPort, _cmsInitData[off*2+1]);
+ }
+ }
+}
+
+Player_V2CMS::Voice2 *Player_V2CMS::getFreeVoice() {
+ Voice2 *curVoice = 0;
+ Voice2 *selected = 0;
+ uint8 volume = 0xFF;
+
+ for (int i = 0; i < 8; ++i) {
+ curVoice = &_cmsVoices[i];
+
+ if (curVoice->chanNumber == 0xFF) {
+ if (!curVoice->curVolume) {
+ selected = curVoice;
+ break;
+ }
+
+ if (curVoice->curVolume < volume) {
+ selected = curVoice;
+ volume = selected->curVolume;
+ }
+ }
+ }
+
+ if (selected) {
+ selected->chanNumber = _lastMidiCommand & 0x0F;
+
+ uint8 channel = selected->chanNumber;
+ Voice2 *oldChannel = _midiChannel[channel];
+ _midiChannel[channel] = selected;
+ selected->nextVoice = oldChannel;
+ }
+
+ return selected;
+}
+
+void Player_V2CMS::playNote(byte *&data) {
+ byte channel = _lastMidiCommand & 0x0F;
+ if (_midiChannelUse[channel]) {
+ Voice2 *freeVoice = getFreeVoice();
+ if (freeVoice) {
+ Voice *voice = &_cmsVoicesBase[freeVoice->chanNumber];
+ freeVoice->attackRate = voice->attack;
+ freeVoice->decayRate = voice->decay;
+ freeVoice->sustainRate = voice->sustain;
+ freeVoice->releaseRate = voice->release;
+ freeVoice->octaveAdd = voice->octadd;
+ freeVoice->vibratoRate = freeVoice->curVibratoRate = voice->vibrato & 0xFF;
+ freeVoice->vibratoDepth = freeVoice->curVibratoUnk = voice->vibrato >> 8;
+ freeVoice->unkVibratoRate = freeVoice->unkRate = voice->vibrato2 & 0xFF;
+ freeVoice->unkVibratoDepth = freeVoice->unkCount = voice->vibrato2 >> 8;
+ freeVoice->maxAmpl = 0xFF;
+
+ uint8 rate = freeVoice->attackRate;
+ uint8 volume = freeVoice->curVolume >> 1;
+
+ if (rate < volume)
+ rate = volume;
+
+ rate -= freeVoice->attackRate;
+ freeVoice->curVolume = rate;
+ freeVoice->playingNote = *data;
+
+ int effectiveNote = freeVoice->playingNote + 3;
+ if (effectiveNote < 0 || effectiveNote >= ARRAYSIZE(_midiNotes)) {
+ warning("Player_V2CMS::playNote: Note %d out of bounds", effectiveNote);
+ effectiveNote = CLIP<int>(effectiveNote, 0, ARRAYSIZE(_midiNotes) - 1);
+ }
+
+ int octave = _midiNotes[effectiveNote].baseOctave + freeVoice->octaveAdd - 3;
+ if (octave < 0)
+ octave = 0;
+ if (octave > 7)
+ octave = 7;
+ if (!octave)
+ ++octave;
+ freeVoice->curOctave = octave;
+ freeVoice->curFreq = _midiNotes[effectiveNote].frequency;
+ freeVoice->curVolume = 0;
+ freeVoice->nextProcessState = Voice2::kEnvelopeAttack;
+ if (!(_lastMidiCommand & 1))
+ freeVoice->channel = 0xF0;
+ else
+ freeVoice->channel = 0x0F;
+ }
+ }
+ data += 2;
+}
+
+Player_V2CMS::Voice2 *Player_V2CMS::getPlayVoice(byte param) {
+ byte channelNum = _lastMidiCommand & 0x0F;
+ Voice2 *curVoice = _midiChannel[channelNum];
+
+ if (curVoice) {
+ Voice2 *prevVoice = 0;
+ while (true) {
+ if (curVoice->playingNote == param)
+ break;
+
+ prevVoice = curVoice;
+ curVoice = curVoice->nextVoice;
+ if (!curVoice)
+ return 0;
+ }
+
+ if (prevVoice)
+ prevVoice->nextVoice = curVoice->nextVoice;
+ else
+ _midiChannel[channelNum] = curVoice->nextVoice;
+ }
+
+ return curVoice;
+}
+
+void Player_V2CMS::clearNote(byte *&data) {
+ Voice2 *voice = getPlayVoice(*data);
+ if (voice) {
+ voice->chanNumber = 0xFF;
+ voice->nextVoice = 0;
+ voice->nextProcessState = Voice2::kEnvelopeRelease;
+ }
+ data += 2;
+}
+
+void Player_V2CMS::play() {
+ _octaveMask = 0xF0;
+ channel_data *chan = &_channels[0].d;
+
+ byte noiseGen = 3;
+
+ for (int i = 1; i <= 4; ++i) {
+ if (chan->time_left) {
+ uint16 freq = chan->freq;
+
+ if (i == 4) {
+ if ((freq >> 8) & 0x40) {
+ noiseGen = freq & 0xFF;
+ } else {
+ noiseGen = 3;
+ _sfxFreq[0] = _sfxFreq[3];
+ _sfxOctave[0] = (_sfxOctave[0] & 0xF0) | ((_sfxOctave[1] & 0xF0) >> 4);
+ }
+ } else {
+ if (freq == 0) {
+ freq = 0xFFC0;
+ }
+
+ int cmsOct = 2;
+ int freqOct = 0x8000;
+
+ while (true) {
+ if (freq >= freqOct) {
+ break;
+ }
+ freqOct >>= 1;
+ ++cmsOct;
+ if (cmsOct == 8) {
+ --cmsOct;
+ freq = 1024;
+ break;
+ }
+ }
+ byte oct = cmsOct << 4;
+ oct |= cmsOct;
+
+ oct &= _octaveMask;
+ oct |= (~_octaveMask) & _sfxOctave[(i & 3) >> 1];
+ _sfxOctave[(i & 3) >> 1] = oct;
+
+ freq >>= -(cmsOct - 9);
+ _sfxFreq[i & 3] = (-(freq - 511)) & 0xFF;
+ }
+ _sfxAmpl[i & 3] = _volumeTable[chan->volume >> 12];
+ } else {
+ _sfxAmpl[i & 3] = 0;
+ }
+
+ chan = &_channels[i].d;
+ _octaveMask ^= 0xFF;
+ }
+
+ // with the high nibble of the volumeReg value
+ // the right channels amplitude is set
+ // with the low value the left channels amplitude
+ _cmsEmu->portWrite(0x221, 0);
+ _cmsEmu->portWrite(0x220, _sfxAmpl[0]);
+ _cmsEmu->portWrite(0x221, 1);
+ _cmsEmu->portWrite(0x220, _sfxAmpl[1]);
+ _cmsEmu->portWrite(0x221, 2);
+ _cmsEmu->portWrite(0x220, _sfxAmpl[2]);
+ _cmsEmu->portWrite(0x221, 3);
+ _cmsEmu->portWrite(0x220, _sfxAmpl[3]);
+ _cmsEmu->portWrite(0x221, 8);
+ _cmsEmu->portWrite(0x220, _sfxFreq[0]);
+ _cmsEmu->portWrite(0x221, 9);
+ _cmsEmu->portWrite(0x220, _sfxFreq[1]);
+ _cmsEmu->portWrite(0x221, 10);
+ _cmsEmu->portWrite(0x220, _sfxFreq[2]);
+ _cmsEmu->portWrite(0x221, 11);
+ _cmsEmu->portWrite(0x220, _sfxFreq[3]);
+ _cmsEmu->portWrite(0x221, 0x10);
+ _cmsEmu->portWrite(0x220, _sfxOctave[0]);
+ _cmsEmu->portWrite(0x221, 0x11);
+ _cmsEmu->portWrite(0x220, _sfxOctave[1]);
+ _cmsEmu->portWrite(0x221, 0x14);
+ _cmsEmu->portWrite(0x220, 0x3E);
+ _cmsEmu->portWrite(0x221, 0x15);
+ _cmsEmu->portWrite(0x220, 0x01);
+ _cmsEmu->portWrite(0x221, 0x16);
+ _cmsEmu->portWrite(0x220, noiseGen);
+}
+
+void Player_V2CMS::playMusicChips(const MusicChip *table) {
+ int cmsPort = 0x21E;
+
+ do {
+ cmsPort += 2;
+ _cmsEmu->portWrite(cmsPort+1, 0);
+ _cmsEmu->portWrite(cmsPort, table->ampl[0]);
+ _cmsEmu->portWrite(cmsPort+1, 1);
+ _cmsEmu->portWrite(cmsPort, table->ampl[1]);
+ _cmsEmu->portWrite(cmsPort+1, 2);
+ _cmsEmu->portWrite(cmsPort, table->ampl[2]);
+ _cmsEmu->portWrite(cmsPort+1, 3);
+ _cmsEmu->portWrite(cmsPort, table->ampl[3]);
+ _cmsEmu->portWrite(cmsPort+1, 8);
+ _cmsEmu->portWrite(cmsPort, table->freq[0]);
+ _cmsEmu->portWrite(cmsPort+1, 9);
+ _cmsEmu->portWrite(cmsPort, table->freq[1]);
+ _cmsEmu->portWrite(cmsPort+1, 10);
+ _cmsEmu->portWrite(cmsPort, table->freq[2]);
+ _cmsEmu->portWrite(cmsPort+1, 11);
+ _cmsEmu->portWrite(cmsPort, table->freq[3]);
+ _cmsEmu->portWrite(cmsPort+1, 0x10);
+ _cmsEmu->portWrite(cmsPort, table->octave[0]);
+ _cmsEmu->portWrite(cmsPort+1, 0x11);
+ _cmsEmu->portWrite(cmsPort, table->octave[1]);
+ _cmsEmu->portWrite(cmsPort+1, 0x14);
+ _cmsEmu->portWrite(cmsPort, 0x3F);
+ _cmsEmu->portWrite(cmsPort+1, 0x15);
+ _cmsEmu->portWrite(cmsPort, 0x00);
+ ++table;
+ } while ((cmsPort & 2) == 0);
+}
+
+const Player_V2CMS::MidiNote Player_V2CMS::_midiNotes[132] = {
+ { 3, 0 }, { 31, 0 }, { 58, 0 }, { 83, 0 },
+ { 107, 0 }, { 130, 0 }, { 151, 0 }, { 172, 0 },
+ { 191, 0 }, { 209, 0 }, { 226, 0 }, { 242, 0 },
+ { 3, 1 }, { 31, 1 }, { 58, 1 }, { 83, 1 },
+ { 107, 1 }, { 130, 1 }, { 151, 1 }, { 172, 1 },
+ { 191, 1 }, { 209, 1 }, { 226, 1 }, { 242, 1 },
+ { 3, 2 }, { 31, 2 }, { 58, 2 }, { 83, 2 },
+ { 107, 2 }, { 130, 2 }, { 151, 2 }, { 172, 2 },
+ { 191, 2 }, { 209, 2 }, { 226, 2 }, { 242, 2 },
+ { 3, 3 }, { 31, 3 }, { 58, 3 }, { 83, 3 },
+ { 107, 3 }, { 130, 3 }, { 151, 3 }, { 172, 3 },
+ { 191, 3 }, { 209, 3 }, { 226, 3 }, { 242, 3 },
+ { 3, 4 }, { 31, 4 }, { 58, 4 }, { 83, 4 },
+ { 107, 4 }, { 130, 4 }, { 151, 4 }, { 172, 4 },
+ { 191, 4 }, { 209, 4 }, { 226, 4 }, { 242, 4 },
+ { 3, 5 }, { 31, 5 }, { 58, 5 }, { 83, 5 },
+ { 107, 5 }, { 130, 5 }, { 151, 5 }, { 172, 5 },
+ { 191, 5 }, { 209, 5 }, { 226, 5 }, { 242, 5 },
+ { 3, 6 }, { 31, 6 }, { 58, 6 }, { 83, 6 },
+ { 107, 6 }, { 130, 6 }, { 151, 6 }, { 172, 6 },
+ { 191, 6 }, { 209, 6 }, { 226, 6 }, { 242, 6 },
+ { 3, 7 }, { 31, 7 }, { 58, 7 }, { 83, 7 },
+ { 107, 7 }, { 130, 7 }, { 151, 7 }, { 172, 7 },
+ { 191, 7 }, { 209, 7 }, { 226, 7 }, { 242, 7 },
+ { 3, 8 }, { 31, 8 }, { 58, 8 }, { 83, 8 },
+ { 107, 8 }, { 130, 8 }, { 151, 8 }, { 172, 8 },
+ { 191, 8 }, { 209, 8 }, { 226, 8 }, { 242, 8 },
+ { 3, 9 }, { 31, 9 }, { 58, 9 }, { 83, 9 },
+ { 107, 9 }, { 130, 9 }, { 151, 9 }, { 172, 9 },
+ { 191, 9 }, { 209, 9 }, { 226, 9 }, { 242, 9 },
+ { 3, 10 }, { 31, 10 }, { 58, 10 }, { 83, 10 },
+ { 107, 10 }, { 130, 10 }, { 151, 10 }, { 172, 10 },
+ { 191, 10 }, { 209, 10 }, { 226, 10 }, { 242, 10 }
+};
+
+const byte Player_V2CMS::_attackRate[16] = {
+ 0, 2, 4, 7, 14, 26, 48, 82,
+ 128, 144, 160, 176, 192, 208, 224, 255
+};
+
+const byte Player_V2CMS::_decayRate[16] = {
+ 0, 1, 2, 3, 4, 6, 12, 24,
+ 48, 96, 192, 215, 255, 255, 255, 255
+};
+
+const byte Player_V2CMS::_sustainRate[16] = {
+ 255, 180, 128, 96, 80, 64, 56, 48,
+ 42, 36, 32, 28, 24, 20, 16, 0
+};
+
+const byte Player_V2CMS::_releaseRate[16] = {
+ 0, 1, 2, 4, 6, 9, 14, 22,
+ 36, 56, 80, 100, 120, 140, 160, 255
+};
+
+const byte Player_V2CMS::_volumeTable[16] = {
+ 0x00, 0x10, 0x10, 0x11, 0x11, 0x21, 0x22, 0x22,
+ 0x33, 0x44, 0x55, 0x66, 0x88, 0xAA, 0xCC, 0xFF
+};
+
+const byte Player_V2CMS::_cmsInitData[26] = {
+ 0x1C, 0x02,
+ 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00,
+ 0x14, 0x3F, 0x15, 0x00, 0x16, 0x00, 0x18, 0x00, 0x19, 0x00, 0x1C, 0x01
+};
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_v2cms.h b/engines/scumm/players/player_v2cms.h
new file mode 100644
index 0000000000..fe42720215
--- /dev/null
+++ b/engines/scumm/players/player_v2cms.h
@@ -0,0 +1,177 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_V2CMS_H
+#define SCUMM_PLAYERS_PLAYER_V2CMS_H
+
+#include "scumm/players/player_v2base.h" // for channel_data
+
+class CMSEmulator;
+
+namespace Scumm {
+
+/**
+ * Scumm V2 CMS/Gameblaster MIDI driver.
+ */
+class Player_V2CMS : public Player_V2Base {
+public:
+ Player_V2CMS(ScummEngine *scumm, Audio::Mixer *mixer);
+ virtual ~Player_V2CMS();
+
+ // MusicEngine API
+ virtual void setMusicVolume(int vol);
+ virtual void startSound(int sound);
+ virtual void stopSound(int sound);
+ virtual void stopAllSounds();
+ virtual int getMusicTimer();
+ virtual int getSoundStatus(int sound) const;
+
+ // AudioStream API
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+ virtual bool isStereo() const { return true; }
+
+private:
+ struct Voice {
+ byte attack;
+ byte decay;
+ byte sustain;
+ byte release;
+ byte octadd;
+ int16 vibrato;
+ int16 vibrato2;
+ int16 noise;
+ };
+
+ struct Voice2 {
+ byte *amplitudeOutput;
+ byte *freqOutput;
+ byte *octaveOutput;
+
+ uint8 channel;
+ int8 sustainLevel;
+ uint8 attackRate;
+ uint8 maxAmpl;
+ uint8 decayRate;
+ uint8 sustainRate;
+ uint8 releaseRate;
+ uint8 releaseTime;
+ int8 vibratoRate;
+ int8 vibratoDepth;
+
+ int8 curVibratoRate;
+ int8 curVibratoUnk;
+
+ int8 unkVibratoRate;
+ int8 unkVibratoDepth;
+
+ int8 unkRate;
+ int8 unkCount;
+
+ enum EnvelopeState {
+ kEnvelopeAttack,
+ kEnvelopeDecay,
+ kEnvelopeSustain,
+ kEnvelopeRelease
+ };
+
+ EnvelopeState nextProcessState;
+ uint8 curVolume;
+ uint8 curOctave;
+ uint8 curFreq;
+
+ int8 octaveAdd;
+
+ int8 playingNote;
+ Voice2 *nextVoice;
+
+ byte chanNumber;
+ };
+
+ struct MusicChip {
+ byte ampl[4];
+ byte freq[4];
+ byte octave[2];
+ };
+
+ Voice _cmsVoicesBase[16];
+ Voice2 _cmsVoices[8];
+ MusicChip _cmsChips[2];
+
+ uint8 _tempo;
+ uint8 _tempoSum;
+ byte _looping;
+ byte _octaveMask;
+ int16 _midiDelay;
+ Voice2 *_midiChannel[16];
+ byte _midiChannelUse[16];
+ byte *_midiData;
+ byte *_midiSongBegin;
+
+ int _loadedMidiSong;
+
+ byte _sfxFreq[4], _sfxAmpl[4], _sfxOctave[2];
+
+ byte _lastMidiCommand;
+ uint _outputTableReady;
+ byte _voiceTimer;
+
+ int _musicTimer, _musicTimerTicks;
+
+ void loadMidiData(byte *data, int sound);
+ void play();
+
+ void processChannel(Voice2 *channel);
+ void processRelease(Voice2 *channel);
+ void processAttack(Voice2 *channel);
+ void processDecay(Voice2 *channel);
+ void processSustain(Voice2 *channel);
+ void processVibrato(Voice2 *channel);
+
+ void playMusicChips(const MusicChip *table);
+ void playNote(byte *&data);
+ void clearNote(byte *&data);
+ void offAllChannels();
+ void playVoice();
+ void processMidiData();
+
+ Voice2 *getFreeVoice();
+ Voice2 *getPlayVoice(byte param);
+
+ struct MidiNote {
+ byte frequency;
+ byte baseOctave;
+ };
+
+ static const MidiNote _midiNotes[132];
+ static const byte _attackRate[16];
+ static const byte _decayRate[16];
+ static const byte _sustainRate[16];
+ static const byte _releaseRate[16];
+ static const byte _volumeTable[16];
+ static const byte _cmsInitData[26];
+
+ CMSEmulator *_cmsEmu;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_v3a.cpp b/engines/scumm/players/player_v3a.cpp
new file mode 100644
index 0000000000..ca0eedc90a
--- /dev/null
+++ b/engines/scumm/players/player_v3a.cpp
@@ -0,0 +1,357 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+#include "engines/engine.h"
+#include "scumm/players/player_v3a.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+static const uint16 note_freqs[4][12] = {
+ {0x06B0, 0x0650, 0x05F4, 0x05A0, 0x054C, 0x0500, 0x04B8, 0x0474, 0x0434, 0x03F8, 0x03C0, 0x0388},
+ {0x0358, 0x0328, 0x02FA, 0x02D0, 0x02A6, 0x0280, 0x025C, 0x023A, 0x021A, 0x01FC, 0x01E0, 0x01C4},
+ {0x01AC, 0x0194, 0x017D, 0x0168, 0x0153, 0x0140, 0x012E, 0x011D, 0x010D, 0x00FE, 0x00F0, 0x00E2},
+ {0x00D6, 0x00CA, 0x00BE, 0x00B4, 0x00A9, 0x00A0, 0x0097, 0x008E, 0x0086, 0x007F, 0x00F0, 0x00E2}
+};
+
+Player_V3A::Player_V3A(ScummEngine *scumm, Audio::Mixer *mixer) {
+ int i;
+ _vm = scumm;
+ for (i = 0; i < V3A_MAXMUS; i++) {
+ _mus[i].id = 0;
+ _mus[i].dur = 0;
+ }
+ for (i = 0; i < V3A_MAXSFX; i++) {
+ _sfx[i].id = 0;
+ _sfx[i].dur = 0;
+ }
+
+ _curSong = 0;
+ _songData = NULL;
+ _songPtr = 0;
+ _songDelay = 0;
+
+ _music_timer = 0;
+
+ _isinit = false;
+
+ _mod = new Player_MOD(mixer);
+ _mod->setUpdateProc(update_proc, this, 60);
+}
+
+Player_V3A::~Player_V3A() {
+ int i;
+ delete _mod;
+ if (_isinit) {
+ for (i = 0; _wavetable[i] != NULL; i++) {
+ for (int j = 0; j < 6; j++) {
+ free(_wavetable[i]->_idat[j]);
+ free(_wavetable[i]->_ldat[j]);
+ }
+ free(_wavetable[i]);
+ }
+ free(_wavetable);
+ }
+}
+
+void Player_V3A::setMusicVolume (int vol) {
+ _mod->setMusicVolume(vol);
+}
+
+int Player_V3A::getMusChan (int id) const {
+ int i;
+ for (i = 0; i < V3A_MAXMUS; i++) {
+ if (_mus[i].id == id)
+ break;
+ }
+ if (i == V3A_MAXMUS) {
+ if (id == 0)
+ warning("player_v3a - out of music channels");
+ return -1;
+ }
+ return i;
+}
+int Player_V3A::getSfxChan (int id) const {
+ int i;
+ for (i = 0; i < V3A_MAXSFX; i++) {
+ if (_sfx[i].id == id)
+ break;
+ }
+ if (i == V3A_MAXSFX) {
+ if (id == 0)
+ warning("player_v3a - out of sfx channels");
+ return -1;
+ }
+ return i;
+}
+
+void Player_V3A::stopAllSounds() {
+ int i;
+ for (i = 0; i < V3A_MAXMUS; i++) {
+ if (_mus[i].id)
+ _mod->stopChannel(_mus[i].id);
+ _mus[i].id = 0;
+ _mus[i].dur = 0;
+ }
+ _curSong = 0;
+ _songPtr = 0;
+ _songDelay = 0;
+ _songData = NULL;
+ for (i = 0; i < V3A_MAXSFX; i++) {
+ if (_sfx[i].id)
+ _mod->stopChannel(_sfx[i].id | 0x100);
+ _sfx[i].id = 0;
+ _sfx[i].dur = 0;
+ }
+}
+
+void Player_V3A::stopSound(int nr) {
+ int i;
+ if (nr == 0) { // Amiga Loom does this near the end, when Chaos casts SILENCE on Hetchel
+ stopAllSounds();
+ return;
+ }
+ if (nr == _curSong) {
+ for (i = 0; i < V3A_MAXMUS; i++) {
+ if (_mus[i].id)
+ _mod->stopChannel(_mus[i].id);
+ _mus[i].id = 0;
+ _mus[i].dur = 0;
+ }
+ _curSong = 0;
+ _songPtr = 0;
+ _songDelay = 0;
+ _songData = NULL;
+ } else {
+ i = getSfxChan(nr);
+ if (i != -1) {
+ _mod->stopChannel(nr | 0x100);
+ _sfx[i].id = 0;
+ _sfx[i].dur = 0;
+ }
+ }
+}
+
+void Player_V3A::startSound(int nr) {
+ assert(_vm);
+ byte *data = _vm->getResourceAddress(rtSound, nr);
+ assert(data);
+
+ if ((_vm->_game.id != GID_INDY3) && (_vm->_game.id != GID_LOOM))
+ error("player_v3a - unknown game");
+
+ if (!_isinit) {
+ int i;
+ unsigned char *ptr;
+ int offset = 4;
+ int numInstruments;
+
+ if (_vm->_game.id == GID_INDY3) {
+ ptr = _vm->getResourceAddress(rtSound, 83);
+ numInstruments = 12;
+ } else {
+ ptr = _vm->getResourceAddress(rtSound, 79);
+ numInstruments = 9;
+ }
+ assert(ptr);
+ _wavetable = (instData **)malloc((numInstruments + 1) * sizeof(void *));
+ for (i = 0; i < numInstruments; i++) {
+ _wavetable[i] = (instData *)malloc(sizeof(instData));
+ for (int j = 0; j < 6; j++) {
+ int off, len;
+ off = READ_BE_UINT16(ptr + offset + 0);
+ _wavetable[i]->_ilen[j] = len = READ_BE_UINT16(ptr + offset + 2);
+ if (len) {
+ _wavetable[i]->_idat[j] = (char *)malloc(len);
+ memcpy(_wavetable[i]->_idat[j],ptr + off,len);
+ } else _wavetable[i]->_idat[j] = NULL;
+ off = READ_BE_UINT16(ptr + offset + 4);
+ _wavetable[i]->_llen[j] = len = READ_BE_UINT16(ptr + offset + 6);
+ if (len) {
+ _wavetable[i]->_ldat[j] = (char *)malloc(len);
+ memcpy(_wavetable[i]->_ldat[j],ptr + off,len);
+ } else _wavetable[i]->_ldat[j] = NULL;
+ _wavetable[i]->_oct[j] = READ_BE_UINT16(ptr + offset + 8);
+ offset += 10;
+ }
+ if (_vm->_game.id == GID_INDY3) {
+ _wavetable[i]->_pitadjust = 0;
+ offset += 2;
+ } else {
+ _wavetable[i]->_pitadjust = READ_BE_UINT16(ptr + offset + 2);
+ offset += 4;
+ }
+ }
+ _wavetable[i] = NULL;
+ _isinit = true;
+ }
+
+ if (getSoundStatus(nr))
+ stopSound(nr); // if a sound is playing, restart it
+
+ if (data[26]) {
+ if (_curSong)
+ stopSound(_curSong);
+ _curSong = nr;
+ _songData = data;
+ _songPtr = 0x1C;
+ _songDelay = 1;
+ _music_timer = 0;
+ } else {
+ int size = READ_BE_UINT16(data + 12);
+ int rate = 3579545 / READ_BE_UINT16(data + 20);
+ char *sound = (char *)malloc(size);
+ int vol = (data[24] << 1) | (data[24] >> 5); // if I boost this to 0-255, it gets too loud and starts to clip
+ memcpy(sound, data + READ_BE_UINT16(data + 8), size);
+ int loopStart = 0, loopEnd = 0;
+ int loopcount = data[27];
+ if (loopcount > 1) {
+ loopStart = READ_BE_UINT16(data + 10) - READ_BE_UINT16(data + 8);
+ loopEnd = READ_BE_UINT16(data + 14);
+ }
+ int i = getSfxChan();
+ if (i == -1) {
+ free(sound);
+ return;
+ }
+ _sfx[i].id = nr;
+ _sfx[i].dur = 1 + loopcount * 60 * size / rate;
+ if (READ_BE_UINT16(data + 16)) {
+ _sfx[i].rate = READ_BE_UINT16(data + 20) << 16;
+ _sfx[i].delta = (int32)READ_BE_UINT32(data + 32);
+ _sfx[i].dur = READ_BE_UINT32(data + 40);
+ } else {
+ _sfx[i].delta = 0;
+ }
+ _mod->startChannel(nr | 0x100, sound, size, rate, vol, loopStart, loopEnd);
+ }
+}
+
+void Player_V3A::update_proc(void *param) {
+ ((Player_V3A *)param)->playMusic();
+}
+
+void Player_V3A::playMusic() {
+ int i;
+ for (i = 0; i < V3A_MAXMUS; i++) {
+ if (_mus[i].id) {
+ _mus[i].dur--;
+ if (_mus[i].dur)
+ continue;
+ _mod->stopChannel(_mus[i].id);
+ _mus[i].id = 0;
+ }
+ }
+ for (i = 0; i < V3A_MAXSFX; i++) {
+ if (_sfx[i].id) {
+ if (_sfx[i].delta) {
+ uint16 oldrate = _sfx[i].rate >> 16;
+ _sfx[i].rate += _sfx[i].delta;
+ if (_sfx[i].rate < (55 << 16))
+ _sfx[i].rate = 55 << 16; // at rates below 55, frequency
+ uint16 newrate = _sfx[i].rate >> 16; // exceeds 65536, which is bad
+ if (oldrate != newrate)
+ _mod->setChannelFreq(_sfx[i].id | 0x100, 3579545 / newrate);
+ }
+ _sfx[i].dur--;
+ if (_sfx[i].dur)
+ continue;
+ _mod->stopChannel(_sfx[i].id | 0x100);
+ _sfx[i].id = 0;
+ }
+ }
+
+ _music_timer++;
+ if (!_curSong)
+ return;
+ if (_songDelay && --_songDelay)
+ return;
+ if (_songPtr == 0) {
+ // at the end of the song, and it wasn't looped - kill it
+ _curSong = 0;
+ return;
+ }
+ while (1) {
+ int inst, pit, vol, dur, oct;
+ inst = _songData[_songPtr++];
+ if ((inst & 0xF0) != 0x80) {
+ // tune is at the end - figure out what's still playing
+ // and see how long we have to wait until we stop/restart
+ for (i = 0; i < V3A_MAXMUS; i++) {
+ if (_songDelay < _mus[i].dur)
+ _songDelay = _mus[i].dur;
+ }
+ if (inst == 0xFB) // it's a looped song, restart it afterwards
+ _songPtr = 0x1C;
+ else _songPtr = 0; // otherwise, terminate it
+ break;
+ }
+ inst &= 0xF;
+ pit = _songData[_songPtr++];
+ vol = _songData[_songPtr++] & 0x7F; // if I boost this to 0-255, it gets too loud and starts to clip
+ dur = _songData[_songPtr++];
+ if (pit == 0) {
+ _songDelay = dur;
+ break;
+ }
+ pit += _wavetable[inst]->_pitadjust;
+ oct = (pit / 12) - 2;
+ pit = pit % 12;
+ if (oct < 0)
+ oct = 0;
+ if (oct > 5)
+ oct = 5;
+ int rate = 3579545 / note_freqs[_wavetable[inst]->_oct[oct]][pit];
+ if (!_wavetable[inst]->_llen[oct])
+ dur = _wavetable[inst]->_ilen[oct] * 60 / rate;
+ char *data = (char *)malloc(_wavetable[inst]->_ilen[oct] + _wavetable[inst]->_llen[oct]);
+ if (_wavetable[inst]->_idat[oct])
+ memcpy(data, _wavetable[inst]->_idat[oct], _wavetable[inst]->_ilen[oct]);
+ if (_wavetable[inst]->_ldat[oct])
+ memcpy(data + _wavetable[inst]->_ilen[oct], _wavetable[inst]->_ldat[oct], _wavetable[inst]->_llen[oct]);
+
+ i = getMusChan();
+ if (i == -1) {
+ free(data);
+ return;
+ }
+ _mus[i].id = i + 1;
+ _mus[i].dur = dur + 1;
+ _mod->startChannel(_mus[i].id, data, _wavetable[inst]->_ilen[oct] + _wavetable[inst]->_llen[oct], rate, vol,
+ _wavetable[inst]->_ilen[oct], _wavetable[inst]->_ilen[oct] + _wavetable[inst]->_llen[oct]);
+ }
+}
+
+int Player_V3A::getMusicTimer() {
+ return _music_timer / 30;
+}
+
+int Player_V3A::getSoundStatus(int nr) const {
+ if (nr == _curSong)
+ return 1;
+ if (getSfxChan(nr) != -1)
+ return 1;
+ return 0;
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_v3a.h b/engines/scumm/players/player_v3a.h
new file mode 100644
index 0000000000..3f84a74e51
--- /dev/null
+++ b/engines/scumm/players/player_v3a.h
@@ -0,0 +1,101 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_V3A_H
+#define SCUMM_PLAYERS_PLAYER_V3A_H
+
+#include "common/scummsys.h"
+#include "scumm/music.h"
+#include "scumm/players/player_mod.h"
+
+class Mixer;
+
+namespace Scumm {
+
+class ScummEngine;
+
+/**
+ * Scumm V3 Amiga sound/music driver.
+ */
+class Player_V3A : public MusicEngine {
+public:
+ Player_V3A(ScummEngine *scumm, Audio::Mixer *mixer);
+ virtual ~Player_V3A();
+
+ virtual void setMusicVolume(int vol);
+ virtual void startSound(int sound);
+ virtual void stopSound(int sound);
+ virtual void stopAllSounds();
+ virtual int getMusicTimer();
+ virtual int getSoundStatus(int sound) const;
+
+private:
+ enum {
+ V3A_MAXMUS = 24,
+ V3A_MAXSFX = 16
+ };
+
+ struct musChan {
+ int id;
+ int dur;
+ };
+
+ struct sfxChan {
+ int id;
+ int dur;
+ uint32 rate;
+ int32 delta;
+ };
+
+ struct instData {
+ char *_idat[6];
+ uint16 _ilen[6];
+ char *_ldat[6];
+ uint16 _llen[6];
+ uint16 _oct[6];
+ int16 _pitadjust;
+ };
+
+ ScummEngine *_vm;
+ Player_MOD *_mod;
+
+ musChan _mus[V3A_MAXMUS];
+ sfxChan _sfx[V3A_MAXSFX];
+
+ int _curSong;
+ uint8 *_songData;
+ uint16 _songPtr;
+ uint16 _songDelay;
+ int _music_timer;
+ bool _isinit;
+
+ instData **_wavetable;
+
+ int getMusChan (int id = 0) const;
+ int getSfxChan (int id = 0) const;
+ static void update_proc(void *param);
+ void playMusic();
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_v3m.cpp b/engines/scumm/players/player_v3m.cpp
new file mode 100644
index 0000000000..e30e31aff9
--- /dev/null
+++ b/engines/scumm/players/player_v3m.cpp
@@ -0,0 +1,214 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ We have the following information from Lars Christensen (lechimp) and
+ Jamieson Christian (jamieson630):
+
+ RESOURCE DATA
+ LE 2 bytes Resource size
+ 2 bytes Unknown
+ 2 bytes 'so'
+ 14 bytes Unknown
+ BE 2 bytes Instrument for Stream 1
+ BE 2 bytes Instrument for Stream 2
+ BE 2 bytes Instrument for Stream 3
+ BE 2 bytes Instrument for Stream 4
+ BE 2 bytes Instrument for Stream 5
+ BE 2 bytes Offset to Stream 1
+ BE 2 bytes Offset to Stream 2
+ BE 2 bytes Offset to Stream 3
+ BE 2 bytes Offset to Stream 4
+ BE 2 bytes Offset to Stream 5
+ ? bytes The streams
+
+ STREAM DATA
+ BE 2 bytes Unknown (always 1?)
+ 2 bytes Unknown (always 0?)
+ BE 2 bytes Number of events in stream
+ ? bytes Stream data
+
+ Each stream event is exactly 3 bytes, therefore one can
+ assert that numEvents == (streamSize - 6) / 3. The
+ polyphony of a stream appears to be 1; in other words, only
+ one note at a time can be playing in each stream. The next
+ event is not executed until the current note (or rest) is
+ finished playing; therefore, note duration also serves as the
+ time delta between events.
+
+ FOR EACH EVENTS
+ BE 2 bytes Note duration
+ 1 byte Note number to play (0 = rest/silent)
+
+ Oh, and quick speculation -- Stream 1 may be used for a
+ single-voice interleaved version of the music, where Stream 2-
+ 5 represent a version of the music in up to 4-voice
+ polyphony, one voice per stream. I postulate thus because
+ the first stream of the Mac Loom theme music contains
+ interleaved voices, whereas the second stream seemed to
+ contain only the pizzicato bottom-end harp. Stream 5, in this
+ example, is empty, so if my speculation is correct, this
+ particular musical number supports 3-voice polyphony at
+ most. I must check out Streams 3 and 4 to see what they
+ contain.
+
+ ==========
+
+ The instruments appear to be identified by their resource IDs:
+
+ 1000 Dual Harp
+ 10895 harp1
+ 11445 strings1
+ 11548 silent
+ 13811 staff1
+ 15703 brass1
+ 16324 flute1
+ 25614 accordian 1
+ 28110 f horn1
+ 29042 bassoon1
+*/
+
+#include "common/macresman.h"
+#include "common/translation.h"
+#include "engines/engine.h"
+#include "gui/message.h"
+#include "scumm/players/player_v3m.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer)
+ : Player_Mac(scumm, mixer, 5, 0x1E, true) {
+ assert(_vm->_game.id == GID_LOOM);
+
+ // Channel 0 seems to be what was played on low-end macs, that couldn't
+ // handle multi-channel music and play the game at the same time. I'm
+ // not sure if stream 4 is ever used, but let's use it just in case.
+}
+
+// \xAA is a trademark glyph in Mac OS Roman. We try that, but also the Windows
+// version, the UTF-8 version, and just plain without in case the file system
+// can't handle exotic characters like that.
+
+static const char *loomFileNames[] = {
+ "Loom\xAA",
+ "Loom\x99",
+ "Loom\xE2\x84\xA2",
+ "Loom"
+};
+
+bool Player_V3M::checkMusicAvailable() {
+ Common::MacResManager resource;
+
+ for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) {
+ if (resource.exists(loomFileNames[i])) {
+ return true;
+ }
+ }
+
+ GUI::MessageDialog dialog(_(
+ "Could not find the 'Loom' Macintosh executable to read the\n"
+ "instruments from. Music will be disabled."), _("OK"));
+ dialog.runModal();
+ return false;
+}
+
+bool Player_V3M::loadMusic(const byte *ptr) {
+ Common::MacResManager resource;
+ bool found = false;
+
+ for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) {
+ if (resource.open(loomFileNames[i])) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return false;
+ }
+
+ if (ptr[4] != 's' || ptr[5] != 'o') {
+ // Like the original we ignore all sound resources which do not have
+ // a 'so' tag in them.
+ // See bug #3602239 ("Mac Loom crashes using opening spell on
+ // gravestone") for a case where this is required. Loom Mac tries to
+ // play resource 11 here. This resource is no Mac sound resource
+ // though, it is a PC Speaker resource. A test with the original
+ // interpreter also has shown that no sound is played while the
+ // screen is shaking.
+ debug(5, "Player_V3M::loadMusic: Skipping unknown music type %02X%02X", ptr[4], ptr[5]);
+ resource.close();
+ return false;
+ }
+
+ uint i;
+ for (i = 0; i < 5; i++) {
+ int instrument = READ_BE_UINT16(ptr + 20 + 2 * i);
+ int offset = READ_BE_UINT16(ptr + 30 + 2 * i);
+
+ _channel[i]._looped = false;
+ _channel[i]._length = READ_BE_UINT16(ptr + offset + 4) * 3;
+ _channel[i]._data = ptr + offset + 6;
+ _channel[i]._pos = 0;
+ _channel[i]._pitchModifier = 0;
+ _channel[i]._velocity = 0;
+ _channel[i]._remaining = 0;
+ _channel[i]._notesLeft = true;
+
+ Common::SeekableReadStream *stream = resource.getResource(RES_SND, instrument);
+ if (_channel[i].loadInstrument(stream)) {
+ debug(6, "Player_V3M::loadMusic: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str());
+ } else {
+ resource.close();
+ return false;
+ }
+ }
+
+ resource.close();
+ return true;
+}
+
+bool Player_V3M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) {
+ _channel[ch]._instrument.newNote();
+ if (_channel[ch]._pos >= _channel[ch]._length) {
+ if (!_channel[ch]._looped) {
+ _channel[ch]._notesLeft = false;
+ return false;
+ }
+ _channel[ch]._pos = 0;
+ }
+ uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
+ byte note = _channel[ch]._data[_channel[ch]._pos + 2];
+ samples = durationToSamples(duration);
+ if (note > 0) {
+ pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument);
+ velocity = 127;
+ } else {
+ pitchModifier = 0;
+ velocity = 0;
+ }
+ _channel[ch]._pos += 3;
+ return true;
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_v3m.h b/engines/scumm/players/player_v3m.h
new file mode 100644
index 0000000000..615d736035
--- /dev/null
+++ b/engines/scumm/players/player_v3m.h
@@ -0,0 +1,54 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_V3M_H
+#define SCUMM_PLAYERS_PLAYER_V3M_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+#include "common/mutex.h"
+#include "scumm/music.h"
+#include "scumm/players/player_mac.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+class Mixer;
+
+namespace Scumm {
+
+class ScummEngine;
+
+/**
+ * Scumm V3 Macintosh music driver.
+ */
+class Player_V3M : public Player_Mac {
+public:
+ Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer);
+
+ virtual bool checkMusicAvailable();
+ virtual bool loadMusic(const byte *ptr);
+ virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity);
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_v4a.cpp b/engines/scumm/players/player_v4a.cpp
new file mode 100644
index 0000000000..59f49625c3
--- /dev/null
+++ b/engines/scumm/players/player_v4a.cpp
@@ -0,0 +1,190 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "engines/engine.h"
+#include "scumm/players/player_v4a.h"
+#include "scumm/scumm.h"
+
+#include "common/file.h"
+
+namespace Scumm {
+
+Player_V4A::Player_V4A(ScummEngine *scumm, Audio::Mixer *mixer)
+ : _vm(scumm),
+ _mixer(mixer),
+ _tfmxMusic(_mixer->getOutputRate(), true),
+ _tfmxSfx(_mixer->getOutputRate(), true),
+ _musicHandle(),
+ _sfxHandle(),
+ _musicId(),
+ _sfxSlots(),
+ _initState(0),
+ _signal(0) {
+
+ assert(scumm);
+ assert(mixer);
+ assert(_vm->_game.id == GID_MONKEY_VGA);
+ _tfmxMusic.setSignalPtr(&_signal, 1);
+}
+
+bool Player_V4A::init() {
+ if (_vm->_game.id != GID_MONKEY_VGA)
+ error("player_v4a - unknown game");
+
+ Common::File fileMdat, fileSample;
+
+ if (fileMdat.open("music.dat") && fileSample.open("sample.dat")) {
+ // explicitly request that no instance delets the resources automatically
+ if (_tfmxMusic.load(fileMdat, fileSample, false)) {
+ _tfmxSfx.setModuleData(_tfmxMusic);
+ return true;
+ }
+ } else
+ warning("player_v4a: couldnt load one of the music resources: music.dat, sample.dat");
+
+ return false;
+}
+
+Player_V4A::~Player_V4A() {
+ _mixer->stopHandle(_musicHandle);
+ _mixer->stopHandle(_sfxHandle);
+ _tfmxMusic.freeResources();
+}
+
+void Player_V4A::setMusicVolume(int vol) {
+ debug(5, "player_v4a: setMusicVolume %i", vol);
+}
+
+void Player_V4A::stopAllSounds() {
+ debug(5, "player_v4a: stopAllSounds");
+ if (_initState > 0) {
+ _tfmxMusic.stopSong();
+ _signal = 0;
+ _musicId = 0;
+
+ _tfmxSfx.stopSong();
+ clearSfxSlots();
+ } else
+ _mixer->stopHandle(_musicHandle);
+}
+
+void Player_V4A::stopSound(int nr) {
+ debug(5, "player_v4a: stopSound %d", nr);
+ if (nr == 0)
+ return;
+ if (nr == _musicId) {
+ _musicId = 0;
+ if (_initState > 0)
+ _tfmxMusic.stopSong();
+ else
+ _mixer->stopHandle(_musicHandle);
+ _signal = 0;
+ } else {
+ const int chan = getSfxChan(nr);
+ if (chan != -1) {
+ setSfxSlot(chan, 0);
+ _tfmxSfx.stopMacroEffect(chan);
+ }
+ }
+}
+
+void Player_V4A::startSound(int nr) {
+ static const int8 monkeyCommands[52] = {
+ -1, -2, -3, -4, -5, -6, -7, -8,
+ -9, -10, -11, -12, -13, -14, 18, 17,
+ -17, -18, -19, -20, -21, -22, -23, -24,
+ -25, -26, -27, -28, -29, -30, -31, -32,
+ -33, 16, -35, 0, 1, 2, 3, 7,
+ 8, 10, 11, 4, 5, 14, 15, 12,
+ 6, 13, 9, 19
+ };
+
+ const byte *ptr = _vm->getResourceAddress(rtSound, nr);
+ assert(ptr);
+
+ const int val = ptr[9];
+ if (val < 0 || val >= ARRAYSIZE(monkeyCommands)) {
+ warning("player_v4a: illegal Songnumber %i", val);
+ return;
+ }
+
+ if (!_initState)
+ _initState = init() ? 1 : -1;
+
+ if (_initState < 0)
+ return;
+
+ int index = monkeyCommands[val];
+ const byte type = ptr[6];
+ if (index < 0) { // SoundFX
+ index = -index - 1;
+ debug(3, "player_v4a: play %d: custom %i - %02X", nr, index, type);
+
+ // start an empty Song so timing is setup
+ if (_tfmxSfx.getSongIndex() < 0)
+ _tfmxSfx.doSong(0x18);
+
+ const int chan = _tfmxSfx.doSfx((uint16)index);
+ if (chan >= 0 && chan < ARRAYSIZE(_sfxSlots))
+ setSfxSlot(chan, nr, type);
+ else
+ warning("player_v4a: custom %i is not of required type", index);
+
+ // the Tfmx-player never "ends" the output by itself, so this should be threadsafe
+ if (!_mixer->isSoundHandleActive(_sfxHandle))
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, &_sfxHandle, &_tfmxSfx, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
+
+ } else { // Song
+ debug(3, "player_v4a: play %d: song %i - %02X", nr, index, type);
+ if (ptr[6] != 0x7F)
+ warning("player_v4a: Song has wrong type");
+
+ _tfmxMusic.doSong(index);
+ _signal = 2;
+
+ // the Tfmx-player never "ends" the output by itself, so this should be threadsafe
+ if (!_mixer->isSoundHandleActive(_musicHandle))
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, &_tfmxMusic, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
+ _musicId = nr;
+ }
+}
+
+int Player_V4A::getMusicTimer() {
+ // A workaround if the modplayer couldnt load the datafiles - just return a number big enough to pass all tests
+ if (_initState < 0)
+ return 2000;
+ if (_musicId) {
+ // The titlesong (and a few others) is running with ~70 ticks per second and the scale seems to be based on that.
+ // The Game itself doesnt get the timing from the Tfmx Player however, so we just use the elapsed time
+ // 357 ~ 1000 * 25 * (1 / 70)
+ return _mixer->getSoundElapsedTime(_musicHandle) / 357;
+ }
+ return 0;
+}
+
+int Player_V4A::getSoundStatus(int nr) const {
+ // For music the game queues a variable the Tfmx Player sets through a special command.
+ // For sfx there seems to be no way to queue them, and the game doesnt try to.
+ return (nr == _musicId) ? _signal : 0;
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_v4a.h b/engines/scumm/players/player_v4a.h
new file mode 100644
index 0000000000..c8c7b63ed2
--- /dev/null
+++ b/engines/scumm/players/player_v4a.h
@@ -0,0 +1,96 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_V4A_H
+#define SCUMM_PLAYERS_PLAYER_V4A_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+#include "scumm/music.h"
+#include "audio/mixer.h"
+#include "audio/mods/tfmx.h"
+
+class Mixer;
+
+namespace Scumm {
+
+class ScummEngine;
+
+/**
+ * Scumm V4 Amiga sound/music driver.
+ */
+class Player_V4A : public MusicEngine {
+public:
+ Player_V4A(ScummEngine *scumm, Audio::Mixer *mixer);
+ virtual ~Player_V4A();
+
+ virtual void setMusicVolume(int vol);
+ virtual void startSound(int sound);
+ virtual void stopSound(int sound);
+ virtual void stopAllSounds();
+ virtual int getMusicTimer();
+ virtual int getSoundStatus(int sound) const;
+
+private:
+ ScummEngine *const _vm;
+ Audio::Mixer *const _mixer;
+
+ Audio::Tfmx _tfmxMusic;
+ Audio::Tfmx _tfmxSfx;
+ Audio::SoundHandle _musicHandle;
+ Audio::SoundHandle _sfxHandle;
+
+ int _musicId;
+ uint16 _signal;
+
+ struct SfxChan {
+ int id;
+// byte type;
+ } _sfxSlots[4];
+
+ int8 _initState; // < 0: failed, 0: uninitialized, > 0: initialized
+
+ int getSfxChan(int id) const {
+ for (int i = 0; i < ARRAYSIZE(_sfxSlots); ++i)
+ if (_sfxSlots[i].id == id)
+ return i;
+ return -1;
+ }
+
+ void setSfxSlot(int channel, int id, byte type = 0) {
+ _sfxSlots[channel].id = id;
+// _sfxSlots[channel].type = type;
+ }
+
+ void clearSfxSlots() {
+ for (int i = 0; i < ARRAYSIZE(_sfxSlots); ++i){
+ _sfxSlots[i].id = 0;
+// _sfxSlots[i].type = 0;
+ }
+ }
+
+ bool init();
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_v5m.cpp b/engines/scumm/players/player_v5m.cpp
new file mode 100644
index 0000000000..77b6d52a3c
--- /dev/null
+++ b/engines/scumm/players/player_v5m.cpp
@@ -0,0 +1,246 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ From Markus Magnuson (superqult) we got this information:
+ Mac0
+ ---
+ 4 bytes - 'SOUN'
+ BE 4 bytes - block length
+
+ 4 bytes - 'Mac0'
+ BE 4 bytes - (blockLength - 27)
+ 28 bytes - ???
+
+ do this three times (once for each channel):
+ 4 bytes - 'Chan'
+ BE 4 bytes - channel length
+ 4 bytes - instrument name (e.g. 'MARI')
+
+ do this for ((chanLength-24)/4) times:
+ 2 bytes - note duration
+ 1 byte - note value
+ 1 byte - note velocity
+
+ 4 bytes - ???
+ 4 bytes - 'Loop'/'Done'
+ 4 bytes - ???
+
+ 1 byte - 0x09
+ ---
+
+ The instruments presumably correspond to the snd resource names in the
+ Monkey Island executable:
+
+ Instruments
+ "MARI" - MARIMBA
+ "PLUC" - PLUCK
+ "HARM" - HARMONIC
+ "PIPE" - PIPEORGAN
+ "TROM" - TROMBONE
+ "STRI" - STRINGS
+ "HORN" - HORN
+ "VIBE" - VIBES
+ "SHAK" - SHAKUHACHI
+ "PANP" - PANPIPE
+ "WHIS" - WHISTLE
+ "ORGA" - ORGAN3
+ "BONG" - BONGO
+ "BASS" - BASS
+
+ ---
+
+ Note values <= 1 are silent.
+*/
+
+#include "common/macresman.h"
+#include "common/translation.h"
+#include "engines/engine.h"
+#include "gui/message.h"
+#include "scumm/players/player_v5m.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer)
+ : Player_Mac(scumm, mixer, 3, 0x07, false) {
+ assert(_vm->_game.id == GID_MONKEY);
+}
+
+// Try both with and without underscore in the filename, because hfsutils may
+// turn the space into an underscore. At least, it did for me.
+
+static const char *monkeyIslandFileNames[] = {
+ "Monkey Island",
+ "Monkey_Island"
+};
+
+bool Player_V5M::checkMusicAvailable() {
+ Common::MacResManager resource;
+
+ for (int i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) {
+ if (resource.exists(monkeyIslandFileNames[i])) {
+ return true;
+ }
+ }
+
+ GUI::MessageDialog dialog(_(
+ "Could not find the 'Monkey Island' Macintosh executable to read the\n"
+ "instruments from. Music will be disabled."), _("OK"));
+ dialog.runModal();
+ return false;
+}
+
+bool Player_V5M::loadMusic(const byte *ptr) {
+ Common::MacResManager resource;
+ bool found = false;
+ uint i;
+
+ for (i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) {
+ if (resource.open(monkeyIslandFileNames[i])) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return false;
+ }
+
+ ptr += 8;
+ // TODO: Decipher the unknown bytes in the header. For now, skip 'em
+ ptr += 28;
+
+ Common::MacResIDArray idArray = resource.getResIDArray(RES_SND);
+
+ // Load the three channels and their instruments
+ for (i = 0; i < 3; i++) {
+ assert(READ_BE_UINT32(ptr) == MKTAG('C', 'h', 'a', 'n'));
+ uint32 len = READ_BE_UINT32(ptr + 4);
+ uint32 instrument = READ_BE_UINT32(ptr + 8);
+
+ _channel[i]._length = len - 20;
+ _channel[i]._data = ptr + 12;
+ _channel[i]._looped = (READ_BE_UINT32(ptr + len - 8) == MKTAG('L', 'o', 'o', 'p'));
+ _channel[i]._pos = 0;
+ _channel[i]._pitchModifier = 0;
+ _channel[i]._velocity = 0;
+ _channel[i]._remaining = 0;
+ _channel[i]._notesLeft = true;
+
+ for (uint j = 0; j < idArray.size(); j++) {
+ Common::String name = resource.getResName(RES_SND, idArray[j]);
+ if (instrument == READ_BE_UINT32(name.c_str())) {
+ debug(6, "Player_V5M::loadMusic: Channel %d: Loading instrument '%s'", i, name.c_str());
+ Common::SeekableReadStream *stream = resource.getResource(RES_SND, idArray[j]);
+
+ if (!_channel[i].loadInstrument(stream)) {
+ resource.close();
+ return false;
+ }
+
+ break;
+ }
+ }
+
+ ptr += len;
+ }
+
+ resource.close();
+
+ // The last note of each channel is just zeroes. We will adjust this
+ // note so that all the channels end at the same time.
+
+ uint32 samples[3];
+ uint32 maxSamples = 0;
+ for (i = 0; i < 3; i++) {
+ samples[i] = 0;
+ for (uint j = 0; j < _channel[i]._length; j += 4) {
+ samples[i] += durationToSamples(READ_BE_UINT16(&_channel[i]._data[j]));
+ }
+ if (samples[i] > maxSamples) {
+ maxSamples = samples[i];
+ }
+ }
+
+ for (i = 0; i < 3; i++) {
+ _lastNoteSamples[i] = maxSamples - samples[i];
+ }
+
+ return true;
+}
+
+bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) {
+ if (_channel[ch]._pos >= _channel[ch]._length) {
+ if (!_channel[ch]._looped) {
+ _channel[ch]._notesLeft = false;
+ return false;
+ }
+ // FIXME: Jamieson630: The jump seems to be happening
+ // too quickly! There should maybe be a pause after
+ // the last Note Off? But I couldn't find one in the
+ // MI1 Lookout music, where I was hearing problems.
+ _channel[ch]._pos = 0;
+ }
+ uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
+ byte note = _channel[ch]._data[_channel[ch]._pos + 2];
+ samples = durationToSamples(duration);
+
+ if (note != 1) {
+ _channel[ch]._instrument.newNote();
+ }
+
+ if (note > 1) {
+ pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument);
+ velocity = _channel[ch]._data[_channel[ch]._pos + 3];
+ } else if (note == 1) {
+ // This is guesswork, but Monkey Island uses two different
+ // "special" note values: 0, which is clearly a rest, and 1
+ // which is... I thought at first it was a "soft" key off, to
+ // fade out the note, but listening to the music in a Mac
+ // emulator (which unfortunately doesn't work all that well),
+ // I hear no trace of fading out.
+ //
+ // It could mean "change the volume on the current note", but
+ // I can't hear that either, and it always seems to use the
+ // exact same velocity on this note.
+ //
+ // So it appears it really just is a "hold the current note",
+ // but why? Couldn't they just have made the original note
+ // longer?
+
+ pitchModifier = _channel[ch]._pitchModifier;
+ velocity = _channel[ch]._velocity;
+ } else {
+ pitchModifier = 0;
+ velocity = 0;
+ }
+
+ _channel[ch]._pos += 4;
+
+ if (_channel[ch]._pos >= _channel[ch]._length) {
+ samples = _lastNoteSamples[ch];
+ }
+ return true;
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_v5m.h b/engines/scumm/players/player_v5m.h
new file mode 100644
index 0000000000..e3a457f8af
--- /dev/null
+++ b/engines/scumm/players/player_v5m.h
@@ -0,0 +1,57 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_V5M_H
+#define SCUMM_PLAYERS_PLAYER_V5M_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+#include "common/mutex.h"
+#include "scumm/music.h"
+#include "scumm/players/player_mac.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+class Mixer;
+
+namespace Scumm {
+
+class ScummEngine;
+
+/**
+ * Scumm V5 Macintosh music driver.
+ */
+class Player_V5M : public Player_Mac {
+public:
+ Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer);
+
+ virtual bool checkMusicAvailable();
+ virtual bool loadMusic(const byte *ptr);
+ virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity);
+
+private:
+ uint32 _lastNoteSamples[3];
+};
+
+} // End of namespace Scumm
+
+#endif