aboutsummaryrefslogtreecommitdiff
path: root/engines/scumm/player_towns.cpp
diff options
context:
space:
mode:
authorFlorian Kagerer2010-08-18 21:38:43 +0000
committerFlorian Kagerer2010-08-18 21:38:43 +0000
commit29a5c6a45b90e64cf5d618da4cdcd6e6da3425fb (patch)
tree16a964ac2f849c71399028e3debaf0faef45b447 /engines/scumm/player_towns.cpp
parentc04d3e46899194af3558f611af634dc7124ab7c2 (diff)
downloadscummvm-rg350-29a5c6a45b90e64cf5d618da4cdcd6e6da3425fb.tar.gz
scummvm-rg350-29a5c6a45b90e64cf5d618da4cdcd6e6da3425fb.tar.bz2
scummvm-rg350-29a5c6a45b90e64cf5d618da4cdcd6e6da3425fb.zip
SCUMM/FM-TOWNS: start rewriting audio code
- Start rewriting audio code for FM-TOWNS versions of Loom, Indy3 and Monkey Island 1 using the recently added code in towns_audio.cpp (Zak should work the same way, but I can't test, since I don't own that one). - All sound types (pcm, euphony and cd audio) now support volume and balance control (e.g. try walking into/out of the kitchen and opening/closing the door in the Scumm Bar in Monkey Island 1 or walking into/out of the circus tent). - Pcm sounds now support proper loop start/end and note offsets (e.g. try out the hammer sound in the forge in LOOM for example). - some other minor improvements - The FM-Towns versions of Indy 4 and Monkey Island 2 are not affected. I don't have Monkey Island 2, but I presume that it will work like Indy 4. Adding support for these will be a separate task, since they work quite differently. svn-id: r52198
Diffstat (limited to 'engines/scumm/player_towns.cpp')
-rw-r--r--engines/scumm/player_towns.cpp557
1 files changed, 557 insertions, 0 deletions
diff --git a/engines/scumm/player_towns.cpp b/engines/scumm/player_towns.cpp
new file mode 100644
index 0000000000..ab1b1ed585
--- /dev/null
+++ b/engines/scumm/player_towns.cpp
@@ -0,0 +1,557 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+
+#include "scumm/sound.h"
+#include "scumm/player_towns.h"
+
+namespace Scumm {
+
+Player_Towns::Player_Towns(ScummEngine *vm, Audio::Mixer *mixer) : _vm(vm) {
+ _cdaCurrentSound = _eupCurrentSound = _cdaNumLoops = 0;
+ _cdaForceRestart = 0;
+ memset(_pcmCurrentSound, 0, sizeof(_pcmCurrentSound));
+ _cdaVolLeft = _cdaVolRight = 0;
+
+ _eupVolLeft = _eupVolRight = 0;
+ memset(&_ovrCur, 0, sizeof(SoundOvrParameters));
+ _soundOverride = 0;
+
+ if (_vm->_game.version == 3) {
+ _soundOverride = new SoundOvrParameters[200];
+ memset(_soundOverride, 0, 200 * sizeof(SoundOvrParameters));
+ }
+
+ _eupLooping = false;
+ _unkFlags = 0x33;
+
+ _driver = new TownsEuphonyDriver(mixer);
+}
+
+Player_Towns::~Player_Towns() {
+ delete[] _soundOverride;
+ delete _driver;
+}
+
+bool Player_Towns::init() {
+ if (!_driver)
+ return false;
+
+ if (!_driver->init())
+ return false;
+
+ _driver->reserveSoundEffectChannels(8);
+
+ // 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.
+ _driver->intf()->setSoundEffectChanMask(-1);
+
+ setVolumeCD(255, 255);
+
+ return true;
+}
+
+void Player_Towns::setMusicVolume(int vol) {
+ _driver->setMusicVolume(vol);
+}
+
+void Player_Towns::setSfxVolume(int vol) {
+ _driver->setSoundEffectVolume(vol);
+}
+
+void Player_Towns::startSound(int sound) {
+ uint8 *ptr = _vm->getResourceAddress(rtSound, sound);
+ if (_vm->_game.version != 3) {
+ ptr += 2;
+ } else if (_soundOverride && sound > 0 && sound < 200) {
+ memcpy(&_ovrCur, &_soundOverride[sound], sizeof(SoundOvrParameters));
+ memset(&_soundOverride[sound], 0, sizeof(SoundOvrParameters));
+ }
+
+ int type = ptr[13];
+
+ if (type == 0) {
+ playPcmTrack(sound, ptr + 6);
+ } else if (type == 1) {
+ playEuphonyTrack(sound, ptr + 6);
+ } else if (type == 2) {
+ playCdaTrack(sound, ptr + 6);
+ }
+ memset(&_ovrCur, 0, sizeof(SoundOvrParameters));
+}
+
+void Player_Towns::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::stopAllSounds() {
+ _cdaCurrentSound = 0;
+ _vm->_sound->stopCD();
+ _vm->_sound->stopCDTimer();
+
+ // Loom disasm seems to stop only CD audio and PCM sounds here
+ /*_eupCurrentSound = 0;
+ _eupLooping = false;
+ _driver->stopParser();*/
+
+ stopPcmTrack(0);
+}
+
+int Player_Towns::getSoundStatus(int sound) const {
+ if (sound == _cdaCurrentSound)
+ return _vm->_sound->pollCD();
+ if (sound == _eupCurrentSound)
+ return _driver->parserIsPlaying() ? 1 : 0;
+ for (int i = 1; i < 9; i++) {
+ if (_pcmCurrentSound[i].index == sound)
+ return _driver->soundEffectIsPlaying(i + 0x3f) ? 1 : 0;
+ }
+ return 0;
+}
+
+int32 Player_Towns::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::doCommand: Unknown command %d", args[0]);
+ break;
+ }
+
+ return res;
+}
+
+void Player_Towns::setVolumeCD(int left, int right) {
+ _cdaVolLeft = left & 0xff;
+ _cdaVolRight = right & 0xff;
+ _driver->setOutputVolume(1, left >> 1, right >> 1);
+}
+
+void Player_Towns::setSoundVolume(int sound, int left, int right) {
+ if (_soundOverride && sound > 0 && sound < 200) {
+ _soundOverride[sound].vLeft = left;
+ _soundOverride[sound].vRight = right;
+ }
+}
+
+void Player_Towns::setSoundNote(int sound, int note) {
+ if (_soundOverride && sound > 0 && sound < 200)
+ _soundOverride[sound].note = note;
+}
+
+void Player_Towns::saveLoadWithSerializer(Serializer *ser) {
+ _cdaCurrentSoundTemp = (_vm->_sound->pollCD() && _cdaNumLoops > 1) ? _cdaCurrentSound & 0xff : 0;
+ _cdaNumLoopsTemp = _cdaNumLoops & 0xff;
+
+ static const SaveLoadEntry cdEntries[] = {
+ MKLINE(Player_Towns, _cdaCurrentSoundTemp, sleUint8, VER(81)),
+ MKLINE(Player_Towns, _cdaNumLoopsTemp, sleUint8, VER(81)),
+ MKLINE(Player_Towns, _cdaVolLeft, sleUint8, VER(81)),
+ MKLINE(Player_Towns, _cdaVolRight, sleUint8, VER(81)),
+ MKEND()
+ };
+
+ ser->saveLoadEntries(this, cdEntries);
+
+ if (!_eupLooping && !_driver->parserIsPlaying())
+ _eupCurrentSound = 0;
+
+ static const SaveLoadEntry eupEntries[] = {
+ MKLINE(Player_Towns, _eupCurrentSound, sleUint8, VER(81)),
+ MKLINE(Player_Towns, _eupLooping, sleUint8, VER(81)),
+ MKLINE(Player_Towns, _eupVolLeft, sleUint8, VER(81)),
+ MKLINE(Player_Towns, _eupVolRight, sleUint8, VER(81)),
+ MKEND()
+ };
+
+ ser->saveLoadEntries(this, eupEntries);
+
+ 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 (_driver->soundEffectIsPlaying(i + 0x3f))
+ continue;
+
+ _driver->stopSoundEffect(i + 0x3f);
+
+ _pcmCurrentSound[i].index = 0;
+ }
+
+ ser->saveLoadArrayOf(_pcmCurrentSound, 9, sizeof(PcmCurrentSound), pcmEntries);
+}
+
+void Player_Towns::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);
+ }
+ }
+
+ for (int i = 1; i < 9; i++) {
+ if (!_pcmCurrentSound[i].index)
+ continue;
+
+ 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);
+ }
+}
+
+int Player_Towns::getNextFreePcmChannel(int sound, int sfxChanRelIndex) {
+ int chan = 0;
+ for (int i = 8; i; i--) {
+ if (!_pcmCurrentSound[i].index) {
+ chan = i;
+ continue;
+ }
+
+ if (_driver->soundEffectIsPlaying(i + 0x3f))
+ continue;
+
+ chan = i;
+ _vm->_sound->stopSound(_pcmCurrentSound[chan].index);
+ }
+
+ if (!chan) {
+ uint16 l = 0xffff;
+ uint8 *ptr = 0;
+ for (int i = 8; i; i--) {
+ ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index) + 6;
+ uint16 a = READ_LE_UINT16(ptr + 10);
+ if (a <= l) {
+ chan = i;
+ l = a;
+ }
+ }
+
+ ptr = _vm->getResourceAddress(rtSound, sound) + 6;
+ if (l <= READ_LE_UINT16(ptr + 10))
+ _vm->_sound->stopSound(_pcmCurrentSound[chan].index);
+ else
+ chan = 0;
+ }
+
+ if (chan) {
+ _pcmCurrentSound[chan].index = sound;
+ _pcmCurrentSound[chan].chan = sfxChanRelIndex;
+ }
+
+ return chan;
+}
+
+void Player_Towns::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::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);
+
+ if (ptr[13] == 0) {
+ velo >>= 1;
+
+ if (!velo)
+ velo = 1;
+
+ pan = pan ? (((pan << 7) - pan) + 50) / 100 : 64;
+
+ playPcmTrack(sound, ptr + 6, velo, pan, note);
+
+ } 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::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::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->chanEnable(i, *src++);
+ for (int i = 0; i < 32; i++)
+ _driver->chanMode(i, 0xff);
+ for (int i = 0; i < 32; i++)
+ _driver->chanOrdr(i, *src++);
+ for (int i = 0; i < 32; i++)
+ _driver->chanVolumeShift(i, *src++);
+ for (int i = 0; i < 32; i++)
+ _driver->chanNoteShift(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 = _ovrCur.vLeft;
+ _eupVolRight = _ovrCur.vRight;
+ int lvl = _ovrCur.vLeft + _ovrCur.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::playPcmTrack(int sound, const uint8 *data, int velo, int pan, int note) {
+ const uint8 *ptr = data;
+ const uint8 *sfxData = ptr + 16;
+
+ int note2, velocity;
+
+ if (velo)
+ velocity = velo;
+ else if (_ovrCur.vLeft + _ovrCur.vRight)
+ velocity = (_ovrCur.vLeft + _ovrCur.vRight) >> 2;
+ else
+ velocity = ptr[8] >> 1;
+
+ int numChan = ptr[14];
+ for (int i = 0; i < numChan; i++) {
+ int chan = getNextFreePcmChannel(sound, i);
+ if (!chan)
+ return;
+
+ _driver->intf()->callback(70, _unkFlags);
+ _driver->chanPanPos(chan + 0x3f, pan);
+
+ if (note)
+ note2 = note;
+ else if (_ovrCur.note)
+ note2 = _ovrCur.note;
+ else
+ note2 = sfxData[28];
+
+ _driver->playSoundEffect(chan + 0x3f, note2, velocity, sfxData);
+
+ _pcmCurrentSound[chan].note = note2;
+ _pcmCurrentSound[chan].velo = velocity;
+ _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::playCdaTrack(int sound, const uint8 *data, bool skipTrackVelo) {
+ const uint8 *ptr = data;
+
+ if (!sound)
+ return;
+
+ if (!skipTrackVelo) {
+ if (_ovrCur.vLeft + _ovrCur.vRight)
+ setVolumeCD(_ovrCur.vLeft, _ovrCur.vRight);
+ 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;
+}
+
+void Player_Towns::stopPcmTrack(int sound) {
+ for (int i = 1; i < 9; i++) {
+ if (sound == _pcmCurrentSound[i].index || !sound) {
+ _driver->stopSoundEffect(i + 0x3f);
+ _pcmCurrentSound[i].index = 0;
+ }
+ }
+}
+
+} // End of namespace Scumm
+