aboutsummaryrefslogtreecommitdiff
path: root/scumm/sound.cpp
diff options
context:
space:
mode:
authorMax Horn2002-08-29 23:45:15 +0000
committerMax Horn2002-08-29 23:45:15 +0000
commit41c2d6fba344f8688b9462371f13727053ccd890 (patch)
tree687cdd692a2aba7ac85214a6066824fc14456d12 /scumm/sound.cpp
parent4b83c17b31eaab340c8eccee2f64cd68e6c6feb8 (diff)
downloadscummvm-rg350-41c2d6fba344f8688b9462371f13727053ccd890.tar.gz
scummvm-rg350-41c2d6fba344f8688b9462371f13727053ccd890.tar.bz2
scummvm-rg350-41c2d6fba344f8688b9462371f13727053ccd890.zip
sound system cleanup
svn-id: r4867
Diffstat (limited to 'scumm/sound.cpp')
-rw-r--r--scumm/sound.cpp1205
1 files changed, 1205 insertions, 0 deletions
diff --git a/scumm/sound.cpp b/scumm/sound.cpp
new file mode 100644
index 0000000000..9b2e49b03b
--- /dev/null
+++ b/scumm/sound.cpp
@@ -0,0 +1,1205 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2001 Ludvig Strigeus
+ * Copyright (C) 2001/2002 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header$
+ *
+ */
+
+#include "stdafx.h"
+#include "scumm/scumm.h"
+#include "sound.h"
+#include "sound/mididrv.h"
+#include "scumm/imuse.h"
+#include "scumm/actor.h"
+#include "config-file.h"
+#include "util.h"
+
+#ifdef _WIN32_WCE
+extern void *bsearch(const void *, const void *, size_t,
+ size_t, int (*x) (const void *, const void *));
+#endif
+
+Sound::Sound(Scumm *parent) {
+ _scumm = parent;
+ _numberBundleMusic = -1;
+ _musicBundleBufFinal = NULL;
+ _musicBundleBufOutput = NULL;
+}
+
+Sound::~Sound() {
+}
+
+void Sound::addSoundToQueue(int sound) {
+ if (!(_scumm->_features & GF_AFTER_V7)) {
+ _scumm->_vars[_scumm->VAR_LAST_SOUND] = sound;
+ _scumm->ensureResourceLoaded(rtSound, sound);
+ addSoundToQueue2(sound);
+ } else {
+ // WARNING ! This may break something, maybe this sould be put inside if(_gameID == GID_FT) ?
+ // But why addSoundToQueue should not queue sound ?
+ _scumm->ensureResourceLoaded(rtSound, sound);
+ addSoundToQueue2(sound);
+ }
+
+// if (_features & GF_AUDIOTRACKS)
+// warning("Requesting audio track: %d", sound);
+}
+
+void Sound::addSoundToQueue2(int sound) {
+ if (_soundQue2Pos < 10) {
+ _soundQue2[_soundQue2Pos++] = sound;
+ }
+}
+
+void Sound::processSoundQues() {
+ byte d;
+ int i, j;
+ int num;
+ int16 data[16];
+ IMuse *se;
+
+ processSfxQueues();
+
+ while (_soundQue2Pos) {
+ d = _soundQue2[--_soundQue2Pos];
+ if (d)
+ playSound(d);
+ }
+
+ for (i = 0; i < _soundQuePos;) {
+ num = _soundQue[i++];
+ if (i + num > _soundQuePos) {
+ warning("processSoundQues: invalid num value");
+ break;
+ }
+ for (j = 0; j < 16; j++)
+ data[j] = 0;
+ if (num > 0) {
+ for (j = 0; j < num; j++)
+ data[j] = _soundQue[i + j];
+ i += num;
+
+ se = _scumm->_imuse;
+#if 0
+ debug(1, "processSoundQues(%d,%d,%d,%d,%d,%d,%d,%d,%d)",
+ data[0] >> 8,
+ data[0] & 0xFF,
+ data[1], data[2], data[3], data[4], data[5], data[6], data[7]
+ );
+#endif
+
+ if ((_scumm->_gameId == GID_DIG) && (data[0] == 4096)){
+ playBundleMusic(data[1] - 1);
+ }
+
+ if (!(_scumm->_features & GF_AFTER_V7)) {
+ if (se)
+ _scumm->_vars[_scumm->VAR_SOUNDRESULT] =
+ (short)se->do_command(data[0], data[1], data[2], data[3], data[4],
+ data[5], data[6], data[7]);
+ }
+
+ }
+ }
+ _soundQuePos = 0;
+}
+
+static char * read_creative_voc_file(byte * ptr, int & size, int & rate) {
+ assert(strncmp((char*)ptr, "Creative Voice File\x1A", 20) == 0);
+ int offset = READ_LE_UINT16(ptr+20);
+ short version = READ_LE_UINT16(ptr+22);
+ short code = READ_LE_UINT16(ptr+24);
+ assert(version == 0x010A || version == 0x0114);
+ assert(code == ~version + 0x1234);
+ bool quit = 0;
+ char * ret_sound = 0; size = 0;
+ while(!quit) {
+ int len = READ_LE_UINT32(ptr + offset);
+ offset += 4;
+ int code = len & 0xFF; // FIXME not sure this is endian correct
+ len >>= 8;
+ switch(code) {
+ case 0: quit = 1; break;
+ case 1: {
+ int time_constant = ptr[offset++];
+ int packing = ptr[offset++];
+ len -= 2;
+ rate = 1000000L / (256L - time_constant);
+ debug(9, "VOC Data Bloc : %d, %d, %d", rate, packing, len);
+ if(packing == 0) {
+ if(size) {
+ ret_sound = (char*)realloc(ret_sound, size + len);
+ } else {
+ ret_sound = (char*)malloc(len);
+ }
+ memcpy(ret_sound + size, ptr + offset, len);
+ size += len;
+ } else {
+ warning("VOC file packing %d unsupported", packing);
+ }
+ } break;
+ case 6: // begin of loop
+ debug(3, "loops in Creative files not supported");
+ break;
+ case 7: // end of loop
+ break;
+ default:
+ warning("Invalid code in VOC file : %d", code);
+ //~ quit = 1;
+ break;
+ }
+ offset += len;
+ }
+ debug(9, "VOC Data Size : %d", size);
+ return ret_sound;
+}
+
+void Sound::playSound(int sound) {
+ byte *ptr;
+
+ ptr = _scumm->getResourceAddress(rtSound, sound);
+ if (ptr) {
+ if (READ_UINT32_UNALIGNED(ptr) == MKID('SOUN')) {
+ ptr += 8;
+ _scumm->_vars[_scumm->VAR_MI1_TIMER] = 0;
+ playCDTrack(ptr[16], ptr[17] == 0xff ? -1 : ptr[17],
+ (ptr[18] * 60 + ptr[19]) * 75 + ptr[20], 0);
+
+ _scumm->current_cd_sound = sound;
+ return;
+ }
+ // Support for SFX in Monkey Island 1, Mac version
+ // This is rather hackish right now, but works OK. SFX are not sounding
+ // 100% correct, though, not sure right now what is causing this.
+ else if (READ_UINT32_UNALIGNED(ptr) == MKID('Mac1')) {
+
+ // Read info from the header
+ int size = READ_UINT32_UNALIGNED(ptr+0x60);
+ int rate = READ_UINT32_UNALIGNED(ptr+0x64) >> 16;
+
+ // Skip over the header (fixed size)
+ ptr += 0x72;
+
+ // Allocate a sound buffer, copy the data into it, and play
+ char *sound = (char*)malloc(size);
+ memcpy(sound, ptr, size);
+ _scumm->_mixer->playRaw(NULL, sound, size, rate, SoundMixer::FLAG_UNSIGNED | SoundMixer::FLAG_AUTOFREE);
+ return;
+ }
+ // Support for Putt-Putt sounds - very hackish, too 8-)
+ else if (READ_UINT32_UNALIGNED(ptr) == MKID('DIGI')) {
+ // TODO - discover what data the first chunk, HSHD, contains
+ // it might be useful here.
+ ptr += 8 + READ_BE_UINT32_UNALIGNED(ptr+12);
+ if (READ_UINT32_UNALIGNED(ptr) != MKID('SDAT'))
+ return; // abort
+
+ int size = READ_BE_UINT32_UNALIGNED(ptr+4);
+ int rate = 8000; // FIXME - what value here ?!? 8000 is just a guess
+
+ // Allocate a sound buffer, copy the data into it, and play
+ char *sound = (char*)malloc(size);
+ memcpy(sound, ptr + 8, size);
+ _scumm->_mixer->playRaw(NULL, sound, size, rate, SoundMixer::FLAG_UNSIGNED | SoundMixer::FLAG_AUTOFREE);
+ return;
+ }
+ else if (READ_UINT32_UNALIGNED(ptr) == MKID('Crea')) {
+ int size, rate;
+ char * sound = read_creative_voc_file(ptr, size, rate);
+ if(sound != NULL) {
+ _scumm->_mixer->playRaw(NULL, sound, size, rate, SoundMixer::FLAG_UNSIGNED | SoundMixer::FLAG_AUTOFREE);
+ }
+ return;
+ }
+ // Support for sampled sound effects in Monkey1 and Monkey2
+ else if (READ_UINT32_UNALIGNED(ptr) == MKID('SBL ')) {
+ debug(2, "Using SBL sound effect");
+
+ // TODO - Figuring out how the SBL chunk works. Here's an
+ // example:
+ //
+ // 53 42 4c 20 00 00 11 ae |SBL ....|
+ // 41 55 68 64 00 00 00 03 |AUhd....|
+ // 00 00 80 41 55 64 74 00 |...AUdt.|
+ // 00 11 9b 01 96 11 00 a6 |........|
+ // 00 7f 7f 7e 7e 7e 7e 7e |...~~~~~|
+ // 7e 7f 7f 80 80 7f 7f 7f |~.......|
+ // 7f 80 80 7f 7e 7d 7d 7e |....~}}~|
+ // 7e 7e 7e 7e 7e 7e 7e 7f |~~~~~~~.|
+ // 7f 7f 7f 80 80 80 80 80 |........|
+ // 80 81 80 80 7f 7f 80 85 |........|
+ // 8b 8b 83 78 72 6d 6f 75 |...xrmou|
+ // 7a 78 77 7d 83 84 83 81 |zxw}....|
+ //
+ // The length of the AUhd chunk always seems to be 3 bytes.
+ // Let's skip that for now.
+ //
+ // The starting offset, length and sample rate is all pure
+ // guesswork. The result sounds reasonable to me, but I've
+ // never heard the original.
+
+ int size = READ_BE_UINT32_UNALIGNED(ptr + 4) - 27;
+ int rate = 8000;
+
+ // Allocate a sound buffer, copy the data into it, and play
+ char *sound = (char*)malloc(size);
+ memcpy(sound, ptr + 33, size);
+ _scumm->_mixer->playRaw(NULL, sound, size, rate, SoundMixer::FLAG_UNSIGNED | SoundMixer::FLAG_AUTOFREE);
+ return;
+ } else if (_scumm->_features & GF_OLD256) {
+ char *sound;
+ int size = READ_LE_UINT32(ptr);
+
+ #if 0
+ // FIXME - this is just some debug output for Zak256
+ if (size != 30) {
+ char name[9];
+ memcpy(name, ptr+22, 8);
+ name[8] = 0;
+ printf("Going to play Zak256 sound '%s':\n", name);
+ hexdump(ptr, 0x40);
+ }
+ /*
+ There seems to be some pattern in the Zak256 sound data. Two typical
+ examples are these:
+
+ d7 10 00 00 53 4f d1 10 |....SO..|
+ 00 00 00 00 04 00 ff 00 |........|
+ 64 00 00 00 01 00 64 6f |d.....do|
+ 6f 72 6f 70 65 6e 40 a8 |oropen@.|
+ 57 14 a1 10 00 00 50 08 |W.....P.|
+ 00 00 00 00 00 00 b3 07 |........|
+ 00 00 3c 00 00 00 04 80 |..<.....|
+ 03 02 0a 01 8c 82 87 81 |........|
+
+ 5b 07 00 00 53 4f 55 07 |[...SOU.|
+ 00 00 00 00 04 00 ff 00 |........|
+ 64 00 00 00 01 00 64 72 |d.....dr|
+ 77 6f 70 65 6e 00 53 a8 |wopen.S.|
+ 57 14 25 07 00 00 92 03 |W.%.....|
+ 00 00 00 00 00 00 88 03 |........|
+ 00 00 3c 00 00 00 82 82 |..<.....|
+ 83 84 86 88 89 8b 89 89 |........|
+
+ As you can see, there are quite some patterns, e.g.
+ the 00 00 00 3c - the sound data seems to start at
+ offset 54.
+ */
+ #endif
+
+ ptr += 0x16;
+ if (size == 30) {
+ int result = 0;
+ int track = *ptr;
+
+ if (track == _scumm->current_cd_sound)
+ result = pollCD();
+ if (result == 1) return;
+
+ playCDTrack(track, 1, 0, 0);
+ return;
+ }
+
+ size -= 0x36;
+ sound = (char*)malloc(size);
+ for (int x = 0; x < size; x++) {
+ int bit = *ptr++;
+ if (bit<0x80) sound[x] = 0x7F-bit; else sound[x] = bit;
+ }
+
+ // FIXME: Maybe something in the header signifies looping? Need to
+ // track it down and add a mixer flag or something (see also bug .
+ _scumm->_mixer->playRaw(NULL, sound, size, 11000, SoundMixer::FLAG_UNSIGNED | SoundMixer::FLAG_AUTOFREE);
+ return;
+ }
+
+ if (_scumm->_gameId == GID_MONKEY_VGA)
+ return; /* FIXME */
+
+ }
+
+ IMuse *se = _scumm->_imuse;
+ if (se) {
+ _scumm->getResourceAddress(rtSound, sound);
+ se->start_sound(sound);
+ }
+}
+
+void Sound::processSfxQueues() {
+ Actor *a;
+ int act;
+ bool b, finished;
+
+ if (_talk_sound_mode != 0) {
+ if (_talk_sound_mode == 2)
+ _talkChannel = startTalkSound(_talk_sound_a, _talk_sound_b, _talk_sound_mode);
+ else
+ startTalkSound(_talk_sound_a, _talk_sound_b, _talk_sound_mode);
+ _talk_sound_mode = 0;
+ }
+
+ if (_scumm->_vars[_scumm->VAR_TALK_ACTOR]) { //_sfxMode == 2) {
+ act = _scumm->_vars[_scumm->VAR_TALK_ACTOR];
+ if (_talkChannel < 0)
+ finished = false;
+ else if (_scumm->_mixer->_channels[_talkChannel] == NULL)
+ finished = true;
+ else
+ finished = false;
+
+
+ if (act != 0 && (uint) act < 0x80 && !_scumm->string[0].no_talk_anim) {
+ a = _scumm->derefActorSafe(act, "processSfxQueues");
+ if (a->room == _scumm->_currentRoom && (finished || !_endOfMouthSync)) {
+ b = true;
+ if (!finished)
+ b = isMouthSyncOff(_curSoundPos);
+ if (_mouthSyncMode != b) {
+ _mouthSyncMode = b;
+ a->startAnimActor(b ? a->talkFrame2 : a->talkFrame1);
+ }
+ }
+ }
+
+ if (finished && _scumm->_talkDelay == 0) {
+ _scumm->stopTalk();
+ _sfxMode = 0;
+ _talkChannel = -1;
+ }
+ }
+
+ if (_sfxMode == 1) {
+ if (isSfxFinished()) {
+ _sfxMode = 0;
+ }
+ }
+}
+
+#ifdef COMPRESSED_SOUND_FILE
+static int compar(const void *a, const void *b)
+{
+ return ((MP3OffsetTable *) a)->org_offset -
+ ((MP3OffsetTable *) b)->org_offset;
+}
+#endif
+
+int Sound::startTalkSound(uint32 offset, uint32 b, int mode) {
+ int num = 0, i;
+ byte file_byte, file_byte_2;
+ int size;
+
+ if (!_sfxFile) {
+ warning("startTalkSound: SFX file is not open");
+ return -1;
+ }
+
+ if (b > 8) {
+ num = (b - 8) >> 1;
+ }
+#ifdef COMPRESSED_SOUND_FILE
+ if (offset_table != NULL) {
+ MP3OffsetTable *result = NULL, key;
+
+ key.org_offset = offset;
+ result = (MP3OffsetTable *) bsearch(&key, offset_table, num_sound_effects,
+ sizeof(MP3OffsetTable), compar);
+
+ if (result == NULL) {
+ warning("startTalkSound: did not find sound at offset %d !", offset);
+ return -1;
+ }
+ if (2 * num != result->num_tags) {
+ warning("startTalkSound: number of tags do not match (%d - %d) !", b,
+ result->num_tags);
+ num = result->num_tags;
+ }
+ offset = result->new_offset;
+ size = result->compressed_size;
+ } else
+#endif
+ {
+ offset += 8;
+ size = -1;
+ }
+
+ _scumm->fileSeek((FILE *) _sfxFile, offset, SEEK_SET);
+ i = 0;
+ while (num > 0) {
+ _scumm->fileRead((FILE *) _sfxFile, &file_byte, sizeof(file_byte));
+ _scumm->fileRead((FILE *) _sfxFile, &file_byte_2, sizeof(file_byte_2));
+ _mouthSyncTimes[i++] = file_byte | (file_byte_2 << 8);
+ num--;
+ }
+ _mouthSyncTimes[i] = 0xFFFF;
+ _sfxMode = mode;
+ _curSoundPos = 0;
+ _mouthSyncMode = true;
+
+ return startSfxSound(_sfxFile, size);
+}
+
+void Sound::stopTalkSound() {
+ if (_sfxMode == 2) {
+ stopSfxSound();
+ _sfxMode = 0;
+ }
+}
+
+bool Sound::isMouthSyncOff(uint pos) {
+ uint j;
+ bool val = true;
+ uint16 *ms = _mouthSyncTimes;
+
+ _endOfMouthSync = false;
+ do {
+ val ^= 1;
+ j = *ms++;
+ if (j == 0xFFFF) {
+ _endOfMouthSync = true;
+ break;
+ }
+ } while (pos > j);
+ return val;
+}
+
+
+int Sound::isSoundRunning(int sound) {
+ IMuse *se;
+ int i;
+
+ if (sound == _scumm->current_cd_sound)
+ return pollCD();
+
+ i = _soundQue2Pos;
+ while (i--) {
+ if (_soundQue2[i] == sound)
+ return 1;
+ }
+
+ if (isSoundInQueue(sound))
+ return 1;
+
+ if (!_scumm->isResourceLoaded(rtSound, sound))
+ return 0;
+
+ se = _scumm->_imuse;
+ if (!se)
+ return 0;
+ return se->get_sound_status(sound);
+}
+
+bool Sound::isSoundInQueue(int sound) {
+ int i = 0, j, num;
+ int16 table[16];
+
+ while (i < _soundQuePos) {
+ num = _soundQue[i++];
+
+ memset(table, 0, sizeof(table));
+
+ if (num > 0) {
+ for (j = 0; j < num; j++)
+ table[j] = _soundQue[i + j];
+ i += num;
+ if (table[0] == 0x10F && table[1] == 8 && table[2] == sound)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void Sound::stopSound(int a) {
+ IMuse *se;
+ int i;
+
+ if (a != 0 && a == _scumm->current_cd_sound) {
+ _scumm->current_cd_sound = 0;
+ stopCD();
+ }
+
+ se = _scumm->_imuse;
+ if (se)
+ se->stop_sound(a);
+
+ for (i = 0; i < 10; i++)
+ if (_soundQue2[i] == (byte)a)
+ _soundQue2[i] = 0;
+}
+
+void Sound::stopAllSounds()
+{
+ IMuse *se = _scumm->_imuse;
+
+ if (_scumm->current_cd_sound != 0) {
+ _scumm->current_cd_sound = 0;
+ stopCD();
+ }
+
+ if (se) {
+ se->stop_all_sounds();
+ se->clear_queue();
+ }
+ clearSoundQue();
+ stopSfxSound();
+}
+
+void Sound::clearSoundQue() {
+ _soundQue2Pos = 0;
+ memset(_soundQue2, 0, sizeof(_soundQue2));
+}
+
+void Sound::soundKludge(int16 * list) {
+ int16 *ptr;
+ int i;
+
+ if (list[0] == -1) {
+ processSoundQues();
+ return;
+ }
+ _soundQue[_soundQuePos++] = 8;
+
+ ptr = _soundQue + _soundQuePos;
+ _soundQuePos += 8;
+
+ for (i = 0; i < 8; i++)
+ *ptr++ = list[i];
+ if (_soundQuePos > 0x100)
+ error("Sound que buffer overflow");
+}
+
+void Sound::talkSound(uint32 a, uint32 b, int mode) {
+ _talk_sound_a = a;
+ _talk_sound_b = b;
+ _talk_sound_mode = mode;
+}
+
+/* The sound code currently only supports General Midi.
+ * General Midi is used in Day Of The Tentacle.
+ * Roland music is also playable, but doesn't sound well.
+ * A mapping between roland instruments and GM instruments
+ * is needed.
+ */
+
+void Sound::setupSound() {
+ if (_scumm->_imuse) {
+ _scumm->_imuse->setBase(_scumm->res.address[rtSound]);
+
+ _sound_volume_music = scummcfg->getInt("music_volume", kDefaultMusicVolume);
+ _sound_volume_master = scummcfg->getInt("master_volume", kDefaultMasterVolume);
+ _sound_volume_sfx = scummcfg->getInt("sfx_volume", kDefaultSFXVolume);
+
+ _scumm->_imuse->set_master_volume(_sound_volume_master);
+ _scumm->_imuse->set_music_volume(_sound_volume_music);
+ _scumm->_mixer->setVolume(_sound_volume_sfx);
+ _scumm->_mixer->setMusicVolume(_sound_volume_music);
+ }
+ _sfxFile = openSfxFile();
+}
+
+void Sound::pauseSounds(bool pause) {
+ IMuse *se = _scumm->_imuse;
+ if (se)
+ se->pause(pause);
+
+ _soundsPaused = pause;
+ _scumm->_mixer->pause(pause);
+}
+
+int Sound::startSfxSound(void *file, int file_size) {
+ char ident[8];
+ int block_type;
+ byte work[8];
+ uint size = 0;
+ int rate, comp;
+ byte *data;
+
+ // FIXME: Day of the Tentacle frequently assumes that starting one sound
+ // effect will automatically stop any other that may be playing at that
+ // time. Do any other games need this?
+
+ if (_scumm->_gameId == GID_TENTACLE)
+ stopSfxSound();
+
+#ifdef COMPRESSED_SOUND_FILE
+ if (file_size > 0) {
+ data = (byte *)calloc(file_size + MAD_BUFFER_GUARD, 1);
+
+ if (fread(data, file_size, 1, (FILE *) file) != 1) {
+ /* no need to free the memory since error will shut down */
+ error("startSfxSound: cannot read %d bytes", size);
+ return -1;
+ }
+ return playSfxSound_MP3(data, file_size);
+ }
+#endif
+ if (fread(ident, 8, 1, (FILE *) file) != 1)
+ goto invalid;
+
+ if (!memcmp(ident, "VTLK", 4)) {
+ fseek((FILE *) file, SOUND_HEADER_BIG_SIZE - 8, SEEK_CUR);
+ } else if (!memcmp(ident, "Creative", 8)) {
+ fseek((FILE *) file, SOUND_HEADER_SIZE - 8, SEEK_CUR);
+ } else {
+ invalid:;
+ warning("startSfxSound: invalid header");
+ return -1;
+ }
+
+ block_type = getc((FILE *) file);
+ if (block_type != 1) {
+ warning("startSfxSound: Expecting block_type == 1, got %d", block_type);
+ return -1;
+ }
+
+ fread(work, 3, 1, (FILE *) file);
+
+ size = (work[0] | (work[1] << 8) | (work[2] << 16)) - 2;
+ rate = getc((FILE *) file);
+ comp = getc((FILE *) file);
+
+ if (comp != 0) {
+ warning("startSfxSound: Unsupported compression type %d", comp);
+ return -1;
+ }
+
+ data = (byte *)malloc(size);
+ if (data == NULL) {
+ error("startSfxSound: out of memory");
+ return -1;
+ }
+
+ if (fread(data, size, 1, (FILE *) file) != 1) {
+ /* no need to free the memory since error will shut down */
+ error("startSfxSound: cannot read %d bytes", size);
+ return -1;
+ }
+
+ return playSfxSound(data, size, 1000000 / (256 - rate), true);
+}
+
+
+#ifdef COMPRESSED_SOUND_FILE
+static int get_int(FILE * f) {
+ int ret = 0;
+ for (int size = 0; size < 4; size++) {
+ int c = fgetc(f);
+ if (c == EOF) {
+ error("Unexpected end of file !!!");
+ }
+ ret <<= 8;
+ ret |= c;
+ }
+ return ret;
+}
+#endif
+
+void * Sound::openSfxFile() {
+ char buf[256];
+ FILE *file = NULL;
+
+ /* Try opening the file <_exe_name>.sou first, eg tentacle.sou.
+ * That way, you can keep .sou files for multiple games in the
+ * same directory */
+#ifdef COMPRESSED_SOUND_FILE
+ offset_table = NULL;
+
+ sprintf(buf, "%s%s.so3", _scumm->getGameDataPath(), _scumm->_exe_name);
+ file = fopen(buf, "rb");
+ if (!file) {
+ sprintf(buf, "%smonster.so3", _scumm->getGameDataPath());
+ file = fopen(buf, "rb");
+ }
+ if (file != NULL) {
+ /* Now load the 'offset' index in memory to be able to find the MP3 data
+
+ The format of the .SO3 file is easy :
+ - number of bytes of the 'index' part
+ - N times the following fields (4 bytes each) :
+ + offset in the original sound file
+ + offset of the MP3 data in the .SO3 file WITHOUT taking into account
+ the index field and the 'size' field
+ + the number of 'tags'
+ + the size of the MP3 data
+ - and then N times :
+ + the tags
+ + the MP3 data
+ */
+ int size, compressed_offset;
+ MP3OffsetTable *cur;
+
+ compressed_offset = get_int(file);
+ offset_table = (MP3OffsetTable *) malloc(compressed_offset);
+ num_sound_effects = compressed_offset / 16;
+
+ size = compressed_offset;
+ cur = offset_table;
+ while (size > 0) {
+ cur[0].org_offset = get_int(file);
+ cur[0].new_offset = get_int(file) + compressed_offset + 4; /* The + 4 is to take into accound the 'size' field */
+ cur[0].num_tags = get_int(file);
+ cur[0].compressed_size = get_int(file);
+ size -= 4 * 4;
+ cur++;
+ }
+ return file;
+ }
+#endif
+ sprintf(buf, "%s%s.sou", _scumm->getGameDataPath(), _scumm->_exe_name);
+ file = fopen(buf, "rb");
+ if (!file) {
+ sprintf(buf, "%smonster.sou", _scumm->getGameDataPath());
+ file = fopen(buf, "rb");
+ }
+ return file;
+}
+
+void Sound::stopSfxSound() {
+ _scumm->_mixer->stopAll();
+}
+
+
+bool Sound::isSfxFinished() {
+ return !_scumm->_mixer->hasActiveChannel();
+}
+
+uint32 Sound::decode12BitsSample(byte * src, byte ** dst, uint32 size) {
+ uint32 s_size = (size * 4) / 3;
+ byte * ptr = *dst = (byte*)malloc (s_size + 4);
+
+ uint32 r = 0, tmp, l;
+ for (l = 0; l < size; l += 3) {
+ tmp = (src[l + 1] & 0x0f) << 8;
+ tmp = (tmp | src[l + 0]) << 4;
+ tmp -= 0x8000;
+ ptr[r++] = (byte)((tmp >> 8) & 0xff);
+ ptr[r++] = (byte)(tmp & 0xff);
+
+ tmp = (src[l + 1] & 0xf0) << 4;
+ tmp = (tmp | src[l + 2]) << 4;
+ tmp -= 0x8000;
+ ptr[r++] = (byte)((tmp >> 8) & 0xff);
+ ptr[r++] = (byte)(tmp & 0xff);
+ }
+
+ return r;
+}
+
+static void music_handler (Scumm * scumm) {
+ scumm->_sound->bundleMusicHandler(scumm);
+}
+
+#define OUTPUT_SIZE 66150 // ((22050 * 2 * 2) / 4) * 3
+
+void Sound::playBundleMusic(int32 song) {
+ char buf[256];
+
+ if (_numberBundleMusic == -1) {
+ sprintf(buf, "%s%smusic.bun", _scumm->getGameDataPath(), _scumm->_exe_name);
+ if (_scumm->_bundle->openMusicFile((char*)&buf) == false)
+ return;
+ _musicBundleBufFinal = (byte*)malloc(OUTPUT_SIZE);
+ _musicBundleBufOutput = (byte*)malloc(10 * 0x2000);
+ _currentSampleBundleMusic = 0;
+ _offsetSampleBundleMusic = 0;
+ _offsetBufBundleMusic = 0;
+ _pauseBundleMusic = false;
+ _numberSamplesBundleMusic = _scumm->_bundle->getNumberOfMusicSamplesByIndex(song);
+ _numberBundleMusic = song;
+ _scumm->_timer->installProcedure(&music_handler, 1000);
+ return;
+ }
+ if (_numberBundleMusic != song) {
+ _numberSamplesBundleMusic = _scumm->_bundle->getNumberOfMusicSamplesByIndex(song);
+ _numberBundleMusic = song;
+ _currentSampleBundleMusic = 0;
+ _offsetSampleBundleMusic = 0;
+ _offsetBufBundleMusic = 0;
+ }
+}
+
+void Sound::pauseBundleMusic(bool state) {
+ _pauseBundleMusic = state;
+}
+
+void Sound::stopBundleMusic() {
+ _scumm->_timer->releaseProcedure(&music_handler);
+ _numberBundleMusic = -1;
+ if (_musicBundleBufFinal) {
+ free(_musicBundleBufFinal);
+ _musicBundleBufFinal = NULL;
+ }
+ if (_musicBundleBufOutput) {
+ free(_musicBundleBufOutput);
+ _musicBundleBufOutput = NULL;
+ }
+}
+
+void Sound::bundleMusicHandler(Scumm * scumm) {
+ byte * ptr;
+ int32 l, num = _numberSamplesBundleMusic, length, k;
+ int32 rate = 22050;
+ int32 tag, size = -1, header_size = 0;
+
+ ptr = _musicBundleBufOutput;
+
+ if (_pauseBundleMusic)
+ return;
+
+ for (k = 0, l = _currentSampleBundleMusic; l < num; k++) {
+ length = _scumm->_bundle->decompressMusicSampleByIndex(_numberBundleMusic, l, (_musicBundleBufOutput + ((k * 0x2000) + _offsetBufBundleMusic)));
+ _offsetSampleBundleMusic += length;
+
+ if (l == 0) {
+ tag = READ_BE_UINT32(ptr); ptr += 4;
+ if (tag != MKID_BE('iMUS')) {
+ warning("Decompression of bundle sound failed");
+ _numberBundleMusic = -1;
+ return;
+ }
+
+ ptr += 12;
+ while(tag != MKID_BE('DATA')) {
+ tag = READ_BE_UINT32(ptr); ptr += 4;
+ switch(tag) {
+ case MKID_BE('FRMT'):
+ size = READ_BE_UINT32(ptr); ptr += 24;
+ break;
+ case MKID_BE('TEXT'):
+ case MKID_BE('REGN'):
+ case MKID_BE('STOP'):
+ case MKID_BE('JUMP'):
+ size = READ_BE_UINT32(ptr); ptr += size + 4;
+ break;
+ case MKID_BE('DATA'):
+ size = READ_BE_UINT32(ptr); ptr += 4;
+ break;
+
+ default:
+ error("Unknown sound header %c%c%c%c", tag>>24, tag>>16, tag>>8, tag);
+ }
+ }
+ if (size < 0) {
+ warning("Decompression sound failed (no size field)");
+ _numberBundleMusic = -1;
+ return;
+ }
+ header_size = (ptr - _musicBundleBufOutput);
+ }
+
+ l++;
+ _currentSampleBundleMusic = l;
+
+ if (_offsetSampleBundleMusic >= OUTPUT_SIZE + header_size) {
+ memcpy(_musicBundleBufFinal, (_musicBundleBufOutput + header_size), OUTPUT_SIZE);
+ _offsetBufBundleMusic = _offsetSampleBundleMusic - OUTPUT_SIZE - header_size;
+ memcpy(_musicBundleBufOutput, (_musicBundleBufOutput + (OUTPUT_SIZE + header_size)), _offsetBufBundleMusic);
+ _offsetSampleBundleMusic = _offsetBufBundleMusic;
+ break;
+ }
+ }
+
+ if (_currentSampleBundleMusic == num)
+ _currentSampleBundleMusic = 0;
+
+ size = OUTPUT_SIZE;
+ ptr = _musicBundleBufFinal;
+
+ byte * buffer = NULL;
+ uint32 final_size = decode12BitsSample(ptr, &buffer, size);
+ _scumm->_mixer->playRaw(NULL, buffer, final_size, rate, SoundMixer::FLAG_AUTOFREE | SoundMixer::FLAG_16BITS | SoundMixer::FLAG_STEREO);
+}
+
+void Sound::playBundleSound(char *sound) {
+ char buf[256];
+ byte * ptr;
+
+ sprintf(buf, "%s%svoice.bun", _scumm->getGameDataPath(), _scumm->_exe_name);
+ _scumm->_bundle->openVoiceFile((char*)&buf);
+ ptr = (byte *)malloc(1000000);
+ if (_scumm->_bundle->decompressVoiceSampleByName(sound, ptr) == 0) {
+ delete ptr;
+ return;
+ }
+
+ int rate = 22050;
+ int tag, size = -1;
+
+ tag = READ_BE_UINT32(ptr); ptr+=4;
+ if (tag != MKID_BE('iMUS')) {
+ warning("Decompression of bundle sound failed");
+ free(ptr);
+ return;
+ }
+
+ ptr += 12;
+ while(tag != MKID_BE('DATA')) {
+ tag = READ_BE_UINT32(ptr); ptr+=4;
+ switch(tag) {
+ case MKID_BE('FRMT'):
+ size = READ_BE_UINT32(ptr); ptr+=16;
+ rate = READ_BE_UINT32(ptr); ptr+=8;
+ break;
+ case MKID_BE('TEXT'):
+ case MKID_BE('REGN'):
+ case MKID_BE('STOP'):
+ case MKID_BE('JUMP'):
+ size = READ_BE_UINT32(ptr); ptr+=size+4;
+ break;
+
+ case MKID_BE('DATA'):
+ size = READ_BE_UINT32(ptr); ptr+=4;
+ break;
+
+ default:
+ error("Unknown sound header %c%c%c%c", tag>>24, tag>>16, tag>>8, tag);
+ }
+ }
+
+ if (size < 0) {
+ warning("Decompression sound failed (no size field)");
+ free(ptr);
+ return;
+ }
+
+ byte * final = (byte *)malloc(size);
+ memcpy(final, ptr, size);
+ _scumm->_mixer->playRaw(NULL, final, size, rate, SoundMixer::FLAG_UNSIGNED | SoundMixer::FLAG_AUTOFREE);
+}
+
+int Sound::playSfxSound(void *sound, uint32 size, uint rate, bool isUnsigned) {
+ if (_soundsPaused)
+ return -1;
+ byte flags = SoundMixer::FLAG_AUTOFREE;
+ if (isUnsigned)
+ flags |= SoundMixer::FLAG_UNSIGNED;
+ return _scumm->_mixer->playRaw(NULL, sound, size, rate, flags);
+}
+
+int Sound::playSfxSound_MP3(void *sound, uint32 size) {
+#ifdef COMPRESSED_SOUND_FILE
+ if (_soundsPaused)
+ return -1;
+ return _scumm->_mixer->playMP3(NULL, sound, size, SoundMixer::FLAG_AUTOFREE);
+#endif
+ return -1;
+}
+
+void Sound::playCDTrack(int track, int num_loops, int start, int delay)
+{
+#ifdef COMPRESSED_SOUND_FILE
+ if (playMP3CDTrack(track, num_loops, start, delay) == -1)
+#endif
+ _scumm->_system->play_cdrom(track, num_loops, start, delay);
+}
+
+void Sound::stopCD()
+{
+#ifdef COMPRESSED_SOUND_FILE
+ if (stopMP3CD() == -1)
+#endif
+ _scumm->_system->stop_cdrom();
+}
+
+int Sound::pollCD()
+{
+#ifdef COMPRESSED_SOUND_FILE
+ if (pollMP3CD())
+ return 1;
+#endif
+ return _scumm->_system->poll_cdrom();
+}
+
+void Sound::updateCD()
+{
+#ifdef COMPRESSED_SOUND_FILE
+ if (updateMP3CD() == -1)
+#endif
+ _scumm->_system->update_cdrom();
+}
+
+#ifdef COMPRESSED_SOUND_FILE
+
+int Sound::getCachedTrack(int track) {
+ int i;
+ char track_name[1024];
+ FILE* file;
+ int current_index;
+ struct mad_stream stream;
+ struct mad_frame frame;
+ unsigned char buffer[8192];
+ unsigned int buflen = 0;
+ int count = 0;
+
+ // See if we find the track in the cache
+ for (i=0; i<CACHE_TRACKS; i++)
+ if (_cached_tracks[i] == track) {
+ if (_mp3_tracks[i])
+ return i;
+ else
+ return -1;
+ }
+ current_index = _current_cache++;
+ _current_cache %= CACHE_TRACKS;
+
+ // Not found, see if it exists
+ sprintf(track_name, "%strack%d.mp3", _scumm->getGameDataPath(), track);
+ file = fopen(track_name, "rb");
+ _cached_tracks[current_index] = track;
+
+ /* First, close the previous file */
+ if (_mp3_tracks[current_index])
+ fclose(_mp3_tracks[current_index]);
+ _mp3_tracks[current_index] = NULL;
+ if (!file) {
+ // This warning is pretty pointless.
+ debug(1, "Track %d not available in mp3 format", track);
+ return -1;
+ }
+ // Check the format and bitrate
+ mad_stream_init(&stream);
+ mad_frame_init(&frame);
+
+ while (1) {
+ if (buflen < sizeof(buffer)) {
+ int bytes;
+
+ bytes = fread(buffer + buflen, 1, sizeof(buffer) - buflen, file);
+ if (bytes <= 0) {
+ if (bytes == -1) {
+ warning("Invalid format for track %d", track);
+ goto error;
+ }
+ break;
+ }
+
+ buflen += bytes;
+ }
+
+ mad_stream_buffer(&stream, buffer, buflen);
+
+ while (1) {
+ if (mad_frame_decode(&frame, &stream) == -1) {
+ if (!MAD_RECOVERABLE(stream.error))
+ break;
+
+ if (stream.error != MAD_ERROR_BADCRC)
+ continue;
+ }
+
+ if (count++)
+ break;
+ }
+
+ if (count || stream.error != MAD_ERROR_BUFLEN)
+ break;
+
+ memmove(buffer, stream.next_frame,
+ buflen = &buffer[buflen] - stream.next_frame);
+ }
+
+ if (count)
+ memcpy(&_mad_header[current_index], &frame.header, sizeof(mad_header));
+ else {
+ warning("Invalid format for track %d", track);
+ goto error;
+ }
+
+ mad_frame_finish(&frame);
+ mad_stream_finish(&stream);
+ // Get file size
+ fseek(file, 0, SEEK_END);
+ _mp3_size[current_index] = ftell(file);
+ _mp3_tracks[current_index] = file;
+
+ return current_index;
+
+ error:
+ mad_frame_finish(&frame);
+ mad_stream_finish(&stream);
+ fclose(file);
+
+ return -1;
+}
+
+int Sound::playMP3CDTrack(int track, int num_loops, int start, int delay) {
+ int index;
+ unsigned int offset;
+ mad_timer_t duration;
+ _scumm->_vars[_scumm->VAR_MI1_TIMER] = 0;
+
+ if (_soundsPaused)
+ return 0;
+
+ if ((num_loops == 0) && (start == 0)) {
+ return 0;
+ }
+
+ index = getCachedTrack(track);
+ if (index < 0)
+ return -1;
+
+ // Calc offset. As all bitrates are in kilobit per seconds, the division by 200 is always exact
+ offset = (start * (_mad_header[index].bitrate / (8 * 25))) / 3;
+
+ // Calc delay
+ if (!delay) {
+ mad_timer_set(&duration, (_mp3_size[index] * 8) / _mad_header[index].bitrate,
+ (_mp3_size[index] * 8) % _mad_header[index].bitrate, _mad_header[index].bitrate);
+ } else {
+ mad_timer_set(&duration, delay / 75, delay % 75, 75);
+ }
+
+ // Go
+ fseek(_mp3_tracks[index], offset, SEEK_SET);
+
+ if (_mp3_cd_playing == true)
+ _scumm->_mixer->stop(_mp3_index);
+ _mp3_index = _scumm->_mixer->playMP3CDTrack(NULL, _mp3_tracks[index], duration);
+ _mp3_cd_playing = true;
+ return 0;
+}
+
+int Sound::stopMP3CD() {
+ if (_mp3_cd_playing == true) {
+ _scumm->_mixer->stop(_mp3_index);
+ _mp3_cd_playing = false;
+ return 0;
+ }
+ return -1;
+}
+
+int Sound::pollMP3CD() {
+ if (_mp3_cd_playing == true)
+ return 1;
+ return 0;
+}
+
+int Sound::updateMP3CD() {
+ if (_mp3_cd_playing == false)
+ return -1;
+
+ if (_scumm->_mixer->_channels[_mp3_index] == NULL) {
+ warning("Error in MP3 decoding");
+ return -1;
+ }
+
+ if (_scumm->_mixer->_channels[_mp3_index]->soundFinished())
+ stopMP3CD();
+ return 0;
+}
+#endif