aboutsummaryrefslogtreecommitdiff
path: root/engines/scumm/sound.cpp
diff options
context:
space:
mode:
authorMax Horn2006-02-11 22:45:04 +0000
committerMax Horn2006-02-11 22:45:04 +0000
commit26ee630756ebdd7c96bccede0881a8c8b98e8f2b (patch)
tree26e378d5cf990a2b81c2c96e9e683a7f333b62e8 /engines/scumm/sound.cpp
parent2a9a0d4211b1ea5723f1409d91cb95de8984429e (diff)
downloadscummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.gz
scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.bz2
scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.zip
Moved engines to the new engines/ directory
svn-id: r20582
Diffstat (limited to 'engines/scumm/sound.cpp')
-rw-r--r--engines/scumm/sound.cpp2364
1 files changed, 2364 insertions, 0 deletions
diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
new file mode 100644
index 0000000000..26f0c712c8
--- /dev/null
+++ b/engines/scumm/sound.cpp
@@ -0,0 +1,2364 @@
+/* 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/imuse_digi/dimuse.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 {
+
+struct MP3OffsetTable { /* Compressed Sound (.SO3) */
+ int org_offset;
+ int new_offset;
+ int num_tags;
+ int compressed_size;
+};
+
+
+Sound::Sound(ScummEngine *parent)
+ :
+ _vm(parent),
+ _soundQuePos(0),
+ _soundQue2Pos(0),
+ _sfxFile(0),
+ _offsetTable(0),
+ _numSoundEffects(0),
+ _soundMode(kVOCMode),
+ _talk_sound_a1(0),
+ _talk_sound_a2(0),
+ _talk_sound_b1(0),
+ _talk_sound_b2(0),
+ _talk_sound_mode(0),
+ _talk_sound_channel(0),
+ _mouthSyncMode(false),
+ _endOfMouthSync(false),
+ _curSoundPos(0),
+ _overrideFreq(0),
+ _currentCDSound(0),
+ _currentMusic(0),
+ _soundsPaused(false),
+ _sfxMode(0),
+ _heMusic(0),
+ _heMusicTracks(0) {
+
+ memset(_heChannel, 0, sizeof(_heChannel));
+ memset(_soundQue, 0, sizeof(_soundQue));
+ memset(_soundQue2, 0, sizeof(_soundQue2));
+ memset(_mouthSyncTimes, 0, sizeof(_mouthSyncTimes));
+}
+
+Sound::~Sound() {
+ stopCDTimer();
+ delete _sfxFile;
+
+ // HE Specific
+ free(_heMusic);
+}
+
+void Sound::addSoundToQueue(int sound, int heOffset, int heChannel, int heFlags) {
+ if (_vm->VAR_LAST_SOUND != 0xFF)
+ _vm->VAR(_vm->VAR_LAST_SOUND) = sound;
+
+ if (heFlags & 16) {
+ playHESound(sound, heOffset, heChannel, heFlags);
+ return;
+ }
+
+ // HE music resources are in separate file
+ if (sound <= _vm->_numSounds)
+ _vm->ensureResourceLoaded(rtSound, sound);
+
+ addSoundToQueue2(sound, heOffset, heChannel, heFlags);
+}
+
+void Sound::addSoundToQueue2(int sound, int heOffset, int heChannel, int heFlags) {
+ if (_vm->_heversion >= 60 && _soundQue2Pos) {
+ int i = _soundQue2Pos;
+ while (i--) {
+ if (_soundQue2[i].sound == sound && !(heFlags & 2))
+ return;
+ }
+ }
+
+ assert(_soundQue2Pos < ARRAYSIZE(_soundQue2));
+ _soundQue2[_soundQue2Pos].sound = sound;
+ _soundQue2[_soundQue2Pos].offset = heOffset;
+ _soundQue2[_soundQue2Pos].channel = heChannel;
+ _soundQue2[_soundQue2Pos].flags = heFlags;
+ _soundQue2Pos++;
+}
+
+void Sound::processSound() {
+ if (_vm->_heversion >= 60) {
+ processSoundQueues();
+ processSfxQueues();
+ } else {
+ processSfxQueues();
+
+ if (_vm->_features & GF_DIGI_IMUSE)
+ return;
+
+ processSoundQueues();
+ }
+}
+
+void Sound::processSoundQueues() {
+ int i = 0, num;
+ int snd, heOffset, heChannel, heFlags;
+ int data[16];
+
+ if (_vm->_heversion >= 72) {
+ for (i = 0; i <_soundQue2Pos; i++) {
+ snd = _soundQue2[i].sound;
+ heOffset = _soundQue2[i].offset;
+ heChannel = _soundQue2[i].channel;
+ heFlags = _soundQue2[i].flags;
+ if (snd) {
+ if (_vm->_heversion>= 60)
+ playHESound(snd, heOffset, heChannel, heFlags);
+ else
+ playSound(snd);
+ }
+ }
+ _soundQue2Pos = 0;
+ } else {
+ while (_soundQue2Pos) {
+ _soundQue2Pos--;
+ snd = _soundQue2[_soundQue2Pos].sound;
+ heOffset = _soundQue2[_soundQue2Pos].offset;
+ heChannel = _soundQue2[_soundQue2Pos].channel;
+ heFlags = _soundQue2[_soundQue2Pos].flags;
+ if (snd) {
+ if (_vm->_heversion>= 60)
+ playHESound(snd, heOffset, heChannel, heFlags);
+ else
+ playSound(snd);
+ }
+ }
+ }
+
+ while (i < _soundQuePos) {
+ num = _soundQue[i++];
+ if (i + num > _soundQuePos) {
+ error("processSoundQues: invalid num value");
+ break;
+ }
+ memset(data, 0, sizeof(data));
+ if (num > 0) {
+ for (int j = 0; j < num; j++)
+ data[j] = _soundQue[i + j];
+ i += num;
+
+ debugC(DEBUG_IMUSE, "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]);
+
+ if (_vm->_imuse) {
+ _vm->VAR(_vm->VAR_SOUNDRESULT) = (short)_vm->_imuse->doCommand (num, data);
+ }
+ }
+ }
+ _soundQuePos = 0;
+}
+
+void Sound::playSound(int soundID) {
+ byte *mallocedPtr = NULL;
+ byte *ptr;
+ char *sound;
+ int size = -1;
+ int rate;
+ byte flags = Audio::Mixer::FLAG_UNSIGNED | Audio::Mixer::FLAG_AUTOFREE;
+
+ debugC(DEBUG_SOUND, "playSound #%d (room %d)", soundID,
+ _vm->getResourceRoomNr(rtSound, soundID));
+
+ ptr = _vm->getResourceAddress(rtSound, soundID);
+
+ if (!ptr) {
+ 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(ptr) == MKID('Mac1')) {
+ // Read info from the header
+ size = READ_BE_UINT32(ptr+0x60);
+ rate = READ_BE_UINT16(ptr+0x64);
+
+ // Skip over the header (fixed size)
+ ptr += 0x72;
+
+ // Allocate a sound buffer, copy the data into it, and play
+ sound = (char *)malloc(size);
+ memcpy(sound, ptr, size);
+ _vm->_mixer->playRaw(NULL, sound, size, rate, flags, soundID);
+ }
+ // WORKAROUND bug # 1311447
+ else if (READ_UINT32(ptr) == MKID(0x460e200d)) {
+ // This sound resource occurs in the Macintosh version of Monkey Island.
+ // I do now know whether it is used in any place other than the one
+ // mentioned in the bug report above; in case it is, I put a check here.
+ assert(soundID == 39);
+
+ // The samplerate is copied from the sound resouce 39 of the PC CD/VGA
+ // version of Monkey Island.
+
+ // Read info from the header
+ size = READ_BE_UINT32(ptr+4);
+ rate = 6849;
+
+ // Skip over the header (fixed size)
+ ptr += 0x26;
+
+ // Allocate a sound buffer, copy the data into it, and play
+ sound = (char *)malloc(size);
+ memcpy(sound, ptr, size);
+ _vm->_mixer->playRaw(NULL, sound, size, rate, flags, soundID);
+ }
+ // Support for sampled sound effects in Monkey Island 1 and 2
+ else if (READ_UINT32(ptr) == MKID('SBL ')) {
+ debugC(DEBUG_SOUND, "Using SBL sound effect");
+
+ // SBL resources essentially contain VOC sound data.
+ // There are at least two main variants: in one,
+ // there are two subchunks AUhd and AUdt, in the other
+ // the chunks are called WVhd and WVdt. Besides that,
+ // the two variants seem pretty similiar.
+
+ // The first subchunk (AUhd resp. WVhd) seems to always
+ // contain three bytes (00 00 80) of unknown meaning.
+ // After that, a second subchunk contains VOC data.
+ // Two real examples:
+ //
+ // 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 |~~~~~~~.|
+ //
+ // And from the non-interactive Sam & Max demo:
+ //
+ // 53 42 4c 20 00 01 15 6e |SBL ...n|
+ // 57 56 68 64 00 00 00 03 |WVhd....|
+ // 00 00 80 57 56 64 74 00 |...WVdt.|
+ // 01 15 5b 01 56 15 01 a6 |..[.V...|
+ // 00 80 80 80 80 80 80 80 |........|
+ // 80 80 80 80 80 80 80 80 |........|
+ // 80 80 80 80 80 80 80 80 |........|
+ // 80 80 80 80 80 80 80 80 |........|
+
+ size = READ_BE_UINT32(ptr + 4) - 27;
+ ptr += 27;
+
+ // Fingolfin says: after eyeballing a single SEGA
+ // SBL resource, it would seem as if the content of the
+ // data subchunk (AUdt) is XORed with 0x16. At least
+ // then a semi-sane VOC header is revealed, with
+ // a sampling rate of ~25000 Hz (does that make sense?).
+ // I'll add some code to test that theory for now.
+
+ // Check if the resource has already been demangled
+ if ((_vm->_platform == Common::kPlatformSegaCD) && (ptr[0] != 1)) {
+ for (int i = 0; i < size; i++) {
+ ptr[i] ^= 0x16;
+ if (ptr[i] >= 0x7F) {
+ ptr[i] = 0xFE - ptr[i];
+ ptr[i] ^= 0x80;
+ }
+ }
+ }
+
+ // TODO: It would be nice if we could use readVOCFromMemory() here.
+ // We'd have to add the 'Creative Voice File' header for this, though,
+ // or make readVOCFromMemory() less strict.
+
+ VocBlockHeader &voc_block_hdr = *(VocBlockHeader *)ptr;
+ assert(voc_block_hdr.blocktype == 1);
+ size = voc_block_hdr.size[0] + (voc_block_hdr.size[1] << 8) + (voc_block_hdr.size[2] << 16) - 2;
+ rate = getSampleRateFromVOCRate(voc_block_hdr.sr);
+ assert(voc_block_hdr.pack == 0);
+
+ // Allocate a sound buffer, copy the data into it, and play
+ sound = (char *)malloc(size);
+ memcpy(sound, ptr + 6, size);
+ _vm->_mixer->playRaw(NULL, sound, size, rate, flags, soundID);
+ }
+ else if ((_vm->_platform == Common::kPlatformFMTowns && _vm->_version == 3) || READ_UINT32(ptr) == MKID('SOUN') || READ_UINT32(ptr) == MKID('TOWS')) {
+
+ bool tows = READ_UINT32(ptr) == MKID('TOWS');
+ if (_vm->_version == 3) {
+ size = READ_LE_UINT32(ptr);
+ } else {
+ size = READ_BE_UINT32(ptr + 4) - 2;
+ if (tows)
+ size += 8;
+ ptr += 2;
+ }
+
+ rate = 11025;
+ int type = *(ptr + 0x0D);
+ int numInstruments;
+
+ if (tows)
+ type = 0;
+
+ switch (type) {
+ case 0: // Sound effect
+ numInstruments = *(ptr + 0x14);
+ if (tows)
+ numInstruments = 1;
+ ptr += 0x16;
+ size -= 0x16;
+
+ while (numInstruments--) {
+ int waveSize = READ_LE_UINT32(ptr + 0x0C);
+ int loopStart = READ_LE_UINT32(ptr + 0x10) * 2;
+ int loopEnd = READ_LE_UINT32(ptr + 0x14) - 1;
+ rate = READ_LE_UINT32(ptr + 0x18) * 1000 / 0x62;
+ ptr += 0x20;
+ size -= 0x20;
+ if (size < waveSize) {
+ warning("Wrong wave size in sound #%i: %i", soundID, waveSize);
+ waveSize = size;
+ }
+ sound = (char *)malloc(waveSize);
+ for (int x = 0; x < waveSize; x++) {
+ int b = *ptr++;
+ if (b < 0x80)
+ sound[x] = 0x7F - b;
+ else
+ sound[x] = b;
+ }
+ size -= waveSize;
+
+ if (loopEnd > 0)
+ flags |= Audio::Mixer::FLAG_LOOP;
+
+ _vm->_mixer->playRaw(NULL, sound, waveSize, rate, flags, soundID, 255, 0, loopStart, loopEnd);
+ }
+ break;
+ case 1:
+ // Music (Euphony format)
+ if (_vm->_musicEngine)
+ _vm->_musicEngine->startSound(soundID);
+ break;
+ case 2: // CD track resource
+ ptr += 0x16;
+
+ if (soundID == _currentCDSound && pollCD() == 1) {
+ free(mallocedPtr);
+ return;
+ }
+
+ {
+ int track = ptr[0];
+ int loops = ptr[1];
+ int start = (ptr[2] * 60 + ptr[3]) * 75 + ptr[4];
+ int end = (ptr[5] * 60 + ptr[6]) * 75 + ptr[7];
+
+ playCDTrack(track, loops == 0xff ? -1 : loops, start, end <= start ? 0 : end - start);
+ }
+
+ _currentCDSound = soundID;
+ break;
+ default:
+ // All other sound types are ignored
+ break;
+ }
+ }
+ else if ((_vm->_gameId == GID_LOOM) && (_vm->_platform == Common::kPlatformMacintosh)) {
+ // Mac version of Loom uses yet another sound format
+ /*
+ playSound #9 (room 70)
+ 000000: 55 00 00 45 73 6f 00 64 01 00 00 00 00 00 00 00 |U..Eso.d........|
+ 000010: 00 05 00 8e 2a 8f 2d 1c 2a 8f 2a 8f 2d 1c 00 28 |....*.-.*.*.-..(|
+ 000020: 00 31 00 3a 00 43 00 4c 00 01 00 00 00 01 00 64 |.1.:.C.L.......d|
+ 000030: 5a 00 01 00 00 00 01 00 64 00 00 01 00 00 00 01 |Z.......d.......|
+ 000040: 00 64 5a 00 01 00 00 00 01 00 64 5a 00 01 00 00 |.dZ.......dZ....|
+ 000050: 00 01 00 64 00 00 00 00 00 00 00 07 00 00 00 64 |...d...........d|
+ 000060: 64 00 00 4e 73 6f 00 64 01 00 00 00 00 00 00 00 |d..Nso.d........|
+ 000070: 00 05 00 89 3d 57 2d 1c 3d 57 3d 57 2d 1c 00 28 |....=W-.=W=W-..(|
+ playSound #16 (room 69)
+ 000000: dc 00 00 a5 73 6f 00 64 01 00 00 00 00 00 00 00 |....so.d........|
+ 000010: 00 05 00 00 2a 8f 03 e8 03 e8 03 e8 03 e8 00 28 |....*..........(|
+ 000020: 00 79 00 7f 00 85 00 d6 00 01 00 00 00 19 01 18 |.y..............|
+ 000030: 2f 00 18 00 01 18 32 00 18 00 01 18 36 00 18 00 |/.....2.....6...|
+ 000040: 01 18 3b 00 18 00 01 18 3e 00 18 00 01 18 42 00 |..;.....>.....B.|
+ 000050: 18 00 01 18 47 00 18 00 01 18 4a 00 18 00 01 18 |....G.....J.....|
+ 000060: 4e 00 10 00 01 18 53 00 10 00 01 18 56 00 10 00 |N.....S.....V...|
+ 000070: 01 18 5a 00 10 00 02 28 5f 00 01 00 00 00 00 00 |..Z....(_.......|
+ */
+ }
+ else if ((_vm->_platform == Common::kPlatformMacintosh) && (_vm->_gameId == GID_INDY3) && (ptr[26] == 0)) {
+ size = READ_BE_UINT16(ptr + 12);
+ rate = 3579545 / READ_BE_UINT16(ptr + 20);
+ sound = (char *)malloc(size);
+ int vol = ptr[24] * 4;
+ memcpy(sound, ptr + READ_BE_UINT16(ptr + 8), size);
+ _vm->_mixer->playRaw(NULL, sound, size, rate, Audio::Mixer::FLAG_AUTOFREE, soundID, vol, 0);
+ }
+ else {
+
+ if (_vm->_gameId == GID_MONKEY_VGA || _vm->_gameId == GID_MONKEY_EGA
+ || (_vm->_gameId == GID_MONKEY && _vm->_platform == Common::kPlatformMacintosh)) {
+ // Sound is currently not supported at all in the amiga versions of these games
+ if (_vm->_platform == Common::kPlatformAmiga) {
+ int track = -1;
+ if (soundID == 50)
+ track = 17;
+ else if (ptr[6] == 0x7F && ptr[7] == 0x00 && ptr[8] == 0x80) {
+ static const char tracks[16] = {13,14,10,3,4,9,16,5,1,8,2,15,6,7,11,12};
+ if (ptr[9] == 0x0E)
+ track = 18;
+ else
+ track = tracks[ptr[9] - 0x23];
+ }
+ if (track != -1) {
+ playCDTrack(track,((track < 5) || (track > 16)) ? 1 : -1,0,0);
+ stopCDTimer();
+ _currentCDSound = soundID;
+ }
+ return;
+ }
+
+ // Works around the fact that in some places in MonkeyEGA/VGA,
+ // the music is never explicitly stopped.
+ // Rather it seems that starting a new music is supposed to
+ // automatically stop the old song.
+ if (_vm->_imuse) {
+ if (READ_UINT32(ptr) != MKID('ASFX'))
+ _vm->_imuse->stopAllSounds();
+ }
+ }
+
+ if (_vm->_musicEngine) {
+ _vm->_musicEngine->startSound(soundID);
+ }
+ }
+
+ free(mallocedPtr);
+}
+
+void Sound::processSfxQueues() {
+
+ if (_talk_sound_mode != 0) {
+ if (_talk_sound_mode & 1)
+ startTalkSound(_talk_sound_a1, _talk_sound_b1, 1);
+ if (_talk_sound_mode & 2)
+ startTalkSound(_talk_sound_a2, _talk_sound_b2, 2, &_talkChannelHandle);
+ _talk_sound_mode = 0;
+ }
+
+ const int act = _vm->getTalkingActor();
+ if ((_sfxMode & 2) && act != 0) {
+ Actor *a;
+ bool finished;
+
+ if (_vm->_imuseDigital) {
+ finished = !isSoundRunning(kTalkSoundID);
+ } else if (_vm->_heversion >= 60) {
+ finished = !isSoundRunning(1);
+ } else {
+ finished = !_vm->_mixer->isSoundHandleActive(_talkChannelHandle);
+ }
+
+ if ((uint) act < 0x80 && ((_vm->_version == 8) || (_vm->_version <= 7 && !_vm->_string[0].no_talk_anim))) {
+ a = _vm->derefActor(act, "processSfxQueues");
+ if (a->isInCurrentRoom()) {
+ if (isMouthSyncOff(_curSoundPos) && !_mouthSyncMode) {
+ if (!_endOfMouthSync)
+ a->runActorTalkScript(a->_talkStopFrame);
+ _mouthSyncMode = 0;
+ } else if (isMouthSyncOff(_curSoundPos) == 0 && !_mouthSyncMode) {
+ a->runActorTalkScript(a->_talkStartFrame);
+ _mouthSyncMode = 1;
+ }
+
+ if (_vm->_version <= 6 && finished)
+ a->runActorTalkScript(a->_talkStopFrame);
+ }
+ }
+
+ if ((!ConfMan.getBool("subtitles") && finished && _vm->_version <= 6) || (finished && _vm->_talkDelay == 0)) {
+ if (!(_vm->_version == 8 && _vm->VAR(_vm->VAR_HAVE_MSG) == 0))
+ _vm->stopTalk();
+ }
+ }
+
+ if (_sfxMode & 1) {
+ if (isSfxFinished()) {
+ _sfxMode &= ~1;
+ }
+ }
+}
+
+static int compareMP3OffsetTable(const void *a, const void *b) {
+ return ((const MP3OffsetTable *)a)->org_offset - ((const MP3OffsetTable *)b)->org_offset;
+}
+
+void Sound::startTalkSound(uint32 offset, uint32 b, int mode, Audio::SoundHandle *handle) {
+ int num = 0, i;
+ int size = 0;
+ int id = -1;
+
+ if (_vm->_gameId == GID_CMI) {
+ _sfxMode |= mode;
+ return;
+ } else if (_vm->_gameId == GID_DIG) {
+ _sfxMode |= mode;
+ if (!(_vm->_features & GF_DEMO))
+ return;
+
+ char filename[30];
+ char roomname[10];
+
+ if (offset == 1)
+ strcpy(roomname, "logo");
+ else if (offset == 15)
+ strcpy(roomname, "canyon");
+ else if (offset == 17)
+ strcpy(roomname, "pig");
+ else if (offset == 18)
+ strcpy(roomname, "derelict");
+ else if (offset == 19)
+ strcpy(roomname, "wreck");
+ else if (offset == 20)
+ strcpy(roomname, "grave");
+ else if (offset == 23)
+ strcpy(roomname, "nexus");
+ else if (offset == 79)
+ strcpy(roomname, "newton");
+ else {
+ warning("startTalkSound: dig demo: unknown room number: %d", offset);
+ return;
+ }
+
+ _sfxFile->close();
+ sprintf(filename, "audio/%s.%d/%d.voc", roomname, offset, b);
+ _vm->openFile(*_sfxFile, filename);
+ if (!_sfxFile->isOpen()) {
+ sprintf(filename, "%d.%d.voc", offset, b);
+ _vm->openFile(*_sfxFile, filename);
+ }
+ if (!_sfxFile->isOpen()) {
+ warning("startTalkSound: dig demo: voc file not found");
+ return;
+ }
+ } else {
+
+ if (!_sfxFile->isOpen()) {
+ warning("startTalkSound: SFX file is not open");
+ return;
+ }
+
+ // Some games frequently assume that starting one sound effect will
+ // automatically stop any other that may be playing at that time. So
+ // that is what we do here, but we make an exception for speech.
+
+ if (mode == 1 && (_vm->_gameId == GID_TENTACLE || _vm->_gameId == GID_SAMNMAX)) {
+ id = 777777 + _talk_sound_channel;
+ _vm->_mixer->stopID(id);
+ }
+
+ if (b > 8) {
+ num = (b - 8) >> 1;
+ }
+
+ if (_offsetTable != NULL) {
+ MP3OffsetTable *result = NULL, key;
+
+ key.org_offset = offset;
+ result = (MP3OffsetTable *)bsearch(&key, _offsetTable, _numSoundEffects,
+ sizeof(MP3OffsetTable), compareMP3OffsetTable);
+
+ if (result == NULL) {
+ warning("startTalkSound: did not find sound at offset %d !", offset);
+ return;
+ }
+ 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 {
+ offset += 8;
+ size = -1;
+ }
+
+ _sfxFile->seek(offset, SEEK_SET);
+
+ assert(num + 1 < (int)ARRAYSIZE(_mouthSyncTimes));
+ for (i = 0; i < num; i++)
+ _mouthSyncTimes[i] = _sfxFile->readUint16BE();
+
+ _mouthSyncTimes[i] = 0xFFFF;
+ _sfxMode |= mode;
+ _curSoundPos = 0;
+ _mouthSyncMode = true;
+ }
+
+ if (!_soundsPaused && _vm->_mixer->isReady()) {
+ AudioStream *input = NULL;
+
+ switch (_soundMode) {
+ case kMP3Mode:
+ #ifdef USE_MAD
+ assert(size > 0);
+ input = makeMP3Stream(_sfxFile, size);
+ #endif
+ break;
+ case kVorbisMode:
+ #ifdef USE_VORBIS
+ assert(size > 0);
+ input = makeVorbisStream(_sfxFile, size);
+ #endif
+ break;
+ case kFlacMode:
+ #ifdef USE_FLAC
+ assert(size > 0);
+ input = makeFlacStream(_sfxFile, size);
+ #endif
+ break;
+ default:
+ input = makeVOCStream(*_sfxFile);
+ }
+
+ if (!input) {
+ warning("startSfxSound failed to load sound");
+ return;
+ }
+
+ if (_vm->_imuseDigital) {
+#ifndef DISABLE_SCUMM_7_8
+ //_vm->_imuseDigital->stopSound(kTalkSoundID);
+ _vm->_imuseDigital->startVoice(kTalkSoundID, input);
+#endif
+ } else {
+ _vm->_mixer->playInputStream(Audio::Mixer::kSFXSoundType, handle, input, id);
+ }
+ }
+}
+
+void Sound::stopTalkSound() {
+ if (_sfxMode & 2) {
+ if (_vm->_imuseDigital) {
+#ifndef DISABLE_SCUMM_7_8
+ _vm->_imuseDigital->stopSound(kTalkSoundID);
+#endif
+ } else if (_vm->_heversion >= 60) {
+ stopSound(1);
+ } else {
+ _vm->_mixer->stopHandle(_talkChannelHandle);
+ }
+ _sfxMode &= ~2;
+ }
+}
+
+bool Sound::isMouthSyncOff(uint pos) {
+ uint j;
+ bool val = true;
+ uint16 *ms = _mouthSyncTimes;
+
+ _endOfMouthSync = false;
+ do {
+ val = !val;
+ j = *ms++;
+ if (j == 0xFFFF) {
+ _endOfMouthSync = true;
+ break;
+ }
+ } while (pos > j);
+ return val;
+}
+
+int Sound::isSoundRunning(int sound) const {
+#ifndef DISABLE_SCUMM_7_8
+ if (_vm->_imuseDigital)
+ return (_vm->_imuseDigital->getSoundStatus(sound) != 0);
+#endif
+
+ if (sound == _currentCDSound)
+ return pollCD();
+
+ if (_vm->_heversion >= 70) {
+ if (sound >= 10000) {
+ return _vm->_mixer->getSoundID(_heSoundChannels[sound - 10000]);
+ }
+ } else if (_vm->_heversion >= 60) {
+ if (sound == -2) {
+ return !isSfxFinished();
+ } else if (sound == -1) {
+ // getSoundStatus(), with a -1, will return the
+ // ID number of the first active music it finds.
+ if (_currentMusic)
+ return (_vm->_mixer->isSoundIDActive(_currentMusic) ? _currentMusic : 0);
+ else if (_vm->_imuse)
+ return (_vm->_imuse->getSoundStatus(sound));
+ }
+ }
+
+ if (_vm->_mixer->isSoundIDActive(sound))
+ return 1;
+
+ if (isSoundInQueue(sound))
+ return 1;
+
+ if (sound > _vm->_numSounds || !_vm->res.isResourceLoaded(rtSound, sound))
+ return 0;
+
+ if (_vm->_musicEngine)
+ return _vm->_musicEngine->getSoundStatus(sound);
+
+ return 0;
+}
+
+/**
+ * Check whether the sound resource with the specified ID is still
+ * used. This is invoked by ScummEngine::isResourceInUse, to determine
+ * which resources can be expired from memory.
+ * Technically, this works very similar to isSoundRunning, however it
+ * calls IMuse::get_sound_active() instead of IMuse::getSoundStatus().
+ * The difference between those two is in how they treat sounds which
+ * are being faded out: get_sound_active() returns true even when the
+ * sound is being faded out, while getSoundStatus() returns false in
+ * that case.
+ */
+bool Sound::isSoundInUse(int sound) const {
+
+#ifndef DISABLE_SCUMM_7_8
+ if (_vm->_imuseDigital)
+ return (_vm->_imuseDigital->getSoundStatus(sound) != 0);
+#endif
+
+ if (sound == _currentCDSound)
+ return pollCD() != 0;
+
+ if (isSoundInQueue(sound))
+ return true;
+
+ if (!_vm->res.isResourceLoaded(rtSound, sound))
+ return false;
+
+ if (_vm->_imuse)
+ return _vm->_imuse->get_sound_active(sound);
+
+ if (_vm->_mixer->isSoundIDActive(sound))
+ return 1;
+
+ return false;
+}
+
+bool Sound::isSoundInQueue(int sound) const {
+ int i, num;
+
+ i = _soundQue2Pos;
+ while (i--) {
+ if (_soundQue2[i].sound == sound)
+ return true;
+ }
+
+ i = 0;
+ while (i < _soundQuePos) {
+ num = _soundQue[i++];
+
+ if (num > 0) {
+ if (_soundQue[i + 0] == 0x10F && _soundQue[i + 1] == 8 && _soundQue[i + 2] == sound)
+ return true;
+ i += num;
+ }
+ }
+ return false;
+}
+
+void Sound::stopSound(int sound) {
+ int i;
+
+ if (_vm->_heversion >= 70) {
+ if ( sound >= 10000) {
+ int chan = sound - 10000;
+ _vm->_mixer->stopHandle(_heSoundChannels[chan]);
+ _heChannel[chan].sound = 0;
+ _heChannel[chan].priority = 0;
+ _heChannel[chan].sbngBlock = 0;
+ _heChannel[chan].codeOffs = 0;
+ memset(_heChannel[chan].soundVars, 0, sizeof(_heChannel[chan].soundVars));
+ }
+ } else if (_vm->_heversion >= 60) {
+ if (sound == -2) {
+ } else if (sound == -1) {
+ // Stop current music
+ if (_currentMusic)
+ _vm->_mixer->stopID(_currentMusic);
+ else if (_vm->_imuse)
+ _vm->_imuse->stopSound(_vm->_imuse->getSoundStatus(-1));
+ }
+ }
+
+ if (sound != 0 && sound == _currentCDSound) {
+ _currentCDSound = 0;
+ stopCD();
+ stopCDTimer();
+ }
+
+ if (!(_vm->_features & GF_DIGI_IMUSE))
+ _vm->_mixer->stopID(sound);
+
+ if (_vm->_musicEngine)
+ _vm->_musicEngine->stopSound(sound);
+
+ for (i = 0; i < ARRAYSIZE(_heChannel); i++) {
+ if (_heChannel[i].sound == sound) {
+ _heChannel[i].sound = 0;
+ _heChannel[i].priority = 0;
+ _heChannel[i].sbngBlock = 0;
+ _heChannel[i].codeOffs = 0;
+ memset(_heChannel[i].soundVars, 0, sizeof(_heChannel[i].soundVars));
+ }
+ }
+
+ for (i = 0; i < ARRAYSIZE(_soundQue2); i++) {
+ if (_soundQue2[i].sound == sound) {
+ _soundQue2[i].sound = 0;
+ _soundQue2[i].offset = 0;
+ _soundQue2[i].channel = 0;
+ _soundQue2[i].flags = 0;
+ }
+ }
+}
+
+void Sound::stopAllSounds() {
+ if (_currentCDSound != 0) {
+ _currentCDSound = 0;
+ stopCD();
+ stopCDTimer();
+ }
+
+ // Clear sound channels for HE games
+ memset(_heChannel, 0, sizeof(_heChannel));
+
+ // Clear the (secondary) sound queue
+ _soundQue2Pos = 0;
+ memset(_soundQue2, 0, sizeof(_soundQue2));
+
+ if (_vm->_musicEngine) {
+ _vm->_musicEngine->stopAllSounds();
+ }
+ if (_vm->_imuse) {
+ // FIXME: Maybe we could merge this call to clear_queue()
+ // into IMuse::stopAllSounds() ?
+ _vm->_imuse->clear_queue();
+ }
+
+ // Stop all SFX
+ if (!_vm->_imuseDigital) {
+ _vm->_mixer->stopAll();
+ }
+}
+
+void Sound::soundKludge(int *list, int num) {
+ int i;
+
+#ifndef DISABLE_SCUMM_7_8
+ if (_vm->_imuseDigital) {
+ _vm->_imuseDigital->parseScriptCmds(list[0], list[1], list[2], list[3], list[4],
+ list[5], list[6], list[7]);
+ return;
+ }
+#endif
+
+ if (list[0] == -1) {
+ processSound();
+ } else {
+ _soundQue[_soundQuePos++] = num;
+
+ for (i = 0; i < num; i++) {
+ _soundQue[_soundQuePos++] = list[i];
+ }
+ }
+}
+
+void Sound::talkSound(uint32 a, uint32 b, int mode, int channel) {
+ if (_vm->_version >= 6 && ConfMan.getBool("speech_mute"))
+ return;
+
+ if (mode == 1) {
+ _talk_sound_a1 = a;
+ _talk_sound_b1 = b;
+ _talk_sound_channel = channel;
+ } else {
+ _talk_sound_a2 = a;
+ _talk_sound_b2 = 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() {
+ delete _sfxFile;
+
+ _sfxFile = openSfxFile();
+
+ if (_vm->_heversion >= 70) {
+ setupHEMusicFile();
+ }
+
+ if (_vm->_gameId == GID_FT) {
+ _vm->VAR(_vm->VAR_VOICE_BUNDLE_LOADED) = _sfxFile->isOpen();
+ }
+}
+
+void Sound::pauseSounds(bool pause) {
+ if (_vm->_imuse)
+ _vm->_imuse->pause(pause);
+
+ // Don't pause sounds if the game isn't active
+ // FIXME - this is quite a nasty hack, replace with something cleaner, and w/o
+ // having to access member vars directly!
+ if (!_vm->_roomResource)
+ return;
+
+ _soundsPaused = pause;
+
+#ifndef DISABLE_SCUMM_7_8
+ if (_vm->_imuseDigital) {
+ _vm->_imuseDigital->pause(pause);
+ }
+#endif
+
+ _vm->_mixer->pauseAll(pause);
+
+ if ((_vm->_features & GF_AUDIOTRACKS) && _vm->VAR(_vm->VAR_MUSIC_TIMER) > 0) {
+ if (pause)
+ stopCDTimer();
+ else
+ startCDTimer();
+ }
+}
+
+ScummFile *Sound::openSfxFile() {
+ struct SoundFileExtensions {
+ const char *ext;
+ SoundMode mode;
+ };
+
+ static const SoundFileExtensions extensions[] = {
+ { "sou", kVOCMode },
+ #ifdef USE_FLAC
+ { "sof", kFlacMode },
+ #endif
+ #ifdef USE_VORBIS
+ { "sog", kVorbisMode },
+ #endif
+ #ifdef USE_MAD
+ { "so3", kMP3Mode },
+ #endif
+ { 0, kVOCMode }
+ };
+
+ char buf[256];
+ char buf1[128];
+ ScummFile *file = new ScummFile();
+ _offsetTable = NULL;
+
+ /* Try opening the file <baseName>.sou first, e.g. tentacle.sou.
+ * That way, you can keep .sou files for multiple games in the
+ * same directory */
+
+ const char *basename[4] = { 0, 0, 0, 0 };
+ basename[0] = _vm->getBaseName();
+ basename[1] = "monster";
+
+ if (_vm->_substResFileNameIndex > 0) {
+
+ strcpy(buf, basename[0]);
+ _vm->generateSubstResFileName(buf, buf1, sizeof(buf1));
+ strcpy(buf, buf1);
+ basename[2] = buf1;
+ }
+
+ for (int j = 0; basename[j] && !file->isOpen(); ++j) {
+ for (int i = 0; extensions[i].ext; ++i) {
+ sprintf(buf, "%s.%s", basename[j], extensions[i].ext);
+ if (_vm->openFile(*file, buf)) {
+ _soundMode = extensions[i].mode;
+ break;
+ }
+ }
+ }
+
+ if (!file->isOpen()) {
+ if ((_vm->_heversion <= 61 && _vm->_platform == Common::kPlatformMacintosh) || (_vm->_heversion >= 70)) {
+ sprintf(buf, "%s.he2", _vm->getBaseName());
+ } else {
+ sprintf(buf, "%s.tlk", _vm->getBaseName());
+ }
+
+ if (_vm->_substResFileNameIndex > 0) {
+ _vm->generateSubstResFileName(buf, buf1, sizeof(buf1));
+ strcpy(buf, buf1);
+ }
+ if (file->open(buf) && _vm->_heversion <= 73)
+ file->setEnc(0x69);
+ _soundMode = kVOCMode;
+ }
+
+ if (_soundMode != kVOCMode) {
+ /* 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 = file->readUint32BE();
+ _offsetTable = (MP3OffsetTable *) malloc(compressed_offset);
+ _numSoundEffects = compressed_offset / 16;
+
+ size = compressed_offset;
+ cur = _offsetTable;
+ while (size > 0) {
+ cur->org_offset = file->readUint32BE();
+ cur->new_offset = file->readUint32BE() + compressed_offset + 4; /* The + 4 is to take into accound the 'size' field */
+ cur->num_tags = file->readUint32BE();
+ cur->compressed_size = file->readUint32BE();
+ size -= 4 * 4;
+ cur++;
+ }
+ }
+
+ return file;
+}
+
+bool Sound::isSfxFinished() const {
+ return !_vm->_mixer->hasActiveChannelOfType(Audio::Mixer::kSFXSoundType);
+}
+
+// We use a real timer in an attempt to get better sync with CD tracks. This is
+// necessary for games like Loom CD.
+
+static void cd_timer_handler(void *refCon) {
+ ScummEngine *scumm = (ScummEngine *)refCon;
+
+ // FIXME: Turn off the timer when it's no longer needed. In theory, it
+ // should be possible to check with pollCD(), but since CD sound isn't
+ // properly restarted when reloading a saved game, I don't dare to.
+
+ scumm->VAR(scumm->VAR_MUSIC_TIMER) += 6;
+}
+
+void Sound::startCDTimer() {
+ int timer_interval;
+
+ // The timer interval has been tuned for Loom CD and the Monkey 1
+ // intro. I have to use 100 for Loom, or there will be a nasty stutter
+ // when Chaos first appears, and I have to use 101 for Monkey 1 or the
+ // intro music will be cut short.
+
+ if (_vm->_gameId == GID_LOOM && _vm->_version == 4)
+ timer_interval = 100;
+ else
+ timer_interval = 101;
+
+ _vm->_timer->removeTimerProc(&cd_timer_handler);
+ _vm->_timer->installTimerProc(&cd_timer_handler, 1000 * timer_interval, _vm);
+}
+
+void Sound::stopCDTimer() {
+ _vm->_timer->removeTimerProc(&cd_timer_handler);
+}
+
+void Sound::playCDTrack(int track, int numLoops, int startFrame, int duration) {
+ // Reset the music timer variable at the start of a new track
+ _vm->VAR(_vm->VAR_MUSIC_TIMER) = 0;
+
+ // Play it
+ if (!_soundsPaused)
+ AudioCD.play(track, numLoops, startFrame, duration);
+
+ // Start the timer after starting the track. Starting an MP3 track is
+ // almost instantaneous, but a CD player may take some time. Hopefully
+ // playCD() will block during that delay.
+ startCDTimer();
+}
+
+void Sound::stopCD() {
+ AudioCD.stop();
+}
+
+int Sound::pollCD() const {
+ return AudioCD.isPlaying();
+}
+
+void Sound::updateCD() {
+ AudioCD.updateCD();
+}
+
+void Sound::saveLoadWithSerializer(Serializer *ser) {
+ static const SaveLoadEntry soundEntries[] = {
+ MKLINE(Sound, _currentCDSound, sleInt16, VER(35)),
+ MKLINE(Sound, _currentMusic, sleInt16, VER(35)),
+ MKEND()
+ };
+
+ ser->saveLoadEntries(this, soundEntries);
+}
+
+
+#pragma mark -
+#pragma mark --- Sound resource handling ---
+#pragma mark -
+
+/*
+ * TODO: The way we handle sound/music resources really is one huge hack.
+ * We probably should reconsider how we do this, and maybe come up with a
+ * better/cleaner solution. Even if we keep the existing code, it really
+ * could stand a thorough cleanup!
+ */
+
+
+int ScummEngine::readSoundResource(int type, int idx) {
+ uint32 pos, total_size, size, tag, basetag, max_total_size;
+ int pri, best_pri;
+ uint32 best_size = 0, best_offs = 0;
+ byte *ptr;
+
+ debugC(DEBUG_RESOURCE, "readSoundResource(%d)", idx);
+
+ pos = 0;
+
+ _fileHandle->readUint32LE();
+ max_total_size = _fileHandle->readUint32BE() - 8;
+ basetag = fileReadDword();
+ total_size = _fileHandle->readUint32BE();
+
+ debugC(DEBUG_RESOURCE, " basetag: %s, total_size=%d", tag2str(TO_BE_32(basetag)), total_size);
+
+ switch (basetag) {
+ case MKID('MIDI'):
+ case MKID('iMUS'):
+ if (_musicType != MDT_PCSPK) {
+ _fileHandle->seek(-8, SEEK_CUR);
+ _fileHandle->read(res.createResource(type, idx, total_size + 8), total_size + 8);
+ return 1;
+ }
+ break;
+ case MKID('SOU '):
+ best_pri = -1;
+ while (pos < total_size) {
+ tag = fileReadDword();
+ size = _fileHandle->readUint32BE() + 8;
+ pos += size;
+
+ pri = -1;
+
+ switch (tag) {
+ case MKID('TOWS'):
+ pri = 16;
+ break;
+ case MKID('SBL '):
+ pri = 15;
+ break;
+ case MKID('ADL '):
+ pri = 1;
+ if (_musicType == MDT_ADLIB)
+ pri = 10;
+ break;
+ case MKID('AMI '):
+ pri = 3;
+ break;
+ case MKID('ROL '):
+ pri = 3;
+ if (_native_mt32)
+ pri = 5;
+ break;
+ case MKID('GMD '):
+ pri = 4;
+ break;
+ case MKID('MAC '): // Occurs in Mac MI2, FOA
+ pri = 2;
+ break;
+ case MKID('SPK '):
+ pri = -1;
+// if (_musicType == MDT_PCSPK)
+// pri = 11;
+ break;
+ }
+
+ if ((_musicType == MDT_PCSPK) && pri != 11)
+ pri = -1;
+
+ debugC(DEBUG_RESOURCE, " tag: %s, total_size=%d, pri=%d", tag2str(TO_BE_32(tag)), size, pri);
+
+
+ if (pri > best_pri) {
+ best_pri = pri;
+ best_size = size;
+ best_offs = _fileHandle->pos();
+ }
+
+ _fileHandle->seek(size - 8, SEEK_CUR);
+ }
+
+ if (best_pri != -1) {
+ _fileHandle->seek(best_offs - 8, SEEK_SET);
+ ptr = res.createResource(type, idx, best_size);
+ _fileHandle->read(ptr, best_size);
+ //dumpResource("sound-", idx, ptr);
+ return 1;
+ }
+ break;
+ case MKID('Mac0'):
+ _fileHandle->seek(-12, SEEK_CUR);
+ total_size = _fileHandle->readUint32BE() - 8;
+ ptr = (byte *)calloc(total_size, 1);
+ _fileHandle->read(ptr, total_size);
+ //dumpResource("sound-", idx, ptr);
+ convertMac0Resource(type, idx, ptr, total_size);
+ free(ptr);
+ return 1;
+
+ case MKID('Mac1'):
+ case MKID('RIFF'):
+ case MKID('TALK'):
+ case MKID('DIGI'):
+ case MKID('Crea'):
+ case MKID(0x460e200d): // WORKAROUND bug # 1311447
+ _fileHandle->seek(-12, SEEK_CUR);
+ total_size = _fileHandle->readUint32BE();
+ ptr = res.createResource(type, idx, total_size);
+ _fileHandle->read(ptr, total_size - 8);
+ //dumpResource("sound-", idx, ptr);
+ return 1;
+
+ case MKID('HSHD'):
+ // HE sound type without SOUN header
+ _fileHandle->seek(-16, SEEK_CUR);
+ total_size = max_total_size + 8;
+ ptr = res.createResource(type, idx, total_size);
+ _fileHandle->read(ptr, total_size);
+ //dumpResource("sound-", idx, ptr);
+ return 1;
+
+ case MKID('FMUS'): {
+ // Used in 3DO version of puttputt joins the parade and probably others
+ // Specifies a separate file to be used for music from what I gather.
+ int tmpsize;
+ Common::File dmuFile;
+ char buffer[128];
+ debugC(DEBUG_SOUND, "Found base tag FMUS in sound %d, size %d", idx, total_size);
+ debugC(DEBUG_SOUND, "It was at position %d", _fileHandle->pos());
+
+ _fileHandle->seek(4, SEEK_CUR);
+ // HSHD size
+ tmpsize = _fileHandle->readUint32BE();
+ // skip to size part of the SDAT block
+ _fileHandle->seek(tmpsize - 4, SEEK_CUR);
+ // SDAT size
+ tmpsize = _fileHandle->readUint32BE();
+
+ // SDAT contains name of file we want
+ _fileHandle->read(buffer, tmpsize - 8);
+ // files seem to be 11 chars (8.3) unused space is replaced by spaces
+ *(strstr(buffer, " ")) = '\0';
+
+ debugC(DEBUG_SOUND, "FMUS file %s", buffer);
+ if (dmuFile.open(buffer) == false) {
+ error("Can't open music file %s*", buffer);
+ res.roomoffs[type][idx] = 0xFFFFFFFF;
+ return 0;
+ }
+ dmuFile.seek(4, SEEK_SET);
+ total_size = dmuFile.readUint32BE();
+ debugC(DEBUG_SOUND, "dmu file size %d", total_size);
+ dmuFile.seek(-8, SEEK_CUR);
+ dmuFile.read(res.createResource(type, idx, total_size), total_size);
+ dmuFile.close();
+ }
+ return 1;
+
+ default:
+ if (FROM_LE_32(basetag) == max_total_size) {
+ _fileHandle->seek(-12, SEEK_CUR);
+ total_size = _fileHandle->readUint32BE();
+ _fileHandle->seek(-8, SEEK_CUR);
+ ptr = res.createResource(type, idx, total_size);
+ _fileHandle->read(ptr, total_size);
+ //dumpResource("sound-", idx, ptr);
+ return 1;
+ }
+ error("Unrecognized base tag 0x%08x in sound %d", TO_BE_32(basetag), idx);
+ }
+ res.roomoffs[type][idx] = 0xFFFFFFFF;
+ return 0;
+}
+
+// Adlib MIDI-SYSEX to set MIDI instruments for small header games.
+static byte ADLIB_INSTR_MIDI_HACK[95] = {
+ 0x00, 0xf0, 0x14, 0x7d, 0x00, // sysex 00: part on/off
+ 0x00, 0x00, 0x03, // part/channel (offset 5)
+ 0x00, 0x00, 0x07, 0x0f, 0x00, 0x00, 0x08, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0xf7,
+ 0x00, 0xf0, 0x41, 0x7d, 0x10, // sysex 16: set instrument
+ 0x00, 0x01, // part/channel (offset 28)
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xf7,
+ 0x00, 0xb0, 0x07, 0x64 // Controller 7 = 100 (offset 92)
+};
+
+static const byte map_param[7] = {
+ 0, 2, 3, 4, 8, 9, 0,
+};
+
+static const byte freq2note[128] = {
+ /*128*/ 6, 6, 6, 6,
+ /*132*/ 7, 7, 7, 7, 7, 7, 7,
+ /*139*/ 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ /*148*/ 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ /*157*/ 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ /*166*/ 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+ /*176*/ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ /*186*/ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ /*197*/ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ /*209*/ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ /*222*/ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+ /*235*/ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ /*249*/ 18, 18, 18, 18, 18, 18, 18
+};
+
+static const uint16 num_steps_table[] = {
+ 1, 2, 4, 5,
+ 6, 7, 8, 9,
+ 10, 12, 14, 16,
+ 18, 21, 24, 30,
+ 36, 50, 64, 82,
+ 100, 136, 160, 192,
+ 240, 276, 340, 460,
+ 600, 860, 1200, 1600
+};
+
+int ScummEngine::convert_extraflags(byte * ptr, byte * src_ptr) {
+ int flags = src_ptr[0];
+
+ int t1, t2, t3, t4, time;
+ int v1, v2, v3;
+
+ if (!(flags & 0x80))
+ return -1;
+
+ t1 = (src_ptr[1] & 0xf0) >> 3;
+ t2 = (src_ptr[2] & 0xf0) >> 3;
+ t3 = (src_ptr[3] & 0xf0) >> 3 | (flags & 0x40 ? 0x80 : 0);
+ t4 = (src_ptr[3] & 0x0f) << 1;
+ v1 = (src_ptr[1] & 0x0f);
+ v2 = (src_ptr[2] & 0x0f);
+ v3 = 31;
+ if ((flags & 0x7) == 0) {
+ v1 = v1 + 31 + 8;
+ v2 = v2 + 31 + 8;
+ } else {
+ v1 = v1 * 2 + 31;
+ v2 = v2 * 2 + 31;
+ }
+
+ /* flags a */
+ if ((flags & 0x7) == 6)
+ ptr[0] = 0;
+ else {
+ ptr[0] = (flags >> 4) & 0xb;
+ ptr[1] = map_param[flags & 0x7];
+ }
+
+ /* extra a */
+ ptr[2] = 0;
+ ptr[3] = 0;
+ ptr[4] = t1 >> 4;
+ ptr[5] = t1 & 0xf;
+ ptr[6] = v1 >> 4;
+ ptr[7] = v1 & 0xf;
+ ptr[8] = t2 >> 4;
+ ptr[9] = t2 & 0xf;
+ ptr[10] = v2 >> 4;
+ ptr[11] = v2 & 0xf;
+ ptr[12] = t3 >> 4;
+ ptr[13] = t3 & 0xf;
+ ptr[14] = t4 >> 4;
+ ptr[15] = t4 & 0xf;
+ ptr[16] = v3 >> 4;
+ ptr[17] = v3 & 0xf;
+
+ time = num_steps_table[t1] + num_steps_table[t2]
+ + num_steps_table[t3 & 0x7f] + num_steps_table[t4];
+ if (flags & 0x20) {
+ int playtime = ((src_ptr[4] >> 4) & 0xf) * 118 +
+ (src_ptr[4] & 0xf) * 8;
+ if (playtime > time)
+ time = playtime;
+ }
+ /*
+ time = ((src_ptr[4] >> 4) & 0xf) * 118 +
+ (src_ptr[4] & 0xf) * 8;
+ */
+ return time;
+}
+
+#define kMIDIHeaderSize 46
+static inline byte *writeMIDIHeader(byte *ptr, const char *type, int ppqn, int total_size) {
+ uint32 dw = TO_BE_32(total_size);
+
+ memcpy(ptr, type, 4); ptr += 4;
+ memcpy(ptr, &dw, 4); ptr += 4;
+ memcpy(ptr, "MDhd", 4); ptr += 4;
+ ptr[0] = 0; ptr[1] = 0; ptr[2] = 0; ptr[3] = 8;
+ ptr += 4;
+ memset(ptr, 0, 8), ptr += 8;
+ memcpy(ptr, "MThd", 4); ptr += 4;
+ ptr[0] = 0; ptr[1] = 0; ptr[2] = 0; ptr[3] = 6;
+ ptr += 4;
+ ptr[0] = 0; ptr[1] = 0; ptr[2] = 0; ptr[3] = 1; // MIDI format 0 with 1 track
+ ptr += 4;
+
+ *ptr++ = ppqn >> 8;
+ *ptr++ = ppqn & 0xFF;
+
+ memcpy(ptr, "MTrk", 4); ptr += 4;
+ memcpy(ptr, &dw, 4); ptr += 4;
+
+ return ptr;
+}
+
+static inline byte *writeVLQ(byte *ptr, int value) {
+ if (value > 0x7f) {
+ if (value > 0x3fff) {
+ *ptr++ = (value >> 14) | 0x80;
+ value &= 0x3fff;
+ }
+ *ptr++ = (value >> 7) | 0x80;
+ value &= 0x7f;
+ }
+ *ptr++ = value;
+ return ptr;
+}
+
+static inline byte Mac0ToGMInstrument(uint32 type, int &transpose) {
+ transpose = 0;
+ switch (type) {
+ case MKID('MARI'): return 12;
+ case MKID('PLUC'): return 45;
+ case MKID('HARM'): return 22;
+ case MKID('PIPE'): return 19;
+ case MKID('TROM'): transpose = -12; return 57;
+ case MKID('STRI'): return 48;
+ case MKID('HORN'): return 60;
+ case MKID('VIBE'): return 11;
+ case MKID('SHAK'): return 77;
+ case MKID('PANP'): return 75;
+ case MKID('WHIS'): return 76;
+ case MKID('ORGA'): return 17;
+ case MKID('BONG'): return 115;
+ case MKID('BASS'): transpose = -24; return 35;
+ default:
+ error("Unknown Mac0 instrument %s found", tag2str(type));
+ }
+}
+
+void ScummEngine::convertMac0Resource(int type, int idx, byte *src_ptr, int size) {
+ /*
+ 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
+ ---
+
+ Instruments (General Midi):
+ "MARI" - Marimba (12)
+ "PLUC" - Pizzicato Strings (45)
+ "HARM" - Harmonica (22)
+ "PIPE" - Church Organ? (19) or Flute? (73) or Bag Pipe (109)
+ "TROM" - Trombone (57)
+ "STRI" - String Ensemble (48 or 49)
+ "HORN" - French Horn? (60) or English Horn? (69)
+ "VIBE" - Vibraphone (11)
+ "SHAK" - Shakuhachi? (77)
+ "PANP" - Pan Flute (75)
+ "WHIS" - Whistle (78) / Bottle (76)
+ "ORGA" - Drawbar Organ (16; but could also be 17-20)
+ "BONG" - Woodblock? (115)
+ "BASS" - Bass (32-39)
+
+
+ Now the task could be to convert this into MIDI, to be fed into iMuse.
+ Or we do something similiar to what is done in Player_V3, assuming
+ we can identify SFX in the MI datafiles for each of the instruments
+ listed above.
+ */
+
+#if 0
+ byte *ptr = res.createResource(type, idx, size);
+ memcpy(ptr, src_ptr, size);
+#else
+ const int ppqn = 480;
+ byte *ptr, *start_ptr;
+
+ int total_size = 0;
+ total_size += kMIDIHeaderSize; // Header
+ total_size += 7; // Tempo META
+ total_size += 3 * 3; // Three program change mesages
+ total_size += 22; // Possible jump SysEx
+ total_size += 5; // EOT META
+
+ int i, len;
+ byte track_instr[3];
+ byte *track_data[3];
+ int track_len[3];
+ int track_transpose[3];
+ bool looped = false;
+
+ src_ptr += 8;
+ // TODO: Decipher the unknown bytes in the header. For now, skip 'em
+ src_ptr += 28;
+
+ // Parse the three channels
+ for (i = 0; i < 3; i++) {
+ assert(*((uint32*)src_ptr) == MKID('Chan'));
+ len = READ_BE_UINT32(src_ptr + 4);
+ track_len[i] = len - 24;
+ track_instr[i] = Mac0ToGMInstrument(*(uint32*)(src_ptr + 8), track_transpose[i]);
+ track_data[i] = src_ptr + 12;
+ src_ptr += len;
+ looped = (*((uint32*)(src_ptr - 8)) == MKID('Loop'));
+
+ // For each note event, we need up to 6 bytes for the
+ // Note On (3 VLQ, 3 event), and 6 bytes for the Note
+ // Off (3 VLQ, 3 event). So 12 bytes total.
+ total_size += 12 * track_len[i];
+ }
+ assert(*src_ptr == 0x09);
+
+ // Create sound resource
+ start_ptr = res.createResource(type, idx, total_size);
+
+ // Insert MIDI header
+ ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size);
+
+ // Write a tempo change Meta event
+ // 473 / 4 Hz, convert to micro seconds.
+ uint32 dw = 1000000 * 437 / 4 / ppqn; // 1000000 * ppqn * 4 / 473;
+ memcpy(ptr, "\x00\xFF\x51\x03", 4); ptr += 4;
+ *ptr++ = (byte)((dw >> 16) & 0xFF);
+ *ptr++ = (byte)((dw >> 8) & 0xFF);
+ *ptr++ = (byte)(dw & 0xFF);
+
+ // Insert program change messages
+ *ptr++ = 0; // VLQ
+ *ptr++ = 0xC0;
+ *ptr++ = track_instr[0];
+ *ptr++ = 0; // VLQ
+ *ptr++ = 0xC1;
+ *ptr++ = track_instr[1];
+ *ptr++ = 0; // VLQ
+ *ptr++ = 0xC2;
+ *ptr++ = track_instr[2];
+
+ // And now, the actual composition. Please turn all cell phones
+ // and pagers off during the performance. Thank you.
+ uint16 nextTime[3] = { 1, 1, 1 };
+ int stage[3] = { 0, 0, 0 };
+
+ while (track_len[0] | track_len[1] | track_len[2]) {
+ int best = -1;
+ uint16 bestTime = 0xFFFF;
+ for (i = 0; i < 3; ++i) {
+ if (track_len[i] && nextTime[i] < bestTime) {
+ bestTime = nextTime[i];
+ best = i;
+ }
+ }
+ assert (best != -1);
+
+ if (!stage[best]) {
+ // We are STARTING this event.
+ if (track_data[best][2] > 1) {
+ // Note On
+ ptr = writeVLQ(ptr, nextTime[best]);
+ *ptr++ = 0x90 | best;
+ *ptr++ = track_data[best][2] + track_transpose[best];
+ *ptr++ = track_data[best][3] * 127 / 100; // Scale velocity
+ for (i = 0; i < 3; ++i)
+ nextTime[i] -= bestTime;
+ }
+ nextTime[best] += READ_BE_UINT16 (track_data[best]);
+ stage[best] = 1;
+ } else {
+ // We are ENDING this event.
+ if (track_data[best][2] > 1) {
+ // There was a Note On, so do a Note Off
+ ptr = writeVLQ(ptr, nextTime[best]);
+ *ptr++ = 0x80 | best;
+ *ptr++ = track_data[best][2] + track_transpose[best];
+ *ptr++ = track_data[best][3] * 127 / 100; // Scale velocity
+ for (i = 0; i < 3; ++i)
+ nextTime[i] -= bestTime;
+ }
+ track_data[best] += 4;
+ track_len[best] -= 4;
+ stage[best] = 0;
+ }
+ }
+
+ // Is this a looped song? If so, effect a loop by
+ // using the S&M maybe_jump SysEx command.
+ // 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.
+ if (looped) {
+ memcpy(ptr, "\x00\xf0\x13\x7d\x30\00", 6); ptr += 6; // maybe_jump
+ memcpy(ptr, "\x00\x00", 2); ptr += 2; // cmd -> 0 means always jump
+ memcpy(ptr, "\x00\x00\x00\x00", 4); ptr += 4; // track -> 0 (only track)
+ memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // beat -> 1 (first beat)
+ memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // tick -> 1
+ memcpy(ptr, "\x00\xf7", 2); ptr += 2; // SysEx end marker
+ }
+
+ // Insert end of song META
+ memcpy(ptr, "\x00\xff\x2f\x00\x00", 5); ptr += 5;
+
+ assert(ptr <= start_ptr + total_size);
+
+ // Rewrite MIDI header, this time with true size
+ total_size = ptr - start_ptr;
+ ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size);
+#endif
+}
+
+void ScummEngine::convertADResource(int type, int idx, byte *src_ptr, int size) {
+
+ // We will ignore the PPQN in the original resource, because
+ // it's invalid anyway. We use a constant PPQN of 480.
+ const int ppqn = 480;
+ uint32 dw;
+ int i, ch;
+ byte *ptr;
+ int total_size = kMIDIHeaderSize + 7 + 8 * sizeof(ADLIB_INSTR_MIDI_HACK) + size;
+ total_size += 24; // Up to 24 additional bytes are needed for the jump sysex
+
+ ptr = res.createResource(type, idx, total_size);
+
+ src_ptr += 2;
+ size -= 2;
+
+ // 0x80 marks a music resource. Otherwise it's a SFX
+ if (*src_ptr == 0x80) {
+ byte ticks, play_once;
+ byte num_instr;
+ byte *channel, *instr, *track;
+
+ ptr = writeMIDIHeader(ptr, "ADL ", ppqn, total_size);
+
+ // The "speed" of the song
+ ticks = *(src_ptr + 1);
+
+ // Flag that tells us whether we should loop the song (0) or play it only once (1)
+ play_once = *(src_ptr + 2);
+
+ // Number of instruments used
+ num_instr = *(src_ptr + 8); // Normally 8
+
+ // copy the pointer to instrument data
+ channel = src_ptr + 9;
+ instr = src_ptr + 0x11;
+
+ // skip over the rest of the header and copy the MIDI data into a buffer
+ src_ptr += 0x11 + 8 * 16;
+ size -= 0x11 + 8 * 16;
+
+ CHECK_HEAP
+
+ track = src_ptr;
+
+ // Convert the ticks into a MIDI tempo.
+ // Unfortunate LOOM and INDY3 have different interpretation
+ // of the ticks value.
+ if (_gameId == GID_INDY3) {
+ // Note: since we fix ppqn at 480, ppqn/473 is almost 1
+ dw = 500000 * 256 / 473 * ppqn / ticks;
+ } else if (_gameId == GID_LOOM && _version == 3) {
+ dw = 500000 * ppqn / 4 / ticks;
+ } else {
+ dw = 500000 * 256 / ticks;
+ }
+ debugC(DEBUG_SOUND, " ticks = %d, speed = %ld", ticks, dw);
+
+ // Write a tempo change Meta event
+ memcpy(ptr, "\x00\xFF\x51\x03", 4); ptr += 4;
+ *ptr++ = (byte)((dw >> 16) & 0xFF);
+ *ptr++ = (byte)((dw >> 8) & 0xFF);
+ *ptr++ = (byte)(dw & 0xFF);
+
+ // Copy our hardcoded instrument table into it
+ // Then, convert the instrument table as given in this song resource
+ // And write it *over* the hardcoded table.
+ // Note: we deliberately.
+
+ /* now fill in the instruments */
+ for (i = 0; i < num_instr; i++) {
+ ch = channel[i] - 1;
+ if (ch < 0 || ch > 15)
+ continue;
+
+ if (instr[i*16 + 13])
+ debugC(DEBUG_SOUND, "Sound %d instrument %d uses percussion", idx, i);
+
+ debugC(DEBUG_SOUND, "Sound %d: instrument %d on channel %d.", idx, i, ch);
+
+ memcpy(ptr, ADLIB_INSTR_MIDI_HACK, sizeof(ADLIB_INSTR_MIDI_HACK));
+
+ ptr[5] += ch;
+ ptr[28] += ch;
+ ptr[92] += ch;
+
+ /* flags_1 */
+ ptr[30 + 0] = (instr[i * 16 + 3] >> 4) & 0xf;
+ ptr[30 + 1] = instr[i * 16 + 3] & 0xf;
+
+ /* oplvl_1 */
+ ptr[30 + 2] = (instr[i * 16 + 4] >> 4) & 0xf;
+ ptr[30 + 3] = instr[i * 16 + 4] & 0xf;
+
+ /* atdec_1 */
+ ptr[30 + 4] = ((~instr[i * 16 + 5]) >> 4) & 0xf;
+ ptr[30 + 5] = (~instr[i * 16 + 5]) & 0xf;
+
+ /* sustrel_1 */
+ ptr[30 + 6] = ((~instr[i * 16 + 6]) >> 4) & 0xf;
+ ptr[30 + 7] = (~instr[i * 16 + 6]) & 0xf;
+
+ /* waveform_1 */
+ ptr[30 + 8] = (instr[i * 16 + 7] >> 4) & 0xf;
+ ptr[30 + 9] = instr[i * 16 + 7] & 0xf;
+
+ /* flags_2 */
+ ptr[30 + 10] = (instr[i * 16 + 8] >> 4) & 0xf;
+ ptr[30 + 11] = instr[i * 16 + 8] & 0xf;
+
+ /* oplvl_2 */
+ ptr[30 + 12] = (instr[i * 16 + 9] >> 4) & 0xf;
+ ptr[30 + 13] = instr[i * 16 + 9] & 0xf;
+
+ /* atdec_2 */
+ ptr[30 + 14] = ((~instr[i * 16 + 10]) >> 4) & 0xf;
+ ptr[30 + 15] = (~instr[i * 16 + 10]) & 0xf;
+
+ /* sustrel_2 */
+ ptr[30 + 16] = ((~instr[i * 16 + 11]) >> 4) & 0xf;
+ ptr[30 + 17] = (~instr[i * 16 + 11]) & 0xf;
+
+ /* waveform_2 */
+ ptr[30 + 18] = (instr[i * 16 + 12] >> 4) & 0xf;
+ ptr[30 + 19] = instr[i * 16 + 12] & 0xf;
+
+ /* feedback */
+ ptr[30 + 20] = (instr[i * 16 + 2] >> 4) & 0xf;
+ ptr[30 + 21] = instr[i * 16 + 2] & 0xf;
+ ptr += sizeof(ADLIB_INSTR_MIDI_HACK);
+ }
+
+ // There is a constant delay of ppqn/3 before the music starts.
+ if (ppqn / 3 >= 128)
+ *ptr++ = (ppqn / 3 >> 7) | 0x80;
+ *ptr++ = ppqn / 3 & 0x7f;
+
+ // Now copy the actual music data
+ memcpy(ptr, track, size);
+ ptr += size;
+
+ if (!play_once) {
+ // The song is meant to be looped. We achieve this by inserting just
+ // before the song end a jump to the song start. More precisely we abuse
+ // a S&M sysex, "maybe_jump" to achieve this effect. We could also
+ // use a set_loop sysex, but it's a bit longer, a little more complicated,
+ // and has no advantage either.
+
+ // First, find the track end
+ byte *end = ptr;
+ ptr -= size;
+ for (; ptr < end; ptr++) {
+ if (*ptr == 0xff && *(ptr + 1) == 0x2f)
+ break;
+ }
+ assert(ptr < end);
+
+ // Now insert the jump. The jump offset is measured in ticks.
+ // We have ppqn/3 ticks before the first note.
+
+ const int jump_offset = ppqn / 3;
+ memcpy(ptr, "\xf0\x13\x7d\x30\00", 5); ptr += 5; // maybe_jump
+ memcpy(ptr, "\x00\x00", 2); ptr += 2; // cmd -> 0 means always jump
+ memcpy(ptr, "\x00\x00\x00\x00", 4); ptr += 4; // track -> there is only one track, 0
+ memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // beat -> for now, 1 (first beat)
+ // Ticks
+ *ptr++ = (byte)((jump_offset >> 12) & 0x0F);
+ *ptr++ = (byte)((jump_offset >> 8) & 0x0F);
+ *ptr++ = (byte)((jump_offset >> 4) & 0x0F);
+ *ptr++ = (byte)(jump_offset & 0x0F);
+ memcpy(ptr, "\x00\xf7", 2); ptr += 2; // sysex end marker
+ }
+ } else {
+
+ /* This is a sfx resource. First parse it quickly to find the parallel
+ * tracks.
+ */
+ ptr = writeMIDIHeader(ptr, "ASFX", ppqn, total_size);
+
+ byte current_instr[3][14];
+ int current_note[3];
+ int track_time[3];
+ byte *track_data[3];
+
+ int track_ctr = 0;
+ byte chunk_type = 0;
+ int delay, delay2, olddelay;
+
+ // Write a tempo change Meta event
+ // 473 / 4 Hz, convert to micro seconds.
+ dw = 1000000 * ppqn * 4 / 473;
+ memcpy(ptr, "\x00\xFF\x51\x03", 4); ptr += 4;
+ *ptr++ = (byte)((dw >> 16) & 0xFF);
+ *ptr++ = (byte)((dw >> 8) & 0xFF);
+ *ptr++ = (byte)(dw & 0xFF);
+
+ for (i = 0; i < 3; i++) {
+ track_time[i] = -1;
+ current_note[i] = -1;
+ }
+ while (size > 0) {
+ assert(track_ctr < 3);
+ track_data[track_ctr] = src_ptr;
+ track_time[track_ctr] = 0;
+ track_ctr++;
+ while (size > 0) {
+ chunk_type = *(src_ptr);
+ if (chunk_type == 1) {
+ src_ptr += 15;
+ size -= 15;
+ } else if (chunk_type == 2) {
+ src_ptr += 11;
+ size -= 11;
+ } else if (chunk_type == 0x80) {
+ src_ptr ++;
+ size --;
+ } else {
+ break;
+ }
+ }
+ if (chunk_type == 0xff)
+ break;
+ src_ptr++;
+ }
+
+ int curtime = 0;
+ for (;;) {
+ int mintime = -1;
+ ch = -1;
+ for (i = 0; i < 3; i++) {
+ if (track_time[i] >= 0 &&
+ (mintime == -1 || mintime > track_time[i])) {
+ mintime = track_time[i];
+ ch = i;
+ }
+ }
+ if (mintime < 0)
+ break;
+
+ src_ptr = track_data[ch];
+ chunk_type = *src_ptr;
+
+ if (current_note[ch] >= 0) {
+ delay = mintime - curtime;
+ curtime = mintime;
+ ptr = writeVLQ(ptr, delay);
+ *ptr++ = 0x80 + ch; // key off channel;
+ *ptr++ = current_note[ch];
+ *ptr++ = 0;
+ current_note[ch] = -1;
+ }
+
+ switch (chunk_type) {
+ case 1:
+ /* Instrument definition */
+ memcpy(current_instr[ch], src_ptr + 1, 14);
+ src_ptr += 15;
+ break;
+
+ case 2:
+ /* tone/parammodulation */
+ memcpy(ptr, ADLIB_INSTR_MIDI_HACK,
+ sizeof(ADLIB_INSTR_MIDI_HACK));
+
+ ptr[5] += ch;
+ ptr[28] += ch;
+ ptr[92] += ch;
+
+ /* flags_1 */
+ ptr[30 + 0] = (current_instr[ch][3] >> 4) & 0xf;
+ ptr[30 + 1] = current_instr[ch][3] & 0xf;
+
+ /* oplvl_1 */
+ ptr[30 + 2] = (current_instr[ch][4] >> 4) & 0xf;
+ ptr[30 + 3] = current_instr[ch][4] & 0xf;
+
+ /* atdec_1 */
+ ptr[30 + 4] = ((~current_instr[ch][5]) >> 4) & 0xf;
+ ptr[30 + 5] = (~current_instr[ch][5]) & 0xf;
+
+ /* sustrel_1 */
+ ptr[30 + 6] = ((~current_instr[ch][6]) >> 4) & 0xf;
+ ptr[30 + 7] = (~current_instr[ch][6]) & 0xf;
+
+ /* waveform_1 */
+ ptr[30 + 8] = (current_instr[ch][7] >> 4) & 0xf;
+ ptr[30 + 9] = current_instr[ch][7] & 0xf;
+
+ /* flags_2 */
+ ptr[30 + 10] = (current_instr[ch][8] >> 4) & 0xf;
+ ptr[30 + 11] = current_instr[ch][8] & 0xf;
+
+ /* oplvl_2 */
+ ptr[30 + 12] = ((current_instr[ch][9]) >> 4) & 0xf;
+ ptr[30 + 13] = (current_instr[ch][9]) & 0xf;
+
+ /* atdec_2 */
+ ptr[30 + 14] = ((~current_instr[ch][10]) >> 4) & 0xf;
+ ptr[30 + 15] = (~current_instr[ch][10]) & 0xf;
+
+ /* sustrel_2 */
+ ptr[30 + 16] = ((~current_instr[ch][11]) >> 4) & 0xf;
+ ptr[30 + 17] = (~current_instr[ch][11]) & 0xf;
+
+ /* waveform_2 */
+ ptr[30 + 18] = (current_instr[ch][12] >> 4) & 0xf;
+ ptr[30 + 19] = current_instr[ch][12] & 0xf;
+
+ /* feedback */
+ ptr[30 + 20] = (current_instr[ch][2] >> 4) & 0xf;
+ ptr[30 + 21] = current_instr[ch][2] & 0xf;
+
+ delay = mintime - curtime;
+ curtime = mintime;
+
+ {
+ delay = convert_extraflags(ptr + 30 + 22, src_ptr + 1);
+ delay2 = convert_extraflags(ptr + 30 + 40, src_ptr + 6);
+ debugC(DEBUG_SOUND, "delays: %d / %d", delay, delay2);
+ if (delay2 >= 0 && delay2 < delay)
+ delay = delay2;
+ if (delay == -1)
+ delay = 0;
+ }
+
+ /* duration */
+ ptr[30 + 58] = 0; // ((delay * 17 / 63) >> 4) & 0xf;
+ ptr[30 + 59] = 0; // (delay * 17 / 63) & 0xf;
+
+ ptr += sizeof(ADLIB_INSTR_MIDI_HACK);
+
+ olddelay = mintime - curtime;
+ curtime = mintime;
+ ptr = writeVLQ(ptr, olddelay);
+
+ {
+ int freq = ((current_instr[ch][1] & 3) << 8)
+ | current_instr[ch][0];
+ if (!freq)
+ freq = 0x80;
+ freq <<= ((current_instr[ch][1] >> 2) & 7) + 1;
+ int note = -11;
+ while (freq >= 0x100) {
+ note += 12;
+ freq >>= 1;
+ }
+ debugC(DEBUG_SOUND, "Freq: %d (%x) Note: %d", freq, freq, note);
+ if (freq < 0x80)
+ note = 0;
+ else
+ note += freq2note[freq - 0x80];
+
+ debugC(DEBUG_SOUND, "Note: %d", note);
+ if (note <= 0)
+ note = 1;
+ else if (note > 127)
+ note = 127;
+
+ // Insert a note on event
+ *ptr++ = 0x90 + ch; // key on channel
+ *ptr++ = note;
+ *ptr++ = 63;
+ current_note[ch] = note;
+ track_time[ch] = curtime + delay;
+ }
+ src_ptr += 11;
+ break;
+
+ case 0x80:
+ track_time[ch] = -1;
+ src_ptr ++;
+ break;
+
+ default:
+ track_time[ch] = -1;
+ }
+ track_data[ch] = src_ptr;
+ }
+ }
+
+ // Insert end of song sysex
+ memcpy(ptr, "\x00\xff\x2f\x00\x00", 5); ptr += 5;
+}
+
+
+int ScummEngine::readSoundResourceSmallHeader(int type, int idx) {
+ uint32 pos, total_size, size, tag;
+ uint32 ad_size = 0, ad_offs = 0;
+ uint32 ro_size = 0, ro_offs = 0;
+ uint32 wa_size = 0, wa_offs = 0;
+
+ debug(4, "readSoundResourceSmallHeader(%d)", idx);
+
+ if ((_gameId == GID_LOOM) && (_version == 3) && (_platform == Common::kPlatformPC) && VAR(VAR_SOUNDCARD) == 4) {
+ // Roland resources in Loom are tagless
+ // So we add an RO tag to allow imuse to detect format
+ byte *ptr, *src_ptr;
+ ro_offs = _fileHandle->pos();
+ ro_size = _fileHandle->readUint16LE();
+
+ src_ptr = (byte *) calloc(ro_size - 4, 1);
+ _fileHandle->seek(ro_offs + 4, SEEK_SET);
+ _fileHandle->read(src_ptr, ro_size -4);
+
+ ptr = res.createResource(type, idx, ro_size + 2);
+ memcpy(ptr, "RO", 2); ptr += 2;
+ memcpy(ptr, src_ptr, ro_size - 4); ptr += ro_size - 4;
+ return 1;
+ } else if (_features & GF_OLD_BUNDLE) {
+ wa_offs = _fileHandle->pos();
+ wa_size = _fileHandle->readUint16LE();
+ _fileHandle->seek(wa_size - 2, SEEK_CUR);
+
+ if (!(_platform == Common::kPlatformAtariST || _platform == Common::kPlatformMacintosh)) {
+ ad_offs = _fileHandle->pos();
+ ad_size = _fileHandle->readUint16LE();
+ }
+ _fileHandle->seek(4, SEEK_CUR);
+ total_size = wa_size + ad_size;
+ } else {
+ total_size = size = _fileHandle->readUint32LE();
+ tag = _fileHandle->readUint16LE();
+ debug(4, " tag='%c%c', size=%d", (char) (tag & 0xff),
+ (char) ((tag >> 8) & 0xff), size);
+
+ if (tag == 0x4F52) { // RO
+ ro_offs = _fileHandle->pos();
+ ro_size = size;
+ } else {
+ pos = 6;
+ while (pos < total_size) {
+ size = _fileHandle->readUint32LE();
+ tag = _fileHandle->readUint16LE();
+ debug(4, " tag='%c%c', size=%d", (char) (tag & 0xff),
+ (char) ((tag >> 8) & 0xff), size);
+ pos += size;
+
+ // MI1 and Indy3 uses one or more nested SO resources, which contains AD and WA
+ // resources.
+ if ((tag == 0x4441) && !(ad_offs)) { // AD
+ ad_size = size;
+ ad_offs = _fileHandle->pos();
+ } else if ((tag == 0x4157) && !(wa_offs)) { // WA
+ wa_size = size;
+ wa_offs = _fileHandle->pos();
+ } else { // other AD, WA and nested SO resources
+ if (tag == 0x4F53) { // SO
+ pos -= size;
+ size = 6;
+ pos += 6;
+ }
+ }
+ _fileHandle->seek(size - 6, SEEK_CUR);
+ }
+ }
+ }
+
+ if ((_musicType == MDT_ADLIB) && ad_offs != 0) {
+ // AD resources have a header, instrument definitions and one MIDI track.
+ // We build an 'ADL ' resource from that:
+ // 8 bytes resource header
+ // 16 bytes MDhd header
+ // 14 bytes MThd header
+ // 8 bytes MTrk header
+ // 7 bytes MIDI tempo sysex
+ // + some default instruments
+ byte *ptr;
+ if (_features & GF_OLD_BUNDLE) {
+ ad_size -= 4;
+ _fileHandle->seek(ad_offs + 4, SEEK_SET);
+ } else {
+ ad_size -= 6;
+ _fileHandle->seek(ad_offs, SEEK_SET);
+ }
+ ptr = (byte *) calloc(ad_size, 1);
+ _fileHandle->read(ptr, ad_size);
+ convertADResource(type, idx, ptr, ad_size);
+ free(ptr);
+ return 1;
+ } else if ((_musicType == MDT_PCSPK) && wa_offs != 0) {
+ if (_features & GF_OLD_BUNDLE) {
+ _fileHandle->seek(wa_offs, SEEK_SET);
+ _fileHandle->read(res.createResource(type, idx, wa_size), wa_size);
+ } else {
+ _fileHandle->seek(wa_offs - 6, SEEK_SET);
+ _fileHandle->read(res.createResource(type, idx, wa_size + 6), wa_size + 6);
+ }
+ return 1;
+ } else if (ro_offs != 0) {
+ _fileHandle->seek(ro_offs - 2, SEEK_SET);
+ _fileHandle->read(res.createResource(type, idx, ro_size - 4), ro_size - 4);
+ return 1;
+ }
+ res.roomoffs[type][idx] = 0xFFFFFFFF;
+ return 0;
+}
+
+
+#pragma mark -
+#pragma mark --- Appendable audio stream ---
+#pragma mark -
+
+
+/**
+ * Wrapped memory stream.
+ */
+template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE>
+class AppendableMemoryStream : public AppendableAudioStream {
+protected:
+ Common::Mutex _mutex;
+
+ byte *_bufferStart;
+ byte *_bufferEnd;
+ byte *_pos;
+ byte *_end;
+ bool _finalized;
+ const int _rate;
+
+ inline bool eosIntern() const { return _end == _pos; };
+public:
+ AppendableMemoryStream(int rate, uint bufferSize);
+ ~AppendableMemoryStream();
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool isStereo() const { return stereo; }
+ bool endOfStream() const { return _finalized && eosIntern(); }
+ bool endOfData() const { return eosIntern(); }
+
+ int getRate() const { return _rate; }
+
+ void append(const byte *data, uint32 len);
+ void finish() { _finalized = true; }
+};
+
+template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE>
+AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::AppendableMemoryStream(int rate, uint bufferSize)
+ : _finalized(false), _rate(rate) {
+
+ // Verify the buffer size is sane
+ if (is16Bit && stereo)
+ assert((bufferSize & 3) == 0);
+ else if (is16Bit || stereo)
+ assert((bufferSize & 1) == 0);
+
+ _bufferStart = (byte *)malloc(bufferSize);
+ _pos = _end = _bufferStart;
+ _bufferEnd = _bufferStart + bufferSize;
+}
+
+template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE>
+AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::~AppendableMemoryStream() {
+ free(_bufferStart);
+}
+
+template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE>
+int AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::readBuffer(int16 *buffer, const int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ int samples = 0;
+ while (samples < numSamples && !eosIntern()) {
+ // Wrap around?
+ if (_pos >= _bufferEnd)
+ _pos = _pos - (_bufferEnd - _bufferStart);
+
+ const byte *endMarker = (_pos > _end) ? _bufferEnd : _end;
+ const int len = MIN(numSamples, samples + (int)(endMarker - _pos) / (is16Bit ? 2 : 1));
+ while (samples < len) {
+ *buffer++ = READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, _pos, isLE);
+ _pos += (is16Bit ? 2 : 1);
+ samples++;
+ }
+ }
+
+ return samples;
+}
+
+template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE>
+void AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::append(const byte *data, uint32 len) {
+ Common::StackLock lock(_mutex);
+
+ // Verify the buffer size is sane
+ if (is16Bit && stereo)
+ assert((len & 3) == 0);
+ else if (is16Bit || stereo)
+ assert((len & 1) == 0);
+
+ // Verify that the stream has not yet been finalized (by a call to finish())
+ assert(!_finalized);
+
+ if (_end + len > _bufferEnd) {
+ // Wrap-around case
+ uint32 size_to_end_of_buffer = _bufferEnd - _end;
+ len -= size_to_end_of_buffer;
+ if ((_end < _pos) || (_bufferStart + len >= _pos)) {
+ debug(2, "AppendableMemoryStream: buffer overflow (A)");
+ return;
+ }
+ memcpy(_end, data, size_to_end_of_buffer);
+ memcpy(_bufferStart, data + size_to_end_of_buffer, len);
+ _end = _bufferStart + len;
+ } else {
+ if ((_end < _pos) && (_end + len >= _pos)) {
+ debug(2, "AppendableMemoryStream: buffer overflow (B)");
+ return;
+ }
+ memcpy(_end, data, len);
+ _end += len;
+ }
+}
+
+
+#define MAKE_WRAPPED(STEREO, UNSIGNED) \
+ if (is16Bit) { \
+ if (isLE) \
+ return new AppendableMemoryStream<STEREO, true, UNSIGNED, true>(rate, len); \
+ else \
+ return new AppendableMemoryStream<STEREO, true, UNSIGNED, false>(rate, len); \
+ } else \
+ return new AppendableMemoryStream<STEREO, false, UNSIGNED, false>(rate, len)
+
+AppendableAudioStream *makeAppendableAudioStream(int rate, byte _flags, uint32 len) {
+ const bool isStereo = (_flags & Audio::Mixer::FLAG_STEREO) != 0;
+ const bool is16Bit = (_flags & Audio::Mixer::FLAG_16BITS) != 0;
+ const bool isUnsigned = (_flags & Audio::Mixer::FLAG_UNSIGNED) != 0;
+ const bool isLE = (_flags & Audio::Mixer::FLAG_LITTLE_ENDIAN) != 0;
+
+ if (isStereo) {
+ if (isUnsigned) {
+ MAKE_WRAPPED(true, true);
+ } else {
+ MAKE_WRAPPED(true, false);
+ }
+ } else {
+ if (isUnsigned) {
+ MAKE_WRAPPED(false, true);
+ } else {
+ MAKE_WRAPPED(false, false);
+ }
+ }
+}
+
+} // End of namespace Scumm