2007-05-30 21:56:52 +00:00
|
|
|
/* 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.
|
2003-09-07 09:33:00 +00:00
|
|
|
*
|
|
|
|
* 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.
|
2003-09-18 19:56:38 +00:00
|
|
|
|
2003-09-07 09:33:00 +00:00
|
|
|
* 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.
|
2003-09-18 19:56:38 +00:00
|
|
|
|
2003-09-07 09:33:00 +00:00
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
2005-10-18 01:30:26 +00:00
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
2003-09-07 09:33:00 +00:00
|
|
|
*
|
2006-02-11 09:55:41 +00:00
|
|
|
* $URL$
|
|
|
|
* $Id$
|
2003-09-18 19:56:38 +00:00
|
|
|
*
|
2003-09-07 09:33:00 +00:00
|
|
|
*/
|
|
|
|
|
2007-09-19 08:40:12 +00:00
|
|
|
|
2006-09-23 00:42:35 +00:00
|
|
|
#include "engines/engine.h"
|
2003-10-03 18:33:57 +00:00
|
|
|
#include "scumm/player_v3a.h"
|
|
|
|
#include "scumm/scumm.h"
|
|
|
|
|
|
|
|
namespace Scumm {
|
2003-09-07 09:33:00 +00:00
|
|
|
|
2003-09-13 01:42:36 +00:00
|
|
|
static const uint16 note_freqs[4][12] = {
|
2003-11-08 21:59:32 +00:00
|
|
|
{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}
|
2003-09-07 09:33:00 +00:00
|
|
|
};
|
|
|
|
|
2006-10-21 12:44:10 +00:00
|
|
|
Player_V3A::Player_V3A(ScummEngine *scumm, Audio::Mixer *mixer) {
|
2003-09-07 09:33:00 +00:00
|
|
|
int i;
|
2004-01-08 20:37:26 +00:00
|
|
|
_vm = scumm;
|
2003-09-24 06:56:30 +00:00
|
|
|
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;
|
|
|
|
}
|
2003-09-07 09:33:00 +00:00
|
|
|
|
|
|
|
_curSong = 0;
|
|
|
|
_songData = NULL;
|
|
|
|
_songPtr = 0;
|
|
|
|
_songDelay = 0;
|
|
|
|
|
|
|
|
_music_timer = 0;
|
|
|
|
|
|
|
|
_isinit = false;
|
2003-09-24 06:56:30 +00:00
|
|
|
|
2006-10-21 12:44:10 +00:00
|
|
|
_mod = new Player_MOD(mixer);
|
2003-09-24 06:56:30 +00:00
|
|
|
_mod->setUpdateProc(update_proc, this, 60);
|
2003-09-07 09:33:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Player_V3A::~Player_V3A() {
|
2003-09-24 06:56:30 +00:00
|
|
|
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]);
|
2003-09-07 09:33:00 +00:00
|
|
|
}
|
2003-09-24 06:56:30 +00:00
|
|
|
free(_wavetable);
|
2003-09-07 09:33:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-11-28 21:24:02 +00:00
|
|
|
void Player_V3A::setMusicVolume (int vol) {
|
|
|
|
_mod->setMusicVolume(vol);
|
2003-09-24 06:56:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2003-09-07 09:33:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Player_V3A::stopAllSounds() {
|
2003-09-13 01:42:36 +00:00
|
|
|
int i;
|
|
|
|
for (i = 0; i < V3A_MAXMUS; i++) {
|
|
|
|
if (_mus[i].id)
|
2003-09-24 06:56:30 +00:00
|
|
|
_mod->stopChannel(_mus[i].id);
|
2003-09-13 01:42:36 +00:00
|
|
|
_mus[i].id = 0;
|
|
|
|
_mus[i].dur = 0;
|
|
|
|
}
|
2003-09-07 09:33:00 +00:00
|
|
|
_curSong = 0;
|
|
|
|
_songPtr = 0;
|
|
|
|
_songDelay = 0;
|
2003-09-13 01:42:36 +00:00
|
|
|
_songData = NULL;
|
|
|
|
for (i = 0; i < V3A_MAXSFX; i++) {
|
|
|
|
if (_sfx[i].id)
|
2003-09-24 06:56:30 +00:00
|
|
|
_mod->stopChannel(_sfx[i].id | 0x100);
|
2003-09-13 01:42:36 +00:00
|
|
|
_sfx[i].id = 0;
|
|
|
|
_sfx[i].dur = 0;
|
2003-09-07 16:16:19 +00:00
|
|
|
}
|
2003-09-07 09:33:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Player_V3A::stopSound(int nr) {
|
|
|
|
int i;
|
2003-09-24 06:56:30 +00:00
|
|
|
if (nr == 0) { // Amiga Loom does this near the end, when Chaos casts SILENCE on Hetchel
|
|
|
|
stopAllSounds();
|
|
|
|
return;
|
|
|
|
}
|
2003-09-07 09:33:00 +00:00
|
|
|
if (nr == _curSong) {
|
2003-09-13 01:42:36 +00:00
|
|
|
for (i = 0; i < V3A_MAXMUS; i++) {
|
|
|
|
if (_mus[i].id)
|
2003-09-24 06:56:30 +00:00
|
|
|
_mod->stopChannel(_mus[i].id);
|
2003-09-13 01:42:36 +00:00
|
|
|
_mus[i].id = 0;
|
|
|
|
_mus[i].dur = 0;
|
|
|
|
}
|
2003-09-07 09:33:00 +00:00
|
|
|
_curSong = 0;
|
|
|
|
_songPtr = 0;
|
|
|
|
_songDelay = 0;
|
2003-09-13 01:42:36 +00:00
|
|
|
_songData = NULL;
|
|
|
|
} else {
|
2003-09-24 06:56:30 +00:00
|
|
|
i = getSfxChan(nr);
|
|
|
|
if (i != -1) {
|
|
|
|
_mod->stopChannel(nr | 0x100);
|
|
|
|
_sfx[i].id = 0;
|
|
|
|
_sfx[i].dur = 0;
|
2003-09-13 01:42:36 +00:00
|
|
|
}
|
2003-09-07 09:33:00 +00:00
|
|
|
}
|
2003-09-13 01:42:36 +00:00
|
|
|
}
|
|
|
|
|
2003-09-07 16:16:19 +00:00
|
|
|
void Player_V3A::startSound(int nr) {
|
2004-01-08 20:37:26 +00:00
|
|
|
assert(_vm);
|
|
|
|
byte *data = _vm->getResourceAddress(rtSound, nr);
|
2003-09-07 16:16:19 +00:00
|
|
|
assert(data);
|
|
|
|
|
2006-02-20 16:51:30 +00:00
|
|
|
if ((_vm->_game.id != GID_INDY3) && (_vm->_game.id != GID_LOOM))
|
2003-09-08 11:29:57 +00:00
|
|
|
error("player_v3a - unknown game!");
|
|
|
|
|
2003-09-07 09:33:00 +00:00
|
|
|
if (!_isinit) {
|
|
|
|
int i;
|
2003-09-08 11:29:57 +00:00
|
|
|
unsigned char *ptr;
|
|
|
|
int offset = 4;
|
|
|
|
int numInstruments;
|
|
|
|
|
2006-02-20 16:51:30 +00:00
|
|
|
if (_vm->_game.id == GID_INDY3) {
|
2004-01-08 20:37:26 +00:00
|
|
|
ptr = _vm->getResourceAddress(rtSound, 83);
|
2003-09-08 11:29:57 +00:00
|
|
|
numInstruments = 12;
|
|
|
|
} else {
|
2004-01-08 20:37:26 +00:00
|
|
|
ptr = _vm->getResourceAddress(rtSound, 79);
|
2003-09-08 11:29:57 +00:00
|
|
|
numInstruments = 9;
|
|
|
|
}
|
2003-09-13 01:42:36 +00:00
|
|
|
assert(ptr);
|
2003-09-08 11:29:57 +00:00
|
|
|
_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;
|
|
|
|
}
|
2006-02-20 16:51:30 +00:00
|
|
|
if (_vm->_game.id == GID_INDY3) {
|
2003-09-08 01:42:24 +00:00
|
|
|
_wavetable[i]->_pitadjust = 0;
|
2003-09-07 09:33:00 +00:00
|
|
|
offset += 2;
|
2003-09-08 11:29:57 +00:00
|
|
|
} else {
|
2003-09-08 01:42:24 +00:00
|
|
|
_wavetable[i]->_pitadjust = READ_BE_UINT16(ptr + offset + 2);
|
2003-09-07 09:33:00 +00:00
|
|
|
offset += 4;
|
|
|
|
}
|
|
|
|
}
|
2003-09-08 11:29:57 +00:00
|
|
|
_wavetable[i] = NULL;
|
2003-09-07 09:33:00 +00:00
|
|
|
_isinit = true;
|
|
|
|
}
|
2003-11-08 21:59:32 +00:00
|
|
|
|
2003-09-13 01:42:36 +00:00
|
|
|
if (getSoundStatus(nr))
|
|
|
|
stopSound(nr); // if a sound is playing, restart it
|
2005-07-30 21:11:48 +00:00
|
|
|
|
2003-09-07 09:33:00 +00:00
|
|
|
if (data[26]) {
|
2003-09-24 06:56:30 +00:00
|
|
|
if (_curSong)
|
|
|
|
stopSound(_curSong);
|
2003-09-07 09:33:00 +00:00
|
|
|
_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);
|
2003-09-24 06:56:30 +00:00
|
|
|
int vol = (data[24] << 1) | (data[24] >> 5); // if I boost this to 0-255, it gets too loud and starts to clip
|
2007-04-02 01:45:28 +00:00
|
|
|
memcpy(sound, data + READ_BE_UINT16(data + 8), size);
|
2003-09-24 06:56:30 +00:00
|
|
|
int loopStart = 0, loopEnd = 0;
|
2007-04-02 01:45:28 +00:00
|
|
|
int loopcount = data[27];
|
|
|
|
if (loopcount > 1) {
|
2003-09-24 06:56:30 +00:00
|
|
|
loopStart = READ_BE_UINT16(data + 10) - READ_BE_UINT16(data + 8);
|
|
|
|
loopEnd = READ_BE_UINT16(data + 14);
|
2003-09-07 09:33:00 +00:00
|
|
|
}
|
2003-09-24 06:56:30 +00:00
|
|
|
int i = getSfxChan();
|
2007-04-02 01:45:28 +00:00
|
|
|
if (i == -1) {
|
2003-12-14 08:57:53 +00:00
|
|
|
free(sound);
|
|
|
|
return;
|
|
|
|
}
|
2003-09-24 06:56:30 +00:00
|
|
|
_sfx[i].id = nr;
|
2007-04-02 01:45:28 +00:00
|
|
|
_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;
|
|
|
|
}
|
2003-09-24 06:56:30 +00:00
|
|
|
_mod->startChannel(nr | 0x100, sound, size, rate, vol, loopStart, loopEnd);
|
2003-09-07 09:33:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-09-24 06:56:30 +00:00
|
|
|
void Player_V3A::update_proc(void *param) {
|
|
|
|
((Player_V3A *)param)->playMusic();
|
2003-09-08 11:29:57 +00:00
|
|
|
}
|
|
|
|
|
2003-09-07 09:33:00 +00:00
|
|
|
void Player_V3A::playMusic() {
|
|
|
|
int i;
|
2003-09-13 01:42:36 +00:00
|
|
|
for (i = 0; i < V3A_MAXMUS; i++) {
|
2003-09-24 06:56:30 +00:00
|
|
|
if (_mus[i].id) {
|
|
|
|
_mus[i].dur--;
|
|
|
|
if (_mus[i].dur)
|
|
|
|
continue;
|
|
|
|
_mod->stopChannel(_mus[i].id);
|
2003-09-13 01:42:36 +00:00
|
|
|
_mus[i].id = 0;
|
|
|
|
}
|
|
|
|
}
|
2003-09-24 06:56:30 +00:00
|
|
|
for (i = 0; i < V3A_MAXSFX; i++) {
|
|
|
|
if (_sfx[i].id) {
|
2007-04-02 01:45:28 +00:00
|
|
|
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);
|
|
|
|
}
|
2003-09-24 06:56:30 +00:00
|
|
|
_sfx[i].dur--;
|
|
|
|
if (_sfx[i].dur)
|
|
|
|
continue;
|
|
|
|
_mod->stopChannel(_sfx[i].id | 0x100);
|
|
|
|
_sfx[i].id = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-09-07 09:33:00 +00:00
|
|
|
_music_timer++;
|
|
|
|
if (!_curSong)
|
|
|
|
return;
|
2003-09-13 01:42:36 +00:00
|
|
|
if (_songDelay && --_songDelay)
|
|
|
|
return;
|
2003-12-14 08:57:53 +00:00
|
|
|
if (_songPtr == 0) {
|
2003-09-13 01:42:36 +00:00
|
|
|
// at the end of the song, and it wasn't looped - kill it
|
2003-09-08 01:42:24 +00:00
|
|
|
_curSong = 0;
|
|
|
|
return;
|
|
|
|
}
|
2003-09-07 09:33:00 +00:00
|
|
|
while (1) {
|
2003-09-13 01:42:36 +00:00
|
|
|
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;
|
2003-12-14 08:57:53 +00:00
|
|
|
else _songPtr = 0; // otherwise, terminate it
|
2003-09-07 09:33:00 +00:00
|
|
|
break;
|
|
|
|
}
|
2003-09-13 01:42:36 +00:00
|
|
|
inst &= 0xF;
|
|
|
|
pit = _songData[_songPtr++];
|
2003-09-24 06:56:30 +00:00
|
|
|
vol = _songData[_songPtr++] & 0x7F; // if I boost this to 0-255, it gets too loud and starts to clip
|
2003-09-13 01:42:36 +00:00
|
|
|
dur = _songData[_songPtr++];
|
|
|
|
if (pit == 0) {
|
2003-09-07 09:33:00 +00:00
|
|
|
_songDelay = dur;
|
|
|
|
break;
|
|
|
|
}
|
2003-09-13 01:42:36 +00:00
|
|
|
pit += _wavetable[inst]->_pitadjust;
|
|
|
|
oct = (pit / 12) - 2;
|
|
|
|
pit = pit % 12;
|
|
|
|
if (oct < 0)
|
|
|
|
oct = 0;
|
|
|
|
if (oct > 5)
|
|
|
|
oct = 5;
|
2003-09-24 06:56:30 +00:00
|
|
|
int rate = 3579545 / note_freqs[_wavetable[inst]->_oct[oct]][pit];
|
2004-02-15 08:06:32 +00:00
|
|
|
if (!_wavetable[inst]->_llen[oct])
|
|
|
|
dur = _wavetable[inst]->_ilen[oct] * 60 / rate;
|
2003-09-07 09:33:00 +00:00
|
|
|
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]);
|
2003-09-24 06:56:30 +00:00
|
|
|
|
|
|
|
i = getMusChan();
|
2007-04-02 01:45:28 +00:00
|
|
|
if (i == -1) {
|
2003-12-14 08:57:53 +00:00
|
|
|
free(data);
|
|
|
|
return;
|
|
|
|
}
|
2003-09-24 06:56:30 +00:00
|
|
|
_mus[i].id = i + 1;
|
2003-12-14 08:57:53 +00:00
|
|
|
_mus[i].dur = dur + 1;
|
2003-09-24 06:56:30 +00:00
|
|
|
_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]);
|
2003-09-07 09:33:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int Player_V3A::getMusicTimer() const {
|
|
|
|
return _music_timer / 30;
|
|
|
|
}
|
|
|
|
|
2003-09-07 17:14:56 +00:00
|
|
|
int Player_V3A::getSoundStatus(int nr) const {
|
2003-09-07 09:33:00 +00:00
|
|
|
if (nr == _curSong)
|
2003-09-07 17:14:56 +00:00
|
|
|
return 1;
|
2003-09-24 06:56:30 +00:00
|
|
|
if (getSfxChan(nr) != -1)
|
|
|
|
return 1;
|
2003-09-07 17:14:56 +00:00
|
|
|
return 0;
|
2003-09-07 09:33:00 +00:00
|
|
|
}
|
2003-10-03 18:33:57 +00:00
|
|
|
|
|
|
|
} // End of namespace Scumm
|