aboutsummaryrefslogtreecommitdiff
path: root/engines/scumm/sound_he.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/scumm/sound_he.cpp')
-rw-r--r--engines/scumm/sound_he.cpp516
1 files changed, 516 insertions, 0 deletions
diff --git a/engines/scumm/sound_he.cpp b/engines/scumm/sound_he.cpp
new file mode 100644
index 0000000000..879e34a99e
--- /dev/null
+++ b/engines/scumm/sound_he.cpp
@@ -0,0 +1,516 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2001 Ludvig Strigeus
+ * Copyright (C) 2001-2006 The ScummVM project
+ *
+ * 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 "common/stdafx.h"
+#include "scumm/actor.h"
+#include "scumm/imuse.h"
+#include "scumm/scumm.h"
+#include "scumm/sound.h"
+#include "scumm/util.h"
+
+#include "common/config-manager.h"
+#include "common/timer.h"
+#include "common/util.h"
+
+#include "sound/adpcm.h"
+#include "sound/audiocd.h"
+#include "sound/flac.h"
+#include "sound/mididrv.h"
+#include "sound/mixer.h"
+#include "sound/mp3.h"
+#include "sound/voc.h"
+#include "sound/vorbis.h"
+#include "sound/wave.h"
+
+namespace Scumm {
+
+int Sound::findFreeSoundChannel() {
+ int chan, min;
+
+ min = _vm->VAR(_vm->VAR_RESERVED_SOUND_CHANNELS);
+ if (min == 0) {
+ _vm->VAR(_vm->VAR_RESERVED_SOUND_CHANNELS) = 8;
+ return 1;
+ }
+
+ if (min < 8) {
+ for (chan = min; chan < ARRAYSIZE(_heChannel); chan++) {
+ if (_vm->_mixer->isSoundHandleActive(_heSoundChannels[chan]) == 0)
+ return chan;
+ }
+ } else {
+ return 1;
+ }
+
+ return min;
+}
+
+int Sound::isSoundCodeUsed(int sound) {
+ int chan = -1;
+ for (int i = 0; i < ARRAYSIZE(_heChannel); i ++) {
+ if (_heChannel[i].sound == sound)
+ chan = i;
+ }
+
+ if (chan != -1) {
+ return _heChannel[chan].sbngBlock;
+ } else {
+ return 0;
+ }
+}
+
+int Sound::getSoundPos(int sound) {
+ int chan = -1;
+ for (int i = 0; i < ARRAYSIZE(_heChannel); i ++) {
+ if (_heChannel[i].sound == sound)
+ chan = i;
+ }
+
+ if (chan != -1) {
+ int time = _vm->getHETimer(chan + 4) * 11025 / 1000;
+ return time;
+ } else {
+ return 0;
+ }
+}
+
+int Sound::getSoundVar(int sound, int var) {
+ if (_vm->_heversion >= 90 && var == 26) {
+ return isSoundCodeUsed(sound);
+ }
+
+ checkRange(25, 0, var, "Illegal sound variable %d");
+
+ int chan = -1;
+ for (int i = 0; i < ARRAYSIZE(_heChannel); i ++) {
+ if (_heChannel[i].sound == sound)
+ chan = i;
+ }
+
+ if (chan != -1) {
+ debug(5, "getSoundVar: sound %d var %d result %d", sound, var, _heChannel[chan].soundVars[var]);
+ return _heChannel[chan].soundVars[var];
+ } else {
+ return 0;
+ }
+}
+
+void Sound::setSoundVar(int sound, int var, int val) {
+ checkRange(25, 0, var, "Illegal sound variable %d");
+
+ int chan = -1;
+ for (int i = 0; i < ARRAYSIZE(_heChannel); i ++) {
+ if (_heChannel[i].sound == sound)
+ chan = i;
+ }
+
+ if (chan != -1) {
+ debug(5, "setSoundVar: sound %d var %d val %d", sound, var, val);
+ _heChannel[chan].soundVars[var] = val;
+ }
+}
+
+void Sound::setOverrideFreq(int freq) {
+ _overrideFreq = freq;
+}
+
+void Sound::setupHEMusicFile() {
+ int i, total_size;
+ char buf[32], buf1[128];
+ Common::File musicFile;
+
+ sprintf(buf, "%s.he4", _vm->getBaseName());
+
+ if (_vm->_substResFileNameIndex > 0) {
+ _vm->generateSubstResFileName(buf, buf1, sizeof(buf1));
+ strcpy(buf, buf1);
+ }
+ if (musicFile.open(buf) == true) {
+ musicFile.seek(4, SEEK_SET);
+ total_size = musicFile.readUint32BE();
+ musicFile.seek(16, SEEK_SET);
+ _heMusicTracks = musicFile.readUint32LE();
+ debug(5, "Total music tracks %d", _heMusicTracks);
+
+ int musicStart = (_vm->_heversion >= 80) ? 56 : 20;
+ musicFile.seek(musicStart, SEEK_SET);
+
+ _heMusic = (HEMusic *)malloc((_heMusicTracks + 1) * sizeof(HEMusic));
+ for (i = 0; i < _heMusicTracks; i++) {
+ _heMusic[i].id = musicFile.readUint32LE();
+ _heMusic[i].offset = musicFile.readUint32LE();
+ _heMusic[i].size = musicFile.readUint32LE();
+
+ if (_vm->_heversion >= 80) {
+ musicFile.seek(+9, SEEK_CUR);
+ } else {
+ musicFile.seek(+13, SEEK_CUR);
+ }
+ }
+
+ musicFile.close();
+ }
+}
+
+bool Sound::getHEMusicDetails(int id, int &musicOffs, int &musicSize) {
+ int i;
+
+ for (i = 0; i < _heMusicTracks; i++) {
+ if (_heMusic[i].id == id) {
+ musicOffs = _heMusic[i].offset;
+ musicSize = _heMusic[i].size;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void Sound::processSoundCode() {
+ byte *codePtr;
+ int chan, tmr, size, time;
+
+ for (chan = 0; chan < ARRAYSIZE(_heChannel); chan++) {
+ if (_heChannel[chan].sound == 0) {
+ continue;
+ }
+
+ if (_heChannel[chan].codeOffs == -1) {
+ continue;
+ }
+
+ tmr = _vm->getHETimer(chan + 4) * 11025 / 1000;
+ tmr += _vm->VAR(_vm->VAR_SOUNDCODE_TMR);
+ if (tmr < 0)
+ tmr = 0;
+
+ if (_heChannel[chan].sound > _vm->_numSounds) {
+ codePtr = _vm->getResourceAddress(rtSpoolBuffer, chan);
+ } else {
+ codePtr = _vm->getResourceAddress(rtSound, _heChannel[chan].sound);
+ }
+ assert(codePtr);
+ codePtr += _heChannel[chan].codeOffs;
+
+ while(1) {
+ size = READ_LE_UINT16(codePtr);
+ time = READ_LE_UINT32(codePtr + 2);
+
+ if (size == 0) {
+ _heChannel[chan].codeOffs = -1;
+ break;
+ }
+
+ debug(5, "Channel %d Timer %d Time %d", chan, tmr, time);
+ if (time >= tmr)
+ break;
+
+ processSoundOpcodes(_heChannel[chan].sound, codePtr + 6, _heChannel[chan].soundVars);
+
+ codePtr += size;
+ _heChannel[chan].codeOffs += size;
+ }
+ }
+}
+
+void Sound::processSoundOpcodes(int sound, byte *codePtr, int *soundVars) {
+ int arg, opcode, var, val;
+
+ while(READ_LE_UINT16(codePtr) != 0) {
+ codePtr += 2;
+ opcode = READ_LE_UINT16(codePtr); codePtr += 2;
+ opcode = (opcode & 0xFFF) >> 4;
+ arg = opcode & 3;
+ opcode &= ~3;
+ debug(5, "processSoundOpcodes: sound %d opcode %d", sound, opcode);
+ switch (opcode) {
+ case 0: // Continue
+ break;
+ case 16: // Set talk state
+ val = READ_LE_UINT16(codePtr); codePtr += 2;
+ setSoundVar(sound, 19, val);
+ break;
+ case 32: // Set var
+ var = READ_LE_UINT16(codePtr); codePtr += 2;
+ val = READ_LE_UINT16(codePtr); codePtr += 2;
+ if (arg == 2) {
+ val = getSoundVar(sound, val);
+ }
+ setSoundVar(sound, var, val);
+ break;
+ case 48: // Add
+ var = READ_LE_UINT16(codePtr); codePtr += 2;
+ val = READ_LE_UINT16(codePtr); codePtr += 2;
+ if (arg == 2) {
+ val = getSoundVar(sound, val);
+ }
+ val = getSoundVar(sound, var) + val;
+ setSoundVar(sound, var, val);
+ break;
+ case 56: // Subtract
+ var = READ_LE_UINT16(codePtr); codePtr += 2;
+ val = READ_LE_UINT16(codePtr); codePtr += 2;
+ if (arg == 2) {
+ val = getSoundVar(sound, val);
+ }
+ val = getSoundVar(sound, var) - val;
+ setSoundVar(sound, var, val);
+ break;
+ case 64: // Multiple
+ var = READ_LE_UINT16(codePtr); codePtr += 2;
+ val = READ_LE_UINT16(codePtr); codePtr += 2;
+ if (arg == 2) {
+ val = getSoundVar(sound, val);
+ }
+ val = getSoundVar(sound, var) * val;
+ setSoundVar(sound, var, val);
+ break;
+ case 80: // Divide
+ var = READ_LE_UINT16(codePtr); codePtr += 2;
+ val = READ_LE_UINT16(codePtr); codePtr += 2;
+ if (arg == 2) {
+ val = getSoundVar(sound, val);
+ }
+ val = getSoundVar(sound, var) / val;
+ setSoundVar(sound, var, val);
+ break;
+ case 96: // Increment
+ var = READ_LE_UINT16(codePtr); codePtr += 2;
+ val = getSoundVar(sound, var) + 1;
+ setSoundVar(sound, var, val);
+ break;
+ case 104: // Decrement
+ var = READ_LE_UINT16(codePtr); codePtr += 2;
+ val = getSoundVar(sound, var) - 1;
+ setSoundVar(sound, var, val);
+ break;
+ default:
+ error("Illegal sound %d opcode %d", sound, opcode);
+ }
+ }
+}
+
+void Sound::playHESound(int soundID, int heOffset, int heChannel, int heFlags) {
+ byte *ptr, *spoolPtr;
+ char *sound;
+ int size = -1;
+ int priority, rate;
+ byte flags = Audio::Mixer::FLAG_UNSIGNED | Audio::Mixer::FLAG_AUTOFREE;
+
+ if (heChannel == -1)
+ heChannel = (_vm->VAR_RESERVED_SOUND_CHANNELS != 0xFF) ? findFreeSoundChannel() : 1;
+
+ debug(5,"playHESound: soundID %d heOffset %d heChannel %d heFlags %d", soundID, heOffset, heChannel, heFlags);
+
+ if (soundID >= 10000) {
+ // Special codes, used in pjgames
+ return;
+ }
+
+ if (soundID > _vm->_numSounds) {
+ int music_offs;
+ char buf[32], buf1[128];
+ Common::File musicFile;
+
+ sprintf(buf, "%s.he4", _vm->getBaseName());
+
+ if (_vm->_substResFileNameIndex > 0) {
+ _vm->generateSubstResFileName(buf, buf1, sizeof(buf1));
+ strcpy(buf, buf1);
+ }
+ if (musicFile.open(buf) == false) {
+ warning("playSound: Can't open music file %s", buf);
+ return;
+ }
+ if (!getHEMusicDetails(soundID, music_offs, size)) {
+ debug(0, "playSound: musicID %d not found", soundID);
+ return;
+ }
+
+ musicFile.seek(music_offs, SEEK_SET);
+
+ if (_vm->_heversion == 70) {
+ spoolPtr = (byte *)malloc(size);
+ musicFile.read(spoolPtr, size);
+ } else {
+ spoolPtr = _vm->res.createResource(rtSpoolBuffer, heChannel, size);
+ assert(spoolPtr);
+ musicFile.read(spoolPtr, size);
+ }
+ musicFile.close();
+
+ if (_vm->_heversion == 70) {
+ _vm->_mixer->stopHandle(_heSoundChannels[heChannel]);
+ _vm->_mixer->playRaw(&_heSoundChannels[heChannel], spoolPtr, size, 11025, flags, soundID);
+ return;
+ }
+ }
+
+ if (soundID > _vm->_numSounds) {
+ ptr = _vm->getResourceAddress(rtSpoolBuffer, heChannel);
+ } else {
+ ptr = _vm->getResourceAddress(rtSound, soundID);
+ }
+
+ if (!ptr) {
+ return;
+ }
+
+ // TODO: Extra sound flags
+ if (heFlags & 1) {
+ flags |= Audio::Mixer::FLAG_LOOP;
+ }
+
+ // Support for sound in later Backyard sports games
+ if (READ_UINT32(ptr) == MKID('RIFF') || READ_UINT32(ptr) == MKID('WSOU')) {
+ uint16 type;
+ int blockAlign;
+
+ if (READ_UINT32(ptr) == MKID('WSOU'))
+ ptr += 8;
+
+ size = READ_LE_UINT32(ptr + 4);
+ Common::MemoryReadStream stream(ptr, size);
+
+ if (!loadWAVFromStream(stream, size, rate, flags, &type, &blockAlign)) {
+ error("playSound: Not a valid WAV file");
+ }
+
+ if (type == 17) {
+ AudioStream *voxStream = new ADPCMInputStream(&stream, size, kADPCMIma, (flags & Audio::Mixer::FLAG_STEREO) ? 2 : 1, blockAlign);
+
+ sound = (char *)malloc(size * 4);
+ size = voxStream->readBuffer((int16*)sound, size * 2);
+ size *= 2; // 16bits.
+ delete voxStream;
+ } else {
+ // Allocate a sound buffer, copy the data into it, and play
+ sound = (char *)malloc(size);
+ memcpy(sound, ptr + stream.pos(), size);
+ }
+ _vm->_mixer->stopHandle(_heSoundChannels[heChannel]);
+ _vm->_mixer->playRaw(&_heSoundChannels[heChannel], sound, size, rate, flags, soundID);
+ }
+ // Support for sound in Humongous Entertainment games
+ else if (READ_UINT32(ptr) == MKID('DIGI') || READ_UINT32(ptr) == MKID('TALK')) {
+ byte *sndPtr = ptr;
+
+ priority = (soundID > _vm->_numSounds) ? 255 : *(ptr + 18);
+ rate = READ_LE_UINT16(ptr + 22);
+ ptr += 8 + READ_BE_UINT32(ptr + 12);
+
+ if (_vm->_mixer->isSoundHandleActive(_heSoundChannels[heChannel])) {
+ int curSnd = _heChannel[heChannel].sound;
+ if (curSnd == 1 && soundID != 1)
+ return;
+ if (curSnd != 0 && curSnd != 1 && soundID != 1 && _heChannel[heChannel].priority > priority)
+ return;
+ }
+
+ int codeOffs = -1;
+ if (READ_UINT32(ptr) == MKID('SBNG')) {
+ codeOffs = ptr - sndPtr + 8;
+ ptr += READ_BE_UINT32(ptr + 4);
+ }
+
+ assert(READ_UINT32(ptr) == MKID('SDAT'));
+ size = READ_BE_UINT32(ptr+4) - 8;
+ if (heOffset < 0 || heOffset > size) {
+ // Occurs when making fireworks in puttmoon
+ debug(0, "playSound: Invalid sound offset (offset %d, size %d) in sound %d", heOffset, size, soundID);
+ heOffset = 0;
+ }
+ size -= heOffset;
+
+ if (_overrideFreq) {
+ // Used by the piano in Fatty Bear's Birthday Surprise
+ rate = _overrideFreq;
+ _overrideFreq = 0;
+ }
+
+ // Allocate a sound buffer, copy the data into it, and play
+ sound = (char *)malloc(size);
+ memcpy(sound, ptr + heOffset + 8, size);
+ _vm->_mixer->stopHandle(_heSoundChannels[heChannel]);
+ _vm->_mixer->playRaw(&_heSoundChannels[heChannel], sound, size, rate, flags, soundID);
+
+ _vm->setHETimer(heChannel + 4);
+ _heChannel[heChannel].sound = soundID;
+ _heChannel[heChannel].priority = priority;
+ _heChannel[heChannel].sbngBlock = (codeOffs != -1) ? 1 : 0;
+ _heChannel[heChannel].codeOffs = codeOffs;
+ memset(_heChannel[heChannel].soundVars, 0, sizeof(_heChannel[heChannel].soundVars));
+ }
+ // Support for PCM music in 3DO versions of Humongous Entertainment games
+ else if (READ_UINT32(ptr) == MKID('MRAW')) {
+ priority = *(ptr + 18);
+ rate = READ_LE_UINT16(ptr + 22);
+ ptr += 8 + READ_BE_UINT32(ptr+12);
+
+ assert(READ_UINT32(ptr) == MKID('SDAT'));
+ size = READ_BE_UINT32(ptr+4) - 8;
+
+ flags = Audio::Mixer::FLAG_AUTOFREE;
+
+ // Allocate a sound buffer, copy the data into it, and play
+ sound = (char *)malloc(size);
+ memcpy(sound, ptr + 8, size);
+ _vm->_mixer->stopID(_currentMusic);
+ _currentMusic = soundID;
+ _vm->_mixer->playRaw(NULL, sound, size, rate, flags, soundID);
+ }
+ else if (READ_UINT32(ptr) == MKID('MIDI')) {
+ if (_vm->_musicEngine) {
+ _vm->_musicEngine->startSound(soundID);
+ }
+ }
+}
+
+void Sound::startHETalkSound(uint32 offset) {
+ byte *ptr;
+ int32 size;
+
+ if (ConfMan.getBool("speech_mute"))
+ return;
+
+ if (!_sfxFile->isOpen()) {
+ error("startHETalkSound: Speech file is not open");
+ return;
+ }
+
+ _sfxMode |= 2;
+ _vm->res.nukeResource(rtSound, 1);
+
+ _sfxFile->seek(offset + 4, SEEK_SET);
+ size = _sfxFile->readUint32BE();
+ _sfxFile->seek(offset, SEEK_SET);
+
+ _vm->res.createResource(rtSound, 1, size);
+ ptr = _vm->getResourceAddress(rtSound, 1);
+ _sfxFile->read(ptr, size);
+
+ int channel = (_vm->VAR_TALK_CHANNEL != 0xFF) ? _vm->VAR(_vm->VAR_TALK_CHANNEL) : 0;
+ addSoundToQueue2(1, 0, channel, 0);
+}
+
+} // End of namespace Scumm