[Scummvm-git-logs] scummvm master -> f731cfd648ad6958c8307f2482d6ca36bd593fb4

aquadran noreply at scummvm.org
Mon Nov 15 23:05:21 UTC 2021


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
f731cfd648 SCUMM: DiMUSE: New Digital iMUSE Engine (#3368)


Commit: f731cfd648ad6958c8307f2482d6ca36bd593fb4
    https://github.com/scummvm/scummvm/commit/f731cfd648ad6958c8307f2482d6ca36bd593fb4
Author: Andrea Boscarino (andywinxp at gmail.com)
Date: 2021-11-16T00:05:18+01:00

Commit Message:
SCUMM: DiMUSE: New Digital iMUSE Engine (#3368)

SCUMM: DiMUSE: Add new implementation of the engine

Changed paths:
  A engines/scumm/imuse_digi/dimuse_cmds.cpp
  A engines/scumm/imuse_digi/dimuse_defs.h
  A engines/scumm/imuse_digi/dimuse_dispatch.cpp
  A engines/scumm/imuse_digi/dimuse_engine.cpp
  A engines/scumm/imuse_digi/dimuse_engine.h
  A engines/scumm/imuse_digi/dimuse_fades.cpp
  A engines/scumm/imuse_digi/dimuse_fades.h
  A engines/scumm/imuse_digi/dimuse_files.cpp
  A engines/scumm/imuse_digi/dimuse_files.h
  A engines/scumm/imuse_digi/dimuse_groups.cpp
  A engines/scumm/imuse_digi/dimuse_groups.h
  A engines/scumm/imuse_digi/dimuse_internalmixer.cpp
  A engines/scumm/imuse_digi/dimuse_internalmixer.h
  A engines/scumm/imuse_digi/dimuse_scripts.cpp
  A engines/scumm/imuse_digi/dimuse_streamer.cpp
  A engines/scumm/imuse_digi/dimuse_tracks.cpp
  A engines/scumm/imuse_digi/dimuse_triggers.cpp
  A engines/scumm/imuse_digi/dimuse_triggers.h
  A engines/scumm/imuse_digi/dimuse_utils.cpp
  A engines/scumm/imuse_digi/dimuse_wave.cpp
  A engines/scumm/imuse_digi/dimuse_waveout.cpp
  R engines/scumm/imuse_digi/dimuse.cpp
  R engines/scumm/imuse_digi/dimuse.h
  R engines/scumm/imuse_digi/dimuse_music.cpp
  R engines/scumm/imuse_digi/dimuse_script.cpp
  R engines/scumm/imuse_digi/dimuse_track.cpp
  R engines/scumm/imuse_digi/dimuse_track.h
  R engines/scumm/smush/imuse_channel.cpp
    base/commandLine.cpp
    engines/scumm/akos.cpp
    engines/scumm/dialogs.cpp
    engines/scumm/imuse_digi/dimuse_bndmgr.cpp
    engines/scumm/imuse_digi/dimuse_bndmgr.h
    engines/scumm/imuse_digi/dimuse_codecs.cpp
    engines/scumm/imuse_digi/dimuse_codecs.h
    engines/scumm/imuse_digi/dimuse_sndmgr.cpp
    engines/scumm/imuse_digi/dimuse_sndmgr.h
    engines/scumm/imuse_digi/dimuse_tables.cpp
    engines/scumm/insane/insane.cpp
    engines/scumm/insane/insane_scenes.cpp
    engines/scumm/module.mk
    engines/scumm/resource.cpp
    engines/scumm/saveload.cpp
    engines/scumm/script_v6.cpp
    engines/scumm/script_v8.cpp
    engines/scumm/scumm.cpp
    engines/scumm/scumm.h
    engines/scumm/smush/channel.h
    engines/scumm/smush/smush_player.cpp
    engines/scumm/smush/smush_player.h
    engines/scumm/sound.cpp
    engines/scumm/sound.h
    engines/scumm/string.cpp


diff --git a/base/commandLine.cpp b/base/commandLine.cpp
index b304181fd1..aec62e7287 100644
--- a/base/commandLine.cpp
+++ b/base/commandLine.cpp
@@ -199,10 +199,6 @@ static const char HELP_STRING[] =
 #ifdef ENABLE_SCUMM
 	"  --tempo=NUM              Set music tempo (in percent, 50-200) for SCUMM games\n"
 	"                           (default: 100)\n"
-#endif
-#if (defined(ENABLE_SCUMM) && defined(ENABLE_SCUMM_7_8)) || defined(ENABLE_GRIM)
-	"  --dimuse-tempo=NUM       Set internal Digital iMuse tempo (10 - 100) per second\n"
-	"                           (default: 10)\n"
 #endif
 	"  --engine-speed=NUM       Set frame per second limit (0 - 100), 0 = no limit\n"
 	"                           (default: 60)\n"
@@ -308,10 +304,6 @@ void registerDefaults() {
 #ifdef ENABLE_SCUMM
 	ConfMan.registerDefault("tempo", 0);
 #endif
-#if (defined(ENABLE_SCUMM) && defined(ENABLE_SCUMM_7_8)) || defined(ENABLE_GRIM)
-	ConfMan.registerDefault("dimuse_tempo", 10);
-#endif
-
 #if defined(ENABLE_SKY) || defined(ENABLE_QUEEN)
 	ConfMan.registerDefault("alt_intro", false);
 #endif
@@ -841,10 +833,7 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
 			DO_LONG_OPTION_INT("tempo")
 			END_OPTION
 #endif
-#if (defined(ENABLE_SCUMM) && defined(ENABLE_SCUMM_7_8)) || defined(ENABLE_GRIM)
-			DO_LONG_OPTION_INT("dimuse-tempo")
-			END_OPTION
-#endif
+
 #if defined(ENABLE_SCUMM) || defined(ENABLE_GROOVIE)
 			DO_LONG_OPTION_BOOL("demo-mode")
 			END_OPTION
diff --git a/engines/scumm/akos.cpp b/engines/scumm/akos.cpp
index 9648dd11ac..39cbf52407 100644
--- a/engines/scumm/akos.cpp
+++ b/engines/scumm/akos.cpp
@@ -24,8 +24,7 @@
 #include "scumm/actor.h"
 #include "scumm/akos.h"
 #include "scumm/bomp.h"
-#include "scumm/imuse/imuse.h"
-#include "scumm/imuse_digi/dimuse.h"
+#include "scumm/imuse_digi/dimuse_engine.h"
 #include "scumm/he/intern_he.h"
 #include "scumm/resource.h"
 #include "scumm/scumm_v7.h"
diff --git a/engines/scumm/dialogs.cpp b/engines/scumm/dialogs.cpp
index 127bad18c2..23722af29e 100644
--- a/engines/scumm/dialogs.cpp
+++ b/engines/scumm/dialogs.cpp
@@ -38,7 +38,6 @@
 #include "scumm/sound.h"
 #include "scumm/scumm.h"
 #include "scumm/imuse/imuse.h"
-#include "scumm/imuse_digi/dimuse.h"
 #include "scumm/verbs.h"
 
 #ifndef DISABLE_HELP
diff --git a/engines/scumm/imuse_digi/dimuse.cpp b/engines/scumm/imuse_digi/dimuse.cpp
deleted file mode 100644
index 8c8213254c..0000000000
--- a/engines/scumm/imuse_digi/dimuse.cpp
+++ /dev/null
@@ -1,588 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#include "common/system.h"
-#include "common/timer.h"
-
-#include "scumm/actor.h"
-#include "scumm/scumm_v7.h"
-#include "scumm/sound.h"
-#include "scumm/imuse_digi/dimuse.h"
-#include "scumm/imuse_digi/dimuse_bndmgr.h"
-#include "scumm/imuse_digi/dimuse_codecs.h"
-#include "scumm/imuse_digi/dimuse_track.h"
-#include "scumm/imuse_digi/dimuse_tables.h"
-
-#include "audio/audiostream.h"
-#include "audio/mixer.h"
-#include "audio/decoders/raw.h"
-
-namespace Scumm {
-
-void IMuseDigital::timer_handler(void *refCon) {
-	IMuseDigital *imuseDigital = (IMuseDigital *)refCon;
-	imuseDigital->callback();
-}
-
-IMuseDigital::IMuseDigital(ScummEngine_v7 *scumm, Audio::Mixer *mixer, int fps)
-	: _vm(scumm), _mixer(mixer) {
-	assert(_vm);
-	assert(mixer);
-
-	_pause = false;
-	_sound = new ImuseDigiSndMgr(_vm);
-	assert(_sound);
-	_callbackFps = fps;
-	if (_vm->_game.id == GID_FT)
-		_callbackFps *= 2;
-	resetState();
-	for (int l = 0; l < MAX_DIGITAL_TRACKS + MAX_DIGITAL_FADETRACKS; l++) {
-		_track[l] = new Track;
-		assert(_track[l]);
-		_track[l]->reset();
-		_track[l]->trackId = l;
-	}
-	_vm->getTimerManager()->installTimerProc(timer_handler, 1000000 / _callbackFps, this, "IMuseDigital");
-
-	_audioNames = NULL;
-	_numAudioNames = 0;
-}
-
-IMuseDigital::~IMuseDigital() {
-	_vm->getTimerManager()->removeTimerProc(timer_handler);
-	stopAllSounds();
-	for (int l = 0; l < MAX_DIGITAL_TRACKS + MAX_DIGITAL_FADETRACKS; l++) {
-		delete _track[l];
-	}
-	delete _sound;
-	free(_audioNames);
-}
-
-static int32 makeMixerFlags(Track *track) {
-	const int32 flags = track->mixerFlags;
-	int32 mixerFlags = 0;
-	if (flags & kFlagUnsigned)
-		mixerFlags |= Audio::FLAG_UNSIGNED;
-	if (flags & kFlag16Bits)
-		mixerFlags |= Audio::FLAG_16BITS;
-
-#ifdef SCUMM_LITTLE_ENDIAN
-	if (track->sndDataExtComp)
-		mixerFlags |= Audio::FLAG_LITTLE_ENDIAN;
-#endif
-	if (track->littleEndian)
-		mixerFlags |= Audio::FLAG_LITTLE_ENDIAN;
-	if (flags & kFlagStereo)
-		mixerFlags |= Audio::FLAG_STEREO;
-	return mixerFlags;
-}
-
-void IMuseDigital::resetState() {
-	_curMusicState = 0;
-	_curMusicSeq = 0;
-	_curMusicCue = 0;
-	memset(_attributes, 0, sizeof(_attributes));
-	_nextSeqToPlay = 0;
-	_stopingSequence = 0;
-	_radioChatterSFX = 0;
-	_triggerUsed = false;
-	_speechIsPlaying = false;
-}
-
-static void syncWithSerializer(Common::Serializer &s, Track &t) {
-	s.syncAsSByte(t.pan, VER(31));
-	s.syncAsSint32LE(t.vol, VER(31));
-	s.syncAsSint32LE(t.volFadeDest, VER(31));
-	s.syncAsSint32LE(t.volFadeStep, VER(31));
-	s.syncAsSint32LE(t.volFadeDelay, VER(31));
-	s.syncAsByte(t.volFadeUsed, VER(31));
-	s.syncAsSint32LE(t.soundId, VER(31));
-	s.syncArray(t.soundName, 15, Common::Serializer::SByte, VER(31));
-	s.syncAsByte(t.used, VER(31));
-	s.syncAsByte(t.toBeRemoved, VER(31));
-	s.syncAsByte(t.souStreamUsed, VER(31));
-	s.skip(1, VER(31), VER(76)); // mixerStreamRunning
-	s.syncAsSint32LE(t.soundPriority, VER(31));
-	s.syncAsSint32LE(t.regionOffset, VER(31));
-	s.skip(4, VER(31), VER(31)); // trackOffset
-	s.syncAsSint32LE(t.dataOffset, VER(31));
-	s.syncAsSint32LE(t.curRegion, VER(31));
-	s.syncAsSint32LE(t.curHookId, VER(31));
-	s.syncAsSint32LE(t.volGroupId, VER(31));
-	s.syncAsSint32LE(t.soundType, VER(31));
-	s.syncAsSint32LE(t.feedSize, VER(31));
-	s.syncAsSint32LE(t.dataMod12Bit, VER(31));
-	s.syncAsSint32LE(t.mixerFlags, VER(31));
-	s.skip(4, VER(31), VER(42)); // mixerVol
-	s.skip(4, VER(31), VER(42)); // mixerPan
-	s.syncAsByte(t.sndDataExtComp, VER(45));
-}
-
-void IMuseDigital::saveLoadEarly(Common::Serializer &s) {
-	Common::StackLock lock(_mutex, "IMuseDigital::saveLoadEarly()");
-
-	s.skip(4, VER(31), VER(42)); // _volVoice
-	s.skip(4, VER(31), VER(42)); // _volSfx
-	s.skip(4, VER(31), VER(42)); // _volMusic
-	s.syncAsSint32LE(_curMusicState, VER(31));
-	s.syncAsSint32LE(_curMusicSeq, VER(31));
-	s.syncAsSint32LE(_curMusicCue, VER(31));
-	s.syncAsSint32LE(_nextSeqToPlay, VER(31));
-	s.syncAsByte(_radioChatterSFX, VER(76));
-	s.syncArray(_attributes, 188, Common::Serializer::Sint32LE, VER(31));
-
-	for (int l = 0; l < MAX_DIGITAL_TRACKS + MAX_DIGITAL_FADETRACKS; l++) {
-		Track *track = _track[l];
-		if (s.isLoading()) {
-			track->reset();
-		}
-		syncWithSerializer(s, *track);
-		if (s.isLoading()) {
-			_track[l]->trackId = l;
-			if (!track->used)
-				continue;
-			if ((track->toBeRemoved) || (track->souStreamUsed) || (track->curRegion == -1)) {
-				track->used = false;
-				continue;
-			}
-
-			// TODO: The code below has a lot in common with that in IMuseDigital::startSound.
-			// Try to refactor them to reduce the code duplication.
-
-			track->soundDesc = _sound->openSound(track->soundId, track->soundName, track->soundType, track->volGroupId, -1);
-			if (!track->soundDesc)
-				track->soundDesc = _sound->openSound(track->soundId, track->soundName, track->soundType, track->volGroupId, 1);
-			if (!track->soundDesc)
-				track->soundDesc = _sound->openSound(track->soundId, track->soundName, track->soundType, track->volGroupId, 2);
-
-			if (!track->soundDesc) {
-				warning("IMuseDigital::saveOrLoad: Can't open sound so will not be resumed");
-				track->used = false;
-				continue;
-			}
-
-			if (_vm->_game.id == GID_CMI) {
-				track->gainRedFadeDest = 127 * 290;
-			}
-
-			if (_vm->_game.id == GID_FT) {
-				track->gainRedFadeDest = 127 * 180;
-			}
-
-			track->sndDataExtComp = _sound->isSndDataExtComp(track->soundDesc);
-			track->dataOffset = _sound->getRegionOffset(track->soundDesc, track->curRegion);
-			int bits = _sound->getBits(track->soundDesc);
-			int channels = _sound->getChannels(track->soundDesc);
-			int freq = _sound->getFreq(track->soundDesc);
-			track->feedSize = freq * channels;
-			track->mixerFlags = 0;
-			track->littleEndian = track->soundDesc->littleEndian;
-			if (channels == 2)
-				track->mixerFlags = kFlagStereo;
-
-			if ((bits == 12) || (bits == 16)) {
-				track->mixerFlags |= kFlag16Bits;
-				track->feedSize *= 2;
-			} else if (bits == 8) {
-				track->mixerFlags |= kFlagUnsigned;
-			} else
-				error("IMuseDigital::saveOrLoad(): Can't handle %d bit samples", bits);
-
-			track->stream = Audio::makeQueuingAudioStream(freq, (track->mixerFlags & kFlagStereo) != 0);
-
-			_mixer->playStream(track->getType(), &track->mixChanHandle, track->stream, -1, track->getVol(), track->getPan());
-			_mixer->pauseHandle(track->mixChanHandle, true);
-		}
-	}
-}
-
-void IMuseDigital::callback() {
-	Common::StackLock lock(_mutex, "IMuseDigital::callback()");
-	_speechIsPlaying = false;
-	// Check for any track playing a speech line
-	if (_vm->_game.id != GID_DIG) {
-		for (int l = 0; l < MAX_DIGITAL_TRACKS; l++) {
-			if (_track[l]->used && _track[l]->soundId == kTalkSoundID) {
-				// Set flag and break
-				_speechIsPlaying = true;
-				break;
-			}
-		}
-	}
-
-	for (int l = 0; l < MAX_DIGITAL_TRACKS + MAX_DIGITAL_FADETRACKS; l++) {
-		Track *track = _track[l];
-		if (track->used) {
-			// Ignore tracks which are about to finish. Also, if it did finish in the meantime,
-			// mark it as unused.
-			if (!track->stream) {
-				if (!_mixer->isSoundHandleActive(track->mixChanHandle))
-					track->reset();
-				continue;
-			}
-
-			if (_pause)
-				return;
-
-			// Fades and crossfades handling
-			if (track->volFadeUsed) {
-				if (track->vol == track->volFadeDest) // Sanity check (needed for some edge cases in COMI, FT and DIG)
-					track->volFadeUsed = false;
-				if (track->volFadeStep < 0) { // Fade out
-					if (track->vol > track->volFadeDest) {
-						// COMI uses non-linear fade curves
-						if (_vm->_game.id == GID_CMI) {
-							int tempVolume = transformVolumeEqualPowToLinear(track->vol, 1); // Equal power to linear...
-							tempVolume += track->volFadeStep; // Remove step...
-							track->vol = transformVolumeLinearToEqualPow(tempVolume, 1); // Linear to equal power...
-						} else {
-							track->vol += track->volFadeStep; // Remove step...
-						}
-						if (track->vol <= track->volFadeDest) {
-							track->vol = track->volFadeDest;
-							track->volFadeUsed = false;
-							// In COMI we flush the track if we've faded out to the destination volume;
-							// this is because there are no situations in which there is a fade out to a
-							// non-zero volume, so this will always mean that we can free the track.
-							// This is not true in Full Throttle, for example, so we have to make this distinction.
-							if (_vm->_game.id == GID_CMI) {
-								flushTrack(track);
-								continue;
-							}
-						}
-						if (track->vol == 0) {
-							// Fade out complete -> remove this track
-							flushTrack(track);
-							continue;
-						}
-					}
-				} else if (track->volFadeStep > 0) { // Fade in
-					if (track->vol < track->volFadeDest) {
-						// Again, COMI uses non-linear fade curves
-						// Curiously, FT uses a different curve for fade-ins
-						if (_vm->_game.id == GID_CMI) {
-							int tempVolume = transformVolumeEqualPowToLinear(track->vol, 1); // Equal power to linear...
-							tempVolume += track->volFadeStep; // Add step...
-							track->vol = transformVolumeLinearToEqualPow(tempVolume, 1); // Linear to equal power...
-						} else if (_vm->_game.id == GID_FT) {
-							int tempVolume = transformVolumeEqualPowToLinear(track->vol, 6); // Equal power to linear...
-							tempVolume += (track->volFadeStep); // Add step...
-							track->vol = transformVolumeLinearToEqualPow(tempVolume, 6); // Linear to equal power...
-						} else {
-							track->vol += track->volFadeStep; // Add step...
-						}
-						if (track->vol >= track->volFadeDest) {
-							track->vol = track->volFadeDest;
-							track->volFadeUsed = false;
-						}
-					}
-				}
- 				debug(5, "Fade: sound(%d), Vol(%d) in track(%d)", track->soundId, track->vol / 1000, track->trackId);
-			}
-
-			// Music gain reduction during speech (used, at the moment, on FT and COMI)
-			if (_vm->_game.id != GID_DIG && track->volGroupId == IMUSE_VOLGRP_MUSIC) {
-				if (_speechIsPlaying) {
-					// Check if we have to fade down or the reduction volume is already at the right value
-					if (track->gainReduction >= track->gainRedFadeDest) {
-						track->gainRedFadeUsed = false;
-						track->gainReduction = track->gainRedFadeDest; // Clip to destination volume
-					} else {
-						track->gainRedFadeUsed = true;
-					}
-					// Gradually bring up the gain reduction (20 ms)
-					if (track->gainRedFadeUsed && track->gainReduction < track->gainRedFadeDest) {
-						int tempReduction = transformVolumeEqualPowToLinear(track->gainReduction, 2); // Equal power to linear...
-						tempReduction += (track->gainRedFadeDest - track->gainReduction) * 60 * (1000 / _callbackFps) / (1000 * 20); // Add step...
-						track->gainReduction = transformVolumeLinearToEqualPow(tempReduction, 2); // Linear to equal power...
-						debug(5, "Gain reduction: sound(%d), reduction amount(%d) in track(%d)", track->soundId, track->gainReduction / 1000, track->trackId);
-					}
-				} else if (!_speechIsPlaying && track->gainReduction > 0) {
-					// Just like the original interpreter, disable gain reduction immediately without a fade
-					track->gainReduction = 0;
-					debug(5, "Gain reduction: no speech playing reduction stopped for sound(%d) in track(%d)", track->soundId, track->trackId);
-				}
-			}
-
-			if (!track->souStreamUsed) {
-				assert(track->stream);
-				byte *tmpSndBufferPtr = NULL;
-				int32 curFeedSize = 0;
-
-				if (track->curRegion == -1) {
-					switchToNextRegion(track);
-					if (!track->stream)	// Seems we reached the end of the stream
-						continue;
-				}
-
-				int bits = _sound->getBits(track->soundDesc);
-				int channels = _sound->getChannels(track->soundDesc);
-
-				int32 feedSize = track->feedSize / _callbackFps;
-
-				if (track->stream->endOfData()) {
-					feedSize *= 2;
-				}
-
-				if ((bits == 12) || (bits == 16)) {
-					if (channels == 1)
-						feedSize &= ~1;
-					if (channels == 2)
-						feedSize &= ~3;
-				} else if (bits == 8) {
-					if (channels == 2)
-						feedSize &= ~1;
-				} else {
-					warning("IMuseDigita::callback: Unexpected sample width, %d bits", bits);
-					continue;
-				}
-
-				if (feedSize == 0)
-					continue;
-
-				do {
-					if (bits == 12) {
-						byte *tmpPtr = NULL;
-
-						feedSize += track->dataMod12Bit;
-						int tmpFeedSize12Bits = (feedSize * 3) / 4;
-						int tmpLength12Bits = (tmpFeedSize12Bits / 3) * 4;
-						track->dataMod12Bit = feedSize - tmpLength12Bits;
-
-						int32 tmpOffset = (track->regionOffset * 3) / 4;
-						int tmpFeedSize = _sound->getDataFromRegion(track->soundDesc, track->curRegion, &tmpPtr, tmpOffset, tmpFeedSize12Bits);
-						curFeedSize = BundleCodecs::decode12BitsSample(tmpPtr, &tmpSndBufferPtr, tmpFeedSize);
-
-						free(tmpPtr);
-					} else if (bits == 16) {
-						curFeedSize = _sound->getDataFromRegion(track->soundDesc, track->curRegion, &tmpSndBufferPtr, track->regionOffset, feedSize);
-						if (channels == 1) {
-							curFeedSize &= ~1;
-						}
-						if (channels == 2) {
-							curFeedSize &= ~3;
-						}
-					} else if (bits == 8) {
-						curFeedSize = _sound->getDataFromRegion(track->soundDesc, track->curRegion, &tmpSndBufferPtr, track->regionOffset, feedSize);
-						if (_radioChatterSFX && track->soundId == 10000) {
-							if (curFeedSize > feedSize)
-								curFeedSize = feedSize;
-							byte *buf = (byte *)malloc(curFeedSize);
-							int index = 0;
-							int count = curFeedSize - 4;
-							byte *ptr_1 = tmpSndBufferPtr;
-							byte *ptr_2 = tmpSndBufferPtr + 4;
-							int value = ptr_1[0] - 0x80;
-							value += ptr_1[1] - 0x80;
-							value += ptr_1[2] - 0x80;
-							value += ptr_1[3] - 0x80;
-							do {
-								int t = *ptr_1++;
-								int v = t - (value / 4);
-								value = *ptr_2++ - 0x80 + (value - t + 0x80);
-								buf[index++] = v * 2 + 0x80;
-							} while (--count);
-							buf[curFeedSize - 1] = 0x80;
-							buf[curFeedSize - 2] = 0x80;
-							buf[curFeedSize - 3] = 0x80;
-							buf[curFeedSize - 4] = 0x80;
-							free(tmpSndBufferPtr);
-							tmpSndBufferPtr = buf;
-						}
-						if (channels == 2) {
-							curFeedSize &= ~1;
-						}
-					}
-
-					if (curFeedSize > feedSize)
-						curFeedSize = feedSize;
-
-					if (_mixer->isReady()) {
-						track->stream->queueBuffer(tmpSndBufferPtr, curFeedSize, DisposeAfterUse::YES, makeMixerFlags(track));
-						track->regionOffset += curFeedSize;
-					} else
-						free(tmpSndBufferPtr);
-
-					if (_sound->isEndOfRegion(track->soundDesc, track->curRegion)) {
-						switchToNextRegion(track);
-						if (!track->stream)	// Seems we reached the end of the stream
-							break;
-					}
-					feedSize -= curFeedSize;
-					assert(feedSize >= 0);
-				} while (feedSize != 0);
-			}
-			if (_mixer->isReady()) {
-				int effVol = track->getVol();
-				int effPan = track->getPan();
-				if (_vm->_game.id == GID_CMI) {
-					// Adjust audio mix for The Curse of Monkey Island
-					if (track->volGroupId == IMUSE_VOLGRP_MUSIC) {
-						effVol -= track->gainReduction / 1000;
-						if (effVol < 0) // In case a music crossfading happens during gain reduction...
-							effVol = 0;
-						effVol = int(round(effVol * 1.9)); // Adjust default music mix for COMI
-					} else if (track->volGroupId == IMUSE_VOLGRP_VOICE) {
-						// Just in case the speakingActor is not being set...
-						// This allows for a fallback to pan = 64 (center) and volume = 127 (full)
-						if (track->speakingActor != nullptr) {
-							if (track->speakingActor->_talkVolume <= 127)
-								effVol = track->speakingActor->_talkVolume;
-
-							effVol = int(round(effVol * 1.04));
-							effPan = (track->speakingActor->_talkPan != 64) ? 2 * track->speakingActor->_talkPan - 127 : 0;
-						}
-					}
-				} else if (_vm->_game.id == GID_FT) {
-					// Adjust audio mix for Full Throttle
-					// (this affects music and sfx only, speech volume mix
-					// is changed accordingly in IMuseDigital::startSound()
-					// since that's the only handle we have for speech)
-					if (track->volGroupId == IMUSE_VOLGRP_MUSIC) {
-						// Gain reduction
-						effVol -= track->gainReduction / 1000;
-						if (effVol < 0) // In case a music crossfading happens during gain reduction...
-							effVol = 0;
-						effVol = int(round(effVol * 1.5));
-					} else {
-						effVol = int(round(effVol * 1.1));
-					}
-				}
-				_mixer->setChannelVolume(track->mixChanHandle, effVol);
-				_mixer->setChannelBalance(track->mixChanHandle, effPan);
-			}
-		}
-	}
-}
-
-void IMuseDigital::switchToNextRegion(Track *track) {
-	assert(track);
-
-	if (track->trackId >= MAX_DIGITAL_TRACKS) {
-		flushTrack(track);
-		debug(5, "SwToNeReg(trackId:%d) - fadetrack can't go next region, exiting SwToNeReg", track->trackId);
-		return;
-	}
-
-	int num_regions = _sound->getNumRegions(track->soundDesc);
-
-	if (++track->curRegion == num_regions) {
-		flushTrack(track);
-		debug(5, "SwToNeReg(trackId:%d) - end of region, exiting SwToNeReg", track->trackId);
-		return;
-	}
-
-	ImuseDigiSndMgr::SoundDesc *soundDesc = track->soundDesc;
-	if (_triggerUsed && track->soundDesc->numMarkers) {
-		if (_sound->checkForTriggerByRegionAndMarker(soundDesc, track->curRegion, _triggerParams.marker)) {
-			if (_vm->_game.id != GID_CMI) {
-				debug(5, "SwToNeReg(trackId:%d) - trigger %s reached", track->trackId, _triggerParams.marker);
-				debug(5, "SwToNeReg(trackId:%d) - exit current region %d", track->trackId, track->curRegion);
-				debug(5, "SwToNeReg(trackId:%d) - call cloneToFadeOutTrack(delay:%d)", track->trackId, _triggerParams.fadeOutDelay);
-				Track *fadeTrack = cloneToFadeOutTrack(track, _triggerParams.fadeOutDelay);
-				if (fadeTrack) {
-					fadeTrack->dataOffset = _sound->getRegionOffset(fadeTrack->soundDesc, fadeTrack->curRegion);
-					fadeTrack->regionOffset = 0;
-					debug(5, "SwToNeReg(trackId:%d)-sound(%d) select region %d, curHookId: %d", fadeTrack->trackId, fadeTrack->soundId, fadeTrack->curRegion, fadeTrack->curHookId);
-					fadeTrack->curHookId = 0;
-				}
-				flushTrack(track);
-				startMusic(_triggerParams.filename, _triggerParams.soundId, _triggerParams.hookId, _triggerParams.volume);
-				_triggerUsed = false;
-				return;
-			} else {
-				// Behavior for "_end" (and "exit") marker
-				debug(5, "SwToNeReg(trackId:%d) - trigger %s reached", track->trackId, _triggerParams.marker);
-				debug(5, "SwToNeReg(trackId:%d) - exit current region %d", track->trackId, track->curRegion);
-				debug(5, "SwToNeReg(trackId:%d) - call handleComiFadeOut(delay:%d)", track->trackId, _triggerParams.fadeOutDelay);
-				handleFadeOut(track, _triggerParams.fadeOutDelay);
-				track->dataOffset = _sound->getRegionOffset(track->soundDesc, track->curRegion);
-				track->regionOffset = 0;
-				debug(5, "SwToNeReg(trackId:%d)-sound(%d) select region %d, curHookId: %d", track->trackId, track->soundId, track->curRegion, track->curHookId);
-				track->curHookId = 0;
-				if (!scumm_stricmp(_triggerParams.marker, "exit"))
-					startMusic(_triggerParams.filename, _triggerParams.soundId, _triggerParams.hookId, _triggerParams.volume);
-				_triggerUsed = false;
-				return;
-			}
-		}
-	}
-
-	int jumpId = _sound->getJumpIdByRegionAndHookId(soundDesc, track->curRegion, track->curHookId);
-	if ((_vm->_game.id != GID_CMI && jumpId != -1) || (_vm->_game.id == GID_CMI && jumpId != -1 && !track->toBeRemoved && !track->alreadyCrossfading)) {
-		int region = _sound->getRegionIdByJumpId(soundDesc, jumpId);
-		assert(region != -1);
-		int sampleHookId = _sound->getJumpHookId(soundDesc, jumpId);
-		assert(sampleHookId != -1);
-
-		bool isJumpToStart = false;
-		if (_vm->_game.id == GID_CMI) {
-			isJumpToStart = (soundDesc->jump[jumpId].dest == soundDesc->marker[2].pos && !scumm_stricmp(soundDesc->marker[2].ptr, "start"));
-		}
-
-		debug(5, "SwToNeReg(trackId:%d) - JUMP found - sound:%d, track hookId:%d, data hookId:%d", track->trackId, track->soundId, track->curHookId, sampleHookId);
-		if (track->curHookId == sampleHookId) {
-			int fadeDelay = (60 * _sound->getJumpFade(soundDesc, jumpId)) / 1000;
-			debug(5, "SwToNeReg(trackId:%d) - sound(%d) match hookId", track->trackId, track->soundId);
-
-			if (_vm->_game.id == GID_CMI) {
-				// The original exe crossfades the jump to the destination region.
-				// For the time being this is implemented as a simple region switch.
-				track->curRegion = region;
-			} else {
-				if (fadeDelay) {
-					debug(5, "SwToNeReg(trackId:%d) - call cloneToFadeOutTrack(delay:%d)", track->trackId, fadeDelay);
-					Track *fadeTrack = cloneToFadeOutTrack(track, fadeDelay);
-					if (fadeTrack) {
-						fadeTrack->dataOffset = _sound->getRegionOffset(fadeTrack->soundDesc, fadeTrack->curRegion);
-						fadeTrack->regionOffset = 0;
-						debug(5, "SwToNeReg(trackId:%d) - sound(%d) faded track, select region %d, curHookId: %d", fadeTrack->trackId, fadeTrack->soundId, fadeTrack->curRegion, fadeTrack->curHookId);
-						fadeTrack->curHookId = 0;
-					}
-				} else {
-					track->curRegion = region;
-				}
-			}
-
-			debug(5, "SwToNeReg(trackId:%d) - sound(%d) jump to region %d, curHookId: %d", track->trackId, track->soundId, track->curRegion, track->curHookId);
-			track->curHookId = 0;
-		} else {
-			// Check if the jump led to a  "start" marker; if so, we have to enforce it anyway.
-			// Fixes bug/edge-case #11956;
-			// Go see ImuseDigiSndMgr::getJumpIdByRegionAndHookId(...) for further information.
-			if (_vm->_game.id == GID_CMI && isJumpToStart) {
-				track->curRegion = region;
-				debug(5, "SwToNeReg(trackId:%d) - Enforced sound(%d) jump to region %d marked with a \"start\" marker, hookId(%d)", track->trackId, track->soundId, track->curRegion, track->curHookId);
-			} else {
-				debug(5, "SwToNeReg(trackId:%d) - Normal switch region, sound(%d), hookId(%d)", track->trackId, track->soundId, track->curHookId);
-			}
-		}
-	} else {
-		debug(5, "SwToNeReg(trackId:%d) - Normal switch region, sound(%d), hookId(%d)", track->trackId, track->soundId, track->curHookId);
-	}
-	debug(5, "SwToNeReg(trackId:%d) - sound(%d), select region %d", track->trackId, track->soundId, track->curRegion);
-	track->dataOffset = _sound->getRegionOffset(soundDesc, track->curRegion);
-	track->regionOffset = 0;
-	debug(5, "SwToNeReg(trackId:%d) - end of func", track->trackId);
-}
-
-} // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse.h b/engines/scumm/imuse_digi/dimuse.h
deleted file mode 100644
index d6d05d0a0f..0000000000
--- a/engines/scumm/imuse_digi/dimuse.h
+++ /dev/null
@@ -1,175 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#if !defined(SCUMM_IMUSE_DIGI_H) && defined(ENABLE_SCUMM_7_8)
-#define SCUMM_IMUSE_DIGI_H
-
-#include "common/scummsys.h"
-#include "common/mutex.h"
-#include "common/serializer.h"
-#include "common/textconsole.h"
-#include "common/util.h"
-
-#include "scumm/imuse_digi/dimuse.h"
-#include "scumm/imuse_digi/dimuse_bndmgr.h"
-#include "scumm/imuse_digi/dimuse_sndmgr.h"
-#include "scumm/music.h"
-#include "scumm/sound.h"
-
-namespace Audio {
-class AudioStream;
-class Mixer;
-}
-
-namespace Scumm {
-
-enum {
-	MAX_DIGITAL_TRACKS = 8,
-	MAX_DIGITAL_FADETRACKS = 8
-};
-
-struct imuseDigTable;
-struct imuseComiTable;
-class ScummEngine_v7;
-struct Track;
-
-class IMuseDigital : public MusicEngine {
-private:
-
-	int _callbackFps;		// value how many times callback needs to be called per second
-
-	struct TriggerParams {
-		char marker[10];
-		int fadeOutDelay;
-		char filename[13];
-		int soundId;
-		int hookId;
-		int volume;
-	};
-
-	TriggerParams _triggerParams;
-	bool _triggerUsed;
-
-	Track *_track[MAX_DIGITAL_TRACKS + MAX_DIGITAL_FADETRACKS];
-
-	Common::Mutex _mutex;
-	ScummEngine_v7 *_vm;
-	Audio::Mixer *_mixer;
-	ImuseDigiSndMgr *_sound;
-
-	char *_audioNames;		// filenames of sound SFX used in FT
-	int32 _numAudioNames;	// number of above filenames
-
-	bool _pause;			// flag mean that iMuse callback should be idle
-
-	int32 _attributes[188];	// internal attributes for each music file to store and check later
-	int32 _nextSeqToPlay;	// id of sequence type of music needed played
-	int32 _curMusicState;	// current or previous id of music
-	int32 _curMusicSeq;		// current or previous id of sequence music
-	int32 _curMusicCue;		// current cue for current music. used in FT
-	int _stopingSequence;
-	bool _radioChatterSFX;
-	bool _speechIsPlaying;
-
-	static void timer_handler(void *refConf);
-	void callback();
-	void switchToNextRegion(Track *track);
-	int allocSlot(int priority);
-	int startSound(int soundId, const char *soundName, int soundType, int volGroupId, Audio::AudioStream *input, int hookId, int volume, int priority, Track *otherTrack);
-	void selectVolumeGroup(int soundId, int volGroupId);
-
-	int32 getPosInMs(int soundId);
-	void getLipSync(int soundId, int syncId, int32 msPos, int32 &width, int32 &height);
-
-	int getSoundIdByName(const char *soundName);
-	void fadeOutMusic(int fadeDelay);
-	void fadeOutMusicAndStartNew(int fadeDelay, const char *filename, int soundId);
-	void setTrigger(TriggerParams *trigger);
-	void setHookIdForMusic(int hookId);
-	Track *cloneToFadeOutTrack(Track *track, int fadeDelay);
-	Track *handleFadeOut(Track *track, int fadeDelay);
-	int transformVolumeLinearToEqualPow(int volume, int mode);
-	int transformVolumeEqualPowToLinear(int volume, int mode);
-
-	void setFtMusicState(int stateId);
-	void setFtMusicSequence(int seqId);
-	void setFtMusicCuePoint(int cueId);
-	void playFtMusic(const char *songName, int opcode, int volume);
-
-	void setComiMusicState(int stateId);
-	void setComiMusicSequence(int seqId);
-	void setComiDemoMusicState(int stateId);
-	void playComiMusic(const char *songName, const imuseComiTable *table, int attribPos, bool sequence);
-	void playComiDemoMusic(const char *songName, const imuseComiTable *table, int attribPos);
-
-	void setDigMusicState(int stateId);
-	void setDigMusicSequence(int seqId);
-	void playDigMusic(const char *songName, const imuseDigTable *table, int attribPos, bool sequence);
-
-	void flushTrack(Track *track);
-
-public:
-	IMuseDigital(ScummEngine_v7 *scumm, Audio::Mixer *mixer, int fps);
-	~IMuseDigital() override;
-
-	void setAudioNames(int32 num, char *names);
-
-	int startVoice(int soundId, Audio::AudioStream *input);
-	int startVoice(int soundId, const char *soundName);
-	int startMusic(int soundId, int volume);
-	int startMusic(const char *soundName, int soundId, int hookId, int volume);
-	int startMusicWithOtherPos(const char *soundName, int soundId, int hookId, int volume, Track *otherTrack);
-	int startSfx(int soundId, int priority);
-	void startSound(int sound) override
-		{ error("IMuseDigital::startSound(int) should be never called"); }
-
-	void saveLoadEarly(Common::Serializer &ser);
-	void resetState();
-	void setRadioChatterSFX(bool state) {
-		_radioChatterSFX = state;
-	}
-
-	void setPriority(int soundId, int priority);
-	void setVolume(int soundId, int volume);
-	void setPan(int soundId, int pan);
-	void setFade(int soundId, int destVolume, int delay60HzTicks);
-	int getCurMusicSoundId();
-	void setHookId(int soundId, int hookId);
-	void setMusicVolume(int vol) override {}
-	void stopSound(int sound) override;
-	void stopAllSounds() override;
-	void pause(bool pause);
-	void parseScriptCmds(int cmd, int soundId, int sub_cmd, int d, int e, int f, int g, int h);
-	void refreshScripts();
-	void flushTracks();
-	int getSoundStatus(int sound) const override;
-	int32 getCurMusicPosInMs();
-	int32 getCurVoiceLipSyncWidth();
-	int32 getCurVoiceLipSyncHeight();
-	int32 getCurMusicLipSyncWidth(int syncId);
-	int32 getCurMusicLipSyncHeight(int syncId);
-	int32 getSoundElapsedTimeInMs(int soundId);
-};
-
-} // End of namespace Scumm
-
-#endif
diff --git a/engines/scumm/imuse_digi/dimuse_bndmgr.cpp b/engines/scumm/imuse_digi/dimuse_bndmgr.cpp
index e7602528ad..8b115cc7cc 100644
--- a/engines/scumm/imuse_digi/dimuse_bndmgr.cpp
+++ b/engines/scumm/imuse_digi/dimuse_bndmgr.cpp
@@ -132,7 +132,7 @@ int BundleDirCache::matchFile(const char *filename) {
 			_budleDirCache[freeSlot].indexTable[i].index = i;
 		}
 		qsort(_budleDirCache[freeSlot].indexTable, _budleDirCache[freeSlot].numFiles,
-				sizeof(IndexNode), (int (*)(const void*, const void*))scumm_stricmp);
+				sizeof(IndexNode), (int (*)(const void *, const void *))scumm_stricmp);
 		return freeSlot;
 	} else {
 		return fileId;
@@ -145,6 +145,7 @@ BundleMgr::BundleMgr(BundleDirCache *cache) {
 	_compTable = nullptr;
 	_numFiles = 0;
 	_numCompItems = 0;
+	_lastBlockDecompressedSize = 0;
 	_curSampleId = -1;
 	_fileBundleId = -1;
 	_file = new ScummFile();
@@ -160,7 +161,7 @@ Common::SeekableReadStream *BundleMgr::getFile(const char *filename, int32 &offs
 	BundleDirCache::IndexNode target;
 	strcpy(target.filename, filename);
 	BundleDirCache::IndexNode *found = (BundleDirCache::IndexNode *)bsearch(&target, _indexTable, _numFiles,
-			sizeof(BundleDirCache::IndexNode), (int (*)(const void*, const void*))scumm_stricmp);
+			sizeof(BundleDirCache::IndexNode), (int (*)(const void *, const void *))scumm_stricmp);
 	if (found) {
 		_file->seek(_bundleTable[found->index].offset, SEEK_SET);
 		offset = _bundleTable[found->index].offset;
@@ -171,7 +172,7 @@ Common::SeekableReadStream *BundleMgr::getFile(const char *filename, int32 &offs
 	return nullptr;
 }
 
-bool BundleMgr::open(const char *filename, bool &compressed, bool errorFlag) {
+bool BundleMgr::open(const char *filename, bool &isCompressed, bool errorFlag) {
 	if (_file->isOpen())
 		return true;
 
@@ -186,7 +187,7 @@ bool BundleMgr::open(const char *filename, bool &compressed, bool errorFlag) {
 
 	int slot = _cache->matchFile(filename);
 	assert(slot != -1);
-	compressed = _cache->isSndDataExtComp(slot);
+	isCompressed = _cache->isSndDataExtComp(slot);
 	_numFiles = _cache->getNumFiles(slot);
 	assert(_numFiles);
 	_bundleTable = _cache->getTable(slot);
@@ -195,6 +196,8 @@ bool BundleMgr::open(const char *filename, bool &compressed, bool errorFlag) {
 	_compTableLoaded = false;
 	_isUncompressed = false;
 	_outputSize = 0;
+	_lastBlockDecompressedSize = 0;
+	_curDecompressedFilePos = 0;
 	_lastBlock = -1;
 
 	return true;
@@ -206,6 +209,8 @@ void BundleMgr::close() {
 		_bundleTable = nullptr;
 		_numFiles = 0;
 		_numCompItems = 0;
+		_lastBlockDecompressedSize = 0;
+		_curDecompressedFilePos = 0;
 		_compTableLoaded = false;
 		_isUncompressed = false;
 		_lastBlock = -1;
@@ -229,8 +234,8 @@ bool BundleMgr::loadCompTable(int32 index) {
 
 	_numCompItems = _file->readUint32BE();
 	assert(_numCompItems > 0);
-	_file->seek(8, SEEK_CUR);
-
+	_file->seek(4, SEEK_CUR);
+	_lastBlockDecompressedSize = _file->readUint32BE();
 	if (tag != MKTAG('C','O','M','P')) {
 		debug("BundleMgr::loadCompTable() Compressed sound %d (%s:%d) invalid (%s)", index, _file->getName(), _bundleTable[index].offset, tag2str(tag));
 		return false;
@@ -254,122 +259,165 @@ bool BundleMgr::loadCompTable(int32 index) {
 	return true;
 }
 
-int32 BundleMgr::decompressSampleByCurIndex(int32 offset, int32 size, byte **compFinal, int headerSize, bool headerOutside) {
-	bool ignored = false;
-	return decompressSampleByIndex(_curSampleId, offset, size, compFinal, headerSize, headerOutside, ignored);
+int32 BundleMgr::seekFile(int32 offset, int mode) {
+	// We don't actually seek the file, but instead try to find that the specified offset exists
+	// within the decompressed blocks, and save that offset in _curDecompressedFilePos
+	int result = 0;
+	switch (mode) {
+	case SEEK_END:
+		if (_isUncompressed) {
+			result = offset + _bundleTable[_curSampleId].size;
+		} else {
+			result = offset + ((_numCompItems - 1) * DIMUSE_BUN_CHUNK_SIZE) + _lastBlockDecompressedSize;
+		}
+		_curDecompressedFilePos = result;
+		break;
+	case SEEK_SET:
+	default:
+		if (_isUncompressed) {
+			result = offset;
+			_curDecompressedFilePos = result;
+		} else {
+			int destBlock = offset / DIMUSE_BUN_CHUNK_SIZE + (offset % DIMUSE_BUN_CHUNK_SIZE != 0);
+			if (destBlock <= _numCompItems) {
+				result = offset;
+				_curDecompressedFilePos = result;
+			}
+		}
+		break;
+	}
+	return result;
 }
 
-int32 BundleMgr::decompressSampleByIndex(int32 index, int32 offset, int32 size, byte **compFinal, int headerSize, bool headerOutside,
-					 bool &uncompressedBundle) {
-	int32 i, finalSize, outputSize;
-	int skip, firstBlock, lastBlock;
-
-	assert(0 <= index && index < _numFiles);
+int32 BundleMgr::readFile(const char *name, int32 size, byte **comp_final, bool header_outside) {
+	int32 final_size = 0;
 
-	if (_file->isOpen() == false) {
-		error("BundleMgr::decompressSampleByIndex() File is not open");
+	if (!_file->isOpen()) {
+		error("BundleMgr::readFile() File is not open");
 		return 0;
 	}
 
-	if (_curSampleId == -1)
-		_curSampleId = index;
+	// Find the sound in the bundle
+	BundleDirCache::IndexNode target;
+	strncpy(target.filename, name, sizeof(target.filename));
+	target.filename[sizeof(target.filename) - 1] = '\0';
+	BundleDirCache::IndexNode *found = (BundleDirCache::IndexNode *)bsearch(&target, _indexTable, _numFiles,
+		sizeof(BundleDirCache::IndexNode), (int(*)(const void *, const void *))scumm_stricmp);
 
-	assert(_curSampleId == index);
+	if (found) {
+		int32 i, finalSize, outputSize;
+		int skip, firstBlock, lastBlock;
+		int headerSize = 0;
+
+		assert(0 <= found->index && found->index < _numFiles);
 
-	if (!_compTableLoaded) {
-		_compTableLoaded = loadCompTable(index);
-		if (!_compTableLoaded)
+		if (_file->isOpen() == false) {
+			error("BundleMgr::readFile() File is not open");
 			return 0;
-	}
+		}
 
-	uncompressedBundle = _isUncompressed;
+		if (_curSampleId == -1)
+			_curSampleId = found->index;
 
-	if (_isUncompressed) {
-		_file->seek(_bundleTable[index].offset + offset + headerSize, SEEK_SET);
-		*compFinal = (byte *)malloc(size);
-		assert(*compFinal);
-		_file->read(*compFinal, size);
-		return size;
-	}
+		assert(_curSampleId == found->index);
 
-	firstBlock = (offset + headerSize) / 0x2000;
-	lastBlock = (offset + headerSize + size - 1) / 0x2000;
-
-	// Clip last_block by the total number of blocks (= "comp items")
-	if ((lastBlock >= _numCompItems) && (_numCompItems > 0))
-		lastBlock = _numCompItems - 1;
-
-	int32 blocksFinalSize = 0x2000 * (1 + lastBlock - firstBlock);
-	*compFinal = (byte *)malloc(blocksFinalSize);
-	assert(*compFinal);
-	finalSize = 0;
-
-	skip = (offset + headerSize) % 0x2000;
-
-	for (i = firstBlock; i <= lastBlock; i++) {
-		if (_lastBlock != i) {
-			// CMI hack: one more zero byte at the end of input buffer
-			_compInputBuff[_compTable[i].size] = 0;
-			_file->seek(_bundleTable[index].offset + _compTable[i].offset, SEEK_SET);
-			_file->read(_compInputBuff, _compTable[i].size);
-			_outputSize = BundleCodecs::decompressCodec(_compTable[i].codec, _compInputBuff, _compOutputBuff, _compTable[i].size);
-			if (_outputSize > 0x2000) {
-				error("_outputSize: %d", _outputSize);
-			}
-			_lastBlock = i;
+		if (!_compTableLoaded) {
+			_compTableLoaded = loadCompTable(found->index);
+			if (!_compTableLoaded)
+				return 0;
 		}
 
-		outputSize = _outputSize;
+		if (_isUncompressed) {
+			_file->seek(_bundleTable[found->index].offset + _curDecompressedFilePos + headerSize, SEEK_SET);
+			*comp_final = (byte *)malloc(size);
+			assert(*comp_final);
+			_file->read(*comp_final, size);
+			_curDecompressedFilePos += size;
+			return size;
+		}
 
-		if (headerOutside) {
-			outputSize -= skip;
-		} else {
-			if ((headerSize != 0) && (skip >= headerSize))
+		firstBlock = (_curDecompressedFilePos + headerSize) / DIMUSE_BUN_CHUNK_SIZE;
+		lastBlock = (_curDecompressedFilePos + headerSize + size - 1) / DIMUSE_BUN_CHUNK_SIZE;
+
+		// Clip last_block by the total number of blocks (= "comp items")
+		if ((lastBlock >= _numCompItems) && (_numCompItems > 0))
+			lastBlock = _numCompItems - 1;
+
+		int32 blocksFinalSize = DIMUSE_BUN_CHUNK_SIZE * (1 + lastBlock - firstBlock);
+		*comp_final = (byte *)malloc(blocksFinalSize);
+		assert(*comp_final);
+		finalSize = 0;
+
+		skip = (_curDecompressedFilePos + headerSize) % DIMUSE_BUN_CHUNK_SIZE; // Excess length after the last block
+
+		for (i = firstBlock; i <= lastBlock; i++) {
+			if (_lastBlock != i) {
+				// CMI hack: one more zero byte at the end of input buffer
+				_compInputBuff[_compTable[i].size] = 0;
+				_file->seek(_bundleTable[found->index].offset + _compTable[i].offset, SEEK_SET);
+				_file->read(_compInputBuff, _compTable[i].size);
+				_outputSize = BundleCodecs::decompressCodec(_compTable[i].codec, _compInputBuff, _compOutputBuff, _compTable[i].size);
+
+				if (_outputSize > DIMUSE_BUN_CHUNK_SIZE) {
+					error("_outputSize: %d", _outputSize);
+				}
+				_lastBlock = i;
+			}
+
+			outputSize = _outputSize;
+
+			if (header_outside) {
 				outputSize -= skip;
-		}
+			} else {
+				if ((headerSize != 0) && (skip >= headerSize))
+					outputSize -= skip;
+			}
 
-		if ((outputSize + skip) > 0x2000) // workaround
-			outputSize -= (outputSize + skip) - 0x2000;
+			if ((outputSize + skip) > DIMUSE_BUN_CHUNK_SIZE) // workaround
+				outputSize -= (outputSize + skip) - DIMUSE_BUN_CHUNK_SIZE;
 
-		if (outputSize > size)
-			outputSize = size;
+			if (outputSize > size)
+				outputSize = size;
 
-		assert(finalSize + outputSize <= blocksFinalSize);
+			assert(finalSize + outputSize <= blocksFinalSize);
 
-		memcpy(*compFinal + finalSize, _compOutputBuff + skip, outputSize);
-		finalSize += outputSize;
+			memcpy(*comp_final + finalSize, _compOutputBuff + skip, outputSize);
+			finalSize += outputSize;
 
-		size -= outputSize;
-		assert(size >= 0);
-		if (size == 0)
-			break;
+			size -= outputSize;
+			assert(size >= 0);
+			if (size == 0)
+				break;
+
+			skip = 0;
+		}
+		_curDecompressedFilePos += finalSize;
 
-		skip = 0;
+		return finalSize;
 	}
 
-	return finalSize;
+	debug(2, "BundleMgr::readFile() Failed finding sound %s", name);
+	return final_size;
 }
 
-int32 BundleMgr::decompressSampleByName(const char *name, int32 offset, int32 size, byte **comp_final, bool header_outside,
-					bool &uncompressedBundle) {
-	int32 final_size = 0;
-
-	if (!_file->isOpen()) {
-		error("BundleMgr::decompressSampleByName() File is not open");
-		return 0;
-	}
+bool BundleMgr::isExtCompBun(byte gameId) {
+	bool isExtComp = false;
+	if (gameId == GID_CMI) {
+		bool isExtComp1 = false, isExtComp2 = false, isExtComp3 = false, isExtComp4 = false;
+		this->open("voxdisk1.bun", isExtComp1); this->close();
+		this->open("voxdisk2.bun", isExtComp2); this->close();
+		this->open("musdisk1.bun", isExtComp3); this->close();
+		this->open("musdisk2.bun", isExtComp4); this->close();
 
-	BundleDirCache::IndexNode target;
-	strcpy(target.filename, name);
-	BundleDirCache::IndexNode *found = (BundleDirCache::IndexNode *)bsearch(&target, _indexTable, _numFiles,
-			sizeof(BundleDirCache::IndexNode), (int (*)(const void*, const void*))scumm_stricmp);
-	if (found) {
-		final_size = decompressSampleByIndex(found->index, offset, size, comp_final, 0, header_outside, uncompressedBundle);
-		return final_size;
+		isExtComp = isExtComp1 | isExtComp2 | isExtComp3 | isExtComp4;
+	} else {
+		bool isExtComp1 = false, isExtComp2 = false;
+		this->open("digvoice.bun", isExtComp1); this->close();
+		this->open("digmusic.bun", isExtComp2); this->close();
+		isExtComp = isExtComp1 | isExtComp2;
 	}
 
-	debug(2, "BundleMgr::decompressSampleByName() Failed finding sound %s", name);
-	return final_size;
+	return isExtComp;
 }
 
 } // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_bndmgr.h b/engines/scumm/imuse_digi/dimuse_bndmgr.h
index ca7e434bf2..0a45db9f2e 100644
--- a/engines/scumm/imuse_digi/dimuse_bndmgr.h
+++ b/engines/scumm/imuse_digi/dimuse_bndmgr.h
@@ -25,6 +25,7 @@
 
 #include "common/scummsys.h"
 #include "common/file.h"
+#include "scumm/imuse_digi/dimuse_defs.h"
 
 namespace Scumm {
 
@@ -81,7 +82,9 @@ private:
 
 	int _numFiles;
 	int _numCompItems;
+	int _lastBlockDecompressedSize;
 	int _curSampleId;
+	int _curDecompressedFilePos;
 	BaseScummFile *_file;
 	bool _compTableLoaded;
 	bool _isUncompressed;
@@ -90,7 +93,6 @@ private:
 	byte *_compInputBuff;
 	int _outputSize;
 	int _lastBlock;
-
 	bool loadCompTable(int32 index);
 
 public:
@@ -98,12 +100,12 @@ public:
 	BundleMgr(BundleDirCache *_cache);
 	~BundleMgr();
 
-	bool open(const char *filename, bool &compressed, bool errorFlag = false);
+	bool open(const char *filename, bool &isCompressed, bool errorFlag = false);
 	void close();
 	Common::SeekableReadStream *getFile(const char *filename, int32 &offset, int32 &size);
-	int32 decompressSampleByName(const char *name, int32 offset, int32 size, byte **compFinal, bool headerOutside, bool &uncompressedBundle);
-	int32 decompressSampleByIndex(int32 index, int32 offset, int32 size, byte **compFinal, int header_size, bool headerOutside, bool &uncompressedBundle);
-	int32 decompressSampleByCurIndex(int32 offset, int32 size, byte **compFinal, int headerSize, bool headerOutside);
+	int32 seekFile(int32 offset, int size);
+	int32 readFile(const char *name, int32 size, byte **compFinal, bool headerOutside);
+	bool isExtCompBun(byte gameId);
 };
 
 } // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_cmds.cpp b/engines/scumm/imuse_digi/dimuse_cmds.cpp
new file mode 100644
index 0000000000..cd88878acc
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_cmds.cpp
@@ -0,0 +1,271 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/imuse_digi/dimuse_engine.h"
+
+namespace Scumm {
+
+// We have some uintptr arguments as commands 28, 29 and 30 actually require pointer arguments
+// Unfortunately this makes function calls for other command a little less pretty...
+int IMuseDigital::cmdsHandleCmd(int cmd, uint8 *ptr, int a, int b, int c, int d, int e,
+	int f, int g, int h, int i, int j, int k, int l, int m, int n) {
+
+	// Convert the character constant (single quotes '') to string
+	char marker[5];
+	if (!_isEarlyDiMUSE && (cmd == 17 || cmd == 18 || cmd == 19)) {
+		for (int index = 0; index < 4; index++) {
+#if defined SCUMM_BIG_ENDIAN
+			marker[index] = (b >> (8 * index)) & 0xff;
+#elif defined SCUMM_LITTLE_ENDIAN
+			marker[3 - index] = (b >> (8 * index)) & 0xff;
+#endif
+		}
+		marker[4] = '\0';
+	}
+
+	switch (cmd) {
+	case 0:
+		return cmdsInit();
+	case 3:
+		return cmdsPause();
+	case 4:
+		return cmdsResume();
+	case 7:
+		_groupsHandler->setGroupVol(a, b);
+		break;
+	case 8:
+		cmdsStartSound(a, b);
+		break;
+	case 9:
+		cmdsStopSound(a);
+		break;
+	case 10:
+		cmdsStopAllSounds();
+		break;
+	case 11:
+		return cmdsGetNextSound(a);
+	case 12:
+		cmdsSetParam(a, b, c);
+		break;
+	case 13:
+		return cmdsGetParam(a, b);
+	case 14:
+		return _fadesHandler->fadeParam(a, b, c, d);
+	case 15:
+		return cmdsSetHook(a, b);
+	case 16:
+		return cmdsGetHook(a);
+	case 17:
+		return _triggersHandler->setTrigger(a, marker, c, d, e, f, g, h, i, j, k, l, m, n);
+	case 18:
+		return _triggersHandler->checkTrigger(a, marker, c);
+	case 19:
+		return _triggersHandler->clearTrigger(a, marker, c);
+	case 20:
+		return _triggersHandler->deferCommand(a, b, c, d, e, f, g, h, i, j, k, l, m, n);
+	case 25:
+		return waveStartStream(a, b, c);
+	case 26:
+		if (_isEarlyDiMUSE)
+			return waveSwitchStream(a, b, ptr, d, e);
+		else
+			return waveSwitchStream(a, b, c, d, e);
+	case 27:
+		return waveProcessStreams();
+	case 29:
+		return waveFeedStream(a, ptr, c, d);
+	default:
+		debug(5, "IMuseDigital::cmdsHandleCmd(): bogus/unused opcode ignored (%d).", cmd);
+		return -1;
+	}
+
+	return 0;
+}
+
+int IMuseDigital::cmdsInit() {
+	_cmdsRunning60HzCount = 0;
+	_cmdsRunning10HzCount = 0;
+
+	if (_groupsHandler->init() || _fadesHandler->init() ||
+		_triggersHandler->init() || waveInit()) {
+		return -1;
+	}
+
+	_cmdsPauseCount = 0;
+	return 48;
+}
+
+int IMuseDigital::cmdsDeinit() {
+	waveTerminate();
+	waveOutDeinit();
+	_triggersHandler->deinit();
+	_fadesHandler->deinit();
+	_cmdsPauseCount = 0;
+	_cmdsRunning60HzCount = 0;
+	_cmdsRunning10HzCount = 0;
+
+	return 0;
+}
+
+int IMuseDigital::cmdsTerminate() {
+	return 0;
+}
+
+int IMuseDigital::cmdsPause() {
+	int result = 0;
+
+	if (_cmdsPauseCount == 0) {
+		result = wavePause();
+	}
+
+	if (!result) {
+		result = _cmdsPauseCount + 1;
+	}
+	_cmdsPauseCount++;
+
+	return result;
+}
+
+int IMuseDigital::cmdsResume() {
+	int result = 0;
+
+	if (_cmdsPauseCount == 1) {
+		result = waveResume();
+	}
+
+	if (_cmdsPauseCount != 0) {
+		_cmdsPauseCount--;
+	}
+
+	if (!result) {
+		result = _cmdsPauseCount;
+	}
+
+	return result;
+}
+
+void IMuseDigital::cmdsSaveLoad(Common::Serializer &ser) {
+	// Serialize in this order:
+	// - Open files
+	// - Fades
+	// - Triggers
+	// - Pass the control to waveSaveLoad and then tracksSaveLoad, which will serialize:
+	//     - Dispatches
+	//     - Tracks (with SYNCs, if the game is COMI)
+	// - State and sequence info
+	// - Attributes
+	// - Full Throttle's music cue ID
+
+	_filesHandler->saveLoad(ser);
+	_fadesHandler->saveLoad(ser);
+	_triggersHandler->saveLoad(ser);
+	waveSaveLoad(ser);
+	ser.syncAsSint32LE(_curMusicState, VER(103));
+	ser.syncAsSint32LE(_curMusicSeq, VER(103));
+	ser.syncAsSint32LE(_nextSeqToPlay, VER(103));
+	ser.syncAsByte(_radioChatterSFX, VER(103));
+	ser.syncArray(_attributes, 188, Common::Serializer::Sint32LE, VER(103));
+	ser.syncAsSint32LE(_curMusicCue, VER(103));
+}
+
+int IMuseDigital::cmdsStartSound(int soundId, int priority) {
+	uint8 *src = _filesHandler->getSoundAddrData(soundId);
+
+	if (src == nullptr) {
+		debug(5, "IMuseDigital::cmdsStartSound(): ERROR: resource address for sound %d is NULL", soundId);
+		return -1;
+	}
+
+	// Check for the "Creative Voice File" header
+	if (_isEarlyDiMUSE && READ_BE_UINT32(src) == MKTAG('C', 'r', 'e', 'a'))
+		return waveStartSound(soundId, priority);
+
+	// Check for the "iMUS" header
+	if (READ_BE_UINT32(src) == MKTAG('i', 'M', 'U', 'S'))
+		return waveStartSound(soundId, priority);
+
+	return -1;
+}
+
+int IMuseDigital::cmdsStopSound(int soundId) {
+	int result = _filesHandler->getNextSound(soundId);
+
+	if (result != 2)
+		return -1;
+
+	return waveStopSound(soundId);
+}
+
+int IMuseDigital::cmdsStopAllSounds() {
+	return _triggersHandler->clearAllTriggers() | waveStopAllSounds();
+}
+
+int IMuseDigital::cmdsGetNextSound(int soundId) {
+	return waveGetNextSound(soundId);
+}
+
+int IMuseDigital::cmdsSetParam(int soundId, int subCmd, int value) {
+	int result = _filesHandler->getNextSound(soundId);
+
+	if (result != 2)
+		return -1;
+
+	return waveSetParam(soundId, subCmd, value);
+}
+
+int IMuseDigital::cmdsGetParam(int soundId, int subCmd) {
+	int result = _filesHandler->getNextSound(soundId);
+
+	if (subCmd != 0) {
+		if (subCmd == DIMUSE_P_TRIGS_SNDS) {
+			return _triggersHandler->countPendingSounds(soundId);
+		}
+
+		if (result == 2) {
+			return waveGetParam(soundId, subCmd);
+		}
+
+		result = (subCmd == DIMUSE_P_SND_TRACK_NUM) - 1;
+	}
+
+	return result;
+}
+
+int IMuseDigital::cmdsSetHook(int soundId, int hookId) {
+	int result = _filesHandler->getNextSound(soundId);
+
+	if (result != 2)
+		return -1;
+
+	return waveSetHook(soundId, hookId);
+}
+
+int IMuseDigital::cmdsGetHook(int soundId) {
+	int result = _filesHandler->getNextSound(soundId);
+
+	if (result != 2)
+		return -1;
+
+	return waveGetHook(soundId);
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_codecs.cpp b/engines/scumm/imuse_digi/dimuse_codecs.cpp
index 44c57a6bb7..7d7763137c 100644
--- a/engines/scumm/imuse_digi/dimuse_codecs.cpp
+++ b/engines/scumm/imuse_digi/dimuse_codecs.cpp
@@ -31,25 +31,6 @@ namespace Scumm {
 
 namespace BundleCodecs {
 
-uint32 decode12BitsSample(const byte *src, byte **dst, uint32 size) {
-	uint32 loop_size = size / 3;
-	uint32 s_size = loop_size * 4;
-	byte *ptr = *dst = (byte *)malloc(s_size);
-	assert(ptr);
-
-	uint32 tmp;
-	while (loop_size--) {
-		byte v1 = *src++;
-		byte v2 = *src++;
-		byte v3 = *src++;
-		tmp = ((((v2 & 0x0f) << 8) | v1) << 4) - 0x8000;
-		WRITE_BE_UINT16(ptr, tmp); ptr += 2;
-		tmp = ((((v2 & 0xf0) << 4) | v3) << 4) - 0x8000;
-		WRITE_BE_UINT16(ptr, tmp); ptr += 2;
-	}
-	return s_size;
-}
-
 /*
  * The "IMC" codec below (see cases 13 & 15 in decompressCodec) is actually a
  * variant of the IMA codec, see also
@@ -305,7 +286,13 @@ int32 decompressADPCM(byte *compInput, byte *compOutput, int channels) {
 
 			// Clip outputWord to 16 bit signed, and write it into the destination stream
 			outputWord = CLIP<int32>(outputWord, -0x8000, 0x7fff);
-			WRITE_BE_UINT16(dst + destPos, outputWord);
+
+			// This is being written as-is (LE), without concerns regarding endianness:
+			// this is because the internal DiMUSE mixer handles the data in LE format,
+			// and we'll convert data to the appropriate format using the QueuingAudioStream flags
+			// when flushing the final audio data to the output stream (see IMuseDigital::waveOutWrite())
+			WRITE_UINT16(dst + destPos, outputWord);
+
 			destPos += channels << 1;
 
 			// Adjust the curTablePos
diff --git a/engines/scumm/imuse_digi/dimuse_codecs.h b/engines/scumm/imuse_digi/dimuse_codecs.h
index 163d0250fa..01b9c88c0d 100644
--- a/engines/scumm/imuse_digi/dimuse_codecs.h
+++ b/engines/scumm/imuse_digi/dimuse_codecs.h
@@ -29,7 +29,6 @@ namespace Scumm {
 
 namespace BundleCodecs {
 
-uint32 decode12BitsSample(const byte *src, byte **dst, uint32 size);
 int32 decompressCodec(int32 codec, byte *compInput, byte *compOutput, int32 inputSize);
 
 void initializeImcTables();
diff --git a/engines/scumm/imuse_digi/dimuse_defs.h b/engines/scumm/imuse_digi/dimuse_defs.h
new file mode 100644
index 0000000000..56b8bb2245
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_defs.h
@@ -0,0 +1,215 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#if !defined(SCUMM_IMUSE_DIGI_DEFS_H) && defined(ENABLE_SCUMM_7_8)
+#define SCUMM_IMUSE_DIGI_DEFS_H
+
+namespace Scumm {
+
+#define DIMUSE_MAX_GROUPS      16
+#define DIMUSE_MAX_FADES       16
+#define DIMUSE_MAX_TRIGGERS    8
+#define DIMUSE_MAX_DEFERS      8
+#define DIMUSE_MAX_TRACKS      8
+#define DIMUSE_MAX_MAP_SIZE    2048
+#define DIMUSE_MAX_DISPATCHES  8
+#define DIMUSE_MAX_STREAMZONES 50
+#define DIMUSE_MAX_FADE_VOLUME 8323072
+#define DIMUSE_MAX_STREAMS     3
+
+#define DIMUSE_LARGE_FADES    1
+#define DIMUSE_SMALL_FADES    4
+#define DIMUSE_LARGE_FADE_DIM 350000
+#define DIMUSE_SMALL_FADE_DIM 44100
+
+#define DIMUSE_SAMPLERATE     22050
+#define DIMUSE_NUM_WAVE_BUFS  8
+#define DIMUSE_SMUSH_SOUNDID  12345678
+#define DIMUSE_BUN_CHUNK_SIZE 0x2000
+#define DIMUSE_GROUP_SFX      1
+#define DIMUSE_GROUP_SPEECH   2
+#define DIMUSE_GROUP_MUSIC    3
+#define DIMUSE_GROUP_MUSICEFF 4
+#define DIMUSE_BUFFER_SPEECH  1
+#define DIMUSE_BUFFER_MUSIC   2
+#define DIMUSE_BUFFER_SFX     3
+
+// Parameters IDs
+#define DIMUSE_P_BOGUS_ID       0x0
+#define DIMUSE_P_SND_TRACK_NUM  0x100
+#define DIMUSE_P_TRIGS_SNDS     0x200
+#define DIMUSE_P_MARKER         0x300
+#define DIMUSE_P_GROUP          0x400
+#define DIMUSE_P_PRIORITY       0x500
+#define DIMUSE_P_VOLUME         0x600
+#define DIMUSE_P_PAN            0x700
+#define DIMUSE_P_DETUNE         0x800
+#define DIMUSE_P_TRANSPOSE      0x900
+#define DIMUSE_P_MAILBOX        0xA00
+#define DIMUSE_P_UNKNOWN        0xF00
+#define DIMUSE_P_SND_HAS_STREAM 0x1800
+#define DIMUSE_P_STREAM_BUFID   0x1900
+#define DIMUSE_P_SND_POS_IN_MS  0x1A00
+
+struct IMuseDigiDispatch;
+struct IMuseDigiTrack;
+struct IMuseDigiStreamZone;
+
+typedef struct {
+	int sound;
+	char text[256];
+	int opcode;
+	int a;
+	int b;
+	int c;
+	int d;
+	int e;
+	int f;
+	int g;
+	int h;
+	int i;
+	int j;
+	int clearLater;
+} IMuseDigiTrigger;
+
+typedef struct {
+	int counter;
+	int opcode;
+	int a;
+	int b;
+	int c;
+	int d;
+	int e;
+	int f;
+	int g;
+	int h;
+	int i;
+	int j;
+} IMuseDigiDefer;
+
+typedef struct {
+	int status;
+	int sound;
+	int param;
+	int currentVal;
+	int counter;
+	int length;
+	int slope;
+	int slopeMod;
+	int modOvfloCounter;
+	int nudge;
+} IMuseDigiFade;
+
+struct IMuseDigiTrack {
+	IMuseDigiTrack *prev;
+	IMuseDigiTrack *next;
+	IMuseDigiDispatch *dispatchPtr;
+	int soundId;
+	int marker;
+	int group;
+	int priority;
+	int vol;
+	int effVol;
+	int pan;
+	int detune;
+	int transpose;
+	int pitchShift;
+	int mailbox;
+	int jumpHook;
+	int32 syncSize_0;
+	byte *syncPtr_0;
+	int32 syncSize_1;
+	byte *syncPtr_1;
+	int32 syncSize_2;
+	byte *syncPtr_2;
+	int32 syncSize_3;
+	byte *syncPtr_3;
+};
+
+struct IMuseDigiStreamZone {
+	IMuseDigiStreamZone *prev;
+	IMuseDigiStreamZone *next;
+	int useFlag;
+	int32 offset;
+	int32 size;
+	int fadeFlag;
+};
+
+typedef struct {
+	int soundId;
+	int32 curOffset;
+	int32 endOffset;
+	int bufId;
+	uint8 *buf;
+	int32 bufFreeSize;
+	int32 loadSize;
+	int32 criticalSize;
+	int32 maxRead;
+	int32 loadIndex;
+	int32 readIndex;
+	int paused;
+	int vocLoopFlag;
+	int32 vocLoopTriggerOffset;
+} IMuseDigiStream;
+
+typedef struct {
+	uint8 *buffer;
+	int32 bufSize;
+	int32 loadSize;
+	int32 criticalSize;
+} IMuseDigiSndBuffer;
+
+struct IMuseDigiDispatch {
+	IMuseDigiTrack *trackPtr;
+	int wordSize;
+	int sampleRate;
+	int channelCount;
+	int32 currentOffset;
+	int32 audioRemaining;
+	int32 map[DIMUSE_MAX_MAP_SIZE];
+	IMuseDigiStream *streamPtr;
+	int streamBufID;
+	IMuseDigiStreamZone *streamZoneList;
+	int streamErrFlag;
+	uint8 *fadeBuf;
+	int32 fadeOffset;
+	int32 fadeRemaining;
+	int fadeWordSize;
+	int fadeSampleRate;
+	int fadeChannelCount;
+	int fadeSyncFlag;
+	int32 fadeSyncDelta;
+	int fadeVol;
+	int fadeSlope;
+	int32 vocLoopStartingPoint;
+};
+
+typedef struct {
+	int bytesPerSample;
+	int numChannels;
+	uint8 *mixBuf;
+	int mixBufSize;
+	int sizeSampleKB;
+} waveOutParamsStruct;
+
+} // End of namespace Scumm
+#endif
diff --git a/engines/scumm/imuse_digi/dimuse_dispatch.cpp b/engines/scumm/imuse_digi/dimuse_dispatch.cpp
new file mode 100644
index 0000000000..5f18a4646b
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_dispatch.cpp
@@ -0,0 +1,1743 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/imuse_digi/dimuse_engine.h"
+#include "scumm/imuse_digi/dimuse_defs.h"
+
+namespace Scumm {
+
+int IMuseDigital::dispatchInit() {
+	_dispatchBuffer = (uint8 *)malloc(DIMUSE_SMALL_FADES * DIMUSE_SMALL_FADE_DIM + DIMUSE_LARGE_FADE_DIM * DIMUSE_LARGE_FADES);
+
+	if (_dispatchBuffer) {
+		_dispatchLargeFadeBufs = _dispatchBuffer;
+		_dispatchSmallFadeBufs = _dispatchBuffer + (DIMUSE_LARGE_FADE_DIM * DIMUSE_LARGE_FADES);
+
+		for (int i = 0; i < DIMUSE_LARGE_FADES; i++) {
+			_dispatchLargeFadeFlags[i] = 0;
+		}
+
+		for (int i = 0; i < DIMUSE_SMALL_FADES; i++) {
+			_dispatchSmallFadeFlags[i] = 0;
+		}
+
+		for (int i = 0; i < DIMUSE_MAX_STREAMZONES; i++) {
+			_streamZones[i].useFlag = 0;
+			_streamZones[i].fadeFlag = 0;
+			_streamZones[i].prev = nullptr;
+			_streamZones[i].next = nullptr;
+			_streamZones[i].size = 0;
+			_streamZones[i].offset = 0;
+		}
+
+		for (int i = 0; i < DIMUSE_MAX_DISPATCHES; i++) {
+			_dispatches[i].trackPtr = nullptr;
+			_dispatches[i].wordSize = 0;
+			_dispatches[i].sampleRate = 0;
+			_dispatches[i].channelCount = 0;
+			_dispatches[i].currentOffset = 0;
+			_dispatches[i].audioRemaining = 0;
+			memset(_dispatches[i].map, 0, sizeof(_dispatches[i].map));
+			_dispatches[i].streamPtr = nullptr;
+			_dispatches[i].streamBufID = 0;
+			_dispatches[i].streamZoneList = nullptr;
+			_dispatches[i].streamErrFlag = 0;
+			_dispatches[i].fadeBuf = nullptr;
+			_dispatches[i].fadeOffset = 0;
+			_dispatches[i].fadeRemaining = 0;
+			_dispatches[i].fadeWordSize = 0;
+			_dispatches[i].fadeSampleRate = 0;
+			_dispatches[i].fadeChannelCount	= 0;
+			_dispatches[i].fadeSyncFlag = 0;
+			_dispatches[i].fadeSyncDelta = 0;
+			_dispatches[i].fadeVol = 0;
+			_dispatches[i].fadeSlope = 0;
+			_dispatches[i].vocLoopStartingPoint = 0;
+		}
+
+	} else {
+		debug(5, "IMuseDigital::dispatchInit(): ERROR: couldn't allocate buffers\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+IMuseDigiDispatch *IMuseDigital::dispatchGetDispatchByTrackId(int trackId) {
+	return &_dispatches[trackId];
+}
+
+void IMuseDigital::dispatchSaveLoad(Common::Serializer &ser) {
+
+	for (int l = 0; l < DIMUSE_MAX_DISPATCHES; l++) {
+		ser.syncAsSint32LE(_dispatches[l].wordSize, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].sampleRate, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].channelCount, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].currentOffset, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].audioRemaining, VER(103));
+		ser.syncArray(_dispatches[l].map, 2048, Common::Serializer::Sint32LE, VER(103));
+
+		// This is only needed to signal if the sound originally had a stream associated with it
+		int hasStream = 0;
+		if (ser.isSaving()) {
+			if (_dispatches[l].streamPtr)
+				hasStream = 1;
+			ser.syncAsSint32LE(hasStream, VER(103));
+		} else {
+			ser.syncAsSint32LE(hasStream, VER(103));
+			_dispatches[l].streamPtr = hasStream ? (IMuseDigiStream *)1 : nullptr;
+		}
+
+		ser.syncAsSint32LE(_dispatches[l].streamBufID, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].streamErrFlag, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].fadeOffset, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].fadeRemaining, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].fadeWordSize, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].fadeSampleRate, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].fadeChannelCount, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].fadeSyncFlag, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].fadeSyncDelta, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].fadeVol, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].fadeSlope, VER(103));
+		ser.syncAsSint32LE(_dispatches[l].vocLoopStartingPoint, VER(103));
+	}
+
+	if (ser.isLoading()) {
+		for (int i = 0; i < DIMUSE_LARGE_FADES; i++) {
+			_dispatchLargeFadeFlags[i] = 0;
+		}
+
+		for (int i = 0; i < DIMUSE_SMALL_FADES; i++) {
+			_dispatchSmallFadeFlags[i] = 0;
+		}
+
+		for (int i = 0; i < DIMUSE_MAX_STREAMZONES; i++) {
+			_streamZones[i].useFlag = 0;
+		}
+	}
+}
+
+int IMuseDigital::dispatchRestoreStreamZones() {
+	IMuseDigiDispatch *curDispatchPtr;
+	IMuseDigiStreamZone *curStreamZone;
+	int32 sizeToFeed = _isEarlyDiMUSE ? 0x800 : 0x4000;
+
+	curDispatchPtr = _dispatches;
+	for (int i = 0; i < _trackCount; i++) {
+		curDispatchPtr = &_dispatches[i];
+		curDispatchPtr->fadeBuf = nullptr;
+
+		if (curDispatchPtr->trackPtr->soundId && curDispatchPtr->streamPtr) {
+			// Try allocating the stream
+			curDispatchPtr->streamPtr = streamerAllocateSound(curDispatchPtr->trackPtr->soundId, curDispatchPtr->streamBufID, sizeToFeed);
+
+			if (curDispatchPtr->streamPtr) {
+				streamerSetSoundToStreamFromOffset(curDispatchPtr->streamPtr, curDispatchPtr->trackPtr->soundId, curDispatchPtr->currentOffset);
+
+				if (_isEarlyDiMUSE) {
+					if (curDispatchPtr->vocLoopStartingPoint)
+						streamerSetLoopFlag(curDispatchPtr->streamPtr, curDispatchPtr->currentOffset + curDispatchPtr->audioRemaining);
+				} else if (curDispatchPtr->audioRemaining) {
+					// Try allocating the first streamZone of the dispatch
+					curStreamZone = dispatchAllocateStreamZone();
+					curDispatchPtr->streamZoneList = curStreamZone;
+
+					if (curStreamZone) {
+						curStreamZone->offset = curDispatchPtr->currentOffset;
+						curStreamZone->size = 0;
+						curStreamZone->fadeFlag = 0;
+					} else {
+						debug(5, "IMuseDigital::dispatchRestoreStreamZones(): unable to allocate streamZone during restore");
+					}
+				}
+			} else {
+				debug(5, "IMuseDigital::dispatchRestoreStreamZones(): unable to start stream during restore");
+			}
+		}
+	}
+	return 0;
+}
+
+int IMuseDigital::dispatchAllocateSound(IMuseDigiTrack *trackPtr, int groupId) {
+	IMuseDigiDispatch *trackDispatch;
+	IMuseDigiDispatch *dispatchToDeallocate;
+	IMuseDigiStreamZone *streamZoneList;
+	int navigateMapResult;
+	int32 sizeToFeed = _isEarlyDiMUSE ? 0x800 : 0x4000;
+
+	trackDispatch = trackPtr->dispatchPtr;
+	trackDispatch->currentOffset = 0;
+	trackDispatch->audioRemaining = 0;
+	trackDispatch->fadeBuf = 0;
+
+	if (_isEarlyDiMUSE) {
+		trackDispatch->vocLoopStartingPoint = 0;
+	} else {
+		memset(trackDispatch->map, 0, sizeof(trackDispatch->map));
+	}
+
+	if (groupId) {
+		trackDispatch->streamPtr = streamerAllocateSound(trackPtr->soundId, groupId, sizeToFeed);
+		trackDispatch->streamBufID = groupId;
+
+		if (!trackDispatch->streamPtr) {
+			debug(5, "IMuseDigital::dispatchAllocateSound(): unable to allocate stream for sound %d", trackPtr->soundId);
+			return -1;
+		}
+
+		if (_isEarlyDiMUSE)
+			return 0;
+
+		trackDispatch->streamZoneList = 0;
+		trackDispatch->streamErrFlag = 0;
+	} else {
+		trackDispatch->streamPtr = 0;
+		if (_isEarlyDiMUSE)
+			return dispatchSeekToNextChunk(trackDispatch);
+	}
+
+	navigateMapResult = dispatchNavigateMap(trackDispatch);
+	if (!navigateMapResult || navigateMapResult == -3)
+		return 0;
+
+	// At this point, something went wrong, so deallocate what we have to...
+	debug(5, "IMuseDigital::dispatchAllocateSound(): problem starting sound (%d) in dispatch", trackPtr->soundId);
+
+	// Remove streamZones from list
+	dispatchToDeallocate = trackDispatch->trackPtr->dispatchPtr;
+	if (dispatchToDeallocate->streamPtr) {
+		streamZoneList = dispatchToDeallocate->streamZoneList;
+		streamerClearSoundInStream(dispatchToDeallocate->streamPtr);
+		if (dispatchToDeallocate->streamZoneList) {
+			do {
+				streamZoneList->useFlag = 0;
+				removeStreamZoneFromList(&dispatchToDeallocate->streamZoneList, streamZoneList);
+			} while (streamZoneList);
+		}
+	}
+
+	if (!dispatchToDeallocate->fadeBuf)
+		return -1;
+
+	// Mark the fade corresponding to our fadeBuf as unused
+	dispatchDeallocateFade(dispatchToDeallocate, "dispatchAllocateSound");
+	return -1;
+}
+
+int IMuseDigital::dispatchRelease(IMuseDigiTrack *trackPtr) {
+	IMuseDigiDispatch *dispatchToDeallocate;
+	IMuseDigiStreamZone *streamZoneList;
+
+	dispatchToDeallocate = trackPtr->dispatchPtr;
+
+	// Remove streamZones from list
+	if (dispatchToDeallocate->streamPtr) {
+		streamerClearSoundInStream(dispatchToDeallocate->streamPtr);
+
+		if (_isEarlyDiMUSE)
+			return 0;
+
+		streamZoneList = dispatchToDeallocate->streamZoneList;
+		if (dispatchToDeallocate->streamZoneList) {
+			do {
+				streamZoneList->useFlag = 0;
+				removeStreamZoneFromList(&dispatchToDeallocate->streamZoneList, streamZoneList);
+			} while (dispatchToDeallocate->streamZoneList);
+		}
+	}
+
+	if (!dispatchToDeallocate->fadeBuf)
+		return 0;
+
+	// Mark the fade corresponding to our fadeBuf as unused
+	dispatchDeallocateFade(dispatchToDeallocate, "dispatchRelease");
+	return 0;
+}
+
+int IMuseDigital::dispatchSwitchStream(int oldSoundId, int newSoundId, int fadeLength, int unusedFadeSyncFlag, int offsetFadeSyncFlag) {
+	int32 effFadeLen, effFadeSize, strZnSize;
+	int getMapResult, i;
+	IMuseDigiDispatch *curDispatch = _dispatches;
+
+	effFadeLen = fadeLength;
+
+	if (fadeLength > 2000)
+		effFadeLen = 2000;
+
+	for (i = 0; i < _trackCount; i++) {
+		curDispatch = &_dispatches[i];
+		if (oldSoundId && curDispatch->trackPtr->soundId == oldSoundId && curDispatch->streamPtr) {
+			break;
+		}
+	}
+
+	if (i >= _trackCount) {
+		debug(5, "IMuseDigital::dispatchSwitchStream(): couldn't find sound, index went past _trackCount (%d)", _trackCount);
+		return -1;
+	}
+
+	if (curDispatch->streamZoneList) {
+		if (!curDispatch->wordSize) {
+			debug(5, "IMuseDigital::dispatchSwitchStream(): found streamZoneList but NULL wordSize");
+			return -1;
+		}
+
+		if (curDispatch->fadeBuf) {
+			// Mark the fade corresponding to our fadeBuf as unused
+			dispatchDeallocateFade(curDispatch, "dispatchSwitchStream");
+		}
+
+		_dispatchFadeSize = dispatchGetFadeSize(curDispatch, effFadeLen);
+
+		strZnSize = curDispatch->streamZoneList->size;
+		if (strZnSize >= _dispatchFadeSize)
+			strZnSize = _dispatchFadeSize;
+		_dispatchFadeSize = strZnSize;
+
+		// Validate and adjust the fade dispatch size further;
+		// this should correctly align the dispatch size to avoid starting a fade without
+		// inverting the stereo image by mistake
+		dispatchValidateFadeSize(curDispatch, _dispatchFadeSize, "dispatchSwitchStream");
+
+		curDispatch->fadeBuf = dispatchAllocateFade(_dispatchFadeSize, "dispatchSwitchStream");
+
+		// If we were able to allocate a fade, set up a fade out for the old sound.
+		// We'll copy data from the stream buffer to the fade buffer
+		if (curDispatch->fadeBuf) {
+			curDispatch->fadeOffset = 0;
+			curDispatch->fadeRemaining = 0;
+			curDispatch->fadeWordSize = curDispatch->wordSize;
+			curDispatch->fadeSampleRate = curDispatch->sampleRate;
+			curDispatch->fadeChannelCount = curDispatch->channelCount;
+			curDispatch->fadeSyncFlag = unusedFadeSyncFlag | offsetFadeSyncFlag;
+			curDispatch->fadeSyncDelta = 0;
+			curDispatch->fadeVol = DIMUSE_MAX_FADE_VOLUME;
+			curDispatch->fadeSlope = 0;
+
+			while (curDispatch->fadeRemaining < _dispatchFadeSize) {
+				effFadeSize = _dispatchFadeSize - curDispatch->fadeRemaining;
+				if ((_dispatchFadeSize - curDispatch->fadeRemaining) >= 0x4000)
+					effFadeSize = 0x4000;
+
+				memcpy(&curDispatch->fadeBuf[curDispatch->fadeRemaining], streamerGetStreamBuffer(curDispatch->streamPtr, effFadeSize), effFadeSize);
+
+				curDispatch->fadeRemaining += effFadeSize;
+			}
+		} else {
+			debug(5, "IMuseDigital::dispatchSwitchStream(): WARNING: couldn't allocate fade buffer (from sound %d to sound %d)", oldSoundId, newSoundId);
+		}
+	}
+
+	// Clear fades and triggers for the old soundId
+	char emptyMarker[1] = "";
+	_fadesHandler->clearFadeStatus(curDispatch->trackPtr->soundId, -1);
+	_triggersHandler->clearTrigger(curDispatch->trackPtr->soundId, (char *)emptyMarker, -1);
+
+	// Setup the new soundId
+	curDispatch->trackPtr->soundId = newSoundId;
+
+	streamerSetReadIndex(curDispatch->streamPtr, streamerGetFreeBufferAmount(curDispatch->streamPtr));
+
+	if (offsetFadeSyncFlag && curDispatch->streamZoneList) {
+		// Start the soundId from an offset
+		streamerSetSoundToStreamFromOffset(curDispatch->streamPtr, newSoundId, curDispatch->currentOffset);
+		while (curDispatch->streamZoneList->next) {
+			curDispatch->streamZoneList->next->useFlag = 0;
+			removeStreamZoneFromList(&curDispatch->streamZoneList->next, curDispatch->streamZoneList->next);
+		}
+		curDispatch->streamZoneList->size = 0;
+
+		return 0;
+	} else {
+		// Start the soundId from the beginning
+		streamerSetSoundToStreamFromOffset(curDispatch->streamPtr, newSoundId, 0);
+
+		while (curDispatch->streamZoneList) {
+			curDispatch->streamZoneList->useFlag = 0;
+			removeStreamZoneFromList(&curDispatch->streamZoneList, curDispatch->streamZoneList);
+		}
+
+		curDispatch->currentOffset = 0;
+		curDispatch->audioRemaining = 0;
+		memset(curDispatch->map, 0, sizeof(curDispatch->map));
+
+		getMapResult = dispatchNavigateMap(curDispatch);
+		if (!getMapResult || getMapResult == -3) {
+			return 0;
+		} else {
+			debug(5, "IMuseDigital::dispatchSwitchStream(): problem switching stream in dispatch (from sound %d to sound %d)", oldSoundId, newSoundId);
+			tracksClear(curDispatch->trackPtr);
+			return -1;
+		}
+	}
+}
+
+int IMuseDigital::dispatchSwitchStream(int oldSoundId, int newSoundId, uint8 *crossfadeBuffer, int crossfadeBufferSize, int vocLoopFlag) {
+	IMuseDigiDispatch *dispatchPtr = nullptr;
+	uint8 *streamBuf;
+	int i;
+	int32 effAudioRemaining, audioRemaining, offset;
+
+	for (i = 0; i < _trackCount; i++) {
+		dispatchPtr = &_dispatches[i];
+		if (oldSoundId && dispatchPtr->trackPtr->soundId == oldSoundId && dispatchPtr->streamPtr) {
+			break;
+		}
+	}
+
+	if (i >= _trackCount) {
+		debug(5, "IMuseDigital::dispatchSwitchStream(): couldn't find sound, index went past _trackCount (%d)", _trackCount);
+		return -1;
+	}
+
+	offset = dispatchPtr->currentOffset;
+	audioRemaining = dispatchPtr->audioRemaining;
+	dispatchPtr->trackPtr->soundId = newSoundId;
+	dispatchPtr->fadeBuf = crossfadeBuffer;
+	dispatchPtr->fadeRemaining = 0;
+	dispatchPtr->fadeSyncDelta = 0;
+	dispatchPtr->fadeVol = DIMUSE_MAX_FADE_VOLUME;
+	dispatchPtr->fadeSlope = 0;
+
+	if (crossfadeBufferSize) {
+		do {
+			if (!streamerGetFreeBufferAmount(dispatchPtr->streamPtr)
+				|| (!dispatchPtr->audioRemaining && dispatchSeekToNextChunk(dispatchPtr))) {
+				break;
+			}
+
+			effAudioRemaining = dispatchPtr->audioRemaining;
+			if (crossfadeBufferSize - dispatchPtr->fadeRemaining < effAudioRemaining)
+				effAudioRemaining = crossfadeBufferSize - dispatchPtr->fadeRemaining;
+
+			if (effAudioRemaining >= streamerGetFreeBufferAmount(dispatchPtr->streamPtr))
+				effAudioRemaining = streamerGetFreeBufferAmount(dispatchPtr->streamPtr);
+
+			if (effAudioRemaining >= 0x800)
+				effAudioRemaining = 0x800;
+
+			streamBuf = streamerGetStreamBuffer(dispatchPtr->streamPtr, effAudioRemaining);
+			memcpy(&crossfadeBuffer[dispatchPtr->fadeRemaining], streamBuf, effAudioRemaining);
+
+			dispatchPtr->fadeRemaining += effAudioRemaining;
+			dispatchPtr->currentOffset += effAudioRemaining;
+			dispatchPtr->audioRemaining -= effAudioRemaining;
+		} while (dispatchPtr->fadeRemaining < crossfadeBufferSize);
+	}
+
+	streamerSetReadIndex(dispatchPtr->streamPtr, streamerGetFreeBufferAmount(dispatchPtr->streamPtr));
+	streamerSetSoundToStreamFromOffset(dispatchPtr->streamPtr, newSoundId, vocLoopFlag ? offset : 0);
+
+	if (vocLoopFlag) {
+		if (dispatchPtr->vocLoopStartingPoint)
+			streamerSetLoopFlag(dispatchPtr->streamPtr, dispatchPtr->audioRemaining + dispatchPtr->currentOffset);
+	} else {
+		streamerRemoveLoopFlag(dispatchPtr->streamPtr);
+	}
+
+	dispatchPtr->currentOffset = vocLoopFlag ? offset : 0;
+	dispatchPtr->audioRemaining = vocLoopFlag ? audioRemaining : 0;
+
+	if (!vocLoopFlag) {
+		dispatchPtr->vocLoopStartingPoint = 0;
+	}
+
+	return 0;
+}
+
+void IMuseDigital::dispatchProcessDispatches(IMuseDigiTrack *trackPtr, int feedSize, int sampleRate) {
+	IMuseDigiDispatch *dispatchPtr;
+	int32 effFeedSize, effWordSize, effRemainingAudio, effRemainingFade, effSampleRate;
+	int32 inFrameCount, mixVolume, mixStartingPoint, elapsedFadeDelta;
+	int navigateMapResult;
+	uint8 *srcBuf, *soundAddrData;
+
+	dispatchPtr = trackPtr->dispatchPtr;
+	if (dispatchPtr->streamPtr && dispatchPtr->streamZoneList)
+		dispatchPredictStream(dispatchPtr);
+
+	// If a fade has previously been allocated
+	if (dispatchPtr->fadeBuf) {
+		inFrameCount = 8 * dispatchPtr->fadeRemaining / (dispatchPtr->fadeWordSize * dispatchPtr->fadeChannelCount);
+
+		if (_vm->_game.id == GID_DIG) {
+			effSampleRate = dispatchPtr->fadeSampleRate;
+		} else {
+			effSampleRate = (trackPtr->pitchShift * dispatchPtr->fadeSampleRate) >> 8;
+		}
+
+		if (inFrameCount >= effSampleRate * feedSize / sampleRate) {
+			inFrameCount = effSampleRate * feedSize / sampleRate;
+			effFeedSize = feedSize;
+		} else {
+			effFeedSize = sampleRate * inFrameCount / effSampleRate;
+		}
+
+		if (dispatchPtr->fadeWordSize == 12 && dispatchPtr->fadeChannelCount == 1)
+			inFrameCount &= 0xFFFFFFFE;
+
+		// If the fade is still going on
+		if (inFrameCount) {
+			// Update the fade volume
+			effRemainingFade = ((dispatchPtr->fadeWordSize * dispatchPtr->fadeChannelCount) * inFrameCount) / 8;
+			mixVolume = dispatchUpdateFadeMixVolume(dispatchPtr, effRemainingFade);
+
+			// Send it all to the mixer
+			srcBuf = &dispatchPtr->fadeBuf[dispatchPtr->fadeOffset];
+
+			_internalMixer->mix(
+				srcBuf,
+				inFrameCount,
+				dispatchPtr->fadeWordSize,
+				dispatchPtr->fadeChannelCount,
+				effFeedSize,
+				0,
+				mixVolume,
+				trackPtr->pan);
+
+			dispatchPtr->fadeOffset += effRemainingFade;
+			dispatchPtr->fadeRemaining -= effRemainingFade;
+
+			// Deallocate fade if it ended
+			if (!dispatchPtr->fadeRemaining) {
+				dispatchDeallocateFade(dispatchPtr, "dispatchProcessDispatches");
+			}
+		} else {
+			debug(5, "IMuseDigital::dispatchProcessDispatches(): WARNING: fade for sound %d ends with incomplete frame (or odd 12-bit mono frame)", trackPtr->soundId);
+
+			// Fade ended, deallocate it
+			dispatchDeallocateFade(dispatchPtr, "dispatchProcessDispatches");
+		}
+
+		if (!dispatchPtr->fadeRemaining)
+			dispatchPtr->fadeBuf = nullptr;
+	}
+
+	// This index keeps track of the offset position until which we have
+	// filled the buffer; with each update it is incremented by the effective
+	// feed size.
+	mixStartingPoint = 0;
+
+	while (1) {
+		// If the current region is finished playing
+		// go check for any event on the map for the current offset
+		if (!dispatchPtr->audioRemaining) {
+			_dispatchFadeStartedFlag = 0;
+			navigateMapResult = dispatchNavigateMap(dispatchPtr);
+
+			if (navigateMapResult)
+				break;
+
+			if (_dispatchFadeStartedFlag) {
+				// We reached a JUMP, therefore we have to crossfade to
+				// the destination region: start a fade-out
+				if (_vm->_game.id == GID_DIG) {
+					effSampleRate = dispatchPtr->fadeSampleRate;
+				} else {
+					effSampleRate = (trackPtr->pitchShift * dispatchPtr->fadeSampleRate) >> 8;
+				}
+
+				inFrameCount = 8 * dispatchPtr->fadeRemaining / (dispatchPtr->fadeWordSize * dispatchPtr->fadeChannelCount);
+				if (inFrameCount >= effSampleRate * feedSize / sampleRate) {
+					inFrameCount = effSampleRate * feedSize / sampleRate;
+					effFeedSize = feedSize;
+				} else {
+					effFeedSize = sampleRate * inFrameCount / effSampleRate;
+				}
+
+				if (dispatchPtr->fadeWordSize == 12 && dispatchPtr->fadeChannelCount == 1)
+					inFrameCount &= 0xFFFFFFFE;
+
+				if (!inFrameCount)
+					debug(5, "IMuseDigital::dispatchProcessDispatches(): WARNING: fade for sound %d ends with incomplete frame (or odd 12-bit mono frame)", trackPtr->soundId);
+
+				// Update the fade volume
+				effRemainingFade = (inFrameCount * dispatchPtr->fadeWordSize * dispatchPtr->fadeChannelCount) / 8;
+				mixVolume = dispatchUpdateFadeMixVolume(dispatchPtr, effRemainingFade);
+
+				// Send it all to the mixer
+				srcBuf = &dispatchPtr->fadeBuf[dispatchPtr->fadeOffset];
+
+				_internalMixer->mix(
+					srcBuf,
+					inFrameCount,
+					dispatchPtr->fadeWordSize,
+					dispatchPtr->fadeChannelCount,
+					effFeedSize,
+					mixStartingPoint,
+					mixVolume,
+					trackPtr->pan);
+
+				dispatchPtr->fadeOffset += effRemainingFade;
+				dispatchPtr->fadeRemaining -= effRemainingFade;
+
+				if (!dispatchPtr->fadeRemaining) {
+					// Fade ended, deallocate it
+					dispatchDeallocateFade(dispatchPtr, "dispatchProcessDispatches");
+				}
+			}
+		}
+
+		if (!feedSize)
+			return;
+
+		if (_vm->_game.id == GID_DIG) {
+			effSampleRate = dispatchPtr->sampleRate;
+		} else {
+			effSampleRate = (trackPtr->pitchShift * dispatchPtr->sampleRate) >> 8;
+		}
+
+		effWordSize = dispatchPtr->channelCount * dispatchPtr->wordSize;
+		inFrameCount = effSampleRate * feedSize / sampleRate;
+
+		if (inFrameCount <= (8 * dispatchPtr->audioRemaining / effWordSize)) {
+			effFeedSize = feedSize;
+		} else {
+			inFrameCount = 8 * dispatchPtr->audioRemaining / effWordSize;
+			effFeedSize = sampleRate * (8 * dispatchPtr->audioRemaining / effWordSize) / effSampleRate;
+		}
+
+		if (dispatchPtr->wordSize == 12 && dispatchPtr->channelCount == 1)
+			inFrameCount &= 0xFFFFFFFE;
+
+		if (!inFrameCount) {
+			if (_vm->_game.id == GID_DIG || dispatchPtr->wordSize == 12)
+				debug(5, "IMuseDigital::dispatchProcessDispatches(): WARNING: region in sound %d ends with incomplete frame (or odd 12-bit mono frame)", trackPtr->soundId);
+			tracksClear(trackPtr);
+			return;
+		}
+
+		// Play the audio of the current region
+		effRemainingAudio = (effWordSize * inFrameCount) / 8;
+
+		if (dispatchPtr->streamPtr) {
+			srcBuf = streamerGetStreamBuffer(dispatchPtr->streamPtr, effRemainingAudio);
+			if (!srcBuf) {
+				dispatchPtr->streamErrFlag = 1;
+				if (dispatchPtr->fadeBuf && dispatchPtr->fadeSyncFlag)
+					dispatchPtr->fadeSyncDelta += feedSize;
+
+				streamerQueryStream(
+					dispatchPtr->streamPtr,
+					_dispatchCurStreamBufSize,
+					_dispatchCurStreamCriticalSize,
+					_dispatchCurStreamFreeSpace,
+					_dispatchCurStreamPaused);
+
+				if (_dispatchCurStreamPaused) {
+					debug(5, "IMuseDigital::dispatchProcessDispatches(): WARNING: stopping starving paused stream for sound %d", dispatchPtr->trackPtr->soundId);
+					tracksClear(trackPtr);
+				}
+
+				return;
+			}
+			dispatchPtr->streamZoneList->offset += effRemainingAudio;
+			dispatchPtr->streamZoneList->size -= effRemainingAudio;
+			dispatchPtr->streamErrFlag = 0;
+		} else {
+			soundAddrData = _filesHandler->getSoundAddrData(trackPtr->soundId);
+			if (!soundAddrData) {
+				debug(5, "IMuseDigital::dispatchProcessDispatches(): ERROR: soundAddrData for sound %d is NULL", trackPtr->soundId);
+				// Try to gracefully play nothing instead of getting stuck on an infinite loop
+				dispatchPtr->currentOffset += effRemainingAudio;
+				dispatchPtr->audioRemaining -= effRemainingAudio;
+				return;
+			}
+
+			srcBuf = &soundAddrData[dispatchPtr->currentOffset];
+		}
+
+		if (dispatchPtr->fadeBuf) {
+			// If the fadeSyncFlag is active (e.g. we are crossfading
+			// to another version of the same music piece), do this... thing,
+			// and update the fadeSyncDelta
+			if (dispatchPtr->fadeSyncFlag) {
+				if (dispatchPtr->fadeSyncDelta) {
+					elapsedFadeDelta = effFeedSize;
+					if (effFeedSize >= dispatchPtr->fadeSyncDelta)
+						elapsedFadeDelta = dispatchPtr->fadeSyncDelta;
+
+					dispatchPtr->fadeSyncDelta -= elapsedFadeDelta;
+					effFeedSize -= elapsedFadeDelta;
+
+					if (_vm->_game.id == GID_DIG) {
+						effSampleRate = dispatchPtr->sampleRate;
+					} else {
+						effSampleRate = (trackPtr->pitchShift * dispatchPtr->sampleRate) >> 8;
+					}
+
+					inFrameCount = effFeedSize * effSampleRate / sampleRate;
+
+					if (dispatchPtr->wordSize == 12 && dispatchPtr->channelCount == 1)
+						inFrameCount &= 0xFFFFFFFE;
+
+					srcBuf = &srcBuf[effRemainingAudio - ((dispatchPtr->wordSize * inFrameCount * dispatchPtr->channelCount) / 8)];
+				}
+			}
+
+			// If there's still a fadeBuffer active in our dispatch
+			// we balance the volume of the considered track with
+			// the fade volume, effectively creating a crossfade
+			if (dispatchPtr->fadeBuf) {
+				// Fade-in
+				mixVolume = dispatchUpdateFadeSlope(dispatchPtr);
+			} else {
+				mixVolume = trackPtr->effVol;
+			}
+		} else {
+			mixVolume = trackPtr->effVol;
+		}
+
+		// Real-time lo-fi Radio voice effect
+		if (trackPtr->mailbox)
+			_internalMixer->setRadioChatter();
+
+		_internalMixer->mix(
+			srcBuf,
+			inFrameCount,
+			dispatchPtr->wordSize,
+			dispatchPtr->channelCount,
+			effFeedSize,
+			mixStartingPoint,
+			mixVolume,
+			trackPtr->pan);
+
+		_internalMixer->clearRadioChatter();
+		mixStartingPoint += effFeedSize;
+		feedSize -= effFeedSize;
+
+		dispatchPtr->currentOffset += effRemainingAudio;
+		dispatchPtr->audioRemaining -= effRemainingAudio;
+	}
+
+	// Behavior of errors and STOP marker
+	if (navigateMapResult == -1)
+		tracksClear(trackPtr);
+
+	if (dispatchPtr->fadeBuf && dispatchPtr->fadeSyncFlag)
+		dispatchPtr->fadeSyncDelta += feedSize;
+}
+
+void IMuseDigital::dispatchProcessDispatches(IMuseDigiTrack *trackPtr, int feedSize) {
+	IMuseDigiDispatch *dispatchPtr = nullptr;
+	IMuseDigiStream *streamPtr;
+	uint8 *buffer, *srcBuf;
+	int32 fadeChunkSize = 0;
+	int32 tentativeFeedSize, inFrameCount, fadeSyncDelta, mixStartingPoint, seekResult;
+	int mixVolume;
+
+	dispatchPtr = trackPtr->dispatchPtr;
+	tentativeFeedSize = (dispatchPtr->sampleRate == 22050) ? feedSize : feedSize / 2;
+
+	if (dispatchPtr->fadeBuf) {
+		if (tentativeFeedSize >= dispatchPtr->fadeRemaining) {
+			fadeChunkSize = dispatchPtr->fadeRemaining;
+		} else {
+			fadeChunkSize = tentativeFeedSize;
+		}
+
+		mixVolume = dispatchUpdateFadeMixVolume(dispatchPtr, fadeChunkSize);
+		_internalMixer->mix(dispatchPtr->fadeBuf, fadeChunkSize, 8, 1, feedSize, 0, mixVolume, trackPtr->pan);
+		dispatchPtr->fadeRemaining -= fadeChunkSize;
+		dispatchPtr->fadeBuf += fadeChunkSize;
+		if (dispatchPtr->fadeRemaining == fadeChunkSize)
+			dispatchPtr->fadeBuf = nullptr;
+	}
+
+	mixStartingPoint = 0;
+	while (1) {
+		if (!dispatchPtr->audioRemaining) {
+			seekResult = dispatchSeekToNextChunk(dispatchPtr);
+			if (seekResult)
+				break;
+		}
+
+		if (!tentativeFeedSize)
+			return;
+
+		inFrameCount = dispatchPtr->audioRemaining;
+		if (tentativeFeedSize < inFrameCount)
+			inFrameCount = tentativeFeedSize;
+
+		streamPtr = dispatchPtr->streamPtr;
+		if (streamPtr) {
+			buffer = streamerGetStreamBuffer(streamPtr, inFrameCount);
+			if (!buffer) {
+				if (dispatchPtr->fadeBuf)
+					dispatchPtr->fadeSyncDelta += fadeChunkSize;
+				return;
+			}
+		} else {
+			srcBuf = _filesHandler->getSoundAddrData(trackPtr->soundId);
+			if (!srcBuf)
+				return;
+			buffer = &srcBuf[dispatchPtr->currentOffset];
+		}
+
+		if (dispatchPtr->fadeBuf) {
+			if (dispatchPtr->fadeSyncDelta) {
+				fadeSyncDelta = dispatchPtr->fadeSyncDelta;
+				if (dispatchPtr->fadeSyncDelta >= inFrameCount)
+					fadeSyncDelta = inFrameCount;
+				inFrameCount -= fadeSyncDelta;
+				dispatchPtr->fadeSyncDelta -= fadeSyncDelta;
+				buffer += fadeSyncDelta;
+				dispatchPtr->currentOffset += fadeSyncDelta;
+				dispatchPtr->audioRemaining -= fadeSyncDelta;
+			}
+		}
+
+		if (inFrameCount) {
+			if (dispatchPtr->fadeBuf) {
+				mixVolume = dispatchUpdateFadeSlope(dispatchPtr);
+			} else {
+				mixVolume = trackPtr->effVol;
+			}
+
+			_internalMixer->mix(buffer, inFrameCount, 8, 1, feedSize, mixStartingPoint, mixVolume, trackPtr->pan);
+			mixStartingPoint += inFrameCount;
+			tentativeFeedSize -= inFrameCount;
+			dispatchPtr->currentOffset += inFrameCount;
+			dispatchPtr->audioRemaining -= inFrameCount;
+		}
+	}
+
+	if (seekResult == -1)
+		tracksClear(trackPtr);
+
+	if (dispatchPtr->fadeBuf)
+		dispatchPtr->fadeSyncDelta += fadeChunkSize;
+}
+
+void IMuseDigital::dispatchPredictFirstStream() {
+	Common::StackLock lock(_mutex);
+
+	for (int i = 0; i < _trackCount; i++) {
+		if (_dispatches[i].trackPtr->soundId && _dispatches[i].streamPtr && _dispatches[i].streamZoneList)
+			dispatchPredictStream(&_dispatches[i]);
+	}
+}
+
+int IMuseDigital::dispatchNavigateMap(IMuseDigiDispatch *dispatchPtr) {
+	int32 *mapCurEvent;
+	int32 blockTag, effFadeSize, elapsedFadeSize, regionOffset;
+	char *marker = NULL;
+
+	int getMapResult = dispatchGetMap(dispatchPtr);
+	if (getMapResult)
+		return getMapResult;
+
+	if (dispatchPtr->audioRemaining
+		|| (dispatchPtr->streamPtr && dispatchPtr->streamZoneList->offset != dispatchPtr->currentOffset)) {
+		debug(5, "IMuseDigital::dispatchNavigateMap(): ERROR: navigation error in dispatch");
+		return -1;
+	}
+
+	mapCurEvent = NULL;
+	while (1) {
+		mapCurEvent = dispatchGetNextMapEvent(dispatchPtr->map, dispatchPtr->currentOffset, mapCurEvent);
+
+		if (!mapCurEvent) {
+			debug(5, "IMuseDigital::dispatchNavigateMap(): ERROR: no more map events at offset %dx", dispatchPtr->currentOffset);
+			return -1;
+		}
+
+		blockTag = mapCurEvent[0];
+		switch (blockTag) {
+		case MKTAG('J', 'U', 'M', 'P'):
+			// Handle any event found at this offset
+			// Jump block (fixed size: 28 bytes)
+			// - The tag 'JUMP' (4 bytes)
+			// - Block size in bytes minus 8 (4 bytes)
+			// - Block offset (hook position) (4 bytes)
+			// - Jump destination offset (4 bytes)
+			// - Hook ID (4 bytes)
+			// - Fade time in ms (4 bytes)
+			if (!checkHookId(dispatchPtr->trackPtr->jumpHook, mapCurEvent[4])) {
+				// This is the right hookId, let's jump
+				dispatchPtr->currentOffset = mapCurEvent[3];
+				if (dispatchPtr->streamPtr) {
+					if (dispatchPtr->streamZoneList->size || !dispatchPtr->streamZoneList->next) {
+						debug(5, "IMuseDigital::dispatchNavigateMap(): next streamZone is unallocated, calling dispatchPrepareToJump()");
+						dispatchPrepareToJump(dispatchPtr, dispatchPtr->streamZoneList, mapCurEvent, 1);
+					}
+
+					debug(5, "IMuseDigital::dispatchNavigateMap(): \n"
+							 "\tJUMP found for sound %d with valid candidateHookId (%d), \n"
+							 "\tgoing to offset %d with a crossfade of %d ms",
+						  dispatchPtr->trackPtr->soundId, mapCurEvent[4], mapCurEvent[3], mapCurEvent[5]);
+
+					dispatchPtr->streamZoneList->useFlag = 0;
+					removeStreamZoneFromList(&dispatchPtr->streamZoneList, dispatchPtr->streamZoneList);
+
+					if (dispatchPtr->streamZoneList->fadeFlag) {
+						if (dispatchPtr->fadeBuf) {
+							// Mark the fade corresponding to our fadeBuf as unused
+							dispatchDeallocateFade(dispatchPtr, "dispatchNavigateMap");
+						}
+
+						_dispatchJumpFadeSize = dispatchPtr->streamZoneList->size;
+						dispatchPtr->fadeBuf = dispatchAllocateFade(_dispatchJumpFadeSize, "dispatchNavigateMap");
+
+						// If the fade buffer is allocated
+						// set up the fade
+						if (dispatchPtr->fadeBuf) {
+							dispatchPtr->fadeWordSize = dispatchPtr->wordSize;
+							dispatchPtr->fadeSampleRate = dispatchPtr->sampleRate;
+							dispatchPtr->fadeChannelCount = dispatchPtr->channelCount;
+							dispatchPtr->fadeOffset = 0;
+							dispatchPtr->fadeRemaining = 0;
+							dispatchPtr->fadeSyncFlag = 0;
+							dispatchPtr->fadeSyncDelta = 0;
+							dispatchPtr->fadeVol = DIMUSE_MAX_FADE_VOLUME;
+							dispatchPtr->fadeSlope = 0;
+
+							// Clone the old sound in the fade buffer for just the duration of the fade
+							if (_dispatchJumpFadeSize) {
+								do {
+									effFadeSize = _dispatchJumpFadeSize - dispatchPtr->fadeRemaining;
+									if ((_dispatchJumpFadeSize - dispatchPtr->fadeRemaining) >= 0x4000)
+										effFadeSize = 0x4000;
+
+									memcpy(&dispatchPtr->fadeBuf[dispatchPtr->fadeRemaining],
+										   streamerGetStreamBuffer(dispatchPtr->streamPtr, effFadeSize),
+										   effFadeSize);
+
+									elapsedFadeSize = effFadeSize + dispatchPtr->fadeRemaining;
+									dispatchPtr->fadeRemaining = elapsedFadeSize;
+								} while (_dispatchJumpFadeSize > elapsedFadeSize);
+							}
+							_dispatchFadeStartedFlag = 1;
+						}
+						dispatchPtr->streamZoneList->useFlag = 0;
+						removeStreamZoneFromList(&dispatchPtr->streamZoneList, dispatchPtr->streamZoneList);
+					}
+				}
+				mapCurEvent = nullptr;
+			}
+
+			continue;
+		case MKTAG('S', 'Y', 'N', 'C'):
+			// SYNC block (fixed size: x bytes)
+			// - The tag 'SYNC' (4 bytes)
+			// - SYNC size in bytes (4 bytes)
+			// - SYNC data (variable length)
+
+			// It is possible to gather a total maximum of 4 SYNCs for a single track;
+			// this is not a problem however, as speech files only have one SYNC block,
+			// and the most we get is four (one for each character) in
+			// A Pirate I Was Meant To Be, in Part 3 of COMI
+
+			// Curiously we skip the first four bytes of data, ending up having the first
+			// four bytes of the next block in our syncPtr; but this is exactly what happens
+			// within the interpreter, so I'm not going to argue with it
+
+			if (!dispatchPtr->trackPtr->syncPtr_0) {
+				dispatchPtr->trackPtr->syncPtr_0 = (byte *)malloc(mapCurEvent[1]);
+				memcpy(dispatchPtr->trackPtr->syncPtr_0, mapCurEvent + 3, mapCurEvent[1]);
+				dispatchPtr->trackPtr->syncSize_0 = mapCurEvent[1];
+
+			} else if (!dispatchPtr->trackPtr->syncPtr_1) {
+				dispatchPtr->trackPtr->syncPtr_1 = (byte *)malloc(mapCurEvent[1]);
+				memcpy(dispatchPtr->trackPtr->syncPtr_1, mapCurEvent + 3, mapCurEvent[1]);
+				dispatchPtr->trackPtr->syncSize_1 = mapCurEvent[1];
+
+			} else if (!dispatchPtr->trackPtr->syncPtr_2) {
+				dispatchPtr->trackPtr->syncPtr_2 = (byte *)malloc(mapCurEvent[1]);
+				memcpy(dispatchPtr->trackPtr->syncPtr_2, mapCurEvent + 3, mapCurEvent[1]);
+				dispatchPtr->trackPtr->syncSize_2 = mapCurEvent[1];
+
+			} else if (!dispatchPtr->trackPtr->syncPtr_3) {
+				dispatchPtr->trackPtr->syncPtr_3 = (byte *)malloc(mapCurEvent[1]);
+				memcpy(dispatchPtr->trackPtr->syncPtr_3, mapCurEvent + 3, mapCurEvent[1]);
+				dispatchPtr->trackPtr->syncSize_3 = mapCurEvent[1];
+			}
+
+			continue;
+		case MKTAG('F', 'R', 'M', 'T'):
+			// Format block (fixed size: 28 bytes)
+			// - The tag 'FRMT' (4 bytes)
+			// - Block size in bytes minus 8 (4 bytes)
+			// - Block offset (4 bytes)
+			// - Empty field (4 bytes) (which is set to 1 in Grim Fandango, I suspect this is the endianness)
+			// - Word size between 8, 12 and 16 (4 bytes)
+			// - Sample rate (4 bytes)
+			// - Number of channels (4 bytes)
+			dispatchPtr->wordSize = mapCurEvent[4];
+			dispatchPtr->sampleRate = mapCurEvent[5];
+			dispatchPtr->channelCount = mapCurEvent[6];
+
+			continue;
+		case MKTAG('R', 'E', 'G', 'N'):
+			// Region block (fixed size: 16 bytes)
+			// - The tag 'REGN' (4 bytes)
+			// - Block size in bytes minus 8 (4 bytes)
+			// - Block offset (4 bytes)
+			// - Region length (4 bytes)
+			regionOffset = mapCurEvent[2];
+			if (regionOffset == dispatchPtr->currentOffset) {
+				dispatchPtr->audioRemaining = mapCurEvent[3];
+				return 0;
+			} else {
+				debug(5, "IMuseDigital::dispatchNavigateMap(): ERROR: region offset %d != currentOffset %d", regionOffset, dispatchPtr->currentOffset);
+				return -1;
+			}
+		case MKTAG('S', 'T', 'O', 'P'):
+			// Stop block (fixed size: 12 bytes)
+			// Contains:
+			// - The tag 'STOP' (4 bytes)
+			// - Block size in bytes minus 8 (4 bytes)
+			// - Block offset (4 bytes)
+			return -1;
+		case MKTAG('T', 'E', 'X', 'T'):
+			// Marker block (variable size)
+			// Contains:
+			// - The tag 'TEXT' (4 bytes)
+			// - Block size in bytes minus 8 (4 bytes)
+			// - Block offset (4 bytes)
+			// - A string of characters ending with '\0' (variable length)
+			marker = (char *)mapCurEvent + 12;
+			_triggersHandler->processTriggers(dispatchPtr->trackPtr->soundId, marker);
+			if (dispatchPtr->audioRemaining)
+				return 0;
+
+			continue;
+		default:
+			debug(5, "IMuseDigital::dispatchNavigateMap(): ERROR: Unrecognized map event at offset %dx", dispatchPtr->currentOffset);
+			break;
+		};
+	}
+	return -1;
+}
+
+int IMuseDigital::dispatchGetMap(IMuseDigiDispatch *dispatchPtr) {
+	int32 *dstMap;
+	uint8 *rawMap, *copiedBuf, *soundAddrData;
+	int32 size;
+
+	dstMap = dispatchPtr->map;
+
+	// If there's no map, try to fetch it
+	if (dispatchPtr->map[0] != MKTAG('M', 'A', 'P', ' ')) {
+		if (dispatchPtr->currentOffset) {
+			debug(5, "IMuseDigital::dispatchNavigateMap(): found offset but no map");
+			return -1;
+		}
+
+		// If there's a streamPtr it means that this is a sound loaded
+		// from a bundle (either music or speech)
+		if (dispatchPtr->streamPtr) {
+			copiedBuf = (uint8 *)streamerGetStreamBufferAtOffset(dispatchPtr->streamPtr, 0, 0x10);
+
+			if (!copiedBuf) {
+				return -3;
+			}
+
+			if (READ_BE_UINT32(copiedBuf) == MKTAG('i', 'M', 'U', 'S') && READ_BE_UINT32(copiedBuf + 8) == MKTAG('M', 'A', 'P', ' ')) {
+				size = READ_BE_UINT32(copiedBuf + 12) + 24;
+				if (!streamerGetStreamBufferAtOffset(dispatchPtr->streamPtr, 0, size)) {
+					return -3;
+				}
+				rawMap = (uint8 *)streamerGetStreamBuffer(dispatchPtr->streamPtr, size);
+				if (!rawMap) {
+					debug(5, "IMuseDigital::dispatchGetMap(): ERROR: stream read failed after view succeeded");
+					return -1;
+				}
+
+				dispatchPtr->currentOffset = size;
+				if (dispatchConvertMap(rawMap + 8, (uint8 *)dstMap)) {
+					debug(5, "IMuseDigital::dispatchGetMap(): ERROR: dispatchConvertMap() failed");
+					return -1;
+				}
+
+				if (dispatchPtr->map[2] != MKTAG('F', 'R', 'M', 'T')) {
+					debug(5, "IMuseDigital::dispatchGetMap(): ERROR: expected 'FRMT' at start of map");
+					return -1;
+				}
+
+				if (dispatchPtr->map[4] != dispatchPtr->currentOffset) {
+					debug(5, "IMuseDigital::dispatchGetMap(): ERROR: expected data to follow map");
+					return -1;
+				} else {
+					if (dispatchPtr->streamZoneList) {
+						debug(5, "IMuseDigital::dispatchGetMap(): ERROR: expected NULL streamZoneList");
+						return -1;
+					}
+
+					dispatchPtr->streamZoneList = dispatchAllocateStreamZone();
+					if (!dispatchPtr->streamZoneList) {
+						debug(5, "IMuseDigital::dispatchGetMap(): ERROR: couldn't allocate zone");
+						return -1;
+					}
+
+					dispatchPtr->streamZoneList->offset = dispatchPtr->currentOffset;
+					dispatchPtr->streamZoneList->size = streamerGetFreeBufferAmount(dispatchPtr->streamPtr);
+					dispatchPtr->streamZoneList->fadeFlag = 0;
+				}
+			} else {
+				debug(5, "IMuseDigital::dispatchGetMap(): ERROR: unrecognized file format in stream buffer");
+				return -1;
+			}
+
+		} else {
+			// Otherwise, this is a SFX and we must load it using its resource pointer
+			soundAddrData = _filesHandler->getSoundAddrData(dispatchPtr->trackPtr->soundId);
+
+			if (!soundAddrData) {
+				debug(5, "IMuseDigital::dispatchGetMap(): ERROR: couldn't get sound address");
+				return -1;
+			}
+
+			if (READ_BE_UINT32(soundAddrData) == MKTAG('i', 'M', 'U', 'S') && READ_BE_UINT32(soundAddrData + 8) == MKTAG('M', 'A', 'P', ' ')) {
+				dispatchPtr->currentOffset = READ_BE_UINT32(soundAddrData + 12) + 24;
+				if (dispatchConvertMap((soundAddrData + 8), (uint8 *)dstMap)) {
+					debug(5, "IMuseDigital::dispatchGetMap(): ERROR: dispatchConvertMap() failure");
+					return -1;
+				}
+
+				if (dispatchPtr->map[2] != MKTAG('F', 'R', 'M', 'T')) {
+					debug(5, "IMuseDigital::dispatchGetMap(): ERROR: expected 'FRMT' at start of map");
+					return -1;
+				}
+
+				if (dispatchPtr->map[4] != dispatchPtr->currentOffset) {
+					debug(5, "IMuseDigital::dispatchGetMap(): ERROR: expected data to follow map");
+					return -1;
+				}
+			} else {
+				debug(5, "IMuseDigital::dispatchGetMap(): ERROR: unrecognized file format in stream buffer");
+				return -1;
+			}
+		}
+	}
+
+	return 0;
+}
+
+int IMuseDigital::dispatchConvertMap(uint8 *rawMap, uint8 *destMap) {
+	int32 effMapSize;
+	uint8 *mapCurPos;
+	int32 blockName;
+	uint8 *blockSizePtr;
+	uint32 blockSizeMin8;
+	uint8 *firstChar;
+	uint8 *otherChars;
+	uint32 remainingFieldsNum;
+	int32 bytesUntilEndOfMap;
+	uint8 *endOfMapPtr;
+
+	if (READ_BE_UINT32(rawMap) == MKTAG('M', 'A', 'P', ' ')) {
+		bytesUntilEndOfMap = READ_BE_UINT32(rawMap + 4);
+		effMapSize = bytesUntilEndOfMap + 8;
+		if (((_vm->_game.id == GID_DIG
+			|| (_vm->_game.id == GID_CMI && _vm->_game.features & GF_DEMO)) && effMapSize <= 0x400)
+			|| (_vm->_game.id == GID_CMI && effMapSize <= 0x2000)) {
+			memcpy(destMap, rawMap, effMapSize);
+
+			// Fill (or rather, swap32) the fields:
+			// - The 4 bytes string 'MAP '
+			// - Size of the map
+			//int dest = READ_BE_UINT32(destMap);
+			*(int32 *)destMap = READ_BE_UINT32(destMap);
+			*((int32 *)destMap + 1) = READ_BE_UINT32(destMap + 4);
+
+			mapCurPos = destMap + 8;
+			endOfMapPtr = &destMap[effMapSize];
+
+			// Swap32 the rest of the map
+			while (mapCurPos < endOfMapPtr) {
+				// Swap32 the 4 characters block name
+				int32 swapped = READ_BE_UINT32(mapCurPos);
+				*(int32 *)mapCurPos = swapped;
+				blockName = swapped;
+
+				// Advance and Swap32 the block size (minus 8) field
+				blockSizePtr = mapCurPos + 4;
+				blockSizeMin8 = READ_BE_UINT32(blockSizePtr);
+				*(int32 *)blockSizePtr = blockSizeMin8;
+				mapCurPos = blockSizePtr + 4;
+
+				// Swapping32 a TEXT block is different:
+				// it also contains single characters, so we skip them
+				// since they're already good like this
+				if (blockName == MKTAG('T', 'E', 'X', 'T')) {
+					// Swap32 the block offset position
+					*(int32 *)mapCurPos = READ_BE_UINT32(mapCurPos);
+
+					// Skip the single characters
+					firstChar = mapCurPos + 4;
+					mapCurPos += 5;
+					if (*firstChar) {
+						do {
+							otherChars = mapCurPos++;
+						} while (*otherChars);
+					}
+				} else if ((blockSizeMin8 & 0xFFFFFFFC) != 0) {
+					// Basically divide by 4 to retrieve the number
+					// of fields to swap
+					remainingFieldsNum = blockSizeMin8 >> 2;
+
+					// ...and swap them of course
+					do {
+						*(int32 *)mapCurPos = READ_BE_UINT32(mapCurPos);
+						mapCurPos += 4;
+						--remainingFieldsNum;
+					} while (remainingFieldsNum);
+				}
+			}
+
+			// Just a sanity check to see if we've parsed the whole map
+			if (&destMap[bytesUntilEndOfMap] - mapCurPos == -8) {
+				return 0;
+			} else {
+				debug(5, "IMuseDigital::dispatchConvertMap(): ERROR: converted wrong number of bytes");
+				return -1;
+			}
+		} else {
+			debug(5, "IMuseDigital::dispatchConvertMap(): ERROR: map is too big (%d)", effMapSize);
+			return -1;
+		}
+	} else {
+		debug(5, "IMuseDigital::dispatchConvertMap(): ERROR: got bogus map");
+		return -1;
+	}
+
+	return 0;
+}
+
+int32 *IMuseDigital::dispatchGetNextMapEvent(int32 *mapPtr, int32 soundOffset, int32 *mapEvent) {
+	if (mapEvent) {
+		// Advance the map to the next block (mapEvent[1] + 8 is the size of the block)
+		mapEvent = (int32 *)((int8 *)mapEvent + mapEvent[1] + 8);
+
+		if ((int8 *)&mapPtr[2] + mapPtr[1] > (int8 *)mapEvent) {
+			if (mapEvent[2] != soundOffset) {
+				debug(5, "IMuseDigital::dispatchGetNextMapEvent(): ERROR: no more events at offset %d", soundOffset);
+				return nullptr;
+			}
+		} else {
+			debug(5, "IMuseDigital::dispatchGetNextMapEvent(): ERROR: map overrun");
+			return nullptr;
+		}
+
+	} else {
+		// Init the current map position starting from the first block
+		// (cells 0 and 1 are the tag 'MAP ' and the map size respectively)
+		mapEvent = &mapPtr[2];
+
+		// Search for the block with the same offset as ours
+		while (mapEvent[2] != soundOffset) {
+			// Check if we've overrun the offset, to make sure
+			// that there actually is an event at our offset
+			mapEvent = (int32 *)((int8 *)mapEvent + mapEvent[1] + 8);
+
+			if ((int8 *)&mapPtr[2] + mapPtr[1] <= (int8 *)mapEvent) {
+				debug(5, "IMuseDigital::dispatchGetNextMapEvent(): ERROR: couldn't find event at offset %d", soundOffset);
+				return nullptr;
+			}
+		}
+	}
+
+	return mapEvent;
+}
+
+void IMuseDigital::dispatchPredictStream(IMuseDigiDispatch *dispatchPtr) {
+	IMuseDigiStreamZone *szTmp, *lastStreamInList, *curStrZn;
+	int32 cumulativeStreamOffset;
+	int32 *jumpParameters;
+
+	if (!dispatchPtr->streamPtr || !dispatchPtr->streamZoneList) {
+		debug(5, "IMuseDigital::dispatchPredictStream(): ERROR: NULL streamId or streamZoneList");
+		return;
+	}
+
+	szTmp = dispatchPtr->streamZoneList;
+
+	// Get the offset which our stream is currently at
+	cumulativeStreamOffset = 0;
+	do {
+		cumulativeStreamOffset += szTmp->size;
+		lastStreamInList = szTmp;
+		szTmp = szTmp->next;
+	} while (szTmp);
+
+	lastStreamInList->size += streamerGetFreeBufferAmount(dispatchPtr->streamPtr) - cumulativeStreamOffset;
+	curStrZn = dispatchPtr->streamZoneList;
+
+	for (_dispatchBufferedHookId = dispatchPtr->trackPtr->jumpHook; curStrZn; curStrZn = curStrZn->next) {
+		if (!curStrZn->fadeFlag) {
+			jumpParameters = dispatchCheckForJump(dispatchPtr->map, curStrZn, _dispatchBufferedHookId);
+			if (jumpParameters) {
+				// If we've reached a JUMP and it's successful, allocate the streamZone of the destination
+				dispatchPrepareToJump(dispatchPtr, curStrZn, jumpParameters, 0);
+			} else {
+				// If we don't have to jump, just play the next streamZone available
+				dispatchStreamNextZone(dispatchPtr, curStrZn);
+			}
+		}
+	}
+}
+
+int32 *IMuseDigital::dispatchCheckForJump(int32 *mapPtr, IMuseDigiStreamZone *strZnPtr, int &candidateHookId) {
+	int32 *curMapPlace = &mapPtr[2];
+	int32 *endOfMap = (int32 *)((int8 *)&mapPtr[2] + mapPtr[1]);
+	int32 mapPlaceTag, jumpHookPos, jumpHookId, bytesUntilNextPlace;
+
+	while (curMapPlace < endOfMap) {
+		mapPlaceTag = curMapPlace[0];
+		bytesUntilNextPlace = curMapPlace[1] + 8;
+
+		if (mapPlaceTag == MKTAG('J', 'U', 'M', 'P')) {
+			jumpHookPos = curMapPlace[2];
+			jumpHookId = curMapPlace[4];
+
+			if (jumpHookPos > strZnPtr->offset && jumpHookPos <= strZnPtr->size + strZnPtr->offset) {
+				if (!checkHookId(candidateHookId, jumpHookId))
+					return curMapPlace;
+			}
+		}
+		// Advance the map to the next place
+		curMapPlace = (int32 *)((int8 *)curMapPlace + bytesUntilNextPlace);
+	}
+
+	return nullptr;
+}
+
+void IMuseDigital::dispatchPrepareToJump(IMuseDigiDispatch *dispatchPtr, IMuseDigiStreamZone *strZnPtr, int32 *jumpParams, int calledFromNavigateMap) {
+	int32 hookPosition, jumpDestination, fadeTime;
+	IMuseDigiStreamZone *nextStreamZone;
+	IMuseDigiStreamZone *zoneForJump = nullptr;
+	IMuseDigiStreamZone *zoneAfterJump;
+	uint32 streamOffset;
+	IMuseDigiStreamZone *zoneCycle;
+
+	// jumpParams format:
+	// jumpParams[0]: four bytes which form the string 'JUMP'
+	// jumpParams[1]: block size in bytes minus 8 (16 for a JUMP block like this one; total == 24 bytes)
+	// jumpParams[2]: hook position
+	// jumpParams[3]: jump destination
+	// jumpParams[4]: hook ID
+	// jumpParams[5]: fade time in milliseconds
+
+	hookPosition = jumpParams[2];
+	jumpDestination = jumpParams[3];
+	fadeTime = jumpParams[5];
+
+	// Edge cases handling
+	if (strZnPtr->size + strZnPtr->offset == hookPosition) {
+		nextStreamZone = strZnPtr->next;
+		if (nextStreamZone) {
+			if (nextStreamZone->fadeFlag) {
+				// Avoid jumping if the next stream zone is already fading
+				// and its ending position is our jump destination.
+				// Basically: cancel the jump if there's already a fade in progress
+				if (nextStreamZone->offset == hookPosition) {
+					if (nextStreamZone->next) {
+						if (nextStreamZone->next->offset == jumpDestination)
+							return;
+					}
+				}
+			} else {
+				// Avoid jumping if we're trying to jump to the next stream zone
+				if (nextStreamZone->offset == jumpDestination)
+					return;
+			}
+		}
+	}
+
+	// Maximum size of the dispatch for the fade (in bytes)
+	_dispatchSize = dispatchGetFadeSize(dispatchPtr, fadeTime);
+
+	// If this function is being called from dispatchPredictStream,
+	// avoid accepting an oversized dispatch
+	if (!calledFromNavigateMap) {
+		if (_dispatchSize > strZnPtr->size + strZnPtr->offset - hookPosition)
+			return;
+	}
+
+	// Cap the dispatch size, if oversized
+	if (_dispatchSize > strZnPtr->size + strZnPtr->offset - hookPosition)
+		_dispatchSize = strZnPtr->size + strZnPtr->offset - hookPosition;
+
+	// This prevents starting a fade with an inverted stereo image
+	dispatchValidateFadeSize(dispatchPtr, _dispatchSize, "dispatchPrepareToJump");
+
+	if (_vm->_game.id == GID_DIG) {
+		if (hookPosition < jumpDestination)
+			_dispatchSize = 0;
+	} else {
+		if (dispatchPtr->fadeRemaining)
+			_dispatchSize = 0;
+	}
+
+	// Try allocating the two streamZones needed for the jump
+	if (_dispatchSize) {
+		zoneForJump = dispatchAllocateStreamZone();
+		if (!zoneForJump) {
+			debug(5, "IMuseDigital::dispatchPrepareToJump(): ERROR: couldn't allocate streamZone");
+			return;
+		}
+	}
+
+	zoneAfterJump = dispatchAllocateStreamZone();
+	if (!zoneAfterJump) {
+		debug(5, "IMuseDigital::dispatchPrepareToJump(): ERROR: couldn't allocate streamZone");
+		return;
+	}
+
+	strZnPtr->size = hookPosition - strZnPtr->offset;
+	streamOffset = hookPosition - strZnPtr->offset + _dispatchSize;
+
+	// Go to the interested stream zone to calculate the stream offset,
+	// and schedule the sound to stream with that offset
+	zoneCycle = dispatchPtr->streamZoneList;
+	while (zoneCycle != strZnPtr) {
+		streamOffset += zoneCycle->size;
+		zoneCycle = zoneCycle->next;
+	}
+
+	streamerSetLoadIndex(dispatchPtr->streamPtr, streamOffset);
+
+	while (strZnPtr->next) {
+		strZnPtr->next->useFlag = 0;
+		removeStreamZoneFromList(&strZnPtr->next, strZnPtr->next);
+	}
+
+	streamerSetSoundToStreamFromOffset(dispatchPtr->streamPtr,
+		dispatchPtr->trackPtr->soundId, jumpDestination);
+
+	// Prepare the fading zone for the jump
+	// and also a subsequent empty dummy zone
+	if (_dispatchSize) {
+		strZnPtr->next = zoneForJump;
+		zoneForJump->prev = strZnPtr;
+		strZnPtr = zoneForJump;
+		zoneForJump->next = 0;
+		zoneForJump->offset = hookPosition;
+		zoneForJump->size = _dispatchSize;
+		zoneForJump->fadeFlag = 1;
+	}
+
+	strZnPtr->next = zoneAfterJump;
+	zoneAfterJump->prev = strZnPtr;
+	zoneAfterJump->next = nullptr;
+	zoneAfterJump->offset = jumpDestination;
+	zoneAfterJump->size = 0;
+	zoneAfterJump->fadeFlag = 0;
+}
+
+void IMuseDigital::dispatchStreamNextZone(IMuseDigiDispatch *dispatchPtr, IMuseDigiStreamZone *strZnPtr) {
+	int32 cumulativeStreamOffset;
+	IMuseDigiStreamZone *szTmp;
+
+	if (strZnPtr->next) {
+		cumulativeStreamOffset = strZnPtr->size;
+		szTmp = dispatchPtr->streamZoneList;
+		while (szTmp != strZnPtr) {
+			cumulativeStreamOffset += szTmp->size;
+			szTmp = szTmp->next;
+		}
+
+		// Set the stream load index of the sound to the new streamZone
+		streamerSetLoadIndex(dispatchPtr->streamPtr, cumulativeStreamOffset);
+
+		// Remove che previous streamZone from the list, we don't need it anymore
+		while (strZnPtr->next->prev) {
+			strZnPtr->next->prev->useFlag = 0;
+			removeStreamZoneFromList(&strZnPtr->next, strZnPtr->next->prev);
+		}
+
+		streamerSetSoundToStreamFromOffset(
+			dispatchPtr->streamPtr,
+			dispatchPtr->trackPtr->soundId,
+			strZnPtr->size + strZnPtr->offset);
+	}
+}
+
+IMuseDigiStreamZone *IMuseDigital::dispatchAllocateStreamZone() {
+	for (int i = 0; i < DIMUSE_MAX_STREAMZONES; i++) {
+		if (_streamZones[i].useFlag == 0) {
+			_streamZones[i].prev = 0;
+			_streamZones[i].next = 0;
+			_streamZones[i].useFlag = 1;
+			_streamZones[i].offset = 0;
+			_streamZones[i].size = 0;
+			_streamZones[i].fadeFlag = 0;
+
+			return &_streamZones[i];
+		}
+	}
+	debug(5, "IMuseDigital::dispatchAllocateStreamZone(): ERROR: out of streamZones");
+	return nullptr;
+}
+
+uint8 *IMuseDigital::dispatchAllocateFade(int32 &fadeSize, const char *function) {
+	uint8 *allocatedFadeBuf = nullptr;
+	if (fadeSize > DIMUSE_LARGE_FADE_DIM) {
+		debug(5, "IMuseDigital::dispatchAllocateFade(): WARNING: requested fade too large (%d) in %s()", fadeSize, function);
+		fadeSize = DIMUSE_LARGE_FADE_DIM;
+	}
+
+	if (fadeSize <= DIMUSE_SMALL_FADE_DIM) { // Small fade
+		for (int i = 0; i <= DIMUSE_SMALL_FADES; i++) {
+			if (i == DIMUSE_SMALL_FADES) {
+				debug(5, "IMuseDigital::dispatchAllocateFade(): couldn't allocate small fade buffer in %s()", function);
+				allocatedFadeBuf = nullptr;
+				break;
+			}
+
+			if (!_dispatchSmallFadeFlags[i]) {
+				_dispatchSmallFadeFlags[i] = 1;
+				allocatedFadeBuf = &_dispatchSmallFadeBufs[DIMUSE_SMALL_FADE_DIM * i];
+				break;
+			}
+		}
+	} else { // Large fade
+		for (int i = 0; i <= DIMUSE_LARGE_FADES; i++) {
+			if (i == DIMUSE_LARGE_FADES) {
+				debug(5, "IMuseDigital::dispatchAllocateFade(): couldn't allocate large fade buffer in %s()", function);
+				allocatedFadeBuf = nullptr;
+				break;
+			}
+
+			if (!_dispatchLargeFadeFlags[i]) {
+				_dispatchLargeFadeFlags[i] = 1;
+				allocatedFadeBuf = &_dispatchLargeFadeBufs[DIMUSE_LARGE_FADE_DIM * i];
+				break;
+			}
+		}
+
+		// Fallback to a small fade if large fades are unavailable
+		if (!allocatedFadeBuf) {
+			for (int i = 0; i <= DIMUSE_SMALL_FADES; i++) {
+				if (i == DIMUSE_SMALL_FADES) {
+					debug(5, "IMuseDigital::dispatchAllocateFade(): couldn't allocate small fade buffer in %s()", function);
+					allocatedFadeBuf = nullptr;
+					break;
+				}
+
+				if (!_dispatchSmallFadeFlags[i]) {
+					_dispatchSmallFadeFlags[i] = 1;
+					allocatedFadeBuf = &_dispatchSmallFadeBufs[DIMUSE_SMALL_FADE_DIM * i];
+					break;
+				}
+			}
+		}
+	}
+
+	return allocatedFadeBuf;
+}
+
+void IMuseDigital::dispatchDeallocateFade(IMuseDigiDispatch *dispatchPtr, const char *function) {
+	// This function flags the fade corresponding to our fadeBuf as unused
+
+	// First, check if our fade buffer is one of the large fade buffers
+	for (int i = 0; i < DIMUSE_LARGE_FADES; i++) {
+		if (_dispatchLargeFadeBufs + (DIMUSE_LARGE_FADE_DIM * i) == dispatchPtr->fadeBuf) { // Found it!
+			if (_dispatchLargeFadeFlags[i] == 0) {
+				debug(5, "IMuseDigital::dispatchDeallocateFade(): redundant large fade buf de-allocation in %s()", function);
+			}
+			_dispatchLargeFadeFlags[i] = 0;
+			return;
+		}
+	}
+
+	// If not, check between the small fade buffers
+	for (int j = 0; j < DIMUSE_SMALL_FADES; j++) {
+		if (_dispatchSmallFadeBufs + (DIMUSE_SMALL_FADE_DIM * j) == dispatchPtr->fadeBuf) { // Found it!
+			if (_dispatchSmallFadeFlags[j] == 0) {
+				debug(5, "IMuseDigital::dispatchDeallocateFade(): redundant small fade buf de-allocation in %s()", function);
+			}
+			_dispatchSmallFadeFlags[j] = 0;
+			return;
+		}
+	}
+
+	debug(5, "IMuseDigital::dispatchDeallocateFade(): couldn't find fade buf to de-allocate in %s()", function);
+}
+
+int IMuseDigital::dispatchGetFadeSize(IMuseDigiDispatch *dispatchPtr, int fadeLength) {
+	return (dispatchPtr->wordSize * dispatchPtr->channelCount * ((dispatchPtr->sampleRate * fadeLength / 1000) & 0xFFFFFFFE)) / 8;
+}
+
+void IMuseDigital::dispatchValidateFadeSize(IMuseDigiDispatch *dispatchPtr, int32 &dispatchSize, const char *function) {
+	int alignmentModDividend;
+	if (_vm->_game.id == GID_DIG || (_vm->_game.id == GID_CMI && _vm->_game.features & GF_DEMO)) {
+		alignmentModDividend = dispatchPtr->channelCount * (dispatchPtr->wordSize == 8 ? 1 : 3);
+	} else {
+		if (dispatchPtr->wordSize == 8) {
+			alignmentModDividend = dispatchPtr->channelCount * 1;
+		} else {
+			alignmentModDividend = dispatchPtr->channelCount * ((dispatchPtr->wordSize == 12) + 2);
+		}
+	}
+
+	if (alignmentModDividend) {
+		dispatchSize -= dispatchSize % alignmentModDividend;
+	} else {
+		debug(5, "IMuseDigital::dispatchValidateFadeSize(): WARNING: tried mod by 0 while validating fade size in %s(), ignored", function);
+	}
+}
+
+int IMuseDigital::dispatchUpdateFadeMixVolume(IMuseDigiDispatch *dispatchPtr, int32 remainingFade) {
+	int mixVolume = (((dispatchPtr->fadeVol / 65536) + 1) * dispatchPtr->trackPtr->effVol) / 128;
+	dispatchPtr->fadeVol += remainingFade * dispatchPtr->fadeSlope;
+
+	if (dispatchPtr->fadeVol < 0)
+		dispatchPtr->fadeVol = 0;
+	if (dispatchPtr->fadeVol > DIMUSE_MAX_FADE_VOLUME)
+		dispatchPtr->fadeVol = DIMUSE_MAX_FADE_VOLUME;
+
+	return mixVolume;
+}
+
+int IMuseDigital::dispatchUpdateFadeSlope(IMuseDigiDispatch *dispatchPtr) {
+	int32 updatedVolume, effRemainingFade;
+
+	updatedVolume = (dispatchPtr->trackPtr->effVol * (128 - (dispatchPtr->fadeVol / 65536))) / 128;
+	if (!dispatchPtr->fadeSlope) {
+		effRemainingFade = dispatchPtr->fadeRemaining;
+		if (effRemainingFade <= 1)
+			effRemainingFade = 2;
+		dispatchPtr->fadeSlope = -(DIMUSE_MAX_FADE_VOLUME / effRemainingFade);
+	}
+
+	return updatedVolume;
+}
+
+void IMuseDigital::dispatchVOCLoopCallback(int soundId) {
+	IMuseDigiDispatch *curDispatchPtr;
+	uint8 *dataBlockTag;
+
+	if (!soundId)
+		return;
+
+	for (int i = 0; i < _trackCount; i++) {
+		curDispatchPtr = &_dispatches[i];
+		if (curDispatchPtr->trackPtr->soundId == soundId) {
+			dataBlockTag = streamerGetStreamBufferAtOffset(curDispatchPtr->streamPtr, curDispatchPtr->audioRemaining, 1);
+			if (dataBlockTag && dataBlockTag[0] == 7) { // End of loop
+				streamerSetLoadIndex(curDispatchPtr->streamPtr, curDispatchPtr->audioRemaining + 1);
+				streamerSetSoundToStreamFromOffset(curDispatchPtr->streamPtr, curDispatchPtr->trackPtr->soundId, curDispatchPtr->vocLoopStartingPoint);
+			}
+		}
+	}
+}
+
+int IMuseDigital::dispatchSeekToNextChunk(IMuseDigiDispatch *dispatchPtr) {
+	uint8 *headerBuf;
+	uint8 *soundAddrData;
+	int32 resSize;
+
+	while (1) {
+		if (dispatchPtr->streamPtr) {
+			headerBuf = streamerGetStreamBufferAtOffset(dispatchPtr->streamPtr, 0, 0x30);
+			if (headerBuf || (headerBuf = streamerGetStreamBufferAtOffset(dispatchPtr->streamPtr, 0, 1)) != 0) {
+				// Little hack: avoid copying stuff from the resource to the
+				// header buffer beyond the resource size limit
+				resSize = _filesHandler->getSoundAddrDataSize(dispatchPtr->trackPtr->soundId, dispatchPtr->streamPtr != nullptr);
+				if ((resSize - dispatchPtr->currentOffset) < 0x30) {
+					memcpy(_currentVOCHeader, headerBuf, resSize - dispatchPtr->currentOffset);
+				} else {
+					memcpy(_currentVOCHeader, headerBuf, 0x30);
+				}
+			} else {
+				return -3;
+			}
+		} else {
+			soundAddrData = _filesHandler->getSoundAddrData(dispatchPtr->trackPtr->soundId);
+			if (soundAddrData) {
+				// Little hack: see above
+				resSize = _filesHandler->getSoundAddrDataSize(dispatchPtr->trackPtr->soundId, dispatchPtr->streamPtr != nullptr);
+				if ((resSize - dispatchPtr->currentOffset) < 0x30) {
+					memcpy(_currentVOCHeader, &soundAddrData[dispatchPtr->currentOffset], resSize - dispatchPtr->currentOffset);
+				} else {
+					memcpy(_currentVOCHeader, &soundAddrData[dispatchPtr->currentOffset], 0x30);
+				}
+			} else {
+				return -1;
+			}
+		}
+
+		if (READ_BE_UINT32(_currentVOCHeader) == MKTAG('C', 'r', 'e', 'a')) {
+			// We expect to find (everything in Little Endian except where noted):
+			// - The string "Creative Voice File" stored in Big Endian format;
+			// - 0x1A, which the interpreter doesn't check, so we don't either
+			// - Total size of the header, which has to be 0x001A (0x1A00 in LE)
+			// - Version tags: 0x0A (minor), 0x01 (major), this corresponds to version 1.10
+			if (_currentVOCHeader[20] == 0x1A && _currentVOCHeader[21] == 0x0 &&
+				_currentVOCHeader[22] == 0xA && _currentVOCHeader[23] == 0x1) {
+				dispatchPtr->currentOffset += 26;
+				if (dispatchPtr->streamPtr)
+					streamerGetStreamBuffer(dispatchPtr->streamPtr, 26);
+				continue;
+			}
+			return -1;
+		} else {
+			uint8 *headerTag = _currentVOCHeader;
+			if (headerTag[0] != 1 && headerTag[0] != 4 && headerTag[0] != 6 && headerTag[0] != 7)
+				headerTag += 2;
+
+			switch (headerTag[0]) {
+			case 1:
+				dispatchPtr->sampleRate = headerTag[4] > 196 ? 22050 : 11025;
+				dispatchPtr->audioRemaining = (READ_LE_UINT32(headerTag) >> 8) - 2;
+				dispatchPtr->currentOffset += 6;
+
+				// Another little hack to avoid click and pops artifacts:
+				// read one audio sample less if this is the last audio chunk of the file
+				resSize = _filesHandler->getSoundAddrDataSize(dispatchPtr->trackPtr->soundId, dispatchPtr->streamPtr != nullptr);
+				if ((resSize - (dispatchPtr->currentOffset + dispatchPtr->audioRemaining)) < 0x30) {
+					dispatchPtr->audioRemaining -= 2;
+				}
+
+				if (dispatchPtr->streamPtr) {
+					streamerGetStreamBuffer(dispatchPtr->streamPtr, 6);
+					if (dispatchPtr->vocLoopStartingPoint)
+						streamerSetLoopFlag(dispatchPtr->streamPtr, dispatchPtr->audioRemaining + dispatchPtr->currentOffset);
+				}
+				return 0;
+			case 4:
+				// Marker, 2 bytes, theoretically used for triggers, but never actually found in the game;
+				// I am keeping this case here, in order to correctly keep track of the offset
+				dispatchPtr->currentOffset += 6;
+				continue;
+			case 6:
+				dispatchPtr->vocLoopStartingPoint = dispatchPtr->currentOffset;
+				dispatchPtr->currentOffset += 6;
+				if (dispatchPtr->streamPtr)
+					streamerGetStreamBuffer(dispatchPtr->streamPtr, 6);
+				continue;
+			case 7:
+				dispatchPtr->currentOffset = dispatchPtr->vocLoopStartingPoint;
+				if (dispatchPtr->streamPtr)
+					streamerGetStreamBuffer(dispatchPtr->streamPtr, 1);
+				continue;
+			default:
+				return -1;
+			}
+		}
+		return -1;
+	}
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_engine.cpp b/engines/scumm/imuse_digi/dimuse_engine.cpp
new file mode 100644
index 0000000000..71d75f0f55
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_engine.cpp
@@ -0,0 +1,755 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/system.h"
+#include "common/timer.h"
+
+#include "scumm/actor.h"
+#include "scumm/scumm_v7.h"
+#include "scumm/sound.h"
+#include "scumm/imuse_digi/dimuse_engine.h"
+#include "scumm/imuse_digi/dimuse_defs.h"
+#include "scumm/imuse_digi/dimuse_bndmgr.h"
+#include "scumm/imuse_digi/dimuse_sndmgr.h"
+#include "scumm/imuse_digi/dimuse_tables.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+namespace Scumm {
+
+void IMuseDigital::timer_handler(void *refCon) {
+	IMuseDigital *diMUSE = (IMuseDigital *)refCon;
+	diMUSE->callback();
+}
+
+IMuseDigital::IMuseDigital(ScummEngine_v7 *scumm, Audio::Mixer *mixer)
+	: _vm(scumm), _mixer(mixer) {
+	assert(_vm);
+	assert(mixer);
+
+	// 50 Hz rate for the callback
+	_callbackFps = 50;
+	_usecPerInt = 20000;
+
+	_isEarlyDiMUSE = (_vm->_game.id == GID_FT || (_vm->_game.id == GID_DIG && _vm->_game.features & GF_DEMO));
+
+	if (_isEarlyDiMUSE) {
+		memset(_ftCrossfadeBuffer, 0, sizeof(_ftCrossfadeBuffer));
+	}
+
+	_curMixerMusicVolume = _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
+	_curMixerSpeechVolume = _mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType);
+	_curMixerSFXVolume = _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType);
+	_currentSpeechVolume = 0;
+	_currentSpeechFrequency = 0;
+	_currentSpeechPan = 0;
+
+	_waveOutXorTrigger = 0;
+	_waveOutWriteIndex = 0;
+	_waveOutDisableWrite = 0;
+	_waveOutPreferredFeedSize = 0;
+
+	_dispatchFadeSize = 0;
+
+	_stopSequenceFlag = 0;
+	_scriptInitializedFlag = 0;
+	_callbackInterruptFlag = 0;
+
+	_radioChatterSFX = false;
+	_isEngineDisabled = false;
+
+	_audioNames = nullptr;
+	_numAudioNames = 0;
+
+	_emptyMarker[0] = '\0';
+	_internalMixer = new IMuseDigiInternalMixer(mixer);
+	_groupsHandler = new IMuseDigiGroupsHandler(this);
+	_fadesHandler = new IMuseDigiFadesHandler(this);
+	_triggersHandler = new IMuseDigiTriggersHandler(this);
+	_filesHandler = new IMuseDigiFilesHandler(this, scumm);
+
+	diMUSEInitialize();
+	diMUSEInitializeScript();
+	if (_vm->_game.id == GID_CMI) {
+		_filesHandler->allocSoundBuffer(DIMUSE_BUFFER_SPEECH, 176000, 44000, 88000);
+		_filesHandler->allocSoundBuffer(DIMUSE_BUFFER_MUSIC, 528000, 44000, 352000);
+	} else if (_vm->_game.id == GID_DIG && !isFTSoundEngine()) {
+		_filesHandler->allocSoundBuffer(DIMUSE_BUFFER_SPEECH, 88000, 22000, 44000);
+		_filesHandler->allocSoundBuffer(DIMUSE_BUFFER_MUSIC, 528000, 11000, 132000);
+	} else {
+		_filesHandler->allocSoundBuffer(DIMUSE_BUFFER_SPEECH, 110000, 22000, 44000);
+		_filesHandler->allocSoundBuffer(DIMUSE_BUFFER_MUSIC, 220000, 22000, 44000);
+	}
+
+	_filesHandler->allocSoundBuffer(DIMUSE_BUFFER_SFX, 198000, 0, 0);
+
+	_vm->getTimerManager()->installTimerProc(timer_handler, 1000000 / _callbackFps, this, "IMuseDigital");
+}
+
+IMuseDigital::~IMuseDigital() {
+	_vm->getTimerManager()->removeTimerProc(timer_handler);
+	_filesHandler->deallocSoundBuffer(DIMUSE_BUFFER_SPEECH);
+	_filesHandler->deallocSoundBuffer(DIMUSE_BUFFER_MUSIC);
+	_filesHandler->deallocSoundBuffer(DIMUSE_BUFFER_SFX);
+	cmdsDeinit();
+	diMUSETerminate();
+	delete _internalMixer;
+	delete _groupsHandler;
+	delete _fadesHandler;
+	delete _triggersHandler;
+	delete _filesHandler;
+
+	// Deinit the Dispatch module
+	free(_dispatchBuffer);
+	_dispatchBuffer = nullptr;
+
+	// Deinit the WaveOut module
+	free(_waveOutOutputBuffer);
+	_waveOutOutputBuffer = nullptr;
+
+	free(_audioNames);
+}
+
+void IMuseDigital::stopSound(int sound) {
+	diMUSEStopSound(sound);
+}
+
+void IMuseDigital::stopAllSounds() {
+	diMUSEStopAllSounds();
+}
+
+int IMuseDigital::isSoundRunning(int soundId) {
+	return diMUSEGetParam(soundId, DIMUSE_P_SND_TRACK_NUM) > 0;
+}
+
+int IMuseDigital::startVoice(int soundId, const char *soundName, byte speakingActorId) {
+	_filesHandler->closeSoundImmediatelyById(soundId);
+
+	int fileDoesNotExist = 0;
+	if (_vm->_game.id == GID_DIG) {
+		if (!strcmp(soundName, "PIG.018"))
+			fileDoesNotExist = _filesHandler->setCurrentSpeechFilename("PIG.019");
+		else
+			fileDoesNotExist = _filesHandler->setCurrentSpeechFilename(soundName);
+
+		if (fileDoesNotExist)
+			return 1;
+
+		// Workaround for this particular sound file not playing (this is a bug in the original):
+		// this is happening because the sound buffer responsible for speech
+		// is still busy with the previous speech file playing during the SAN
+		// movie. We just stop the SMUSH speech sound before playing NEXUS.029.
+		if (!strcmp(soundName, "NEXUS.029")) {
+			diMUSEStopSound(DIMUSE_SMUSH_SOUNDID + DIMUSE_BUFFER_SPEECH);
+		}
+
+		diMUSEStartStream(kTalkSoundID, 127, DIMUSE_BUFFER_SPEECH);
+		diMUSESetParam(kTalkSoundID, DIMUSE_P_GROUP, DIMUSE_GROUP_SPEECH);
+		if (speakingActorId == _vm->VAR(_vm->VAR_EGO)) {
+			diMUSESetParam(kTalkSoundID, DIMUSE_P_MAILBOX, 0);
+			diMUSESetParam(kTalkSoundID, DIMUSE_P_VOLUME, 127);
+		} else {
+			diMUSESetParam(kTalkSoundID, DIMUSE_P_MAILBOX, _radioChatterSFX);
+			diMUSESetParam(kTalkSoundID, DIMUSE_P_VOLUME, 88);
+		}
+		_filesHandler->closeSound(kTalkSoundID);
+	} else if (_vm->_game.id == GID_CMI) {
+		fileDoesNotExist = _filesHandler->setCurrentSpeechFilename(soundName);
+		if (fileDoesNotExist)
+			return 1;
+
+		diMUSEStartStream(kTalkSoundID, 127, DIMUSE_BUFFER_SPEECH);
+		diMUSESetParam(kTalkSoundID, DIMUSE_P_GROUP, DIMUSE_GROUP_SPEECH);
+
+		// Let's not give the occasion to raise errors here
+		if (_vm->isValidActor(_vm->VAR(_vm->VAR_TALK_ACTOR))) {
+			Actor *a = _vm->derefActor(_vm->VAR(_vm->VAR_TALK_ACTOR), "IMuseDigital::startVoice");
+			if (_vm->VAR(_vm->VAR_VOICE_MODE) == 2)
+				diMUSESetParam(kTalkSoundID, DIMUSE_P_VOLUME, 0);
+			else
+				diMUSESetParam(kTalkSoundID, DIMUSE_P_VOLUME, a->_talkVolume);
+
+			diMUSESetParam(kTalkSoundID, DIMUSE_P_TRANSPOSE, a->_talkFrequency);
+			diMUSESetParam(kTalkSoundID, DIMUSE_P_PAN, a->_talkPan);
+		}
+
+		// The interpreter really calls for processStreams two times in a row,
+		// and who am I to contradict it?
+		diMUSEProcessStreams();
+		diMUSEProcessStreams();
+	}
+
+	return 0;
+}
+
+// Used by FT and DIG demo
+int IMuseDigital::startVoice(const char *fileName, ScummFile *file, uint32 offset, uint32 size) {
+	_filesHandler->setCurrentFtSpeechFile(fileName, file, offset, size);
+	diMUSEStopSound(kTalkSoundID);
+	diMUSEStartStream(kTalkSoundID, 127, DIMUSE_BUFFER_SPEECH);
+	diMUSESetParam(kTalkSoundID, DIMUSE_P_GROUP, DIMUSE_GROUP_SPEECH);
+	return 0;
+}
+
+void IMuseDigital::saveLoadEarly(Common::Serializer &s) {
+	Common::StackLock lock(_mutex, "IMuseDigital::saveLoadEarly()");
+
+	if (s.isLoading()) {
+		diMUSEStopAllSounds();
+		_filesHandler->closeSoundImmediatelyById(kTalkSoundID);
+	}
+
+	if (s.getVersion() < 103) {
+		// Just load the current state and sequence, and play them
+		debug(5, "IMuseDigital::saveLoadEarly(): old savegame detected (version %d), game may load with an undesired audio status", s.getVersion());
+		s.skip(4, VER(31), VER(42)); // _volVoice
+		s.skip(4, VER(31), VER(42)); // _volSfx
+		s.skip(4, VER(31), VER(42)); // _volMusic
+		s.syncAsSint32LE(_curMusicState, VER(31));
+		s.syncAsSint32LE(_curMusicSeq, VER(31));
+		s.syncAsSint32LE(_curMusicCue, VER(31));
+		s.syncAsSint32LE(_nextSeqToPlay, VER(31));
+		s.syncAsByte(_radioChatterSFX, VER(76));
+		s.syncArray(_attributes, 188, Common::Serializer::Sint32LE, VER(31));
+
+		int stateSoundId = 0;
+		int seqSoundId = 0;
+
+		// TODO: Check if FT actually works here
+		if (_vm->_game.id == GID_DIG) {
+			stateSoundId = _digStateMusicTable[_curMusicState].soundId;
+			seqSoundId = _digSeqMusicTable[_curMusicSeq].soundId;
+		} else {
+			if (_vm->_game.features & GF_DEMO) {
+				stateSoundId = _comiDemoStateMusicTable[_curMusicState].soundId;
+			} else {
+				stateSoundId = _comiStateMusicTable[_curMusicState].soundId;
+				seqSoundId = _comiSeqMusicTable[_curMusicSeq].soundId;
+			}
+		}
+
+		_curMusicState = 0;
+		_curMusicSeq = 0;
+		scriptSetSequence(seqSoundId);
+		scriptSetState(stateSoundId);
+		scriptSetCuePoint(_curMusicCue);
+		_curMusicCue = 0;
+	} else {
+		diMUSESaveLoad(s);
+	}
+}
+
+void IMuseDigital::refreshScripts() {
+	if (!_vm->isSmushActive()) {
+		diMUSEProcessStreams();
+		diMUSERefreshScript();
+	}
+}
+
+void IMuseDigital::setRadioChatterSFX(bool state) {
+	_radioChatterSFX = state;
+}
+
+int IMuseDigital::startSfx(int soundId, int priority) {
+	diMUSEStartSound(soundId, priority);
+	diMUSESetParam(soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_SFX);
+	return 0;
+}
+
+void IMuseDigital::callback() {
+	if (_cmdsPauseCount)
+		return;
+
+	if (!_callbackInterruptFlag) {
+		_callbackInterruptFlag = 1;
+		diMUSEHeartbeat();
+		_callbackInterruptFlag = 0;
+	}
+}
+
+void IMuseDigital::diMUSEHeartbeat() {
+	// This is what happens:
+	// - Usual audio stuff like fetching and playing sound (and everything
+	//   within waveapi_callback()) happens at a base 50Hz rate;
+	// - Triggers and fades handling happens at a (somewhat hacky) 60Hz rate;
+	// - Music gain reduction happens at a 10Hz rate.
+
+	int soundId, foundGroupId, musicTargetVolume, musicEffVol, musicVol, tempVol, tempEffVol, factor, step;
+
+	waveOutCallback();
+
+	// Update volumes
+
+	if (_curMixerMusicVolume != _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType)) {
+		_curMixerMusicVolume = _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
+		diMUSESetMusicGroupVol(CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 2, 0, 127));
+	}
+
+	if (_curMixerSpeechVolume != _mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType)) {
+		_curMixerSpeechVolume = _mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType);
+		diMUSESetVoiceGroupVol(CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType) / 2, 0, 127));
+	}
+
+	if (_curMixerSFXVolume != _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType)) {
+		_curMixerSFXVolume = _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType);
+		diMUSESetSFXGroupVol(CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 2, 0, 127));
+	}
+
+	// Handle fades and triggers
+
+	_cmdsRunning60HzCount += _usecPerInt;
+	while (_cmdsRunning60HzCount >= 16667) {
+		_cmdsRunning60HzCount -= 16667;
+		_fadesHandler->loop();
+		_triggersHandler->loop();
+	}
+
+	_cmdsRunning10HzCount += _usecPerInt;
+	if (_cmdsRunning10HzCount < 100000)
+		return;
+
+	do {
+		// SPEECH GAIN REDUCTION 10Hz
+		_cmdsRunning10HzCount -= 100000;
+		soundId = 0;
+		musicTargetVolume = _groupsHandler->setGroupVol(DIMUSE_GROUP_MUSIC, -1);
+		while (1) { // Check all tracks to see if there's a speech file playing
+			soundId = waveGetNextSound(soundId);
+			if (!soundId)
+				break;
+
+			foundGroupId = -1;
+			if (_filesHandler->getNextSound(soundId) == 2) {
+				foundGroupId = waveGetParam(soundId, DIMUSE_P_GROUP); // Check the groupId of this sound
+			}
+
+			if (foundGroupId == DIMUSE_GROUP_SPEECH) {
+				// Remember: when a speech file stops playing this block stops
+				// being executed, so musicTargetVolume returns back to its original value
+				factor = _isEarlyDiMUSE ? 82 : 80;
+				musicTargetVolume = (musicTargetVolume * factor) / 128;
+				break;
+			}
+		}
+
+		musicEffVol = _groupsHandler->setGroupVol(DIMUSE_GROUP_MUSICEFF, -1); // MUSIC EFFECTIVE VOLUME GROUP (used for gain reduction)
+		musicVol = _groupsHandler->setGroupVol(DIMUSE_GROUP_MUSIC, -1); // MUSIC VOLUME SUBGROUP (keeps track of original music volume)
+
+		if (musicEffVol < musicTargetVolume) { // If there is gain reduction already going on...
+			tempEffVol = musicEffVol + 3;
+			if (tempEffVol < musicTargetVolume) {
+				if (musicVol <= tempEffVol) {
+					musicVol = tempEffVol;
+				}
+			} else if (musicVol <= musicTargetVolume) { // Bring up the music volume immediately when speech stops playing
+				musicVol = musicTargetVolume;
+			}
+			_groupsHandler->setGroupVol(DIMUSE_GROUP_MUSICEFF, musicVol);
+		} else if (musicEffVol > musicTargetVolume) {
+			// Bring music volume down to target volume with a -18 (or -6 for FT & DIG demo) step
+		    // if there's speech playing or else, just cap it to the target if it's out of range
+			step = _isEarlyDiMUSE ? 6 : 18;
+			tempVol = musicEffVol - step;
+			if (tempVol <= musicTargetVolume) {
+				if (musicVol >= musicTargetVolume) {
+					musicVol = musicTargetVolume;
+				}
+			} else {
+				if (musicVol >= tempVol) {
+					musicVol = tempVol;
+				}
+			}
+			_groupsHandler->setGroupVol(DIMUSE_GROUP_MUSICEFF, musicVol);
+		}
+
+	} while (_cmdsRunning10HzCount >= 100000);
+}
+
+void IMuseDigital::setPriority(int soundId, int priority) {
+	diMUSESetParam(soundId, DIMUSE_P_PRIORITY, priority);
+}
+
+void IMuseDigital::setVolume(int soundId, int volume) {
+	diMUSESetParam(soundId, DIMUSE_P_VOLUME, volume);
+	if (soundId == kTalkSoundID)
+		_currentSpeechVolume = volume;
+}
+
+void IMuseDigital::setPan(int soundId, int pan) {
+	diMUSESetParam(soundId, DIMUSE_P_PAN, pan);
+	if (soundId == kTalkSoundID)
+		_currentSpeechPan = pan;
+}
+
+void IMuseDigital::setFrequency(int soundId, int frequency) {
+	diMUSESetParam(soundId, DIMUSE_P_TRANSPOSE, frequency);
+	if (soundId == kTalkSoundID)
+		_currentSpeechFrequency = frequency;
+}
+
+int IMuseDigital::getCurSpeechVolume() const {
+	return _currentSpeechVolume;
+}
+
+int IMuseDigital::getCurSpeechPan() const {
+	return _currentSpeechPan;
+}
+
+int IMuseDigital::getCurSpeechFrequency() const {
+	return _currentSpeechFrequency;
+}
+
+void IMuseDigital::flushTracks() {
+	_filesHandler->flushSounds();
+}
+
+// This is used in order to avoid crash everything
+// if a compressed audio resource file is found
+void IMuseDigital::disableEngine() {
+	_isEngineDisabled = true;
+}
+
+bool IMuseDigital::isEngineDisabled() {
+	return _isEngineDisabled;
+}
+
+bool IMuseDigital::isFTSoundEngine() {
+	return _isEarlyDiMUSE;
+}
+
+int32 IMuseDigital::getCurMusicPosInMs() {
+	int soundId, curSoundId;
+
+	curSoundId = 0;
+	soundId = 0;
+	while (1) {
+		curSoundId = diMUSEGetNextSound(curSoundId);
+		if (!curSoundId)
+			break;
+
+		if (diMUSEGetParam(curSoundId, DIMUSE_P_SND_HAS_STREAM) && diMUSEGetParam(curSoundId, DIMUSE_P_STREAM_BUFID) == DIMUSE_BUFFER_MUSIC) {
+			soundId = curSoundId;
+			return diMUSEGetParam(soundId, DIMUSE_P_SND_POS_IN_MS);
+		}
+	}
+
+	return diMUSEGetParam(soundId, DIMUSE_P_SND_POS_IN_MS);
+}
+
+int32 IMuseDigital::getCurVoiceLipSyncWidth() {
+	int32 width, height;
+	getSpeechLipSyncInfo(width, height);
+	return width;
+}
+
+int32 IMuseDigital::getCurVoiceLipSyncHeight() {
+	int32 width, height;
+	getSpeechLipSyncInfo(width, height);
+	return height;
+}
+
+int32 IMuseDigital::getCurMusicLipSyncWidth(int syncId) {
+	int32 width, height;
+	getMusicLipSyncInfo(syncId, width, height);
+	return width;
+}
+
+int32 IMuseDigital::getCurMusicLipSyncHeight(int syncId) {
+	int32 width, height;
+	getMusicLipSyncInfo(syncId, width, height);
+	return height;
+}
+
+void IMuseDigital::getSpeechLipSyncInfo(int32 &width, int32 &height) {
+	int curSpeechPosInMs;
+
+	width = 0;
+	height = 0;
+
+	if (diMUSEGetParam(kTalkSoundID, DIMUSE_P_SND_TRACK_NUM) > 0) {
+		curSpeechPosInMs = diMUSEGetParam(kTalkSoundID, DIMUSE_P_SND_POS_IN_MS);
+		diMUSELipSync(kTalkSoundID, 0, _vm->VAR(_vm->VAR_SYNC) + curSpeechPosInMs + 50, width, height);
+	}
+}
+
+void IMuseDigital::getMusicLipSyncInfo(int syncId, int32 &width, int32 &height) {
+	int soundId;
+	int speechSoundId;
+	int curSpeechPosInMs;
+
+	soundId = 0;
+	speechSoundId = 0;
+	width = 0;
+	height = 0;
+	while (1) {
+		soundId = diMUSEGetNextSound(soundId);
+		if (!soundId)
+			break;
+		if (diMUSEGetParam(soundId, DIMUSE_P_SND_HAS_STREAM)) {
+			if (diMUSEGetParam(soundId, DIMUSE_P_STREAM_BUFID) == DIMUSE_BUFFER_MUSIC) {
+				speechSoundId = soundId;
+				break;
+			}
+		}
+	}
+
+	if (speechSoundId) {
+		curSpeechPosInMs = diMUSEGetParam(speechSoundId, DIMUSE_P_SND_POS_IN_MS);
+		diMUSELipSync(speechSoundId, syncId, _vm->VAR(_vm->VAR_SYNC) + curSpeechPosInMs + 50, width, height);
+	}
+}
+
+int32 IMuseDigital::getSoundElapsedTimeInMs(int soundId) {
+	if (diMUSEGetParam(soundId, DIMUSE_P_SND_HAS_STREAM)) {
+		return diMUSEGetParam(soundId, DIMUSE_P_SND_POS_IN_MS);
+	}
+	return 0;
+}
+
+void IMuseDigital::pause(bool p) {
+	if (p) {
+		debug(5, "IMuseDigital::pause(): pausing...");
+		diMUSEPause();
+	} else {
+		debug(5, "IMuseDigital::pause(): resuming...");
+		diMUSEResume();
+	}
+}
+
+void IMuseDigital::setAudioNames(int32 num, char *names) {
+	free(_audioNames);
+	_numAudioNames = num;
+	_audioNames = names;
+}
+
+int IMuseDigital::getSoundIdByName(const char *soundName) {
+	if (soundName && soundName[0] != 0) {
+		for (int r = 0; r < _numAudioNames; r++) {
+			if (strcmp(soundName, &_audioNames[r * 9]) == 0) {
+				return r;
+			}
+		}
+	}
+
+	return 0;
+}
+
+void IMuseDigital::parseScriptCmds(int cmd, int soundId, int sub_cmd, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n, int o, int p) {
+	int b = soundId;
+	int c = sub_cmd;
+	int id;
+	switch (cmd) {
+	case 0x1000:
+		// SetState
+		diMUSESetState(soundId);
+		break;
+	case 0x1001:
+		// SetSequence
+		diMUSESetSequence(soundId);
+		break;
+	case 0x1002:
+		// SetCuePoint
+		diMUSESetCuePoint(soundId);
+		break;
+	case 0x1003:
+		// SetAttribute
+		diMUSESetAttribute(b, c);
+		break;
+	case 0x2000:
+		// SetGroupSfxVolume
+		diMUSESetSFXGroupVol(CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 2, 0, 127));
+		break;
+	case 0x2001:
+		// SetGroupVoiceVolume
+		diMUSESetVoiceGroupVol(CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType) / 2, 0, 127));
+		break;
+	case 0x2002:
+		// SetGroupMusicVolume
+		diMUSESetMusicGroupVol(CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 2, 0, 127));
+		break;
+	case 10: // StopAllSounds
+	case 12: // SetParam
+	case 14: // FadeParam
+		cmdsHandleCmd(cmd, nullptr, soundId, sub_cmd, d, e, f, g, h, i, j, k, l, m, n, o);
+		break;
+	case 25: // OpenSound
+		if (_vm->_game.id == GID_FT) {
+			id = getSoundIdByName("kstand");
+			_filesHandler->openSound(id);
+		} else if (_vm->_game.id == GID_DIG && _vm->_game.features & GF_DEMO) {
+			// Special opcode used in place of the first setState instruction
+			_filesHandler->openSound(soundId);
+			diMUSEStartStream(soundId, 126, DIMUSE_BUFFER_MUSIC);
+		}
+
+		break;
+	case 26:
+		// Special opcode used in place of successive setState instructions
+		if (_vm->_game.id == GID_DIG && _vm->_game.features & GF_DEMO) {
+			_filesHandler->openSound(c);
+			diMUSESwitchStream(soundId, c, _ftCrossfadeBuffer, sizeof(_ftCrossfadeBuffer), 0);
+			_filesHandler->closeSound(soundId);
+		}
+		break;
+	default:
+		debug("IMuseDigital::parseScriptCmds(): WARNING: unhandled command %d", cmd);
+	}
+}
+
+int IMuseDigital::diMUSETerminate() {
+	if (_scriptInitializedFlag) {
+		diMUSEStopAllSounds();
+		_filesHandler->closeAllSounds();
+	}
+
+	return 0;
+}
+
+int IMuseDigital::diMUSEInitialize() {
+	return cmdsHandleCmd(0);
+}
+
+int IMuseDigital::diMUSEPause() {
+	return cmdsHandleCmd(3);
+}
+
+int IMuseDigital::diMUSEResume() {
+	return cmdsHandleCmd(4);
+}
+
+void IMuseDigital::diMUSESaveLoad(Common::Serializer &ser) {
+	cmdsSaveLoad(ser);
+}
+
+int IMuseDigital::diMUSESetGroupVol(int groupId, int volume) {
+	return cmdsHandleCmd(7, nullptr, groupId, volume);
+}
+
+int IMuseDigital::diMUSEStartSound(int soundId, int priority) {
+	return cmdsHandleCmd(8, nullptr, soundId, priority);
+}
+
+int IMuseDigital::diMUSEStopSound(int soundId) {
+	debug(5, "IMuseDigital::diMUSEStopSound(): %d", soundId);
+	return cmdsHandleCmd(9, nullptr, soundId);
+}
+
+int IMuseDigital::diMUSEStopAllSounds() {
+	debug(5, "IMuseDigital::diMUSEStopAllSounds()");
+	return cmdsHandleCmd(10);
+}
+
+int IMuseDigital::diMUSEGetNextSound(int soundId) {
+	return cmdsHandleCmd(11, nullptr, soundId);
+}
+
+int IMuseDigital::diMUSESetParam(int soundId, int paramId, int value) {
+	return cmdsHandleCmd(12, nullptr, soundId, paramId, value);
+}
+
+int IMuseDigital::diMUSEGetParam(int soundId, int paramId) {
+	return cmdsHandleCmd(13, nullptr, soundId, paramId);
+}
+
+int IMuseDigital::diMUSEFadeParam(int soundId, int opcode, int destValue, int fadeLength) {
+	return cmdsHandleCmd(14, nullptr, soundId, opcode, destValue, fadeLength);
+}
+
+int IMuseDigital::diMUSESetHook(int soundId, int hookId) {
+	return cmdsHandleCmd(15, nullptr, soundId, hookId);
+}
+
+int IMuseDigital::diMUSESetTrigger(int soundId, int marker, int opcode, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n) {
+	return cmdsHandleCmd(17, nullptr, soundId, marker, opcode, d, e, f, g, h, i, j, k, l, m, n);
+}
+
+int IMuseDigital::diMUSEStartStream(int soundId, int priority, int bufferId) {
+	return cmdsHandleCmd(25, nullptr, soundId, priority, bufferId);
+}
+
+int IMuseDigital::diMUSESwitchStream(int oldSoundId, int newSoundId, int fadeDelay, int fadeSyncFlag2, int fadeSyncFlag1) {
+	return cmdsHandleCmd(26, nullptr, oldSoundId, newSoundId, fadeDelay, fadeSyncFlag2, fadeSyncFlag1);
+}
+
+// Variation for FT and DIG demo
+int IMuseDigital::diMUSESwitchStream(int oldSoundId, int newSoundId, uint8 *crossfadeBuffer, int crossfadeBufferSize, int vocLoopFlag) {
+	return cmdsHandleCmd(26, crossfadeBuffer, oldSoundId, newSoundId, -1, crossfadeBufferSize, vocLoopFlag);
+}
+
+int IMuseDigital::diMUSEProcessStreams() {
+	return cmdsHandleCmd(27);
+}
+
+int IMuseDigital::diMUSEFeedStream(int soundId, uint8 *srcBuf, int32 sizeToFeed, int paused) {
+	return cmdsHandleCmd(29, srcBuf, soundId, -1, sizeToFeed, paused);
+}
+
+int IMuseDigital::diMUSELipSync(int soundId, int syncId, int msPos, int32 &width, int32 &height) {
+	return waveLipSync(soundId, syncId, msPos, width, height);
+}
+
+int IMuseDigital::diMUSESetMusicGroupVol(int volume) {
+	debug(5, "IMuseDigital::diMUSESetMusicGroupVol(): %d", volume);
+	return diMUSESetGroupVol(3, volume);
+}
+
+int IMuseDigital::diMUSESetSFXGroupVol(int volume) {
+	debug(5, "IMuseDigital::diMUSESetSFXGroupVol(): %d", volume);
+	return diMUSESetGroupVol(1, volume);
+}
+
+int IMuseDigital::diMUSESetVoiceGroupVol(int volume) {
+	debug(5, "IMuseDigital::diMUSESetVoiceGroupVol(): %d", volume);
+	return diMUSESetGroupVol(2, volume);
+}
+
+void IMuseDigital::diMUSEUpdateGroupVolumes() {
+	waveUpdateGroupVolumes();
+}
+
+int IMuseDigital::diMUSEInitializeScript() {
+	return scriptParse(0, -1, -1);
+}
+
+void IMuseDigital::diMUSERefreshScript() {
+	diMUSEProcessStreams();
+	scriptParse(4, -1, -1);
+}
+
+int IMuseDigital::diMUSESetState(int soundId) {
+	return scriptParse(5, soundId, -1);
+}
+
+int IMuseDigital::diMUSESetSequence(int soundId) {
+	return scriptParse(6, soundId, -1);
+}
+
+int IMuseDigital::diMUSESetCuePoint(int cueId) {
+	return scriptParse(7, cueId, -1);
+}
+
+int IMuseDigital::diMUSESetAttribute(int attrIndex, int attrVal) {
+	return scriptParse(8, attrIndex, attrVal);
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_engine.h b/engines/scumm/imuse_digi/dimuse_engine.h
new file mode 100644
index 0000000000..da0d1bb659
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_engine.h
@@ -0,0 +1,391 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#if !defined(SCUMM_IMUSE_DIGI_H) && defined(ENABLE_SCUMM_7_8)
+#define SCUMM_IMUSE_DIGI_H
+
+#include "common/scummsys.h"
+#include "common/mutex.h"
+#include "common/serializer.h"
+#include "common/textconsole.h"
+#include "common/util.h"
+
+#include "scumm/scumm_v7.h"
+#include "scumm/music.h"
+#include "scumm/sound.h"
+#include "scumm/file.h"
+
+#include "scumm/imuse_digi/dimuse_defs.h"
+#include "scumm/imuse_digi/dimuse_internalmixer.h"
+#include "scumm/imuse_digi/dimuse_groups.h"
+#include "scumm/imuse_digi/dimuse_fades.h"
+#include "scumm/imuse_digi/dimuse_files.h"
+#include "scumm/imuse_digi/dimuse_triggers.h"
+#include "scumm/imuse_digi/dimuse_bndmgr.h"
+#include "scumm/imuse_digi/dimuse_sndmgr.h"
+#include "scumm/imuse_digi/dimuse_tables.h"
+
+#include "audio/mixer.h"
+#include "audio/decoders/raw.h"
+
+namespace Audio {
+class AudioStream;
+class Mixer;
+class QueuingAudioStream;
+}
+
+namespace Scumm {
+class ScummEngine_v7;
+
+struct imuseDigTable;
+struct imuseComiTable;
+struct IMuseDigiDispatch;
+struct IMuseDigiTrack;
+struct IMuseDigiStreamZone;
+
+class IMuseDigital : public MusicEngine {
+private:
+	Common::Mutex _mutex;
+	ScummEngine_v7 *_vm;
+	Audio::Mixer *_mixer;
+
+	IMuseDigiInternalMixer *_internalMixer;
+	IMuseDigiGroupsHandler *_groupsHandler;
+	IMuseDigiFadesHandler *_fadesHandler;
+	IMuseDigiTriggersHandler *_triggersHandler;
+	IMuseDigiFilesHandler *_filesHandler;
+
+	int _callbackFps;
+	static void timer_handler(void *refConf);
+	void callback();
+
+	bool _isEarlyDiMUSE;
+	bool _isEngineDisabled;
+
+	// These three are manipulated in the waveOut functions
+	uint8 *_outputAudioBuffer;
+	int _outputFeedSize;
+	int _outputSampleRate;
+
+	int _currentSpeechVolume, _currentSpeechFrequency, _currentSpeechPan;
+	int _curMixerMusicVolume, _curMixerSpeechVolume, _curMixerSFXVolume;
+	bool _radioChatterSFX;
+
+	int32 _attributes[188];	// internal attributes for each music file to store and check later
+	int32 _nextSeqToPlay;
+	int32 _curMusicState;
+	int32 _curMusicSeq;
+	int32 _curMusicCue;
+
+	char *_audioNames;		// filenames of sound SFX used in FT
+	int32 _numAudioNames;	// number of above filenames
+	uint8 _currentVOCHeader[52]; // Header for the current sound for early DiMUSE
+
+	int _stopSequenceFlag;
+	int _scriptInitializedFlag;
+	char _emptyMarker[1];
+
+	int _usecPerInt; // Microseconds between each callback (will be set to 50 Hz)
+	int _callbackInterruptFlag;
+	void diMUSEHeartbeat();
+
+	void setFtMusicState(int stateId);
+	void setFtMusicSequence(int seqId);
+	void playFtMusic(const char *songName, int opcode, int volume);
+	void setDigMusicState(int stateId);
+	void setDigMusicSequence(int seqId);
+	void playDigMusic(const char *songName, const imuseDigTable *table, int attribPos, bool sequence);
+	void setComiMusicState(int stateId);
+	void setComiMusicSequence(int seqId);
+	void playComiMusic(const char *songName, const imuseComiTable *table, int attribPos, bool sequence);
+	void playComiDemoMusic(const char *songName, const imuseComiTable *table, int attribPos, bool sequence);
+	int getSoundIdByName(const char *soundName);
+
+	// Script
+	int scriptParse(int cmd, int a, int b);
+	int scriptInit();
+	int scriptTerminate();
+	void scriptRefresh();
+	void scriptSetState(int soundId);
+	void scriptSetSequence(int soundId);
+	void scriptSetCuePoint(int cueId);
+	int scriptSetAttribute(int attrIndex, int attrVal);
+
+	// CMDs
+	int _cmdsPauseCount;
+	int _cmdsRunning60HzCount;
+	int _cmdsRunning10HzCount;
+
+	int cmdsInit();
+	int cmdsDeinit();
+	int cmdsTerminate();
+	int cmdsPause();
+	int cmdsResume();
+	void cmdsSaveLoad(Common::Serializer &ser);
+	int cmdsStartSound(int soundId, int priority);
+	int cmdsStopSound(int soundId);
+	int cmdsStopAllSounds();
+	int cmdsGetNextSound(int soundId);
+	int cmdsSetParam(int soundId, int opcode, int value);
+	int cmdsGetParam(int soundId, int opcode);
+	int cmdsSetHook(int soundId, int hookId);
+	int cmdsGetHook(int soundId);
+
+	// Streamer
+	IMuseDigiStream _streams[DIMUSE_MAX_STREAMS];
+	IMuseDigiStream *_lastStreamLoaded;
+	int _streamerBailFlag;
+
+	int streamerInit();
+	IMuseDigiStream *streamerAllocateSound(int soundId, int bufId, int32 maxRead);
+	int streamerClearSoundInStream(IMuseDigiStream *streamPtr);
+	int streamerProcessStreams();
+	uint8 *streamerGetStreamBuffer(IMuseDigiStream *streamPtr, int size);
+	uint8 *streamerGetStreamBufferAtOffset(IMuseDigiStream *streamPtr, int32 offset, int size);
+	int streamerSetReadIndex(IMuseDigiStream *streamPtr, int offset);
+	int streamerSetLoadIndex(IMuseDigiStream *streamPtr, int offset);
+	int streamerGetFreeBufferAmount(IMuseDigiStream *streamPtr);
+	int streamerSetSoundToStreamFromOffset(IMuseDigiStream *streamPtr, int soundId, int32 offset);
+	int streamerQueryStream(IMuseDigiStream *streamPtr, int32 &bufSize, int32 &criticalSize, int32 &freeSpace, int &paused);
+	int streamerFeedStream(IMuseDigiStream *streamPtr, uint8 *srcBuf, int32 sizeToFeed, int paused);
+	int streamerFetchData(IMuseDigiStream *streamPtr);
+	void streamerSetLoopFlag(IMuseDigiStream *streamPtr, int offset);
+	void streamerRemoveLoopFlag(IMuseDigiStream *streamPtr);
+
+	// Tracks
+	IMuseDigiTrack _tracks[DIMUSE_MAX_TRACKS];
+	IMuseDigiTrack *_trackList;
+
+	int _trackCount;
+	int _tracksPauseTimer;
+	int _tracksPrefSampleRate;
+	int _tracksMicroSecsToFeed;
+
+	int tracksInit();
+	void tracksPause();
+	void tracksResume();
+	void tracksSaveLoad(Common::Serializer &ser);
+	void tracksSetGroupVol();
+	void tracksCallback();
+	int tracksStartSound(int soundId, int tryPriority, int group);
+	int tracksStopSound(int soundId);
+	int tracksStopAllSounds();
+	int tracksGetNextSound(int soundId);
+	int tracksFeedStream(int soundId, uint8 *srcBuf, int32 sizeToFeed, int paused);
+	void tracksClear(IMuseDigiTrack *trackPtr);
+	int tracksSetParam(int soundId, int opcode, int value);
+	int tracksGetParam(int soundId, int opcode);
+	int tracksLipSync(int soundId, int syncId, int msPos, int32 &width, int32 &height);
+	int tracksSetHook(int soundId, int hookId);
+	int tracksGetHook(int soundId);
+	IMuseDigiTrack *tracksReserveTrack(int priority);
+	void tracksDeinit();
+
+	// Dispatch
+	IMuseDigiDispatch _dispatches[DIMUSE_MAX_DISPATCHES];
+	IMuseDigiStreamZone _streamZones[DIMUSE_MAX_STREAMZONES];
+	uint8 *_dispatchBuffer;
+	uint8 _ftCrossfadeBuffer[30000]; // Used by FT & DIG demo
+	int32 _dispatchSize;
+	uint8 *_dispatchSmallFadeBufs;
+	uint8 *_dispatchLargeFadeBufs;
+	int32 _dispatchFadeSize;
+	int _dispatchLargeFadeFlags[DIMUSE_LARGE_FADES];
+	int _dispatchSmallFadeFlags[DIMUSE_SMALL_FADES];
+	int _dispatchFadeStartedFlag;
+	int _dispatchBufferedHookId;
+	int32 _dispatchJumpFadeSize;
+	int32 _dispatchCurStreamBufSize;
+	int32 _dispatchCurStreamCriticalSize;
+	int32 _dispatchCurStreamFreeSpace;
+	int _dispatchCurStreamPaused;
+
+	int dispatchInit();
+	IMuseDigiDispatch *dispatchGetDispatchByTrackId(int trackId);
+	void dispatchSaveLoad(Common::Serializer &ser);
+	int dispatchRestoreStreamZones();
+	int dispatchAllocateSound(IMuseDigiTrack *trackPtr, int groupId);
+	int dispatchRelease(IMuseDigiTrack *trackPtr);
+	int dispatchSwitchStream(int oldSoundId, int newSoundId, int fadeLength, int unusedFadeSyncFlag, int offsetFadeSyncFlag);
+	int dispatchSwitchStream(int oldSoundId, int newSoundId, uint8 *crossfadeBuffer, int crossfadeBufferSize, int vocLoopFlag);
+	void dispatchProcessDispatches(IMuseDigiTrack *trackPtr, int feedSize, int sampleRate);
+	void dispatchProcessDispatches(IMuseDigiTrack *trackPtr, int feedSize);
+	void dispatchPredictFirstStream();
+	int dispatchNavigateMap(IMuseDigiDispatch *dispatchPtr);
+	int dispatchGetMap(IMuseDigiDispatch *dispatchPtr);
+	int dispatchConvertMap(uint8 *rawMap, uint8 *destMap);
+	int32 *dispatchGetNextMapEvent(int32 *mapPtr, int32 soundOffset, int32 *mapEvent);
+	void dispatchPredictStream(IMuseDigiDispatch *dispatchPtr);
+	int32 *dispatchCheckForJump(int32 *mapPtr, IMuseDigiStreamZone *strZnPtr, int &candidateHookId);
+	void dispatchPrepareToJump(IMuseDigiDispatch *dispatchPtr, IMuseDigiStreamZone *strZnPtr, int32 *jumpParamsFromMap, int calledFromGetNextMapEvent);
+	void dispatchStreamNextZone(IMuseDigiDispatch *dispatchPtr, IMuseDigiStreamZone *strZnPtr);
+	IMuseDigiStreamZone *dispatchAllocateStreamZone();
+	uint8 *dispatchAllocateFade(int32 &fadeSize, const char *functionName);
+	void dispatchDeallocateFade(IMuseDigiDispatch *dispatchPtr, const char *functionName);
+	int dispatchGetFadeSize(IMuseDigiDispatch *dispatchPtr, int fadeLength);
+	void dispatchValidateFadeSize(IMuseDigiDispatch *dispatchPtr, int32 &dispatchSize, const char *functionName);
+	int dispatchUpdateFadeMixVolume(IMuseDigiDispatch *dispatchPtr, int32 remainingFade);
+	int dispatchUpdateFadeSlope(IMuseDigiDispatch *dispatchPtr);
+	void dispatchVOCLoopCallback(int soundId);
+	int dispatchSeekToNextChunk(IMuseDigiDispatch *dispatchPtr);
+
+	// Wave (mainly a wrapper for Tracks functions)
+	int waveInit();
+	int waveTerminate();
+	int wavePause();
+	int waveResume();
+	void waveSaveLoad(Common::Serializer &ser);
+	void waveUpdateGroupVolumes();
+	int waveStartSound(int soundId, int priority);
+	int waveStopSound(int soundId);
+	int waveStopAllSounds();
+	int waveGetNextSound(int soundId);
+	int waveSetParam(int soundId, int opcode, int value);
+	int waveGetParam(int soundId, int opcode);
+	int waveSetHook(int soundId, int hookId);
+	int waveGetHook(int soundId);
+	int waveStartStream(int soundId, int priority, int groupId);
+	int waveSwitchStream(int oldSoundId, int newSoundId, int fadeLengthMs, int fadeSyncFlag2, int fadeSyncFlag1);
+	int waveSwitchStream(int oldSoundId, int newSoundId, uint8 *crossfadeBuffer, int crossfadeBufferSize, int vocLoopFlag);
+	int waveProcessStreams();
+	int waveFeedStream(int soundId, uint8 *srcBuf, int32 sizeToFeed, int paused);
+	int waveLipSync(int soundId, int syncId, int msPos, int32 &width, int32 &height);
+
+	// Waveapi
+	waveOutParamsStruct waveOutSettings;
+
+	int _waveOutSampleRate;
+	int _waveOutBytesPerSample;
+	int _waveOutNumChannels;
+	int _waveOutZeroLevel;
+	int _waveOutPreferredFeedSize;
+	uint8 *_waveOutMixBuffer;
+	uint8 *_waveOutOutputBuffer;
+
+	int _waveOutXorTrigger;
+	int _waveOutWriteIndex;
+	int _waveOutDisableWrite;
+
+	int waveOutInit(int sampleRate, waveOutParamsStruct *waveOutSettings);
+	void waveOutWrite(uint8 **audioBuffer, int &feedSize, int &sampleRate);
+	int waveOutDeinit();
+	void waveOutCallback();
+	byte waveOutGetStreamFlags();
+
+public:
+	IMuseDigital(ScummEngine_v7 *scumm, Audio::Mixer *mixer);
+	~IMuseDigital() override;
+
+	// Wrapper functions used by the main engine
+
+	void startSound(int sound) override { error("IMuseDigital::startSound(int) should be never called"); }
+	void setMusicVolume(int vol) override {}
+	void stopSound(int sound) override;
+	void stopAllSounds() override;
+	int getSoundStatus(int sound) const override { return 0; }
+	int isSoundRunning(int soundId); // Needed because getSoundStatus is a const function, and I needed a workaround
+	int startVoice(int soundId, const char *soundName, byte speakingActorId);
+	int startVoice(const char *fileName, ScummFile *file, uint32 offset, uint32 size);
+	void saveLoadEarly(Common::Serializer &ser);
+	void setRadioChatterSFX(bool state);
+	void setAudioNames(int32 num, char *names);
+	int  startSfx(int soundId, int priority) ;
+	void setPriority(int soundId, int priority);
+	void setVolume(int soundId, int volume);
+	void setPan(int soundId, int pan);
+	void setFrequency(int soundId, int frequency);
+	int  getCurSpeechVolume() const;
+	int  getCurSpeechPan() const;
+	int  getCurSpeechFrequency() const;
+	void pause(bool pause);
+	void parseScriptCmds(int cmd, int soundId, int sub_cmd, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n, int o, int p);
+	void refreshScripts();
+	void flushTracks();
+	void disableEngine();
+	bool isEngineDisabled();
+
+	bool isFTSoundEngine(); // Used in the handlers to check if we're using the FT version of the engine
+
+	int32 getCurMusicPosInMs();
+	int32 getCurVoiceLipSyncWidth();
+	int32 getCurVoiceLipSyncHeight();
+	int32 getCurMusicLipSyncWidth(int syncId);
+	int32 getCurMusicLipSyncHeight(int syncId);
+	void getSpeechLipSyncInfo(int32 &width, int32 &height);
+	void getMusicLipSyncInfo(int syncId, int32 &width, int32 &height);
+	int32 getSoundElapsedTimeInMs(int soundId);
+
+	// General engine functions
+	int diMUSETerminate();
+	int diMUSEInitialize();
+	int diMUSEPause();
+	int diMUSEResume();
+	void diMUSESaveLoad(Common::Serializer &ser);
+	int diMUSESetGroupVol(int groupId, int volume);
+	int diMUSEStartSound(int soundId, int priority);
+	int diMUSEStopSound(int soundId);
+	int diMUSEStopAllSounds();
+	int diMUSEGetNextSound(int soundId);
+	int diMUSESetParam(int soundId, int paramId, int value);
+	int diMUSEGetParam(int soundId, int paramId);
+	int diMUSEFadeParam(int soundId, int opcode, int destValue, int fadeLength);
+	int diMUSESetHook(int soundId, int hookId);
+	int diMUSESetTrigger(int soundId, int marker, int opcode, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n);
+	int diMUSEStartStream(int soundId, int priority, int groupId);
+	int diMUSESwitchStream(int oldSoundId, int newSoundId, int fadeDelay, int fadeSyncFlag2, int fadeSyncFlag1);
+	int diMUSESwitchStream(int oldSoundId, int newSoundId, uint8 *crossfadeBuffer, int crossfadeBufferSize, int vocLoopFlag);
+	int diMUSEProcessStreams();
+	int diMUSEFeedStream(int soundId, uint8 *srcBuf, int32 sizeToFeed, int paused);
+	int diMUSELipSync(int soundId, int syncId, int msPos, int32 &width, int32 &height);
+	int diMUSESetMusicGroupVol(int volume);
+	int diMUSESetSFXGroupVol(int volume);
+	int diMUSESetVoiceGroupVol(int volume);
+	void diMUSEUpdateGroupVolumes();
+	int diMUSEInitializeScript();
+	void diMUSERefreshScript();
+	int diMUSESetState(int soundId);
+	int diMUSESetSequence(int soundId);
+	int diMUSESetCuePoint(int cueId);
+	int diMUSESetAttribute(int attrIndex, int attrVal);
+
+	// Utils
+	int addTrackToList(IMuseDigiTrack **listPtr, IMuseDigiTrack *listPtr_Item);
+	int removeTrackFromList(IMuseDigiTrack **listPtr, IMuseDigiTrack *itemPtr);
+	int addStreamZoneToList(IMuseDigiStreamZone **listPtr, IMuseDigiStreamZone *listPtr_Item);
+	int removeStreamZoneFromList(IMuseDigiStreamZone **listPtr, IMuseDigiStreamZone *itemPtr);
+	int clampNumber(int value, int minValue, int maxValue);
+	int clampTuning(int value, int minValue, int maxValue);
+	int checkHookId(int &trackHookId, int sampleHookId);
+
+	// CMDs
+	int cmdsHandleCmd(int cmd, uint8 *ptr = nullptr,
+		int a = -1, int b = -1, int c = -1, int d = -1, int e = -1,
+		int f = -1, int g = -1, int h = -1, int i = -1, int j = -1,
+		int k = -1, int l = -1, int m = -1, int n = -1);
+
+	// Script
+	int scriptTriggerCallback(char *marker);
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/imuse_digi/dimuse_fades.cpp b/engines/scumm/imuse_digi/dimuse_fades.cpp
new file mode 100644
index 0000000000..12e9b2a44f
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_fades.cpp
@@ -0,0 +1,168 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/imuse_digi/dimuse_engine.h"
+#include "scumm/imuse_digi/dimuse_fades.h"
+
+namespace Scumm {
+
+IMuseDigiFadesHandler::IMuseDigiFadesHandler(IMuseDigital *engine) {
+	_engine = engine;
+}
+
+IMuseDigiFadesHandler::~IMuseDigiFadesHandler() {}
+
+int IMuseDigiFadesHandler::init() {
+	clearAllFades();
+	return 0;
+}
+
+int IMuseDigiFadesHandler::fadeParam(int soundId, int opcode, int destinationValue, int fadeLength) {
+	if (!soundId || fadeLength < 0)
+		return -5;
+	if (opcode != DIMUSE_P_PRIORITY && opcode != DIMUSE_P_VOLUME && opcode != DIMUSE_P_PAN && opcode != DIMUSE_P_DETUNE && opcode != DIMUSE_P_UNKNOWN && opcode != 17)
+		return -5;
+
+	for (int l = 0; l < DIMUSE_MAX_FADES; l++) {
+		if (_fades[l].status && (_fades[l].sound == soundId) && (_fades[l].param == opcode || opcode == -1)) {
+			_fades[l].status = 0;
+		}
+	}
+
+	if (!fadeLength) {
+		debug(5, "IMuseDigiFadesHandler::fadeParam(): WARNING: allocated fade with zero length for sound %d", soundId);
+		if (opcode != DIMUSE_P_VOLUME || destinationValue) {
+			_engine->diMUSESetParam(soundId, opcode, destinationValue);
+			return 0;
+		} else {
+			_engine->diMUSEStopSound(soundId);
+			return 0;
+		}
+	}
+
+	for (int l = 0; l < DIMUSE_MAX_FADES; l++) {
+		if (!_fades[l].status) {
+			_fades[l].sound = soundId;
+			_fades[l].param = opcode;
+			_fades[l].currentVal = _engine->diMUSEGetParam(soundId, opcode);
+			_fades[l].length = fadeLength;
+			_fades[l].counter = fadeLength;
+			_fades[l].slope = (destinationValue - _fades[l].currentVal) / fadeLength;
+			_fades[l].modOvfloCounter = 0;
+			_fades[l].status = 1;
+			_fadesOn = 1;
+
+			if ((destinationValue - _fades[l].currentVal) < 0) {
+				_fades[l].nudge = -1;
+				_fades[l].slopeMod = (-(destinationValue - _fades[l].currentVal) % fadeLength);
+			} else {
+				_fades[l].nudge = 1;
+				_fades[l].slopeMod = (destinationValue - _fades[l].currentVal) % fadeLength;
+			}
+
+			return 0;
+		}
+	}
+
+	debug(5, "IMuseDigiFadesHandler::fadeParam(): unable to allocate fade for sound %d", soundId);
+	return -6;
+}
+
+void IMuseDigiFadesHandler::clearFadeStatus(int soundId, int opcode) {
+	for (int l = 0; l < DIMUSE_MAX_FADES; l++) {
+		if (_fades[l].status
+			&& _fades[l].sound == soundId
+			&& (_fades[l].param == opcode || opcode == -1)) {
+			_fades[l].status = 0;
+		}
+	}
+}
+
+void IMuseDigiFadesHandler::loop() {
+	if (!_fadesOn)
+		return;
+	_fadesOn = 0;
+
+	for (int l = 0; l < DIMUSE_MAX_FADES; l++) {
+		if (_fades[l].status) {
+			_fadesOn = 1;
+			if (--_fades[l].counter == 0) {
+				_fades[l].status = 0;
+			}
+
+			int currentVolume = _fades[l].currentVal + _fades[l].slope;
+			int currentSlopeMod = _fades[l].modOvfloCounter + _fades[l].slopeMod;
+			_fades[l].modOvfloCounter += _fades[l].slopeMod;
+
+			if (_fades[l].length <= currentSlopeMod) {
+				_fades[l].modOvfloCounter = currentSlopeMod - _fades[l].length;
+				currentVolume += _fades[l].nudge;
+			}
+
+			if (_fades[l].currentVal != currentVolume) {
+				_fades[l].currentVal = currentVolume;
+
+				if ((_fades[l].counter % 6) == 0) {
+					debug(5, "IMuseDigiFadesHandler::loop(): running fade for sound %d with id %d, currently at volume %d", _fades[l].sound, l, currentVolume);
+					if ((_fades[l].param != DIMUSE_P_VOLUME) || currentVolume != 0) {
+						_engine->diMUSESetParam(_fades[l].sound, _fades[l].param, currentVolume);
+						continue;
+					} else {
+						_engine->diMUSEStopSound(_fades[l].sound);
+					}
+				}
+			}
+		}
+	}
+}
+
+void IMuseDigiFadesHandler::deinit() {
+	clearAllFades();
+}
+
+void IMuseDigiFadesHandler::saveLoad(Common::Serializer &ser) {
+	for (int l = 0; l < DIMUSE_MAX_FADES; l++) {
+		ser.syncAsSint32LE(_fades[l].status, VER(103));
+		ser.syncAsSint32LE(_fades[l].sound, VER(103));
+		ser.syncAsSint32LE(_fades[l].param, VER(103));
+		ser.syncAsSint32LE(_fades[l].currentVal, VER(103));
+		ser.syncAsSint32LE(_fades[l].counter, VER(103));
+		ser.syncAsSint32LE(_fades[l].length, VER(103));
+		ser.syncAsSint32LE(_fades[l].slope, VER(103));
+		ser.syncAsSint32LE(_fades[l].slopeMod, VER(103));
+		ser.syncAsSint32LE(_fades[l].modOvfloCounter, VER(103));
+		ser.syncAsSint32LE(_fades[l].nudge, VER(103));
+	}
+
+	if (ser.isLoading())
+		_fadesOn = 1;
+}
+
+void IMuseDigiFadesHandler::clearAllFades() {
+	for (int l = 0; l < DIMUSE_MAX_FADES; l++) {
+		_fades[l].status = 0;
+		_fades[l].sound = 0;
+	}
+	_fadesOn = 0;
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_fades.h b/engines/scumm/imuse_digi/dimuse_fades.h
new file mode 100644
index 0000000000..954f8b9907
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_fades.h
@@ -0,0 +1,54 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#if !defined(SCUMM_IMUSE_DIGI_FADES_H) && defined(ENABLE_SCUMM_7_8)
+#define SCUMM_IMUSE_DIGI_FADES_H
+
+#include "common/scummsys.h"
+#include "common/textconsole.h"
+#include "common/util.h"
+
+namespace Scumm {
+
+class IMuseDigiFadesHandler {
+
+private:
+	IMuseDigital *_engine;
+	IMuseDigiFade _fades[DIMUSE_MAX_FADES];
+	int _fadesOn;
+
+	void clearAllFades();
+public:
+	IMuseDigiFadesHandler(IMuseDigital *engine);
+	~IMuseDigiFadesHandler();
+
+	int init();
+	void deinit();
+	void saveLoad(Common::Serializer &ser);
+	int fadeParam(int soundId, int opcode, int destinationValue, int fadeLength);
+	void clearFadeStatus(int soundId, int opcode);
+	void loop();
+
+};
+
+} // End of namespace Scumm
+#endif
diff --git a/engines/scumm/imuse_digi/dimuse_files.cpp b/engines/scumm/imuse_digi/dimuse_files.cpp
new file mode 100644
index 0000000000..b22bebd31e
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_files.cpp
@@ -0,0 +1,439 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/imuse_digi/dimuse_engine.h"
+#include "scumm/imuse_digi/dimuse_files.h"
+#include "scumm/file.h"
+
+namespace Scumm {
+
+IMuseDigiFilesHandler::IMuseDigiFilesHandler(IMuseDigital *engine, ScummEngine_v7 *vm) {
+	_engine = engine;
+	_sound = new ImuseDigiSndMgr(vm);
+	assert(_sound);
+	_vm = vm;
+
+	_ftSpeechFilename[0] = '\0';
+	_ftSpeechSubFileOffset = 0;
+	_ftSpeechFileSize = 0;
+	_ftSpeechFileCurPos = 0;
+	_ftSpeechFile = nullptr;
+
+	for (int i = 0; i < 4; i++) {
+		IMuseDigiSndBuffer *selectedSoundBuf = &_soundBuffers[i];
+		selectedSoundBuf->buffer = nullptr;
+		selectedSoundBuf->bufSize = 0;
+		selectedSoundBuf->loadSize = 0;
+		selectedSoundBuf->criticalSize = 0;
+	}
+}
+
+IMuseDigiFilesHandler::~IMuseDigiFilesHandler() {
+	if (_ftSpeechFile)
+		_ftSpeechFile->close();
+	delete _sound;
+}
+
+void IMuseDigiFilesHandler::saveLoad(Common::Serializer &ser) {
+	int curSound = 0;
+	ImuseDigiSndMgr::SoundDesc *sounds = _sound->getSounds();
+
+	ser.syncArray(_currentSpeechFilename, 60, Common::Serializer::SByte, VER(103));
+	if (ser.isSaving()) {
+		for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
+			ser.syncAsSint32LE(sounds[l].soundId, VER(103));
+		}
+		if (_engine->isFTSoundEngine()) {
+			ser.syncAsSint32LE(_ftSpeechFileCurPos, VER(103));
+			ser.syncAsSint32LE(_ftSpeechFileSize, VER(103));
+			ser.syncAsSint32LE(_ftSpeechSubFileOffset, VER(103));
+			ser.syncArray(_ftSpeechFilename, sizeof(_ftSpeechFilename), Common::Serializer::SByte, VER(103));
+		}
+	}
+
+	if (ser.isLoading()) {
+		// Close prior sounds if we're reloading (needed for edge cases like the recipe book in COMI)
+		for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
+			_sound->closeSound(&sounds[l]);
+		}
+
+		for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
+			ser.syncAsSint32LE(curSound, VER(103));
+			if (curSound) {
+				openSound(curSound);
+				if (curSound != kTalkSoundID)
+					closeSound(curSound);
+			}
+		}
+
+		if (_engine->isFTSoundEngine()) {
+			ser.syncAsSint32LE(_ftSpeechFileCurPos, VER(103));
+			ser.syncAsSint32LE(_ftSpeechFileSize, VER(103));
+			ser.syncAsSint32LE(_ftSpeechSubFileOffset, VER(103));
+			ser.syncArray(_ftSpeechFilename, sizeof(_ftSpeechFilename), Common::Serializer::SByte, VER(103));
+			_ftSpeechFile = _vm->_sound->restoreDiMUSESpeechFile(_ftSpeechFilename);
+		}
+	}
+}
+
+uint8 *IMuseDigiFilesHandler::getSoundAddrData(int soundId) {
+	if (_engine->isEngineDisabled())
+		return nullptr;
+
+	Common::StackLock lock(_mutex);
+	// This function is always used for SFX (tracks which do not
+	// have a stream pointer), hence the use of the resource address
+	if (soundId != 0) {
+		_vm->_res->lock(rtSound, soundId);
+		byte *ptr = _vm->getResourceAddress(rtSound, soundId);
+		if (!ptr) {
+			_vm->_res->unlock(rtSound, soundId);
+			return nullptr;
+		}
+		return ptr;
+
+	}
+	debug(5, "IMuseDigiFilesHandler::getSoundAddrData(): soundId is 0 or out of range");
+	return nullptr;
+}
+
+int IMuseDigiFilesHandler::getSoundAddrDataSize(int soundId, bool hasStream) {
+	if (_engine->isEngineDisabled())
+		return 0;
+
+	if (hasStream) {
+		ImuseDigiSndMgr::SoundDesc *s = _sound->findSoundById(soundId);
+		if (s) {
+			if (soundId != kTalkSoundID) {
+				return s->resSize;
+			}
+		} else if (soundId == kTalkSoundID) {
+			return _ftSpeechFileSize;
+		}
+	} else {
+		return _vm->getResourceSize(rtSound, soundId);
+	}
+
+	return 0;
+}
+
+int IMuseDigiFilesHandler::getNextSound(int soundId) {
+	int foundSoundId = 0;
+	do {
+		foundSoundId = _engine->diMUSEGetNextSound(foundSoundId);
+		if (!foundSoundId)
+			return -1;
+	} while (foundSoundId != soundId);
+	return 2;
+}
+
+int IMuseDigiFilesHandler::seek(int soundId, int32 offset, int mode, int bufId) {
+	// This function and files_read() are used for sounds for which a stream is needed (speech
+	// and music), therefore they will always refer to sounds in a bundle file for DIG and COMI
+	// The seeked position is in reference to the decompressed sound
+
+	if (_engine->isEngineDisabled())
+		return 0;
+
+	char fileName[60] = "";
+	getFilenameFromSoundId(soundId, fileName, sizeof(fileName));
+
+	ImuseDigiSndMgr::SoundDesc *s = _sound->findSoundById(soundId);
+	if (s || (_engine->isFTSoundEngine() && soundId == kTalkSoundID)) {
+		if (soundId != 0) {
+			if (_engine->isFTSoundEngine()) {
+				switch (mode) {
+				case SEEK_END:
+					if (soundId != kTalkSoundID) {
+						return s->resSize;
+					} else {
+						return _ftSpeechFileSize;
+					}
+				case SEEK_SET:
+				default:
+					if (soundId != kTalkSoundID) {
+						if (offset <= s->resSize) {
+							s->resCurOffset = offset;
+							return offset;
+						}
+					} else {
+						if (offset <= _ftSpeechFileSize) {
+							_ftSpeechFileCurPos = offset;
+							return _ftSpeechFileCurPos;
+						}
+					}
+				}
+			} else {
+				// A soundId > 10000 is a SAN cutscene
+				if ((_vm->_game.id == GID_DIG && !(_vm->_game.features & GF_DEMO)) && (soundId > kTalkSoundID))
+					return 0;
+
+				return s->bundle->seekFile(offset, mode);
+			}
+		} else {
+			debug(5, "IMuseDigiFilesHandler::seek(): soundId is 0 or out of range");
+		}
+	} else {
+		debug(5, "IMuseDigiFilesHandler::seek(): can't find sound %d (%s); did you forget to open it?", soundId, fileName);
+	}
+
+	return 0;
+}
+
+int IMuseDigiFilesHandler::read(int soundId, uint8 *buf, int32 size, int bufId) {
+	// This function and files_seek() are used for sounds for which a stream is needed (speech
+	// and music), therefore they will always refer to sounds in a bundle file for DIG and COMI
+
+	if (_engine->isEngineDisabled())
+		return 0;
+
+	if (soundId != 0) {
+		uint8 *tmpBuf = nullptr;
+		int32 resultingSize;
+
+		// We don't have SoundDesc objects for FT & DIG demo speech files
+		if (_engine->isFTSoundEngine() && soundId == kTalkSoundID) {
+			_ftSpeechFile->seek(_ftSpeechSubFileOffset + _ftSpeechFileCurPos, SEEK_SET);
+			resultingSize = size > _ftSpeechFileSize ? (_ftSpeechFileSize - _ftSpeechFileCurPos) : size;
+			return _ftSpeechFile->read(buf, resultingSize);
+		}
+
+		char fileName[60] = "";
+		getFilenameFromSoundId(soundId, fileName, sizeof(fileName));
+
+		ImuseDigiSndMgr::SoundDesc *s = _sound->getSounds();
+		ImuseDigiSndMgr::SoundDesc *curSnd = nullptr;
+		for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
+			curSnd = &s[i];
+			if (curSnd->inUse) {
+				if (curSnd->soundId == soundId) {
+					if (_engine->isFTSoundEngine()) { // FT & DIG demo
+						resultingSize = size > (curSnd->resSize - curSnd->resCurOffset) ? (curSnd->resSize - curSnd->resCurOffset) : size;
+						tmpBuf = &curSnd->resPtr[curSnd->resCurOffset];
+
+						if (resultingSize != size)
+							debug(5, "IMuseDigiFilesHandler::read(): WARNING: tried to read %d bytes, got %d instead (soundId %d (%s))", size, resultingSize, soundId, fileName);
+
+						memcpy(buf, tmpBuf, resultingSize); // We don't free tmpBuf: it's the resource pointer
+						return resultingSize;
+					} else { // DIG & COMI
+						resultingSize = curSnd->bundle->readFile(fileName, size, &tmpBuf, ((_vm->_game.id == GID_CMI) && !(_vm->_game.features & GF_DEMO)));
+
+						if (resultingSize != size)
+							debug(5, "IMuseDigiFilesHandler::read(): WARNING: tried to read %d bytes, got %d instead (soundId %d (%s))", size, resultingSize, soundId, fileName);
+
+						memcpy(buf, tmpBuf, resultingSize);
+						free(tmpBuf);
+						return resultingSize;
+					}
+				}
+			}
+		}
+
+		debug(5, "IMuseDigiFilesHandler::read(): can't find sound %d (%s); did you forget to open it?", soundId, fileName);
+
+	} else {
+		debug(5, "IMuseDigiFilesHandler::read(): soundId is 0 or out of range");
+	}
+
+	return 0;
+}
+
+IMuseDigiSndBuffer *IMuseDigiFilesHandler::getBufInfo(int bufId) {
+	if (bufId > 0 && bufId <= 4) {
+		return &_soundBuffers[bufId];
+	}
+
+	debug(5, "IMuseDigiFilesHandler::getBufInfo(): ERROR: invalid buffer id");
+	return nullptr;
+}
+
+int IMuseDigiFilesHandler::openSound(int soundId) {
+	if (_engine->isEngineDisabled())
+		return 1;
+
+	ImuseDigiSndMgr::SoundDesc *s = nullptr;
+	if (!_engine->isFTSoundEngine()) {
+		char fileName[60] = "";
+		getFilenameFromSoundId(soundId, fileName, sizeof(fileName));
+
+		int groupId = soundId == kTalkSoundID ? IMUSE_VOLGRP_VOICE : IMUSE_VOLGRP_MUSIC;
+		s = _sound->openSound(soundId, fileName, IMUSE_BUNDLE, groupId, -1);
+		if (!s)
+			s = _sound->openSound(soundId, fileName, IMUSE_BUNDLE, groupId, 1);
+		if (!s)
+			s = _sound->openSound(soundId, fileName, IMUSE_BUNDLE, groupId, 2);
+		if (!s) {
+			debug(5, "IMuseDigiFilesHandler::openSound(): can't open sound %d (%s)", soundId, fileName);
+			return 1;
+		}
+	} else {
+		s = _sound->findSoundById(soundId);
+		if (!s)
+			s = _sound->openSound(soundId, "", IMUSE_RESOURCE, -1, -1);
+		if (!s)
+			s = _sound->openSound(soundId, "", IMUSE_RESOURCE, -1, 1);
+		if (!s)
+			s = _sound->openSound(soundId, "", IMUSE_RESOURCE, -1, 2);
+		if (!s) {
+			debug(5, "IMuseDigiFilesHandler::openSound(): can't open sound %d", soundId);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+void IMuseDigiFilesHandler::closeSound(int soundId) {
+	if (_engine->isEngineDisabled())
+		return;
+
+	_sound->scheduleSoundForDeallocation(soundId);
+}
+
+void IMuseDigiFilesHandler::closeAllSounds() {
+	ImuseDigiSndMgr::SoundDesc *s = _sound->getSounds();
+	for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
+		if (s[i].inUse) {
+			closeSound((&s[i])->soundId);
+		}
+	}
+
+	_engine->flushTracks();
+}
+
+void IMuseDigiFilesHandler::getFilenameFromSoundId(int soundId, char *fileName, size_t size) {
+	if (_engine->isFTSoundEngine())
+		return;
+
+	int i = 0;
+
+	if (soundId == kTalkSoundID) {
+		Common::strlcpy(fileName, _currentSpeechFilename, size);
+		return;
+	}
+
+	if (_vm->_game.id == GID_CMI) {
+		if (_vm->_game.features & GF_DEMO) {
+			while (_comiDemoStateMusicTable[i].soundId != -1) {
+				if (_comiDemoStateMusicTable[i].soundId == soundId) {
+					Common::strlcpy(fileName, _comiDemoStateMusicTable[i].filename, size);
+					return;
+				}
+				i++;
+			}
+		} else {
+			if (soundId < 2000) {
+				while (_comiStateMusicTable[i].soundId != -1) {
+					if (_comiStateMusicTable[i].soundId == soundId) {
+						Common::strlcpy(fileName, _comiStateMusicTable[i].filename, size);
+						return;
+					}
+					i++;
+				}
+			} else {
+				while (_comiSeqMusicTable[i].soundId != -1) {
+					if (_comiSeqMusicTable[i].soundId == soundId) {
+						Common::strlcpy(fileName, _comiSeqMusicTable[i].filename, size);
+						return;
+					}
+					i++;
+				}
+			}
+		}
+	} else if (_vm->_game.id == GID_DIG) {
+		if (soundId < 2000) {
+			while (_digStateMusicTable[i].soundId != -1) {
+				if (_digStateMusicTable[i].soundId == soundId) {
+					Common::strlcpy(fileName, _digStateMusicTable[i].filename, size);
+					return;
+				}
+				i++;
+			}
+		} else {
+			while (_digSeqMusicTable[i].soundId != -1) {
+				if (_digSeqMusicTable[i].soundId == soundId) {
+					Common::strlcpy(fileName, _digSeqMusicTable[i].filename, size);
+					return;
+				}
+				i++;
+			}
+		}
+	}
+}
+
+void IMuseDigiFilesHandler::allocSoundBuffer(int bufId, int32 size, int32 loadSize, int32 criticalSize) {
+	IMuseDigiSndBuffer *selectedSoundBuf;
+
+	selectedSoundBuf = &_soundBuffers[bufId];
+	selectedSoundBuf->buffer = (uint8 *)malloc(size);
+	selectedSoundBuf->bufSize = size;
+	selectedSoundBuf->loadSize = loadSize;
+	selectedSoundBuf->criticalSize = criticalSize;
+}
+
+void IMuseDigiFilesHandler::deallocSoundBuffer(int bufId) {
+	IMuseDigiSndBuffer *selectedSoundBuf;
+
+	selectedSoundBuf = &_soundBuffers[bufId];
+	free(selectedSoundBuf->buffer);
+	selectedSoundBuf->buffer = nullptr;
+}
+
+void IMuseDigiFilesHandler::flushSounds() {
+	if (_engine->isEngineDisabled())
+		return;
+
+	ImuseDigiSndMgr::SoundDesc *s = _sound->getSounds();
+	for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
+		ImuseDigiSndMgr::SoundDesc *curSnd = &s[i];
+		if (curSnd && curSnd->inUse) {
+			if (curSnd->scheduledForDealloc)
+				if (!_engine->diMUSEGetParam(curSnd->soundId, DIMUSE_P_SND_TRACK_NUM) && !_engine->diMUSEGetParam(curSnd->soundId, DIMUSE_P_TRIGS_SNDS))
+					_sound->closeSound(curSnd);
+		}
+	}
+}
+
+int IMuseDigiFilesHandler::setCurrentSpeechFilename(const char *fileName) {
+	Common::strlcpy(_currentSpeechFilename, fileName, sizeof(_currentSpeechFilename));
+	if (openSound(kTalkSoundID))
+		return 1;
+
+	return 0;
+}
+
+void IMuseDigiFilesHandler::setCurrentFtSpeechFile(const char *fileName, ScummFile *file, uint32 offset, uint32 size) {
+	Common::strlcpy(_ftSpeechFilename, fileName, sizeof(_ftSpeechFilename));
+	_ftSpeechFile = file;
+	_ftSpeechSubFileOffset = offset;
+	_ftSpeechFileSize = size;
+}
+
+void IMuseDigiFilesHandler::closeSoundImmediatelyById(int soundId) {
+	if (_engine->isEngineDisabled())
+		return;
+
+	_sound->closeSoundById(soundId);
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_files.h b/engines/scumm/imuse_digi/dimuse_files.h
new file mode 100644
index 0000000000..4fea1fbd97
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_files.h
@@ -0,0 +1,75 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#if !defined(SCUMM_IMUSE_DIGI_FILES_H) && defined(ENABLE_SCUMM_7_8)
+#define SCUMM_IMUSE_DIGI_FILES_H
+
+#include "common/scummsys.h"
+#include "common/textconsole.h"
+#include "common/util.h"
+#include "scumm/resource.h"
+#include "scumm/file.h"
+#include "scumm/imuse_digi/dimuse_bndmgr.h"
+#include "scumm/imuse_digi/dimuse_sndmgr.h"
+
+namespace Scumm {
+
+class IMuseDigiFilesHandler {
+
+private:
+	IMuseDigital *_engine;
+	ImuseDigiSndMgr *_sound;
+	ScummEngine_v7 *_vm;
+	Common::Mutex _mutex;
+	IMuseDigiSndBuffer _soundBuffers[4];
+	char _currentSpeechFilename[60];
+	ScummFile *_ftSpeechFile;
+	char _ftSpeechFilename[160];
+	int _ftSpeechSubFileOffset;
+	int _ftSpeechFileSize;
+	int _ftSpeechFileCurPos;
+
+	void getFilenameFromSoundId(int soundId, char *fileName, size_t size);
+public:
+	IMuseDigiFilesHandler(IMuseDigital *engine, ScummEngine_v7 *vm);
+	~IMuseDigiFilesHandler();
+
+	uint8 *getSoundAddrData(int soundId);
+	int getSoundAddrDataSize(int soundId, bool hasStream);
+	int getNextSound(int soundId);
+	int seek(int soundId, int32 offset, int mode, int bufId);
+	int read(int soundId, uint8 *buf, int32 size, int bufId);
+	IMuseDigiSndBuffer *getBufInfo(int bufId);
+	int openSound(int soundId);
+	void closeSound(int soundId);
+	void closeAllSounds();
+	void allocSoundBuffer(int bufId, int32 size, int32 loadSize, int32 criticalSize);
+	void deallocSoundBuffer(int bufId);
+	void flushSounds();
+	int setCurrentSpeechFilename(const char *fileName);
+	void setCurrentFtSpeechFile(const char *fileName, ScummFile *file, uint32 offset, uint32 size);
+	void closeSoundImmediatelyById(int soundId);
+	void saveLoad(Common::Serializer &ser);
+};
+
+} // End of namespace Scumm
+#endif
diff --git a/engines/scumm/imuse_digi/dimuse_groups.cpp b/engines/scumm/imuse_digi/dimuse_groups.cpp
new file mode 100644
index 0000000000..76ff458597
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_groups.cpp
@@ -0,0 +1,80 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/imuse_digi/dimuse_engine.h"
+#include "scumm/imuse_digi/dimuse_groups.h"
+
+namespace Scumm {
+
+IMuseDigiGroupsHandler::IMuseDigiGroupsHandler(IMuseDigital *engine) {
+	_engine = engine;
+}
+
+IMuseDigiGroupsHandler::~IMuseDigiGroupsHandler() {}
+
+int IMuseDigiGroupsHandler::init() {
+	for (int i = 0; i < DIMUSE_MAX_GROUPS; i++) {
+		_effVols[i] = 127;
+		_vols[i] = 127;
+	}
+	return 0;
+}
+
+int IMuseDigiGroupsHandler::setGroupVol(int id, int volume) {
+	int l;
+
+	if (id >= DIMUSE_MAX_GROUPS) {
+		return -5;
+	}
+
+	if (volume == -1) {
+		return _vols[id];
+	}
+
+	if (volume > 127)
+		return -5;
+
+	if (id) {
+		_vols[id] = volume;
+		_effVols[id] = (_vols[0] * (volume + 1)) / 128;
+	} else {
+		_effVols[0] = volume;
+		_vols[0] = volume;
+
+		for (l = 1; l < DIMUSE_MAX_GROUPS; l++) {
+			_effVols[l] = (volume * (_vols[id] + 1)) / 128;
+		}
+	}
+
+	_engine->diMUSEUpdateGroupVolumes();
+	return _vols[id];
+}
+
+int IMuseDigiGroupsHandler::getGroupVol(int id) {
+	if (id >= DIMUSE_MAX_GROUPS) {
+		return -5;
+	}
+
+	return _effVols[id];
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_groups.h b/engines/scumm/imuse_digi/dimuse_groups.h
new file mode 100644
index 0000000000..f8e787f7eb
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_groups.h
@@ -0,0 +1,50 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#if !defined(SCUMM_IMUSE_DIGI_GROUPS_H) && defined(ENABLE_SCUMM_7_8)
+#define SCUMM_IMUSE_DIGI_GROUPS_H
+
+#include "common/scummsys.h"
+#include "common/mutex.h"
+#include "common/serializer.h"
+#include "common/textconsole.h"
+#include "common/util.h"
+#include "scumm/imuse_digi/dimuse_engine.h"
+
+namespace Scumm {
+
+class IMuseDigiGroupsHandler {
+
+private:
+	IMuseDigital *_engine;
+	int _effVols[DIMUSE_MAX_GROUPS];
+	int _vols[DIMUSE_MAX_GROUPS];
+public:
+	IMuseDigiGroupsHandler(IMuseDigital *engine);
+	~IMuseDigiGroupsHandler();
+	int init();
+	int setGroupVol(int id, int volume);
+	int getGroupVol(int id);
+};
+
+} // End of namespace Scumm
+#endif
diff --git a/engines/scumm/imuse_digi/dimuse_internalmixer.cpp b/engines/scumm/imuse_digi/dimuse_internalmixer.cpp
new file mode 100644
index 0000000000..1d8cf798d5
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_internalmixer.cpp
@@ -0,0 +1,1104 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/mutex.h"
+#include "common/serializer.h"
+#include "common/textconsole.h"
+#include "common/util.h"
+
+#include "scumm/imuse_digi/dimuse_engine.h"
+#include "scumm/imuse_digi/dimuse_internalmixer.h"
+
+namespace Scumm {
+
+IMuseDigiInternalMixer::IMuseDigiInternalMixer(Audio::Mixer *mixer) {
+	_stream = Audio::makeQueuingAudioStream(22050, true);
+	_mixer = mixer;
+	_radioChatter = 0;
+	_amp8Table = nullptr;
+}
+
+IMuseDigiInternalMixer::~IMuseDigiInternalMixer() {
+	free(_amp8Table);
+	_amp8Table = nullptr;
+}
+
+// Lookup table for a linear volume ramp (0 to 16) accounting for panning (-8 to 8)
+static const int8 _stereoVolumeTable[284] = {
+	0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+	0,  0,  0,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,  2,  0,  0,  1,  1,  1,  1,  2,  2,  2,
+	2,  2,  3,  3,  3,  3,  3,  3,  0,  0,  1,  1,  2,  2,  2,  3,  3,  3,  3,  4,  4,  4,  4,  4,  4,  0,
+	0,  1,  1,  2,  2,  3,  3,  4,  4,  4,  4,  5,  5,  5,  5,  5,  0,  1,  1,  2,  2,  3,  3,  4,  4,  5,
+	5,  5,  6,  6,  6,  6,  6,  0,  1,  1,  2,  3,  3,  4,  4,  5,  5,  6,  6,  6,  7,  7,  7,  7,  0,  1,
+	2,  2,  3,  4,  4,  5,  6,  6,  7,  7,  7,  8,  8,  8,  8,  0,  1,  2,  3,  3,  4,  5,  6,  6,  7,  7,
+	8,  8,  9,  9,  9,  9,  0,  1,  2,  3,  4,  5,  6,  6,  7,  8,  8,  9,  9,  10, 10, 10, 10, 0,  1,  2,
+	3,  4,  5,  6,  7,  8,  9,  9,  10, 10, 11, 11, 11, 11, 0,  1,  2,  3,  5,  6,  7,  8,  8,  9,  10, 11,
+	11, 11, 12, 12, 12, 0,  1,  3,  4,  5,  6,  7,  8,  9,  10, 11, 11, 12, 12, 13, 13, 13, 0,  1,  3,  4,
+	5,  7,  8,  9,  10, 11, 12, 12, 13, 13, 14, 14, 14, 0,  1,  3,  4,  6,  7,  8,  10, 11, 12, 12, 13, 14,
+	14, 15, 15, 15, 0,  2,  3,  5,  6,  8,  9,  10, 11, 12, 13, 14, 15, 15, 16, 16, 16, 0,  0,  0
+};
+
+int IMuseDigiInternalMixer::init(int bytesPerSample, int numChannels, uint8 *mixBuf, int mixBufSize, int sizeSampleKB, int mixChannelsNum) {
+	int amplitudeValue;
+	int waveMixChannelsCount;
+	int softLdenominator;
+	int softLnumerator;
+	int softLcurValue;
+	int zeroCenterOffset;
+
+	_outWordSize = bytesPerSample;
+	_outChannelCount = numChannels;
+	_mixBufSize = mixBufSize;
+	_stereoReverseFlag = sizeSampleKB;
+	_mixBuf = mixBuf;
+
+	waveMixChannelsCount = mixChannelsNum;
+
+	// We're allocating:
+	// - The amplitude quantization table for 8-bit audio ((17 * 256) int16 == 2176 int32)
+	// - The amplitude quantization table for 12-bit/16-bit audio ((17 * 4096) int16 == 34816 int32)
+	// - The pseudologarithmic curve for volume fade ((8 * 4096) int16 == 16384 int32)
+	//
+	// For the first two tables 17 represents the volume range (0-16), while
+	// 256 and 4096 are respectively the dynamic range for 8-bit and 12-bit audio;
+	//
+	// Every table is initialized with int16 values, but stored as int32 and manipulated
+	// at different levels of granularity depending on the case (as uint32, uint16 or uint8).
+	// The latter is populated from the middle, inside-out; hence _softLMID.
+
+	int32 *tableMalloc = (int32 *)malloc(53376 * sizeof(int32));
+
+	_amp8Table = tableMalloc;          // Dim: 2176
+	_amp12Table = tableMalloc + 2176;  // Dim: 34816
+	_softLTable = tableMalloc + 36992; // Dim: 16384
+	_softLMID = tableMalloc + 45184;   // Mid table
+
+	if (tableMalloc) {
+		// Any unpopulated cell is expected to be set to 0
+		memset(tableMalloc, 0, 53376 * sizeof(int32));
+		zeroCenterOffset = 0;
+		for (int i = 0; i < 17; i++) {
+			amplitudeValue = -2048 * zeroCenterOffset;
+			for (int j = 0; j < 256; j++) {
+				((int16 *)&_amp8Table[i * 128])[j] = (int16)(amplitudeValue / 127);
+				amplitudeValue += 16 * zeroCenterOffset;
+			}
+
+			zeroCenterOffset += 8;
+			if (zeroCenterOffset == 8)
+				zeroCenterOffset = 7;
+		}
+
+		zeroCenterOffset = 0;
+		for (int i = 0; i < 17; i++) {
+			amplitudeValue = -2048 * zeroCenterOffset;
+			for (int j = 0; j < 4096; j++) {
+				((int16 *)&_amp12Table[i * 2048])[j] = (int16)(amplitudeValue / 127);
+				amplitudeValue += zeroCenterOffset;
+			}
+
+			zeroCenterOffset += 8;
+			if (zeroCenterOffset == 8)
+				zeroCenterOffset = 7;
+		}
+
+		if (_outWordSize == 8) {
+			if (waveMixChannelsCount * 1024 > 0) {
+				softLnumerator = 0;
+				softLdenominator = 2047 * waveMixChannelsCount;
+				for (int i = 0; i < waveMixChannelsCount * 2048; i++) {
+					softLcurValue = softLnumerator / softLdenominator + 1;
+					softLdenominator += waveMixChannelsCount - 1;
+					softLnumerator += 254 * waveMixChannelsCount;
+					softLcurValue /= 2;
+
+					((int8 *)_softLMID)[i] = (int8)softLcurValue + 128;
+					((int8 *)_softLMID)[-i] = 127 - (int8)softLcurValue;
+				}
+			}
+		} else if (waveMixChannelsCount * 2048 > 0) {
+			softLdenominator = 2047 * waveMixChannelsCount;
+			softLnumerator = 0;
+			for (int i = 0; i < waveMixChannelsCount * 2048; i++) {
+				softLcurValue = softLnumerator / softLdenominator + 1;
+				softLcurValue /= 2;
+				softLdenominator += waveMixChannelsCount - 1;
+				softLnumerator += 65534 * waveMixChannelsCount;
+				((int16 *)_softLMID)[i] = (int16)softLcurValue;
+				((int16 *)_softLMID)[-i - 1] = -1 - (int16)softLcurValue;
+			}
+		}
+		_mixer->playStream(Audio::Mixer::kPlainSoundType, &_channelHandle, _stream, -1, Audio::Mixer::kMaxChannelVolume, false);
+		return 0;
+	} else {
+		debug(5, "DiMUSE_InternalMixer::init(): ERROR: couldn't allocate mixer tables");
+		return -1;
+	}
+}
+
+void IMuseDigiInternalMixer::setRadioChatter() {
+	_radioChatter = 1;
+}
+void IMuseDigiInternalMixer::clearRadioChatter() {
+	_radioChatter = 0;
+}
+
+int IMuseDigiInternalMixer::clearMixerBuffer() {
+	if (!_mixBuf)
+		return -1;
+
+	memset(_mixBuf, 0, _mixBufSize);
+
+	return 0;
+}
+
+void IMuseDigiInternalMixer::mix(uint8 *srcBuf, int32 inFrameCount, int wordSize, int channelCount, int feedSize, int32 mixBufStartIndex, int volume, int pan) {
+	int32 *ampTable;
+	int rightChannelVolume;
+	int leftChannelVolume;
+	int channelVolume;
+	int channelPan;
+
+	if (_mixBuf) {
+		if (srcBuf) {
+			if (inFrameCount) {
+				if (channelCount == 1 && _outChannelCount == 2) {
+					channelVolume = volume / 8;
+					if (volume)
+						++channelVolume;
+					if (channelVolume >= 17)
+						channelVolume = 16;
+					channelPan = (pan / 8) - 8;
+					if (pan > 64)
+						++channelPan;
+
+					channelPan = channelVolume == 0 ? 0 : channelPan;
+
+					// Linear volume quantization from the lookup table
+					rightChannelVolume = _stereoVolumeTable[17 * channelVolume + channelPan];
+					leftChannelVolume = _stereoVolumeTable[17 * channelVolume - channelPan];
+					if (wordSize == 8) {
+						mixBits8ConvertToStereo(
+							srcBuf,
+							inFrameCount,
+							feedSize,
+							mixBufStartIndex,
+							&_amp8Table[leftChannelVolume * 128],
+							&_amp8Table[rightChannelVolume * 128]);
+					} else if (wordSize == 12) {
+						mixBits12ConvertToStereo(
+							srcBuf,
+							inFrameCount,
+							feedSize,
+							mixBufStartIndex,
+							&_amp12Table[leftChannelVolume * 2048],
+							&_amp12Table[rightChannelVolume * 2048]);
+					} else {
+						mixBits16ConvertToStereo(
+							srcBuf,
+							inFrameCount,
+							feedSize,
+							mixBufStartIndex,
+							&_amp12Table[leftChannelVolume * 2048],
+							&_amp12Table[rightChannelVolume * 2048]);
+					}
+				} else {
+					channelVolume = volume / 8;
+					if (volume)
+						channelVolume++;
+					if (channelVolume >= 17)
+						channelVolume = 16;
+
+					if (wordSize == 8)
+						ampTable = &_amp8Table[channelVolume * 128];
+					else
+						ampTable = &_amp12Table[channelVolume * 2048];
+
+					if (_outChannelCount == 1) {
+						if (channelCount == 1) {
+							if (wordSize == 8) {
+								mixBits8Mono(srcBuf, inFrameCount, feedSize, mixBufStartIndex, ampTable);
+							} else if (wordSize == 12) {
+								mixBits12Mono(srcBuf, inFrameCount, feedSize, mixBufStartIndex, ampTable);
+							} else {
+								mixBits16Mono(srcBuf, inFrameCount, feedSize, mixBufStartIndex, ampTable);
+							}
+						} else if (wordSize == 8) {
+							mixBits8ConvertToMono(srcBuf, inFrameCount, feedSize, mixBufStartIndex, ampTable);
+						} else if (wordSize == 12) {
+							mixBits12ConvertToMono(srcBuf, inFrameCount, feedSize, mixBufStartIndex, ampTable);
+						} else {
+							mixBits16ConvertToMono(srcBuf, inFrameCount, feedSize, mixBufStartIndex, ampTable);
+						}
+					} else if (wordSize == 8) {
+						mixBits8Stereo(srcBuf, inFrameCount, feedSize, mixBufStartIndex, ampTable);
+					} else if (wordSize == 12) {
+						mixBits12Stereo(srcBuf, inFrameCount, feedSize, mixBufStartIndex, ampTable);
+					} else {
+						mixBits16Stereo(srcBuf, inFrameCount, feedSize, mixBufStartIndex, ampTable);
+					}
+				}
+			}
+		}
+	}
+}
+
+int IMuseDigiInternalMixer::loop(uint8 **destBuffer, int len) {
+	int16 *mixBuffer = (int16 *)_mixBuf;
+	uint8 *destBuffer_tmp = *destBuffer;
+
+	if (!_mixBuf || !destBuffer || !len)
+		return -1;
+
+	if (_outChannelCount == 2)
+		len *= 2;
+
+	if (!_stereoReverseFlag || _outChannelCount == 1) {
+		if (_outWordSize != 16) {
+			if (len) {
+				for (int i = 0; i < len; i++) {
+					destBuffer_tmp[i] = ((uint8 *)_softLMID)[mixBuffer[i]];
+				}
+			}
+		}
+
+		if (len) {
+			for (int i = 0; i < len; i++) {
+				((uint16 *)destBuffer_tmp)[i] = ((uint16 *)_softLMID)[mixBuffer[i]];
+			}
+		}
+	} else {
+		len /= 2;
+		if (_outWordSize == 16) {
+			if (len) {
+				for (int i = 0; i < len; i += 2) {
+					((uint16 *)destBuffer_tmp)[i] = ((uint16 *)_softLMID)[mixBuffer[i + 1]];
+					((uint16 *)destBuffer_tmp)[i + 1] = ((uint16 *)_softLMID)[mixBuffer[i]];
+				}
+			}
+		}
+
+		if (len) {
+			for (int i = 0; i < len; i += 2) {
+				destBuffer_tmp[i] = ((uint8 *)_softLMID)[mixBuffer[i + 1]];
+				destBuffer_tmp[i + 1] = ((uint8 *)_softLMID)[mixBuffer[i]];
+			}
+		}
+	}
+	return 0;
+}
+
+void IMuseDigiInternalMixer::mixBits8Mono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable) {
+	uint16 *mixBufCurCell;
+	uint8 *srcBuf_ptr;
+	uint8 *ptr;
+	int residualLength;
+	int value;
+
+	mixBufCurCell = (uint16 *)(&_mixBuf[2 * mixBufStartIndex]);
+	srcBuf_ptr = srcBuf;
+	if (inFrameCount == feedSize) {
+		if (_radioChatter) {
+			ptr = srcBuf + 4;
+			value = srcBuf[0] - 128 + srcBuf[1] - 128 + srcBuf[2] - 128 + srcBuf[3] - 128;
+			if (feedSize) {
+				for (int i = 0; i < feedSize; i++) {
+					mixBufCurCell[i] += 4 * *((uint16 *)ampTable + (srcBuf_ptr[i] - (value >> 2)));
+					value += ptr[i] - srcBuf_ptr[i];
+				}
+			}
+		} else {
+			if (feedSize) {
+				for (int i = 0; i < feedSize; i++) {
+					mixBufCurCell[i] += *((uint16 *)ampTable + srcBuf_ptr[i]);
+				}
+			}
+		}
+	} else if (2 * inFrameCount == feedSize) {
+		if (inFrameCount - 1 != 0) {
+			for (int i = 0, j = 0; i < inFrameCount - 1; i++, j += 2) {
+				mixBufCurCell[j]	 += *((uint16 *)ampTable + srcBuf_ptr[i]);
+				mixBufCurCell[j + 1] += (*((int16 *)ampTable + srcBuf_ptr[i]) + *((int16 *)ampTable + srcBuf_ptr[i + 1])) >> 1;
+			}
+		}
+		mixBufCurCell[inFrameCount]		+= *((uint16 *)ampTable + srcBuf_ptr[inFrameCount]);
+		mixBufCurCell[inFrameCount + 1] += *((uint16 *)ampTable + srcBuf_ptr[inFrameCount]);
+	} else if (2 * feedSize == inFrameCount) {
+		if (feedSize) {
+			for (int i = 0, j = 0; i < feedSize; i++, j += 2) {
+				mixBufCurCell[i] += *((uint16 *)ampTable + srcBuf_ptr[j]);
+			}
+		}
+	} else {
+		residualLength = -inFrameCount;
+		if (feedSize) {
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[i] += *((uint16 *)ampTable + srcBuf_ptr[0]);
+				// Seek the next srcBuf element until there's excess length
+				for (residualLength += inFrameCount; residualLength >= 0; ++srcBuf_ptr)
+					residualLength -= feedSize;
+			}
+		}
+	}
+}
+
+void IMuseDigiInternalMixer::mixBits12Mono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable) {
+	uint16 *mixBufCurCell;
+	uint8 *srcBuf_ptr;
+	int value;
+	int xorFlag;
+	int residualLength;
+	int term_1;
+	int term_2;
+
+	if ((inFrameCount & 1) != 0) {
+		inFrameCount &= 0xFFFFFFFE;
+		debug(5, "DiMUSE_InternalMixer::mixBits12Mono(): WARNING: odd inFrameCount with 12-bit data");
+	}
+
+	mixBufCurCell = (uint16 *)(&_mixBuf[2 * mixBufStartIndex]);
+	if (feedSize == inFrameCount) {
+		if (inFrameCount / 2) {
+			srcBuf_ptr = srcBuf;
+			for (int i = 0; i < inFrameCount / 2; i++) {
+				mixBufCurCell[0] += *((uint16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)));
+				mixBufCurCell[1] += *((uint16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+				srcBuf_ptr += 3;
+				mixBufCurCell += 2;
+			}
+		}
+	} else if (2 * inFrameCount == feedSize) {
+		srcBuf_ptr = srcBuf;
+		if ((inFrameCount / 2) - 1) {
+			for (int i = 0; i < (inFrameCount / 2) - 1; i++) {
+				value = *((uint16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+
+				mixBufCurCell[0] += *((uint16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF) << 8)));
+				mixBufCurCell[1] += (value + *((uint16 *)ampTable + value)) >> 1;
+				mixBufCurCell[2] += value;
+				mixBufCurCell[3] += ((int16)value + *((int16 *)ampTable + (srcBuf_ptr[3] | ((srcBuf_ptr[4] & 0xF) << 8)))) >> 1;
+				srcBuf_ptr += 3;
+				mixBufCurCell += 4;
+			}
+		}
+		mixBufCurCell[0] +=  *((uint16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)));
+		mixBufCurCell[1] += (*((uint16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)))
+			               + *((uint16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)))) >> 1;
+		mixBufCurCell[2] +=  *((uint16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+		mixBufCurCell[3] +=  *((uint16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+	} else if (2 * feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[i] += *((uint16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF) << 8)));
+				srcBuf_ptr += 3;
+			}
+		}
+	} else {
+		xorFlag = 0;
+		residualLength = -inFrameCount;
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				if (xorFlag) {
+					term_1 = (srcBuf_ptr[1] & 0xF0) << 4;
+					term_2 = srcBuf_ptr[2];
+				} else {
+					term_1 = (srcBuf_ptr[1] & 0xF) << 8;
+					term_2 = srcBuf_ptr[0];
+				}
+
+				mixBufCurCell[i] += *((uint16 *)ampTable + (term_2 | term_1));
+				residualLength += inFrameCount;
+				while (residualLength >= 0) {
+					residualLength -= feedSize;
+					xorFlag ^= 1u;
+					if (!xorFlag)
+						srcBuf_ptr += 3;
+				}
+			}
+		}
+	}
+}
+
+void IMuseDigiInternalMixer::mixBits16Mono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable) {
+	uint16 *mixBufCurCell;
+	uint16 *srcBuf_ptr;
+	int residualLength;
+
+	mixBufCurCell = (uint16 *)(&_mixBuf[2 * mixBufStartIndex]);
+	if (feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_ptr = (uint16 *)srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[i] += *(uint16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[i] & (int16)0xFFF7) >> 3) + 4096);
+			}
+		}
+	} else if (2 * inFrameCount == feedSize) {
+		srcBuf_ptr = (uint16 *)srcBuf;
+		int i = 0;
+		if (inFrameCount - 1 != 0) {
+			for (i = 0; i < inFrameCount - 1; i++) {
+				mixBufCurCell[0] += *(uint16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[i] & (int16)0xFFF7) >> 3) + 4096);
+				mixBufCurCell[1] += (*(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[i] & (int16)0xFFF7) >> 3) + 4096)
+							       + *(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[i + 1] & (int16) 0xFFF7) >> 3) + 4096)) >> 1;
+				mixBufCurCell += 2;
+			}
+		}
+		mixBufCurCell[0] += *(uint16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[i] & (int16)0xFFF7) >> 3) + 4096);
+		mixBufCurCell[1] += *(uint16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[i] & (int16)0xFFF7) >> 3) + 4096);
+	} else {
+		if (2 * feedSize == inFrameCount) {
+			if (feedSize) {
+				srcBuf_ptr = (uint16 *)srcBuf;
+				for (int i = 0; i < feedSize; i++) {
+					mixBufCurCell[i] += *(uint16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096);
+					srcBuf_ptr += 2;
+				}
+			}
+		} else {
+			residualLength = -inFrameCount;
+
+			if (feedSize) {
+				srcBuf_ptr = (uint16 *)srcBuf;
+				for (int i = 0; i < feedSize; i++) {
+					mixBufCurCell[i] += *(uint16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096);
+
+					for (residualLength += inFrameCount; residualLength >= 0; ++srcBuf_ptr)
+						residualLength -= feedSize;
+				}
+			}
+		}
+	}
+}
+
+void IMuseDigiInternalMixer::mixBits8ConvertToMono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable) {
+	uint8 *srcBuf_ptr;
+	int residualLength;
+	uint16 *mixBufCurCell;
+
+	mixBufCurCell = (uint16 *)(&_mixBuf[2 * mixBufStartIndex]);
+	srcBuf_ptr = srcBuf;
+	if (inFrameCount == feedSize) {
+		if (feedSize) {
+			for (int i = 0, j = 0; i < feedSize; i++, j += 2) {
+				mixBufCurCell[i] += (2 * *((int16 *)ampTable + srcBuf_ptr[j])) >> 1;
+			}
+		}
+	} else if (2 * inFrameCount == feedSize) {
+		if (inFrameCount != 1) {
+			for (int i = 0; i < inFrameCount - 1; i++) {
+				mixBufCurCell[0] += (*((int16 *)ampTable + srcBuf_ptr[1]) + *((int16 *)ampTable + srcBuf_ptr[0])) >> 1;
+				int term_1 = (*((int16 *)ampTable + srcBuf_ptr[1]) + *((int16 *)ampTable + srcBuf_ptr[3])) >> 1;
+				int term_2 =  *((int16 *)ampTable + srcBuf_ptr[0]) + *((int16 *)ampTable + srcBuf_ptr[2]);
+				mixBufCurCell[1] += ((term_2 >> 1) + term_1) >> 1;
+
+				srcBuf_ptr += 2;
+				mixBufCurCell += 2;
+			}
+		}
+		mixBufCurCell[0] += (*((int16 *)ampTable + srcBuf_ptr[1]) + *((int16 *)ampTable + srcBuf_ptr[0])) >> 1;
+		mixBufCurCell[1] += (*((int16 *)ampTable + srcBuf_ptr[1]) + *((int16 *)ampTable + srcBuf_ptr[0])) >> 1;
+	} else if (2 * feedSize == inFrameCount) {
+		if (feedSize) {
+			for (int i = 0, j = 0; i < feedSize; i++, j += 4) {
+				mixBufCurCell[i] += (2 * *((int16 *)ampTable + srcBuf_ptr[j])) >> 1;
+			}
+		}
+	} else {
+		residualLength = -inFrameCount;
+		if (feedSize) {
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[i] += (*((int16 *)ampTable + srcBuf_ptr[0]) + *((int16 *)ampTable + srcBuf_ptr[1])) >> 1;
+				// Skip srcBuf elements until there's excess length
+				for (residualLength += inFrameCount; residualLength >= 0; srcBuf_ptr += 2)
+					residualLength -= feedSize;
+			}
+		}
+	}
+}
+
+void IMuseDigiInternalMixer::mixBits12ConvertToMono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable) {
+	uint8 *srcBuf_ptr;
+	int residualLength;
+	uint16 *mixBufCurCell;
+	int term_1;
+	int term_2;
+
+	mixBufCurCell = (uint16 *)(&_mixBuf[2 * mixBufStartIndex]);
+	if (feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[i] += (*((int16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8))) +
+									 *((int16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)))) >> 1;
+				srcBuf_ptr += 3;
+			}
+		}
+	} else if (2 * inFrameCount == feedSize) {
+		srcBuf_ptr = srcBuf;
+		if (inFrameCount - 1 != 0) {
+			for (int i = 0; i < inFrameCount - 1; i++) {
+				term_1 = *((int16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF0) << 8)));
+				term_2 = *((int16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF)  << 4)));
+
+				mixBufCurCell[0] += (term_1 + term_2) >> 1;
+				mixBufCurCell[1] += (((term_1 + *((int16 *)ampTable + (srcBuf_ptr[3] | ((srcBuf_ptr[4] & 0xF)  << 8)))) >> 1)
+								   + ((term_2 + *((int16 *)ampTable + (srcBuf_ptr[5] | ((srcBuf_ptr[4] & 0xF0) << 4)))) >> 1)) >> 1;
+
+				srcBuf_ptr += 3;
+				mixBufCurCell += 2;
+			}
+		}
+
+		mixBufCurCell[0] += (*((int16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)))
+						   + *((int16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)))) >> 1;
+		mixBufCurCell[1] += (*((int16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)))
+						   + *((int16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)))) >> 1;
+
+
+	} else if (2 * feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+
+			for (int i = 0; i < feedSize; i++) {
+
+				mixBufCurCell[i] += (*((int16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)))
+								   + *((int16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)))) >> 1;
+
+				srcBuf_ptr += 6;
+			}
+		}
+	} else {
+		residualLength = -inFrameCount;
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[i] += (*((int16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)))
+								   + *((int16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)))) >> 1;
+
+				for (residualLength += inFrameCount; residualLength >= 0; srcBuf_ptr += 3)
+					residualLength -= feedSize;
+			}
+		}
+	}
+}
+
+void IMuseDigiInternalMixer::mixBits16ConvertToMono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable) {
+	uint16 *mixBufCurCell;
+	uint16 *srcBuf_ptr;
+	int residualLength;
+
+	mixBufCurCell = (uint16 *)(&_mixBuf[2 * mixBufStartIndex]);
+	if (feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_ptr = (uint16 *)srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[i] += (*(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096)
+								   + *(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[1] & (int16)0xFFF7) >> 3) + 4096)) >> 1;
+				srcBuf_ptr += 2;
+			}
+		}
+	} else if (2 * inFrameCount == feedSize) {
+		srcBuf_ptr = (uint16 *)srcBuf;
+		if (inFrameCount - 1 != 0) {
+			for (int i = 0; i < inFrameCount - 1; i++) {
+				mixBufCurCell[0] += (*(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[1] & (int16)0xFFF7) >> 3) + 4096)
+								   + *(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096)) >> 1;
+
+				mixBufCurCell[1] +=  (((*(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096)
+									+   *(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[2] & (int16)0xFFF7) >> 3) + 4096)) >> 1)
+									+ ((*(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[1] & (int16)0xFFF7) >> 3) + 4096)
+									+   *(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[3] & (int16)0xFFF7) >> 3) + 4096)) >> 1)) >> 1;
+				mixBufCurCell += 2;
+				srcBuf_ptr += 2;
+			}
+		}
+
+		mixBufCurCell[0] += (*(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[1] & (int16)0xFFF7) >> 3) + 4096)
+						   + *(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096)) >> 1;
+		mixBufCurCell[1] += (*(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[1] & (int16)0xFFF7) >> 3) + 4096)
+						   + *(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096)) >> 1;
+	} else if (2 * feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_ptr = (uint16 *)srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[i] += (*(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096)
+								   + *(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[1] & (int16)0xFFF7) >> 3) + 4096)) >> 1;
+				srcBuf_ptr += 4;
+			}
+		}
+	} else {
+		residualLength = -inFrameCount;
+
+		if (feedSize) {
+			srcBuf_ptr = (uint16 *)srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[i] += (*(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096)
+								   + *(int16 *)((uint8 *)ampTable + (((int16)srcBuf_ptr[1] & (int16)0xFFF7) >> 3) + 4096)) >> 1;
+
+				for (residualLength += inFrameCount; residualLength >= 0; srcBuf_ptr += 2)
+					residualLength -= feedSize;
+			}
+		}
+	}
+}
+
+void IMuseDigiInternalMixer::mixBits8ConvertToStereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *leftAmpTable, int32 *rightAmpTable) {
+	uint16 *mixBufCurCell;
+	uint8 *srcBuf_ptr;
+	uint8 *ptr;
+	int value;
+	int residualLength;
+
+	mixBufCurCell = (uint16 *)(&_mixBuf[2 * mixBufStartIndex]);
+	if (feedSize == inFrameCount) {
+		if (_radioChatter) {
+			srcBuf_ptr = srcBuf;
+			ptr = srcBuf + 4;
+			value = srcBuf[0] - 128 + srcBuf[1] - 128 + srcBuf[2] - 128 + srcBuf[3] - 128;
+			if (feedSize) {
+				for (int i = 0; i < feedSize; i++) {
+					mixBufCurCell[0] += 4 * *((uint16 *)leftAmpTable  + (srcBuf_ptr[i] - (value >> 2)));
+					mixBufCurCell[1] += 4 * *((uint16 *)rightAmpTable + (srcBuf_ptr[i] - (value >> 2)));
+					value += ptr[i] - srcBuf_ptr[i];
+					mixBufCurCell += 2;
+				}
+			}
+		} else {
+			if (feedSize) {
+				srcBuf_ptr = srcBuf;
+				for (int i = 0; i < feedSize; i++) {
+					mixBufCurCell[0] += *((uint16 *)leftAmpTable  + srcBuf_ptr[i]);
+					mixBufCurCell[1] += *((uint16 *)rightAmpTable + srcBuf_ptr[i]);
+					mixBufCurCell += 2;
+				}
+			}
+		}
+	} else if (2 * inFrameCount == feedSize) {
+		srcBuf_ptr = srcBuf;
+		int i = 0;
+		if (inFrameCount - 1 != 0) {
+			for (i = 0; i < inFrameCount - 1; i++) {
+				mixBufCurCell[0] += *((uint16 *)leftAmpTable  + srcBuf_ptr[i]);
+				mixBufCurCell[1] += *((uint16 *)rightAmpTable + srcBuf_ptr[i]);
+				mixBufCurCell[2] += (*((int16 *)leftAmpTable  + srcBuf_ptr[i]) + *((int16 *)leftAmpTable  + srcBuf_ptr[i + 1])) >> 1;
+				mixBufCurCell[3] += (*((int16 *)rightAmpTable + srcBuf_ptr[i]) + *((int16 *)rightAmpTable + srcBuf_ptr[i + 1])) >> 1;
+				mixBufCurCell += 4;
+			}
+		}
+		mixBufCurCell[0] += *((uint16 *)leftAmpTable  + srcBuf_ptr[i]);
+		mixBufCurCell[1] += *((uint16 *)rightAmpTable + srcBuf_ptr[i]);
+		mixBufCurCell[2] += *((uint16 *)leftAmpTable  + srcBuf_ptr[i]);
+		mixBufCurCell[3] += *((uint16 *)rightAmpTable + srcBuf_ptr[i]);
+	} else if (2 * feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[0] += *((uint16 *)leftAmpTable  + srcBuf_ptr[0]);
+				mixBufCurCell[1] += *((uint16 *)rightAmpTable + srcBuf_ptr[0]);
+				mixBufCurCell += 2;
+				srcBuf_ptr += 2;
+			}
+		}
+	} else {
+		residualLength = -inFrameCount;
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[0] += *((uint16 *)leftAmpTable  + srcBuf_ptr[0]);
+				mixBufCurCell[1] += *((uint16 *)rightAmpTable + srcBuf_ptr[0]);
+				mixBufCurCell += 2;
+
+				for (residualLength += inFrameCount; residualLength > 0; ++srcBuf_ptr)
+					residualLength -= feedSize;
+			}
+		}
+	}
+}
+
+void IMuseDigiInternalMixer::mixBits12ConvertToStereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *leftAmpTable, int32 *rightAmpTable) {
+	uint16 *mixBufCurCell;
+	uint8 *srcBuf_ptr;
+
+	int xorFlag;
+	int residualLength;
+
+	int term_1;
+	int term_2;
+	int term_3;
+	int term_4;
+
+	mixBufCurCell = (uint16 *)(&_mixBuf[4 * mixBufStartIndex]);
+	if (feedSize == inFrameCount) {
+		if (inFrameCount / 2) {
+			srcBuf_ptr = srcBuf;
+			for (int i = 0; i < (inFrameCount / 2); i++) {
+				mixBufCurCell[0] += *((uint16 *)leftAmpTable  + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)));
+				mixBufCurCell[1] += *((uint16 *)rightAmpTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)));
+				mixBufCurCell[2] += *((uint16 *)leftAmpTable  + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+				mixBufCurCell[3] += *((uint16 *)rightAmpTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+				srcBuf_ptr += 3;
+				mixBufCurCell += 4;
+			}
+		}
+	} else if (2 * inFrameCount == feedSize) {
+		srcBuf_ptr = srcBuf;
+		if ((inFrameCount / 2) - 1 != 0) {
+			for (int i = 0; i < (inFrameCount / 2) - 1; i++) {
+				mixBufCurCell[0] += *((uint16 *)leftAmpTable  + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF) << 8)));
+				mixBufCurCell[1] += *((uint16 *)rightAmpTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF) << 8)));
+
+				mixBufCurCell[2] += (*((int16 *)leftAmpTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)))
+								   + *((int16 *)leftAmpTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)))) >> 1;
+
+				mixBufCurCell[3] += (*((int16 *)rightAmpTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)))
+								   + *((int16 *)rightAmpTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)))) >> 1;
+
+				mixBufCurCell[4] += *((uint16 *)leftAmpTable  + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+				mixBufCurCell[5] += *((uint16 *)rightAmpTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+
+				mixBufCurCell[6] += (*((int16 *)leftAmpTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4))) +
+									 *((int16 *)leftAmpTable + (srcBuf_ptr[3] | ((srcBuf_ptr[4] & 0xF)  << 8)))) >> 1;
+
+				mixBufCurCell[7] += (*((int16 *)rightAmpTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4))) +
+									 *((int16 *)rightAmpTable + (srcBuf_ptr[3] | ((srcBuf_ptr[4] & 0xF)  << 8)))) >> 1;
+
+				srcBuf_ptr += 3;
+				mixBufCurCell += 8;
+			}
+		}
+		mixBufCurCell[0] += *((uint16 *)leftAmpTable  + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF) << 8)));
+		mixBufCurCell[1] += *((uint16 *)rightAmpTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF) << 8)));
+
+		mixBufCurCell[2] += (*((int16 *)leftAmpTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)))
+						   + *((int16 *)leftAmpTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)))) >> 1;
+
+		mixBufCurCell[3] += (*((int16 *)rightAmpTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)))
+						   + *((int16  *)rightAmpTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)))) >> 1;
+
+		mixBufCurCell[4] += *((uint16 *)leftAmpTable  + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+		mixBufCurCell[5] += *((uint16 *)rightAmpTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+		mixBufCurCell[6] += *((uint16 *)leftAmpTable  + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+		mixBufCurCell[7] += *((uint16 *)rightAmpTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+	} else if (2 * feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[0] += *((uint16 *)leftAmpTable  + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF) << 8)));
+				mixBufCurCell[1] += *((uint16 *)rightAmpTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF) << 8)));
+				srcBuf_ptr += 3;
+				mixBufCurCell += 2;
+			}
+		}
+	} else {
+		xorFlag = 0;
+		residualLength = -inFrameCount;
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				if (xorFlag) {
+					term_2 = (srcBuf_ptr[1] & 0xF0) << 4;
+					term_1 = srcBuf_ptr[2];
+				} else {
+					term_2 = (srcBuf_ptr[1] & 0xF) << 8;
+					term_1 = srcBuf_ptr[0];
+				}
+				mixBufCurCell[0] += *((uint16 *)leftAmpTable + (term_1 | term_2));
+
+				if (xorFlag) {
+					term_4 = (srcBuf_ptr[1] & 0xF0) << 4;
+					term_3 = srcBuf_ptr[2];
+				} else {
+					term_4 = (srcBuf_ptr[1] & 0xF) << 8;
+					term_3 = srcBuf_ptr[0];
+				}
+				mixBufCurCell[1] += *((uint16 *)rightAmpTable + (term_3 | term_4));
+
+				residualLength += inFrameCount;
+				while (residualLength > 0) {
+					residualLength -= feedSize;
+					xorFlag ^= 1u;
+					if (!xorFlag)
+						srcBuf_ptr += 3;
+				}
+
+				mixBufCurCell += 2;
+			}
+		}
+	}
+}
+
+void IMuseDigiInternalMixer::mixBits16ConvertToStereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *leftAmpTable, int32 *rightAmpTable) {
+	uint16* mixBufCurCell;
+	uint16 *srcBuf_tmp;
+	int residualLength;
+
+	mixBufCurCell = (uint16*)(&_mixBuf[2 * mixBufStartIndex]);
+
+	if (feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_tmp = (uint16 *)srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[0] += *(uint16 *)((uint8 *)leftAmpTable  + (((int16)srcBuf_tmp[i] & (int16)0xFFF7) >> 3) + 4096);
+				mixBufCurCell[1] += *(uint16 *)((uint8 *)rightAmpTable + (((int16)srcBuf_tmp[i] & (int16)0xFFF7) >> 3) + 4096);
+				mixBufCurCell += 2;
+			}
+		}
+	} else if (2 * inFrameCount == feedSize) {
+		srcBuf_tmp = (uint16 *)srcBuf;
+		int i = 0;
+		if (inFrameCount - 1 != 0) {
+			for (i = 0; i < inFrameCount - 1; i++) {
+				mixBufCurCell[0] += *(uint16 *)((uint8 *)leftAmpTable  + (((int16)srcBuf_tmp[i] & (int16)0xFFF7) >> 3) + 4096);
+				mixBufCurCell[1] += *(uint16 *)((uint8 *)rightAmpTable + (((int16)srcBuf_tmp[i] & (int16)0xFFF7) >> 3) + 4096);
+
+				mixBufCurCell[2] += (*(int16 *)((uint8 *)leftAmpTable + (((int16)srcBuf_tmp[i]     & (int16)0xFFF7) >> 3) + 4096)
+								   + *(int16 *)((uint8 *)leftAmpTable + (((int16)srcBuf_tmp[i + 1] & (int16)0xFFF7) >> 3) + 4096)) >> 1;
+
+				mixBufCurCell[3] += (*(int16 *)((uint8 *)rightAmpTable + (((int16)srcBuf_tmp[i]     & (int16)0xFFF7) >> 3) + 4096)
+								   + *(int16 *)((uint8 *)rightAmpTable + (((int16)srcBuf_tmp[i + 1] & (int16)0xFFF7) >> 3) + 4096)) >> 1;
+				mixBufCurCell += 4;
+			}
+		}
+		mixBufCurCell[0] += *(uint16 *)((uint8 *)leftAmpTable  + (((int16)srcBuf_tmp[i] & (int16)0xFFF7) >> 3) + 4096);
+		mixBufCurCell[1] += *(uint16 *)((uint8 *)rightAmpTable + (((int16)srcBuf_tmp[i] & (int16)0xFFF7) >> 3) + 4096);
+		mixBufCurCell[2] += *(uint16 *)((uint8 *)leftAmpTable  + (((int16)srcBuf_tmp[i] & (int16)0xFFF7) >> 3) + 4096);
+		mixBufCurCell[3] += *(uint16 *)((uint8 *)rightAmpTable + (((int16)srcBuf_tmp[i] & (int16)0xFFF7) >> 3) + 4096);
+	} else if (2 * feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_tmp = (uint16 *)srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[0] += *(uint16 *)((uint8 *)leftAmpTable  + (((int16)srcBuf_tmp[0] & (int16)0xFFF7) >> 3) + 4096);
+				mixBufCurCell[1] += *(uint16 *)((uint8 *)rightAmpTable + (((int16)srcBuf_tmp[0] & (int16)0xFFF7) >> 3) + 4096);
+				srcBuf_tmp += 2;
+				mixBufCurCell += 2;
+			}
+		}
+	} else {
+		residualLength = -inFrameCount;
+		if (feedSize) {
+			srcBuf_tmp = (uint16 *)srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[0] += *(uint16 *)((uint8 *)leftAmpTable  + (((int16)srcBuf_tmp[0] & (int16)0xFFF7) >> 3) + 4096);
+				mixBufCurCell[1] += *(uint16 *)((uint8 *)rightAmpTable + (((int16)srcBuf_tmp[0] & (int16)0xFFF7) >> 3) + 4096);
+
+				for (residualLength += inFrameCount; residualLength > 0; ++srcBuf_tmp)
+					residualLength -= feedSize;
+
+				mixBufCurCell += 2;
+			}
+		}
+	}
+}
+
+void IMuseDigiInternalMixer::mixBits8Stereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable) {
+	uint16 *mixBufCurCell;
+	uint8 *srcBuf_ptr;
+	int residualLength;
+
+	mixBufCurCell = (uint16*)(&_mixBuf[4 * mixBufStartIndex]);
+	if (feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[0] += *((uint16 *)ampTable + srcBuf_ptr[0]);
+				mixBufCurCell[1] += *((uint16 *)ampTable + srcBuf_ptr[1]);
+				srcBuf_ptr += 2;
+				mixBufCurCell += 2;
+			}
+		}
+	} else if (2 * inFrameCount == feedSize) {
+		srcBuf_ptr = srcBuf;
+		if (inFrameCount - 1 != 0) {
+			for (int i = 0; i < inFrameCount - 1; i++) {
+				mixBufCurCell[0] += *((uint16 *)ampTable + srcBuf_ptr[0]);
+				mixBufCurCell[1] += *((uint16 *)ampTable + srcBuf_ptr[1]);
+				mixBufCurCell[2] += (*((int16 *)ampTable + srcBuf_ptr[0]) + *((int16 *)ampTable + srcBuf_ptr[2])) >> 1;
+				mixBufCurCell[3] += (*((int16 *)ampTable + srcBuf_ptr[1]) + *((int16 *)ampTable + srcBuf_ptr[3])) >> 1;
+				mixBufCurCell += 4;
+				srcBuf_ptr += 2;
+			}
+		}
+		mixBufCurCell[0] += *((uint16 *)ampTable + srcBuf_ptr[0]);
+		mixBufCurCell[1] += *((uint16 *)ampTable + srcBuf_ptr[1]);
+		mixBufCurCell[2] += *((uint16 *)ampTable + srcBuf_ptr[0]);
+		mixBufCurCell[3] += *((uint16 *)ampTable + srcBuf_ptr[1]);
+	} else if (2 * feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[0] += *((uint16 *)ampTable + srcBuf_ptr[0]);
+				mixBufCurCell[1] += *((uint16 *)ampTable + srcBuf_ptr[1]);
+				srcBuf_ptr += 4;
+				mixBufCurCell += 2;
+			}
+		}
+	} else {
+		residualLength = -inFrameCount;
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[0] += *((uint16 *)ampTable + srcBuf_ptr[0]);
+				mixBufCurCell[1] += *((uint16 *)ampTable + srcBuf_ptr[1]);
+				mixBufCurCell += 2;
+				for (residualLength += inFrameCount; residualLength >= 0; srcBuf_ptr += 2)
+					residualLength -= feedSize;
+			}
+		}
+	}
+}
+
+void IMuseDigiInternalMixer::mixBits12Stereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable) {
+	uint16 *mixBufCurCell;
+	uint8 *srcBuf_ptr;
+	int residualLength;
+
+	mixBufCurCell = (uint16 *)(&_mixBuf[4 * mixBufStartIndex]);
+	if (feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[0] += *((uint16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)));
+				mixBufCurCell[1] += *((uint16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+
+				srcBuf_ptr += 3;
+				mixBufCurCell += 2;
+			}
+		}
+	} else if (2 * inFrameCount == feedSize) {
+		srcBuf_ptr = srcBuf;
+		if (inFrameCount - 1 != 0) {
+			for (int i = 0; i < inFrameCount - 1; i++) {
+				mixBufCurCell[0] +=  *((uint16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)));
+				mixBufCurCell[1] +=  *((uint16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+				mixBufCurCell[2] += (*((int16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)))
+								   + *((int16  *)ampTable + (srcBuf_ptr[3] | ((srcBuf_ptr[4] & 0xF)  << 8)))) >> 1;
+				mixBufCurCell[3] += (*((int16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)))
+								   + *((int16  *)ampTable + (srcBuf_ptr[5] | ((srcBuf_ptr[4] & 0xF0) << 4)))) >> 1;
+
+				srcBuf_ptr += 3;
+				mixBufCurCell += 4;
+			}
+		}
+		mixBufCurCell[0] += *((uint16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)));
+		mixBufCurCell[1] += *((uint16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+		mixBufCurCell[2] += *((uint16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)));
+		mixBufCurCell[3] += *((uint16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+	} else if (2 * feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[0] += *((uint16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)));
+				mixBufCurCell[1] += *((uint16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+
+				srcBuf_ptr += 6;
+				mixBufCurCell += 2;
+			}
+		}
+	} else {
+		residualLength = -inFrameCount;
+		if (feedSize) {
+			srcBuf_ptr = srcBuf;
+
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[0] += *((uint16 *)ampTable + (srcBuf_ptr[0] | ((srcBuf_ptr[1] & 0xF)  << 8)));
+				mixBufCurCell[1] += *((uint16 *)ampTable + (srcBuf_ptr[2] | ((srcBuf_ptr[1] & 0xF0) << 4)));
+
+				mixBufCurCell += 2;
+				for (residualLength += inFrameCount; residualLength >= 0; srcBuf_ptr += 3)
+					residualLength -= feedSize;
+			}
+		}
+	}
+}
+
+void IMuseDigiInternalMixer::mixBits16Stereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable) {
+	uint16 *mixBufCurCell;
+	uint16 *srcBuf_ptr;
+	int residualLength;
+
+	mixBufCurCell = (uint16*)(&_mixBuf[4 * mixBufStartIndex]);
+	if (feedSize == inFrameCount) {
+		if (feedSize) {
+			srcBuf_ptr = (uint16 *)srcBuf;
+
+			for (int i = 0; i < feedSize; i++) {
+				mixBufCurCell[0] += *(uint16 *)((int8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096);
+				mixBufCurCell[1] += *(uint16 *)((int8 *)ampTable + (((int16)srcBuf_ptr[1] & (int16)0xFFF7) >> 3) + 4096);
+				srcBuf_ptr += 2;
+				mixBufCurCell += 2;
+			}
+		}
+	} else if (2 * inFrameCount == feedSize) {
+		srcBuf_ptr = (uint16 *)srcBuf;
+
+		if (inFrameCount - 1 != 0) {
+			for (int i = 0; i < inFrameCount - 1; i++) {
+				mixBufCurCell[0] += *(uint16 *)((int8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096);
+				mixBufCurCell[1] += *(uint16 *)((int8 *)ampTable + (((int16)srcBuf_ptr[1] & (int16)0xFFF7) >> 3) + 4096);
+
+				mixBufCurCell[2] += (*(int16 *)((int8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096)
+								   + *(int16 *)((int8 *)ampTable + (((int16)srcBuf_ptr[2] & (int16)0xFFF7) >> 3) + 4096)) >> 1;
+
+				mixBufCurCell[4] += (*(int16 *)((int8 *)ampTable + (((int16)(((uint8 *)srcBuf_ptr)[2] | (((uint8 *)srcBuf_ptr)[3] << 8)) & (int16)0xFFF7) >> 3) + 4096)
+							       + *(int16 *)((int8 *)ampTable + (((int16)srcBuf_ptr[3] & (int16)0xFFF7) >> 3) + 4096)) >> 1;
+				mixBufCurCell += 4;
+				srcBuf_ptr += 2;
+			}
+		}
+		mixBufCurCell[0] += *(int16 *)((int8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096);
+		mixBufCurCell[1] += *(int16 *)((int8 *)ampTable + (((int16)srcBuf_ptr[1] & (int16)0xFFF7) >> 3) + 4096);
+		mixBufCurCell[2] += *(int16 *)((int8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096);
+		mixBufCurCell[3] += *(int16 *)((int8 *)ampTable + (((int16)srcBuf_ptr[1] & (int16)0xFFF7) >> 3) + 4096);
+	} else {
+		if (2 * feedSize == inFrameCount) {
+			if (feedSize) {
+				srcBuf_ptr = (uint16 *)srcBuf;
+				for (int i = 0; i < feedSize; i++) {
+					mixBufCurCell[0] += *(uint16 *)((int8 *)ampTable + ((((int16)srcBuf_ptr[0] & (int16)0xFFF7)) >> 3) + 4096);
+					mixBufCurCell[1] += *(uint16 *)((int8 *)ampTable + ((((int16)srcBuf_ptr[1] & (int16)0xFFF7)) >> 3) + 4096);
+					mixBufCurCell += 2;
+					srcBuf_ptr += 4;
+				}
+			}
+		} else {
+			residualLength = -inFrameCount;
+			if (feedSize) {
+				srcBuf_ptr = (uint16 *)srcBuf;
+				for (int i = 0; i < feedSize; i++) {
+					mixBufCurCell[0] += *(uint16 *)((int8 *)ampTable + (((int16)srcBuf_ptr[0] & (int16)0xFFF7) >> 3) + 4096);
+					mixBufCurCell[1] += *(uint16 *)((int8 *)ampTable + (((int16)srcBuf_ptr[1] & (int16)0xFFF7) >> 3) + 4096);
+
+					for (residualLength += inFrameCount; residualLength >= 0; srcBuf_ptr += 2)
+						residualLength -= feedSize;
+				}
+			}
+		}
+	}
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_internalmixer.h b/engines/scumm/imuse_digi/dimuse_internalmixer.h
new file mode 100644
index 0000000000..a4e94e49d2
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_internalmixer.h
@@ -0,0 +1,95 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#if !defined(SCUMM_IMUSE_DIGI_V2_MIXER_H) && defined(ENABLE_SCUMM_7_8)
+#define SCUMM_IMUSE_DIGI_V2_MIXER_H
+
+#include "common/scummsys.h"
+#include "common/mutex.h"
+#include "common/serializer.h"
+#include "common/textconsole.h"
+#include "common/util.h"
+
+#include "scumm/imuse_digi/dimuse_engine.h"
+#include "scumm/music.h"
+#include "scumm/sound.h"
+#include "audio/mixer.h"
+#include "audio/audiostream.h"
+
+namespace Audio {
+class AudioStream;
+class Mixer;
+class QueuingAudioStream;
+}
+
+namespace Scumm {
+
+class IMuseDigiInternalMixer {
+
+private:
+	int32 *_amp8Table;
+	int32 *_amp12Table;
+	int32 *_softLMID;
+	int32 *_softLTable;
+
+	uint8 *_mixBuf;
+
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _channelHandle;
+	int _mixBufSize;
+	int _radioChatter;
+	int _outWordSize;
+	int _outChannelCount;
+	int _stereoReverseFlag;
+
+	void mixBits8Mono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
+	void mixBits12Mono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
+	void mixBits16Mono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
+
+	void mixBits8ConvertToMono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
+	void mixBits12ConvertToMono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
+	void mixBits16ConvertToMono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
+
+	void mixBits8ConvertToStereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *leftAmpTable, int32 *rightAmpTable);
+	void mixBits12ConvertToStereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *leftAmpTable, int32 *rightAmpTable);
+	void mixBits16ConvertToStereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *leftAmpTable, int32 *rightAmpTable);
+
+	void mixBits8Stereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
+	void mixBits12Stereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
+	void mixBits16Stereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
+
+public:
+	IMuseDigiInternalMixer(Audio::Mixer *mixer);
+	~IMuseDigiInternalMixer();
+	int  init(int bytesPerSample, int numChannels, uint8 *mixBuf, int mixBufSize, int sizeSampleKB, int mixChannelsNum);
+	void setRadioChatter();
+	void clearRadioChatter();
+	int  clearMixerBuffer();
+
+	void mix(uint8 *srcBuf, int32 inFrameCount, int wordSize, int channelCount, int feedSize, int32 mixBufStartIndex, int volume, int pan);
+	int  loop(uint8 **destBuffer, int len);
+	Audio::QueuingAudioStream *_stream;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/imuse_digi/dimuse_music.cpp b/engines/scumm/imuse_digi/dimuse_music.cpp
deleted file mode 100644
index 1984c9b6f7..0000000000
--- a/engines/scumm/imuse_digi/dimuse_music.cpp
+++ /dev/null
@@ -1,542 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-
-#include "common/scummsys.h"
-#include "scumm/scumm.h"
-#include "scumm/imuse_digi/dimuse.h"
-#include "scumm/imuse_digi/dimuse_tables.h"
-
-namespace Scumm {
-
-#define DIG_STATE_OFFSET 11
-#define DIG_SEQ_OFFSET (DIG_STATE_OFFSET + 65)
-#define COMI_STATE_OFFSET 3
-
-void IMuseDigital::setDigMusicState(int stateId) {
-	int l, num = -1;
-
-	for (l = 0; _digStateMusicTable[l].soundId != -1; l++) {
-		if ((_digStateMusicTable[l].soundId == stateId)) {
-			debug(5, "Set music state: %s, %s", _digStateMusicTable[l].name, _digStateMusicTable[l].filename);
-			num = l;
-			break;
-		}
-	}
-
-	if (num == -1) {
-		for (l = 0; _digStateMusicMap[l].roomId != -1; l++) {
-			if ((_digStateMusicMap[l].roomId == stateId)) {
-				break;
-			}
-		}
-		num = l;
-
-		int offset = _attributes[_digStateMusicMap[num].offset];
-		if (offset == 0) {
-			if (_attributes[_digStateMusicMap[num].attribPos] != 0) {
-				num = _digStateMusicMap[num].stateIndex3;
-			} else {
-				num = _digStateMusicMap[num].stateIndex1;
-			}
-		} else {
-			int stateIndex2 = _digStateMusicMap[num].stateIndex2;
-			if (stateIndex2 == 0) {
-				num = _digStateMusicMap[num].stateIndex1 + offset;
-			} else {
-				num = stateIndex2;
-			}
-		}
-	}
-
-	debug(5, "Set music state: %s, %s", _digStateMusicTable[num].name, _digStateMusicTable[num].filename);
-
-	if (_curMusicState == num)
-		return;
-
-	if (_curMusicSeq == 0) {
-		if (num == 0)
-			playDigMusic(NULL, &_digStateMusicTable[0], num, false);
-		else
-			playDigMusic(_digStateMusicTable[num].name, &_digStateMusicTable[num], num, false);
-	}
-
-	_curMusicState = num;
-}
-
-void IMuseDigital::setDigMusicSequence(int seqId) {
-	int l, num = -1;
-
-	if (seqId == 0)
-		seqId = 2000;
-
-	for (l = 0; _digSeqMusicTable[l].soundId != -1; l++) {
-		if ((_digSeqMusicTable[l].soundId == seqId)) {
-			debug(5, "Set music sequence: %s, %s", _digSeqMusicTable[l].name, _digSeqMusicTable[l].filename);
-			num = l;
-			break;
-		}
-	}
-
-	if (num == -1)
-		return;
-
-	if (_curMusicSeq == num)
-		return;
-
-	if (num != 0) {
-		if (_curMusicSeq && ((_digSeqMusicTable[_curMusicSeq].transitionType == 4)
-				|| (_digSeqMusicTable[_curMusicSeq].transitionType == 6))) {
-			_nextSeqToPlay = num;
-			return;
-		} else {
-			playDigMusic(_digSeqMusicTable[num].name, &_digSeqMusicTable[num], 0, true);
-			_nextSeqToPlay = 0;
-			_attributes[DIG_SEQ_OFFSET + num] = 1; // _attributes[COMI_SEQ_OFFSET] in Comi are not used as it doesn't have 'room' attributes table
-		}
-	} else {
-		if (_nextSeqToPlay != 0) {
-			playDigMusic(_digSeqMusicTable[_nextSeqToPlay].name, &_digSeqMusicTable[_nextSeqToPlay], 0, true);
-			_attributes[DIG_SEQ_OFFSET + _nextSeqToPlay] = 1; // _attributes[COMI_SEQ_OFFSET] in Comi are not used as it doesn't have 'room' attributes table
-			num = _nextSeqToPlay;
-			_nextSeqToPlay = 0;
-		} else {
-			if (_curMusicState != 0) {
-				playDigMusic(_digStateMusicTable[_curMusicState].name, &_digStateMusicTable[_curMusicState], _curMusicState, true);
-			} else
-				playDigMusic(NULL, &_digStateMusicTable[0], _curMusicState, true);
-			num = 0;
-		}
-	}
-
-	_curMusicSeq = num;
-}
-
-void IMuseDigital::playDigMusic(const char *songName, const imuseDigTable *table, int attribPos, bool sequence) {
-	int hookId = 0;
-
-	if (songName != NULL) {
-		if ((_attributes[DIG_SEQ_OFFSET + 38]) && (!_attributes[DIG_SEQ_OFFSET + 41])) {
-			if ((attribPos == 43) || (attribPos == 44))
-				hookId = 3;
-		}
-
-		if ((_attributes[DIG_SEQ_OFFSET + 46] != 0) && (_attributes[DIG_SEQ_OFFSET + 48] == 0)) {
-			if ((attribPos == 38) || (attribPos == 39))
-				hookId = 3;
-		}
-
-		if ((_attributes[DIG_SEQ_OFFSET + 53] != 0)) {
-			if ((attribPos == 50) || (attribPos == 51))
-				hookId = 3;
-		}
-
-		if ((attribPos != 0) && (hookId == 0)) {
-			if (table->attribPos != 0)
-				attribPos = table->attribPos;
-			hookId = _attributes[DIG_STATE_OFFSET + attribPos];
-			if (table->hookId != 0) {
-				if ((hookId != 0) && (table->hookId > 1)) {
-					_attributes[DIG_STATE_OFFSET + attribPos] = 2;
-				} else {
-					_attributes[DIG_STATE_OFFSET + attribPos] = hookId + 1;
-					if (table->hookId < hookId + 1)
-						_attributes[DIG_STATE_OFFSET + attribPos] = 1;
-				}
-			}
-		}
-	}
-
-	if (!songName) {
-		fadeOutMusic(120);
-		return;
-	}
-
-	switch (table->transitionType) {
-	case 0:
-	case 5:
-	default:
-		break;
-	case 3:
-	case 4:
-		if (table->filename[0] == 0) {
-			fadeOutMusic(60);
-			return;
-		}
-		if (table->transitionType == 4)
-			_stopingSequence = 1;
-		if ((!sequence) && (table->attribPos != 0) &&
-				(table->attribPos == _digStateMusicTable[_curMusicState].attribPos)) {
-			fadeOutMusicAndStartNew(108, table->filename, table->soundId);
-		} else {
-			fadeOutMusic(108);
-			startMusic(table->filename, table->soundId, hookId, 127);
-		}
-		break;
-	case 6:
-		_stopingSequence = 1;
-		break;
-	}
-}
-
-void IMuseDigital::setComiMusicState(int stateId) {
-	int l, num = -1;
-
-	if (stateId == 4) // look into #3604 bug, ignore stateId == 4 it's seems needed after all
-		return;
-
-	if (stateId == 0)
-		stateId = 1000;
-
-	for (l = 0; _comiStateMusicTable[l].soundId != -1; l++) {
-		if ((_comiStateMusicTable[l].soundId == stateId)) {
-			debug(5, "Set music state: %s, %s", _comiStateMusicTable[l].name, _comiStateMusicTable[l].filename);
-			num = l;
-			break;
-		}
-	}
-
-	if (num == -1)
-		return;
-
-	if (_curMusicState == num)
-		return;
-
-	if (_curMusicSeq == 0) {
-		if (num == 0)
-			playComiMusic(NULL, &_comiStateMusicTable[0], num, false);
-		else
-			playComiMusic(_comiStateMusicTable[num].name, &_comiStateMusicTable[num], num, false);
-	}
-
-	_curMusicState = num;
-}
-
-void IMuseDigital::setComiMusicSequence(int seqId) {
-	int l, num = -1;
-
-	if (seqId == 0)
-		seqId = 2000;
-
-	for (l = 0; _comiSeqMusicTable[l].soundId != -1; l++) {
-		if ((_comiSeqMusicTable[l].soundId == seqId)) {
-			debug(5, "Set music sequence: %s, %s", _comiSeqMusicTable[l].name, _comiSeqMusicTable[l].filename);
-			num = l;
-			break;
-		}
-	}
-
-	if (num == -1)
-		return;
-
-	if (_curMusicSeq == num)
-		return;
-
-	if (num != 0) {
-		if (_curMusicSeq && ((_comiSeqMusicTable[_curMusicSeq].transitionType == 4)
-				|| (_comiSeqMusicTable[_curMusicSeq].transitionType == 6))) {
-			_nextSeqToPlay = num;
-			return;
-		} else {
-			playComiMusic(_comiSeqMusicTable[num].name, &_comiSeqMusicTable[num], 0, true);
-			_nextSeqToPlay = 0;
-		}
-	} else {
-		if (_nextSeqToPlay != 0) {
-			playComiMusic(_comiSeqMusicTable[_nextSeqToPlay].name, &_comiSeqMusicTable[_nextSeqToPlay], 0, true);
-			num = _nextSeqToPlay;
-			_nextSeqToPlay = 0;
-		} else {
-			if (_curMusicState != 0) {
-				playComiMusic(_comiStateMusicTable[_curMusicState].name, &_comiStateMusicTable[_curMusicState], _curMusicState, true);
-			} else
-				playComiMusic(NULL, &_comiStateMusicTable[0], _curMusicState, true);
-			num = 0;
-		}
-	}
-
-	_curMusicSeq = num;
-}
-
-void IMuseDigital::setComiDemoMusicState(int stateNum) {
-	if (stateNum == -1)
-		return;
-
-	if (_curMusicState == stateNum)
-		return;
-
-	if (stateNum != 0 && stateNum != 2 && stateNum != 4 && stateNum != 8 && stateNum != 9 && stateNum != 16) {
-		debug(5, "Tried to set music state to num: %d, defaulting to 0", stateNum);
-		stateNum = 0;
-	}
-
-	if (_curMusicSeq == 0) {
-		if (stateNum == 0)
-			playComiDemoMusic(NULL, &_comiDemoStateMusicTable[0], stateNum);
-		else
-			playComiDemoMusic(_comiDemoStateMusicTable[stateNum].name, &_comiDemoStateMusicTable[stateNum], stateNum);
-	}
-
-	_curMusicState = stateNum;
-}
-
-void IMuseDigital::playComiMusic(const char *songName, const imuseComiTable *table, int attribPos, bool sequence) {
-	int hookId = 0;
-
-	if ((songName != NULL) && (attribPos != 0)) {
-		if (table->attribPos != 0)
-			attribPos = table->attribPos;
-		hookId = _attributes[COMI_STATE_OFFSET + attribPos];
-		if (table->hookId != 0) {
-			if ((hookId != 0) && (table->hookId > 1)) {
-				_attributes[COMI_STATE_OFFSET + attribPos] = 2;
-			} else {
-				_attributes[COMI_STATE_OFFSET + attribPos] = hookId + 1;
-				if (table->hookId < hookId + 1)
-					_attributes[COMI_STATE_OFFSET + attribPos] = 1;
-			}
-		}
-	}
-
-	if (!songName) {
-		fadeOutMusic(120);
-		return;
-	}
-
-	TriggerParams trigger;
-
-	switch (table->transitionType) {
-	case 0:
-	default:
-		break;
-	case 8:
-		setHookIdForMusic(table->hookId);
-		break;
-	case 9:
-		// Setup the trigger
-		strcpy(trigger.marker, "_end"); trigger.fadeOutDelay = table->fadeOutDelay;
-		strcpy(trigger.filename, table->filename); trigger.soundId = table->soundId;
-		trigger.hookId = table->hookId; trigger.volume = 127;
-		setTrigger(&trigger);
-
-		setHookIdForMusic(table->hookId);
-		break;
-	case 4:
-		if (table->filename[0] == 0) {
-			fadeOutMusic(60);
-			return;
-		}
-		if (getCurMusicSoundId() == table->soundId)
-			return;
-
-		// Setup the trigger
-		strcpy(trigger.marker, "_end"); trigger.fadeOutDelay = table->fadeOutDelay;
-		strcpy(trigger.filename, table->filename); trigger.soundId = table->soundId;
-		trigger.hookId = table->hookId; trigger.volume = 127;
-		setTrigger(&trigger);
-
-		fadeOutMusic(table->fadeOutDelay);
-		startMusic(table->filename, table->soundId, hookId, 127);
-		break;
-	case 2:
-	case 3:
-	case 12:
-		if (table->filename[0] == 0) {
-			fadeOutMusic(60);
-			return;
-		}
-		if (getCurMusicSoundId() == table->soundId)
-			return;
-		if (table->transitionType == 2) {
-			fadeOutMusic(table->fadeOutDelay);
-			startMusic(table->filename, table->soundId, table->hookId, 127);
-			return;
-		}
-		if ((!sequence) && (table->attribPos != 0) &&
-				(table->attribPos == _comiStateMusicTable[_curMusicState].attribPos)) {
-			fadeOutMusicAndStartNew(table->fadeOutDelay, table->filename, table->soundId);
-		} else if (table->transitionType == 12) {
-			// Setup the trigger
-			strcpy(trigger.marker, "exit"); trigger.fadeOutDelay = table->fadeOutDelay;
-			strcpy(trigger.filename, table->filename); trigger.soundId = table->soundId;
-			trigger.hookId = table->hookId; trigger.volume = 127;
-			setTrigger(&trigger);
-		} else {
-			fadeOutMusic(table->fadeOutDelay);
-			startMusic(table->filename, table->soundId, hookId, 127);
-		}
-		break;
-	}
-}
-
-void IMuseDigital::playComiDemoMusic(const char *songName, const imuseComiTable *table, int attribPos) {
-	int hookId = 0;
-
-	if ((songName != NULL) && (attribPos != 0)) {
-		if (table->attribPos != 0)
-			attribPos = table->attribPos;
-		hookId = _attributes[COMI_STATE_OFFSET + attribPos];
-		if (table->hookId != 0) {
-			if ((hookId != 0) && (table->hookId > 1)) {
-				_attributes[COMI_STATE_OFFSET + attribPos] = 2;
-			}
-			else {
-				_attributes[COMI_STATE_OFFSET + attribPos] = hookId + 1;
-				if (table->hookId < hookId + 1)
-					_attributes[COMI_STATE_OFFSET + attribPos] = 1;
-			}
-		}
-	}
-
-	if (!songName) {
-		fadeOutMusic(120);
-		return;
-	}
-
-	switch (table->transitionType) {
-	case 0:
-	default:
-		break;
-	case 3:
-		if (table->filename[0] == 0) {
-			fadeOutMusic(60);
-			return;
-		}
-		if (getCurMusicSoundId() == table->soundId)
-			return;
-		if ((table->attribPos != 0) &&
-				(table->attribPos == _comiDemoStateMusicTable[_curMusicState].attribPos)) {
-			fadeOutMusicAndStartNew(table->fadeOutDelay, table->filename, table->soundId);
-		} else {
-			fadeOutMusic(table->fadeOutDelay);
-			startMusic(table->filename, table->soundId, hookId, 127);
-		}
-		break;
-	}
-}
-
-void IMuseDigital::setFtMusicState(int stateId) {
-	if (stateId > 48)
-		return;
-
-	debug(5, "State music: %s, %s", _ftStateMusicTable[stateId].name, _ftStateMusicTable[stateId].audioName);
-
-	if (_curMusicState == stateId)
-		return;
-
-	if (_curMusicSeq == 0) {
-		if (stateId == 0)
-			playFtMusic(NULL, 0, 0);
-		else
-			playFtMusic(_ftStateMusicTable[stateId].audioName, _ftStateMusicTable[stateId].transitionType, _ftStateMusicTable[stateId].volume);
-	}
-
-	_curMusicState = stateId;
-}
-
-void IMuseDigital::setFtMusicSequence(int seqId) {
-	if (seqId > 52)
-		return;
-
-	debug(5, "Sequence music: %s", _ftSeqNames[seqId].name);
-
-	if (_curMusicSeq == seqId)
-		return;
-
-	if (seqId == 0) {
-		if (_curMusicState == 0)
-			playFtMusic(NULL, 0, 0);
-		else {
-			playFtMusic(_ftStateMusicTable[_curMusicState].audioName, _ftStateMusicTable[_curMusicState].transitionType, _ftStateMusicTable[_curMusicState].volume);
-		}
-	} else {
-		int seq = (seqId - 1) * 4;
-		playFtMusic(_ftSeqMusicTable[seq].audioName, _ftSeqMusicTable[seq].transitionType, _ftSeqMusicTable[seq].volume);
-	}
-
-	_curMusicSeq = seqId;
-	_curMusicCue = 0;
-}
-
-void IMuseDigital::setFtMusicCuePoint(int cueId) {
-	if (cueId > 3)
-		return;
-
-	debug(5, "Cue point sequence: %d", cueId);
-
-	if (_curMusicSeq == 0)
-		return;
-
-	if (_curMusicCue == cueId)
-		return;
-
-	if (cueId == 0)
-		playFtMusic(NULL, 0, 0);
-	else {
-		int seq = ((_curMusicSeq - 1) * 4) + cueId;
-		playFtMusic(_ftSeqMusicTable[seq].audioName, _ftSeqMusicTable[seq].transitionType, _ftSeqMusicTable[seq].volume);
-	}
-
-	_curMusicCue = cueId;
-}
-
-void IMuseDigital::setAudioNames(int32 num, char *names) {
-	free(_audioNames);
-	_numAudioNames = num;
-	_audioNames = names;
-}
-
-int IMuseDigital::getSoundIdByName(const char *soundName) {
-	if (soundName && soundName[0] != 0) {
-		for (int r = 0; r < _numAudioNames; r++) {
-			if (strcmp(soundName, &_audioNames[r * 9]) == 0) {
-				return r;
-			}
-		}
-	}
-
-	return -1;
-}
-
-void IMuseDigital::playFtMusic(const char *songName, int opcode, int volume) {
-	fadeOutMusic(200);
-
-	switch (opcode) {
-	case 0:
-	case 4:
-	default:
-		break;
-	case 1:
-	case 2:
-	case 3:
-		{
-			int soundId = getSoundIdByName(songName);
-			if (soundId != -1) {
-				startMusic(soundId, volume);
-			}
-		}
-		break;
-	}
-}
-
-
-} // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_script.cpp b/engines/scumm/imuse_digi/dimuse_script.cpp
deleted file mode 100644
index d15a62b721..0000000000
--- a/engines/scumm/imuse_digi/dimuse_script.cpp
+++ /dev/null
@@ -1,435 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-
-#include "common/system.h"
-#include "common/timer.h"
-
-#include "scumm/actor.h"
-#include "scumm/scumm_v7.h"
-#include "scumm/sound.h"
-#include "scumm/imuse_digi/dimuse.h"
-#include "scumm/imuse_digi/dimuse_bndmgr.h"
-#include "scumm/imuse_digi/dimuse_track.h"
-
-#include "audio/audiostream.h"
-#include "audio/mixer.h"
-
-namespace Scumm {
-
-void IMuseDigital::parseScriptCmds(int cmd, int b, int c, int d, int e, int f, int g, int h) {
-	int soundId = b;
-	int sub_cmd = c;
-
-	if (!cmd)
-		return;
-
-	switch (cmd) {
-	case 10: // ImuseStopAllSounds
-		stopAllSounds();
-		break;
-	case 12: // ImuseSetParam
-		switch (sub_cmd) {
-		case 0x400: // select group volume
-			selectVolumeGroup(soundId, d);
-			break;
-		case 0x500: // set priority
-			setPriority(soundId, d);
-			break;
-		case 0x600: // set volume
-			if (d >= 0 && d <= 127)
-				setVolume(soundId, d);
-			break;
-		case 0x700: // set pan
-			if (d >= 0 && d <= 127)
-				setPan(soundId, d);
-			break;
-		default:
-			warning("IMuseDigital::doCommand SetParam DEFAULT command %d", sub_cmd);
-			break;
-		}
-		break;
-	case 14: // ImuseFadeParam
-		switch (sub_cmd) {
-		case 0x600: // set volume fading
-			if ((d != 0) && (e == 0))
-				setVolume(soundId, d);
-			else if ((d == 0) && (e == 0))
-				stopSound(soundId);
-			else
-				setFade(soundId, d, e);
-			break;
-		default:
-			warning("IMuseDigital::doCommand FadeParam DEFAULT sub command %d", sub_cmd);
-			break;
-		}
-		break;
-	case 25: // ImuseStartStream
-		debug(3, "ImuseStartStream (%d, %d, %d)", soundId, c, d);
-		break;
-	case 26: // ImuseSwitchStream
-		debug(3, "ImuseSwitchStream (%d, %d, %d, %d, %d)", soundId, c, d, e, f);
-		break;
-	case 0x1000: // ImuseSetState
-		debug(5, "ImuseSetState (%d)", b);
-		if ((_vm->_game.id == GID_DIG) && (_vm->_game.features & GF_DEMO)) {
-			if (b == 1) {
-				fadeOutMusic(200);
-				startMusic(1, 127);
-			} else {
-				if (getSoundStatus(2) == 0) {
-					fadeOutMusic(200);
-					startMusic(2, 127);
-				}
-			}
-		} else if ((_vm->_game.id == GID_CMI) && (_vm->_game.features & GF_DEMO)) {
-			setComiDemoMusicState(b);
-		} else if (_vm->_game.id == GID_DIG) {
-			setDigMusicState(b);
-		} else if (_vm->_game.id == GID_CMI) {
-			setComiMusicState(b);
-		} else if (_vm->_game.id == GID_FT) {
-			setFtMusicState(b);
-		}
-		break;
-	case 0x1001: // ImuseSetSequence
-		debug(5, "ImuseSetSequence (%d)", b);
-		if (_vm->_game.id == GID_DIG) {
-			setDigMusicSequence(b);
-		} else if (_vm->_game.id == GID_CMI) {
-			setComiMusicSequence(b);
-		} else if (_vm->_game.id == GID_FT) {
-			setFtMusicSequence(b);
-		}
-		break;
-	case 0x1002: // ImuseSetCuePoint
-		debug(5, "ImuseSetCuePoint (%d)", b);
-		if (_vm->_game.id == GID_FT) {
-			setFtMusicCuePoint(b);
-		}
-		break;
-	case 0x1003: // ImuseSetAttribute
-		debug(5, "ImuseSetAttribute (%d, %d)", b, c);
-		assert((_vm->_game.id == GID_DIG) || (_vm->_game.id == GID_FT));
-		if (_vm->_game.id == GID_DIG) {
-			_attributes[b] = c;
-		}
-		break;
-	case 0x2000: // ImuseSetGroupSfxVolume
-		break;
-	case 0x2001: // ImuseSetGroupVoiceVolume
-		break;
-	case 0x2002: // ImuseSetGroupMusicVolume
-		break;
-	default:
-		error("IMuseDigital::doCommand DEFAULT command %d", cmd);
-	}
-}
-
-void IMuseDigital::flushTrack(Track *track) {
-	track->toBeRemoved = true;
-
-	if (track->souStreamUsed) {
-		_mixer->stopHandle(track->mixChanHandle);
-	} else if (track->stream) {
-		debug(5, "flushTrack(trackId: %d) - soundId:%d", track->trackId, track->soundId);
-		// Finalize the appendable stream, then remove our reference to it.
-		// Note that there might still be some data left in the buffers of the
-		// appendable stream. We play it nice and wait till all of it
-		// played. The audio mixer will take care of it afterwards (and dispose it).
-		track->stream->finish();
-		track->stream = 0;
-		if (track->soundDesc) {
-			_sound->closeSound(track->soundDesc);
-			track->soundDesc = 0;
-		}
-	}
-
-	if (!_mixer->isSoundHandleActive(track->mixChanHandle)) {
-		track->reset();
-	}
-}
-
-void IMuseDigital::flushTracks() {
-	Common::StackLock lock(_mutex, "IMuseDigital::flushTracks()");
-	debug(6, "flushTracks()");
-	for (int l = 0; l < MAX_DIGITAL_TRACKS + MAX_DIGITAL_FADETRACKS; l++) {
-		Track *track = _track[l];
-		if (track->used && track->toBeRemoved && !_mixer->isSoundHandleActive(track->mixChanHandle)) {
-			debug(5, "flushTracks() - trackId:%d, soundId:%d", track->trackId, track->soundId);
-			track->reset();
-		}
-	}
-}
-
-void IMuseDigital::refreshScripts() {
-	Common::StackLock lock(_mutex, "IMuseDigital::refreshScripts()");
-	debug(6, "refreshScripts()");
-
-	if (_stopingSequence) {
-		// prevent start new music, only fade out old one
-		if (_vm->isSmushActive()) {
-			fadeOutMusic(60);
-			return;
-		}
-		// small delay, it seems help for fix bug #3325
-		if (_stopingSequence++ > 120) {
-			debug(5, "refreshScripts() Force restore music state");
-			parseScriptCmds(0x1001, 0, 0, 0, 0, 0, 0, 0);
-			_stopingSequence = 0;
-		}
-	}
-
-	bool found = false;
-	for (int l = 0; l < MAX_DIGITAL_TRACKS; l++) {
-		Track *track = _track[l];
-		if (track->used && !track->toBeRemoved && (track->volGroupId == IMUSE_VOLGRP_MUSIC)) {
-			found = true;
-			break;
-		}
-	}
-
-	if (!found && _curMusicState) {
-		debug(5, "refreshScripts() Restore music state");
-		parseScriptCmds(0x1001, 0, 0, 0, 0, 0, 0, 0);
-	}
-}
-
-int IMuseDigital::startVoice(int soundId, Audio::AudioStream *input) {
-	debug(5, "startVoiceStream(%d)", soundId);
-	return startSound(soundId, "", 0, IMUSE_VOLGRP_VOICE, input, 0, 127, 127, NULL);
-}
-
-int IMuseDigital::startVoice(int soundId, const char *soundName) {
-	debug(5, "startVoiceBundle(%s, %d)", soundName, soundId);
-	return startSound(soundId, soundName, IMUSE_BUNDLE, IMUSE_VOLGRP_VOICE, NULL, 0, 127, 127, NULL);
-}
-
-int IMuseDigital::startMusic(int soundId, int volume) {
-	debug(5, "startMusicResource(%d)", soundId);
-	return startSound(soundId, "", IMUSE_RESOURCE, IMUSE_VOLGRP_MUSIC, NULL, 0, volume, 126, NULL);
-}
-
-int IMuseDigital::startMusic(const char *soundName, int soundId, int hookId, int volume) {
-	debug(5, "startMusicBundle(%s, soundId:%d, hookId:%d)", soundName, soundId, hookId);
-	return startSound(soundId, soundName, IMUSE_BUNDLE, IMUSE_VOLGRP_MUSIC, NULL, hookId, volume, 126, NULL);
-}
-
-int IMuseDigital::startMusicWithOtherPos(const char *soundName, int soundId, int hookId, int volume, Track *otherTrack) {
-	debug(5, "startMusicWithOtherPos(%s, soundId:%d, hookId:%d, oldSoundId:%d)", soundName, soundId, hookId, otherTrack->soundId);
-	return startSound(soundId, soundName, IMUSE_BUNDLE, IMUSE_VOLGRP_MUSIC, NULL, hookId, volume, 126, otherTrack);
-}
-
-int IMuseDigital::startSfx(int soundId, int priority) {
-	debug(5, "startSfx(%d)", soundId);
-	return startSound(soundId, "", IMUSE_RESOURCE, IMUSE_VOLGRP_SFX, NULL, 0, 127, priority, NULL);
-}
-
-void IMuseDigital::getLipSync(int soundId, int syncId, int32 msPos, int32 &width, int32 &height) {
-	int32 sync_size;
-	byte *sync_ptr;
-
-	msPos /= 16;
-	if (msPos < 65536) {
-		Common::StackLock lock(_mutex, "IMuseDigital::getLipSync()");
-		for (int l = 0; l < MAX_DIGITAL_TRACKS; l++) {
-			Track *track = _track[l];
-			if (track->used && !track->toBeRemoved && (track->soundId == soundId)) {
-				_sound->getSyncSizeAndPtrById(track->soundDesc, syncId, sync_size, &sync_ptr);
-				if ((sync_size != 0) && (sync_ptr != NULL)) {
-					sync_size /= 4;
-					while (sync_size--) {
-						if (READ_BE_UINT16(sync_ptr) >= msPos)
-							break;
-						sync_ptr += 4;
-					}
-					if (sync_size < 0)
-						sync_ptr -= 4;
-					else
-						if (READ_BE_UINT16(sync_ptr) > msPos)
-							sync_ptr -= 4;
-
-					width = sync_ptr[2];
-					height = sync_ptr[3];
-					return;
-				}
-			}
-		}
-	}
-}
-
-int32 IMuseDigital::getPosInMs(int soundId) {
-	Common::StackLock lock(_mutex, "IMuseDigital::getPosInMs()");
-	for (int l = 0; l < MAX_DIGITAL_TRACKS; l++) {
-		Track *track = _track[l];
-		if (track->used && !track->toBeRemoved && (track->soundId == soundId)) {
-			int32 pos = (5 * (track->dataOffset + track->regionOffset)) / (track->feedSize / 200);
-			return pos;
-		}
-	}
-
-	return 0;
-}
-
-int IMuseDigital::getSoundStatus(int soundId) const {
-	Common::StackLock lock(_mutex, "IMuseDigital::getSoundStatus()");
-	debug(5, "IMuseDigital::getSoundStatus(%d)", soundId);
-	for (int l = 0; l < MAX_DIGITAL_TRACKS; l++) {
-		Track *track = _track[l];
-		// Note: We do not check track->toBeRemoved here on purpose (I *think*, at least).
-		// After all, tracks which are about to stop still are running (if only for a brief time).
-		if ((track->soundId == soundId) && track->used) {
-			if (_mixer->isSoundHandleActive(track->mixChanHandle)) {
-				return 1;
-			}
-		}
-	}
-
-	return 0;
-}
-
-void IMuseDigital::stopSound(int soundId) {
-	Common::StackLock lock(_mutex, "IMuseDigital::stopSound()");
-	debug(5, "IMuseDigital::stopSound(%d)", soundId);
-	for (int l = 0; l < MAX_DIGITAL_TRACKS; l++) {
-		Track *track = _track[l];
-		if (track->used && !track->toBeRemoved && (track->soundId == soundId)) {
-			debug(5, "IMuseDigital::stopSound(%d) - stopping sound", soundId);
-			flushTrack(track);
-		}
-	}
-}
-
-int32 IMuseDigital::getCurMusicPosInMs() {
-	Common::StackLock lock(_mutex, "IMuseDigital::getCurMusicPosInMs()");
-	int soundId = -1;
-
-	for (int l = 0; l < MAX_DIGITAL_TRACKS; l++) {
-		Track *track = _track[l];
-		if (track->used && !track->toBeRemoved && (track->volGroupId == IMUSE_VOLGRP_MUSIC)) {
-			soundId = track->soundId;
-		}
-	}
-
-	int32 msPos = getPosInMs(soundId);
-	debug(6, "IMuseDigital::getCurMusicPosInMs(%d) = %d", soundId, msPos);
-	return msPos;
-}
-
-int32 IMuseDigital::getCurVoiceLipSyncWidth() {
-	Common::StackLock lock(_mutex, "IMuseDigital::getCurVoiceLipSyncWidth()");
-	int32 msPos = getPosInMs(kTalkSoundID) + 50;
-	int32 width = 0, height = 0;
-
-	debug(6, "IMuseDigital::getCurVoiceLipSyncWidth(%d)", kTalkSoundID);
-	getLipSync(kTalkSoundID, 0, msPos, width, height);
-	return width;
-}
-
-int32 IMuseDigital::getCurVoiceLipSyncHeight() {
-	Common::StackLock lock(_mutex, "IMuseDigital::getCurVoiceLipSyncHeight()");
-	int32 msPos = getPosInMs(kTalkSoundID) + 50;
-	int32 width = 0, height = 0;
-
-	debug(6, "IMuseDigital::getCurVoiceLipSyncHeight(%d)", kTalkSoundID);
-	getLipSync(kTalkSoundID, 0, msPos, width, height);
-	return height;
-}
-
-int32 IMuseDigital::getCurMusicLipSyncWidth(int syncId) {
-	Common::StackLock lock(_mutex, "IMuseDigital::getCurMusicLipSyncWidth()");
-	int soundId = -1;
-
-	for (int l = 0; l < MAX_DIGITAL_TRACKS; l++) {
-		Track *track = _track[l];
-		if (track->used && !track->toBeRemoved && (track->volGroupId == IMUSE_VOLGRP_MUSIC)) {
-			soundId = track->soundId;
-		}
-	}
-
-	int32 msPos = getPosInMs(soundId) + 50;
-	int32 width = 0, height = 0;
-
-	debug(6, "IMuseDigital::getCurVoiceLipSyncWidth(%d, %d)", soundId, msPos);
-	getLipSync(soundId, syncId, msPos, width, height);
-	return width;
-}
-
-int32 IMuseDigital::getCurMusicLipSyncHeight(int syncId) {
-	Common::StackLock lock(_mutex, "IMuseDigital::getCurMusicLipSyncHeight()");
-	int soundId = -1;
-
-	for (int l = 0; l < MAX_DIGITAL_TRACKS; l++) {
-		Track *track = _track[l];
-		if (track->used && !track->toBeRemoved && (track->volGroupId == IMUSE_VOLGRP_MUSIC)) {
-			soundId = track->soundId;
-		}
-	}
-
-	int32 msPos = getPosInMs(soundId) + 50;
-	int32 width = 0, height = 0;
-
-	debug(6, "IMuseDigital::getCurVoiceLipSyncHeight(%d, %d)", soundId, msPos);
-	getLipSync(soundId, syncId, msPos, width, height);
-	return height;
-}
-
-int32 IMuseDigital::getSoundElapsedTimeInMs(int soundId) {
-	Common::StackLock lock(_mutex, "IMuseDigital::getPosInMs()");
-	for (int l = 0; l < MAX_DIGITAL_TRACKS; l++) {
-		Track *track = _track[l];
-		if (track->used && !track->toBeRemoved && (track->soundId == soundId)) {
-			int32 pos = (_mixer->getSoundElapsedTime(track->mixChanHandle));
-			return pos;
-		}
-	}
-
-	return 0;
-}
-
-void IMuseDigital::stopAllSounds() {
-	Common::StackLock lock(_mutex, "IMuseDigital::stopAllSounds()");
-	debug(5, "IMuseDigital::stopAllSounds");
-
-	for (int l = 0; l < MAX_DIGITAL_TRACKS + MAX_DIGITAL_FADETRACKS; l++) {
-		Track *track = _track[l];
-		if (track->used) {
-			// Stop the sound output, *now*. No need to use toBeRemoved etc.
-			// as we are protected by a mutex, and this method is never called
-			// from IMuseDigital::callback either.
-			_mixer->stopHandle(track->mixChanHandle);
-			if (track->soundDesc) {
-				debug(5, "IMuseDigital::stopAllSounds - stopping sound(%d)", track->soundId);
-				_sound->closeSound(track->soundDesc);
-			}
-
-			// Mark the track as unused
-			track->reset();
-		}
-	}
-}
-
-void IMuseDigital::pause(bool p) {
-	_pause = p;
-}
-
-} // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_scripts.cpp b/engines/scumm/imuse_digi/dimuse_scripts.cpp
new file mode 100644
index 0000000000..a6411867b1
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_scripts.cpp
@@ -0,0 +1,909 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/imuse_digi/dimuse_engine.h"
+
+namespace Scumm {
+
+#define DIG_STATE_OFFSET 11
+#define DIG_SEQ_OFFSET (DIG_STATE_OFFSET + 65)
+#define COMI_STATE_OFFSET 3
+
+int IMuseDigital::scriptParse(int cmd, int a, int b) {
+	if (_scriptInitializedFlag || !cmd) {
+		switch (cmd) {
+		case 0:
+			if (_scriptInitializedFlag) {
+				debug(5, "IMuseDigital::scriptParse(): script module already initialized");
+				return -1;
+			} else {
+				_scriptInitializedFlag = 1;
+				return scriptInit();
+			}
+		case 1:
+			_scriptInitializedFlag = 0;
+			return scriptTerminate();
+		case 2: // script_save(a, b);
+		case 3: // script_restore(a);
+			break;
+		case 4:
+			scriptRefresh();
+			return 0;
+		case 5:
+			scriptSetState(a);
+			return 0;
+		case 6:
+			scriptSetSequence(a);
+			return 0;
+		case 7:
+			scriptSetCuePoint(a);
+			return 0;
+		case 8:
+			return scriptSetAttribute(a, b);
+		default:
+			debug(5, "IMuseDigital::scriptParse(): unrecognized opcode (%d)", cmd);
+			return -1;
+		}
+	} else {
+		debug(5, "IMuseDigital::scriptParse(): script module not initialized");
+		return -1;
+	}
+
+	return -1;
+}
+
+int IMuseDigital::scriptInit() {
+	_curMusicState = 0;
+	_curMusicSeq = 0;
+	_nextSeqToPlay = 0;
+	_curMusicCue = 0;
+	memset(_attributes, 0, sizeof(_attributes));
+	return 0;
+}
+
+int IMuseDigital::scriptTerminate() {
+	diMUSETerminate();
+
+	_curMusicState = 0;
+	_curMusicSeq = 0;
+	_nextSeqToPlay = 0;
+	_curMusicCue = 0;
+	memset(_attributes, 0, sizeof(_attributes));
+	return 0;
+}
+
+void IMuseDigital::scriptRefresh() {
+	int soundId;
+	int nextSound;
+
+	if (_stopSequenceFlag) {
+		scriptSetSequence(0);
+		_stopSequenceFlag = 0;
+	}
+
+	soundId = 0;
+
+	while (1) {
+		nextSound = diMUSEGetNextSound(soundId);
+		soundId = nextSound;
+
+		if (!nextSound)
+			break;
+
+		if (diMUSEGetParam(nextSound, DIMUSE_P_SND_HAS_STREAM) && diMUSEGetParam(soundId, DIMUSE_P_STREAM_BUFID) == DIMUSE_BUFFER_MUSIC) {
+			if (soundId)
+				return;
+			break;
+		}
+	}
+
+	if (_curMusicSeq)
+		scriptSetSequence(0);
+
+	flushTracks();
+}
+
+void IMuseDigital::scriptSetState(int soundId) {
+	if (_vm->_game.id == GID_DIG && !_isEarlyDiMUSE) {
+		setDigMusicState(soundId);
+	} else if (_vm->_game.id == GID_CMI) {
+		setComiMusicState(soundId);
+	} else {
+		setFtMusicState(soundId);
+	}
+}
+
+void IMuseDigital::scriptSetSequence(int soundId) {
+	if (_vm->_game.id == GID_DIG && !_isEarlyDiMUSE) {
+		setDigMusicSequence(soundId);
+	} else if (_vm->_game.id == GID_CMI) {
+		setComiMusicSequence(soundId);
+	} else {
+		setFtMusicSequence(soundId);
+	}
+}
+
+void IMuseDigital::scriptSetCuePoint(int cueId) {
+	if (!_isEarlyDiMUSE)
+		return;
+
+	if (cueId > 3)
+		return;
+
+	debug(5, "IMuseDigital::scriptSetCuePoint(): Cue point sequence: %d", cueId);
+
+	if (_curMusicSeq && _curMusicCue != cueId) {
+		if (cueId == 0)
+			playFtMusic(nullptr, 0, 0);
+		else {
+			int seq = ((_curMusicSeq - 1) * 4) + cueId;
+			playFtMusic(_ftSeqMusicTable[seq].audioName, _ftSeqMusicTable[seq].transitionType, _ftSeqMusicTable[seq].volume);
+		}
+	}
+
+	_curMusicCue = cueId;
+}
+
+int IMuseDigital::scriptSetAttribute(int attrIndex, int attrVal) {
+	// FT appears to set a single attribute to 1 at start-up and
+	// never use it again, so we currently ignore that behavior
+	if (_vm->_game.id == GID_DIG) {
+		_attributes[attrIndex] = attrVal;
+	}
+	return 0;
+}
+
+int IMuseDigital::scriptTriggerCallback(char *marker) {
+	if (marker[0] != '_') {
+		debug(5, "IMuseDigital::scriptTriggerCallback(): got marker != '_end', callback ignored");
+		return -1;
+	}
+	_stopSequenceFlag = 1;
+	return 0;
+}
+
+void Scumm::IMuseDigital::setFtMusicState(int stateId) {
+	if (stateId > 48)
+		return;
+
+	debug(5, "IMuseDigital::setFtMusicState(): State music: %s, %s", _ftStateMusicTable[stateId].name, _ftStateMusicTable[stateId].audioName);
+
+	if (_curMusicState == stateId)
+		return;
+
+	if (_curMusicSeq == 0) {
+		if (stateId == 0) {
+			playFtMusic(nullptr, 0, 0);
+		} else {
+			playFtMusic(_ftStateMusicTable[stateId].audioName, _ftStateMusicTable[stateId].transitionType, _ftStateMusicTable[stateId].volume);
+		}
+	}
+
+	_curMusicState = stateId;
+}
+
+void IMuseDigital::setFtMusicSequence(int seqId) {
+	if (seqId > 52)
+		return;
+
+	debug(5, "IMuseDigital::setFtMusicSequence(): Sequence music: %s", _ftSeqNames[seqId].name);
+
+	if (_curMusicSeq != seqId) {
+		if (seqId == 0) {
+			if (_curMusicState == 0) {
+				playFtMusic(nullptr, 0, 0);
+			} else {
+				playFtMusic(_ftStateMusicTable[_curMusicState].audioName, _ftStateMusicTable[_curMusicState].transitionType, _ftStateMusicTable[_curMusicState].volume);
+			}
+		} else {
+			int seq = (seqId - 1) * 4;
+			playFtMusic(_ftSeqMusicTable[seq].audioName, _ftSeqMusicTable[seq].transitionType, _ftSeqMusicTable[seq].volume);
+		}
+	}
+
+	_curMusicSeq = seqId;
+	_curMusicCue = 0;
+}
+
+
+void IMuseDigital::setDigMusicState(int stateId) {
+	int l, num = -1;
+
+	for (l = 0; _digStateMusicTable[l].soundId != -1; l++) {
+		if ((_digStateMusicTable[l].soundId == stateId)) {
+			debug(5, "IMuseDigital::setDigMusicState(): Set music state: %s, %s", _digStateMusicTable[l].name, _digStateMusicTable[l].filename);
+			num = l;
+			break;
+		}
+	}
+
+	if (num == -1) {
+		for (l = 0; _digStateMusicMap[l].roomId != -1; l++) {
+			if ((_digStateMusicMap[l].roomId == stateId)) {
+				break;
+			}
+		}
+		num = l;
+
+		int offset = _attributes[_digStateMusicMap[num].offset];
+		if (offset == 0) {
+			if (_attributes[_digStateMusicMap[num].attribPos] != 0) {
+				num = _digStateMusicMap[num].stateIndex3;
+			} else {
+				num = _digStateMusicMap[num].stateIndex1;
+			}
+		} else {
+			int stateIndex2 = _digStateMusicMap[num].stateIndex2;
+			if (stateIndex2 == 0) {
+				num = _digStateMusicMap[num].stateIndex1 + offset;
+			} else {
+				num = stateIndex2;
+			}
+		}
+	}
+
+	debug(5, "IMuseDigital::setDigMusicState(): Set music state: %s, %s", _digStateMusicTable[num].name, _digStateMusicTable[num].filename);
+
+	if (_curMusicState == num)
+		return;
+
+	if (_curMusicSeq == 0) {
+		if (num == 0)
+			playDigMusic(nullptr, &_digStateMusicTable[0], num, false);
+		else
+			playDigMusic(_digStateMusicTable[num].name, &_digStateMusicTable[num], num, false);
+	}
+
+	_curMusicState = num;
+}
+
+void IMuseDigital::setDigMusicSequence(int seqId) {
+	int l, num = -1;
+
+	if (seqId == 0)
+		seqId = 2000;
+
+	for (l = 0; _digSeqMusicTable[l].soundId != -1; l++) {
+		if ((_digSeqMusicTable[l].soundId == seqId)) {
+			debug(5, "IMuseDigital::setDigMusicSequence(): Set music sequence: %s, %s", _digSeqMusicTable[l].name, _digSeqMusicTable[l].filename);
+			num = l;
+			break;
+		}
+	}
+
+	if (num == -1)
+		return;
+
+	if (_curMusicSeq == num)
+		return;
+
+	if (num != 0) {
+		if (_curMusicSeq && ((_digSeqMusicTable[_curMusicSeq].transitionType == 4)
+			|| (_digSeqMusicTable[_curMusicSeq].transitionType == 6))) {
+			_nextSeqToPlay = num;
+			return;
+		} else {
+			playDigMusic(_digSeqMusicTable[num].name, &_digSeqMusicTable[num], 0, true);
+			_nextSeqToPlay = 0;
+			_attributes[DIG_SEQ_OFFSET + num] = 1;
+		}
+	} else {
+		if (_nextSeqToPlay != 0) {
+			playDigMusic(_digSeqMusicTable[_nextSeqToPlay].name, &_digSeqMusicTable[_nextSeqToPlay], 0, true);
+			_attributes[DIG_SEQ_OFFSET + _nextSeqToPlay] = 1;
+			num = _nextSeqToPlay;
+			_nextSeqToPlay = 0;
+		} else {
+			if (_curMusicState != 0) {
+				playDigMusic(_digStateMusicTable[_curMusicState].name, &_digStateMusicTable[_curMusicState], _curMusicState, true);
+			} else {
+				playDigMusic(nullptr, &_digStateMusicTable[0], _curMusicState, true);
+			}
+
+			num = 0;
+		}
+	}
+
+	_curMusicSeq = num;
+}
+
+void IMuseDigital::setComiMusicState(int stateId) {
+	int l, num = -1;
+
+	if (stateId == 0)
+		stateId = 1000;
+
+	if ((_vm->_game.features & GF_DEMO) && stateId == 1000)
+		stateId = 0;
+
+	if (!(_vm->_game.features & GF_DEMO)) {
+		for (l = 0; _comiStateMusicTable[l].soundId != -1; l++) {
+			if ((_comiStateMusicTable[l].soundId == stateId)) {
+				debug(5, "IMuseDigital::setComiMusicState(): Set music state: %s, %s", _comiStateMusicTable[l].name, _comiStateMusicTable[l].filename);
+				num = l;
+				break;
+			}
+		}
+	}
+
+	if (num == -1 && !(_vm->_game.features & GF_DEMO))
+		return;
+
+	if (!(_vm->_game.features & GF_DEMO) && _curMusicState == num) {
+		return;
+	} else if ((_vm->_game.features & GF_DEMO) && _curMusicState == stateId) {
+		return;
+	}
+
+	if (_curMusicSeq == 0) {
+		if (_vm->_game.features & GF_DEMO) {
+			if (_curMusicSeq == 0) {
+				if (stateId == 0)
+					playComiDemoMusic(nullptr, &_comiDemoStateMusicTable[0], stateId, false);
+				else
+					playComiDemoMusic(_comiDemoStateMusicTable[stateId].name, &_comiDemoStateMusicTable[stateId], stateId, false);
+			}
+		} else {
+			if (num == 0)
+				playComiMusic(nullptr, &_comiStateMusicTable[0], num, false);
+			else
+				playComiMusic(_comiStateMusicTable[num].name, &_comiStateMusicTable[num], num, false);
+		}
+	}
+
+	if (!(_vm->_game.features & GF_DEMO)) {
+		_curMusicState = num;
+	} else {
+		_curMusicState = stateId;
+	}
+}
+
+void IMuseDigital::setComiMusicSequence(int seqId) {
+	int l, num = -1;
+
+	if (seqId == 0)
+		seqId = 2000;
+
+	for (l = 0; _comiSeqMusicTable[l].soundId != -1; l++) {
+		if ((_comiSeqMusicTable[l].soundId == seqId)) {
+			debug(5, "IMuseDigital::setComiMusicSequence(): Set music sequence: %s, %s", _comiSeqMusicTable[l].name, _comiSeqMusicTable[l].filename);
+			num = l;
+			break;
+		}
+	}
+
+	if (num == -1)
+		return;
+
+	if (_curMusicSeq == num)
+		return;
+
+	if (num != 0) {
+		if (_curMusicSeq && ((_comiSeqMusicTable[_curMusicSeq].transitionType == 4)
+			|| (_comiSeqMusicTable[_curMusicSeq].transitionType == 6))) {
+			_nextSeqToPlay = num;
+			return;
+		} else {
+			playComiMusic(_comiSeqMusicTable[num].name, &_comiSeqMusicTable[num], 0, true);
+			_nextSeqToPlay = 0;
+		}
+	} else {
+		if (_nextSeqToPlay != 0) {
+			playComiMusic(_comiSeqMusicTable[_nextSeqToPlay].name, &_comiSeqMusicTable[_nextSeqToPlay], 0, true);
+			num = _nextSeqToPlay;
+			_nextSeqToPlay = 0;
+		} else {
+			if (_curMusicState != 0) {
+				playComiMusic(_comiStateMusicTable[_curMusicState].name, &_comiStateMusicTable[_curMusicState], _curMusicState, true);
+			} else {
+				playComiMusic(nullptr, &_comiStateMusicTable[0], _curMusicState, true);
+			}
+			num = 0;
+		}
+	}
+
+	_curMusicSeq = num;
+}
+
+void IMuseDigital::playFtMusic(const char *songName, int transitionType, int volume) {
+	int oldSoundId = 0;
+	int soundId;
+
+	// Check for any music piece which is played as a SFX (without an associated stream)
+	// and fade it out
+	for (int i = diMUSEGetNextSound(0); i; i = diMUSEGetNextSound(i)) {
+		if (diMUSEGetParam(i, DIMUSE_P_GROUP) == DIMUSE_GROUP_MUSICEFF && !diMUSEGetParam(i, DIMUSE_P_SND_HAS_STREAM))
+			diMUSEFadeParam(i, DIMUSE_P_VOLUME, 0, 200);
+	}
+
+	// Now grab the current standard music soundId: it will either be crossfaded by SwitchStream,
+	// or faded out
+	for (int j = diMUSEGetNextSound(0); j; j = diMUSEGetNextSound(j)) {
+		if (diMUSEGetParam(j, DIMUSE_P_GROUP) == DIMUSE_GROUP_MUSICEFF && diMUSEGetParam(j, DIMUSE_P_SND_HAS_STREAM))
+			oldSoundId = j;
+	}
+
+	if (songName) {
+		switch (transitionType) {
+		case 0:
+			debug(5, "IMuseDigital::playFtMusic(): NULL transition, ignored");
+			return;
+		case 1:
+			soundId = getSoundIdByName(songName);
+			_filesHandler->openSound(soundId);
+			if (!soundId) {
+				debug(5, "IMuseDigital::playFtMusic(): failed to retrieve soundId for sound \"%s\"", songName);
+				break;
+			}
+
+			if (diMUSEStartSound(soundId, 126))
+				debug(5, "IMuseDigital::playFtMusic(): transition 1, failed to start sound \"%s\"(%d)", songName, soundId);
+
+			_filesHandler->closeSound(soundId);
+			diMUSESetParam(soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+			diMUSESetParam(soundId, DIMUSE_P_VOLUME, volume);
+			break;
+		case 2:
+		case 3:
+			soundId = getSoundIdByName(songName);
+			if (soundId)
+				_filesHandler->openSound(soundId);
+
+			if (soundId) {
+				if (oldSoundId) {
+					if (oldSoundId != soundId || transitionType == 2) {
+						diMUSESwitchStream(oldSoundId, soundId, _ftCrossfadeBuffer, sizeof(_ftCrossfadeBuffer), 0);
+					}
+
+					// WORKAROUND for bug in the original: at the beginning of the game, going in
+					// and out of the bar a couple of times breaks and temporarily stops the music
+					// Here, we override the fade out, just like the remastered does
+					if (oldSoundId == soundId && soundId == 622) {
+						diMUSEFadeParam(soundId, DIMUSE_P_VOLUME, volume, 200);
+					}
+				} else if (diMUSEStartStream(soundId, 126, DIMUSE_BUFFER_MUSIC)) {
+					debug(5, "IMuseDigital::playFtMusic(): failed to start the stream for \"%s\" (%d)", songName, soundId);
+				}
+
+				_filesHandler->closeSound(soundId);
+				diMUSESetParam(soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+				diMUSESetParam(soundId, DIMUSE_P_VOLUME, volume);
+			} else {
+				debug(5, "IMuseDigital::playFtMusic(): failed to retrieve soundId for sound \"%s\" (%d)", songName, soundId);
+			}
+			break;
+		case 4:
+			if (oldSoundId)
+				diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 200);
+			return;
+		default:
+			debug(5, "IMuseDigital::playFtMusic(): bogus transition type, ignored");
+			return;
+		}
+	} else {
+		if (oldSoundId)
+			diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 200);
+	}
+}
+
+void IMuseDigital::playDigMusic(const char *songName, const imuseDigTable *table, int attribPos, bool sequence) {
+	int hookId = 0;
+
+	if (songName != nullptr) {
+		if ((_attributes[DIG_SEQ_OFFSET + 38]) && (!_attributes[DIG_SEQ_OFFSET + 41])) {
+			if ((attribPos == 43) || (attribPos == 44))
+				hookId = 3;
+		}
+
+		if ((_attributes[DIG_SEQ_OFFSET + 46] != 0) && (_attributes[DIG_SEQ_OFFSET + 48] == 0)) {
+			if ((attribPos == 38) || (attribPos == 39))
+				hookId = 3;
+		}
+
+		if ((_attributes[DIG_SEQ_OFFSET + 53] != 0)) {
+			if ((attribPos == 50) || (attribPos == 51))
+				hookId = 3;
+		}
+
+		if ((attribPos != 0) && (hookId == 0)) {
+			if (table->attribPos != 0)
+				attribPos = table->attribPos;
+			hookId = _attributes[DIG_STATE_OFFSET + attribPos];
+			if (table->hookId != 0) {
+				if ((hookId != 0) && (table->hookId > 1)) {
+					_attributes[DIG_STATE_OFFSET + attribPos] = 2;
+				} else {
+					_attributes[DIG_STATE_OFFSET + attribPos] = hookId + 1;
+					if (table->hookId < hookId + 1)
+						_attributes[DIG_STATE_OFFSET + attribPos] = 1;
+				}
+			}
+		}
+	}
+
+	int nextSoundId = 0;
+	while (1) {
+		nextSoundId = diMUSEGetNextSound(nextSoundId);
+		if (!nextSoundId)
+			break;
+
+		// If a sound is found (and its stream is active), fade it out if it's a music track
+		if (diMUSEGetParam(nextSoundId, DIMUSE_P_GROUP) == DIMUSE_GROUP_MUSICEFF && !diMUSEGetParam(nextSoundId, DIMUSE_P_SND_HAS_STREAM))
+			diMUSEFadeParam(nextSoundId, DIMUSE_P_VOLUME, 0, 120);
+	}
+
+	int oldSoundId = 0;
+	nextSoundId = 0;
+	while (1) {
+		nextSoundId = diMUSEGetNextSound(nextSoundId);
+		if (!nextSoundId)
+			break;
+
+		if (diMUSEGetParam(nextSoundId, DIMUSE_P_SND_HAS_STREAM) && (diMUSEGetParam(nextSoundId, DIMUSE_P_STREAM_BUFID) == DIMUSE_BUFFER_MUSIC)) {
+			oldSoundId = nextSoundId;
+			break;
+		}
+	}
+
+	if (!songName) {
+		if (oldSoundId)
+			diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 120);
+		return;
+	}
+
+	switch (table->transitionType) {
+	case 0:
+		debug(5, "IMuseDigital::playDigMusic(): NULL transition, ignored");
+		break;
+	case 1:
+		_filesHandler->openSound(table->soundId);
+		if (table->soundId) {
+			if (diMUSEStartSound(table->soundId, 126))
+				debug(5, "IMuseDigital::playDigMusic(): transition 1, failed to start the sound (%d)", table->soundId);
+			diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 1);
+			diMUSEFadeParam(table->soundId, DIMUSE_P_VOLUME, 127, 120);
+			_filesHandler->closeSound(table->soundId);
+			diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+		} else {
+			debug(5, "IMuseDigital::playDigMusic(): transition 1, empty soundId, ignored");
+		}
+
+		break;
+	case 2:
+	case 3:
+	case 4:
+		_filesHandler->openSound(table->soundId);
+		if (table->filename[0] == 0 || table->soundId == 0) {
+			if (oldSoundId)
+				diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 60);
+			return;
+		}
+
+		if (table->transitionType == 4) {
+			_stopSequenceFlag = 0;
+			diMUSESetTrigger(table->soundId, MKTAG('_', 'e', 'n', 'd'), 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1);
+		}
+
+		if (oldSoundId) {
+			if (table->transitionType == 2) {
+				diMUSESwitchStream(oldSoundId, table->soundId, 1800, 0, 0);
+				diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
+				diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+				diMUSESetHook(table->soundId, hookId);
+				diMUSEProcessStreams();
+				diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF); // Repeated intentionally
+				return;
+			}
+
+			if (oldSoundId != table->soundId) {
+				if ((!sequence) && (table->attribPos != 0) &&
+					(table->attribPos == _digStateMusicTable[_curMusicState].attribPos)) {
+					diMUSESwitchStream(oldSoundId, table->soundId, 1800, 0, 1);
+					diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
+					diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+					diMUSEProcessStreams();
+				} else {
+					diMUSESwitchStream(oldSoundId, table->soundId, 1800, 0, 0);
+					diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
+					diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+					diMUSESetHook(table->soundId, hookId);
+					diMUSEProcessStreams();
+					_filesHandler->closeSound(table->soundId);
+					diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF); // Repeated intentionally
+				}
+			}
+		} else {
+			if (diMUSEStartStream(table->soundId, 126, DIMUSE_BUFFER_MUSIC))
+				debug(5, "IMuseDigital::playDigMusic(): failed to start the stream for sound %d", table->soundId);
+			diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
+			diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+			diMUSESetHook(table->soundId, hookId);
+		}
+		_filesHandler->closeSound(table->soundId);
+		diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF); // Repeated intentionally
+		break;
+	case 5:
+		debug(5, "IMuseDigital::playDigMusic(): no-op transition type (5), ignored");
+		break;
+	case 6:
+		_stopSequenceFlag = 0;
+		diMUSESetTrigger(DIMUSE_SMUSH_SOUNDID + DIMUSE_BUFFER_MUSIC, MKTAG('_', 'e', 'n', 'd'), 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1);
+		break;
+	case 7:
+		if (oldSoundId)
+			diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 60);
+		break;
+	default:
+		debug(5, "IMuseDigital::playDigMusic(): bogus transition type, ignored");
+		break;
+	}
+}
+
+void IMuseDigital::playComiDemoMusic(const char *songName, const imuseComiTable *table, int attribPos, bool sequence) {
+	// This is a stripped down version of playDigMusic
+	int hookId = 0;
+
+	if (songName != nullptr) {
+		if (attribPos != 0) {
+			if (table->attribPos != 0)
+				attribPos = table->attribPos;
+		}
+	}
+
+	int nextSoundId = 0;
+	while (1) {
+		nextSoundId = diMUSEGetNextSound(nextSoundId);
+		if (!nextSoundId)
+			break;
+
+		// If a sound is found (and its stream is active), fade it out if it's a music track
+		if (diMUSEGetParam(nextSoundId, DIMUSE_P_GROUP) == DIMUSE_GROUP_MUSICEFF && !diMUSEGetParam(nextSoundId, DIMUSE_P_SND_HAS_STREAM))
+			diMUSEFadeParam(nextSoundId, DIMUSE_P_VOLUME, 0, 120);
+	}
+
+	int oldSoundId = 0;
+	nextSoundId = 0;
+	while (1) {
+		nextSoundId = diMUSEGetNextSound(nextSoundId);
+		if (!nextSoundId)
+			break;
+
+		if (diMUSEGetParam(nextSoundId, DIMUSE_P_SND_HAS_STREAM) && (diMUSEGetParam(nextSoundId, DIMUSE_P_STREAM_BUFID) == DIMUSE_BUFFER_MUSIC)) {
+			oldSoundId = nextSoundId;
+			break;
+		}
+	}
+
+	if (!songName) {
+		if (oldSoundId)
+			diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 120);
+		return;
+	}
+
+	switch (table->transitionType) {
+	case 3:
+		_filesHandler->openSound(table->soundId);
+		if (table->filename[0] == 0 || table->soundId == 0) {
+			if (oldSoundId)
+				diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 60);
+			return;
+		}
+
+		if (oldSoundId) {
+			if (oldSoundId != table->soundId) {
+				if ((!sequence) && (table->attribPos != 0) &&
+					(table->attribPos == _comiDemoStateMusicTable[_curMusicState].attribPos)) {
+					diMUSESwitchStream(oldSoundId, table->soundId, 1800, 0, 1);
+					diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
+					diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+					diMUSEProcessStreams();
+				} else {
+					diMUSESwitchStream(oldSoundId, table->soundId, 1800, 0, 0);
+					diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
+					diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+					diMUSESetHook(table->soundId, hookId);
+					diMUSEProcessStreams();
+					_filesHandler->closeSound(table->soundId);
+					diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF); // Repeated intentionally
+				}
+			}
+		} else {
+			if (diMUSEStartStream(table->soundId, 126, DIMUSE_BUFFER_MUSIC))
+				debug(5, "IMuseDigital::playComiDemoMusic(): failed to start the stream for sound %d", table->soundId);
+			diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
+			diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+			diMUSESetHook(table->soundId, hookId);
+		}
+		_filesHandler->closeSound(table->soundId);
+		diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF); // Repeated intentionally
+		break;
+	default:
+		debug(5, "IMuseDigital::playDigMusic(): bogus or unused transition type, ignored");
+		break;
+	}
+}
+
+void IMuseDigital::playComiMusic(const char *songName, const imuseComiTable *table, int attribPos, bool sequence) {
+	int hookId = 0;
+	int fadeDelay = 0;
+
+	if ((songName != nullptr) && (attribPos != 0)) {
+		if (table->attribPos != 0)
+			attribPos = table->attribPos;
+		hookId = _attributes[COMI_STATE_OFFSET + attribPos];
+
+		if (table->hookId != 0) {
+			if ((hookId != 0) && (table->hookId > 1)) {
+				_attributes[COMI_STATE_OFFSET + attribPos] = 2;
+			} else {
+				_attributes[COMI_STATE_OFFSET + attribPos] = hookId + 1;
+				if (table->hookId < hookId + 1)
+					_attributes[COMI_STATE_OFFSET + attribPos] = 1;
+			}
+		}
+	}
+
+	int nextSoundId = 0;
+	while (1) {
+		nextSoundId = diMUSEGetNextSound(nextSoundId);
+		if (!nextSoundId)
+			break;
+
+		// If a sound is found (and its stream is active), fade it out if it's a music track
+		if (diMUSEGetParam(nextSoundId, DIMUSE_P_GROUP) == DIMUSE_GROUP_MUSICEFF && !diMUSEGetParam(nextSoundId, DIMUSE_P_SND_HAS_STREAM))
+			diMUSEFadeParam(nextSoundId, DIMUSE_P_VOLUME, 0, 120);
+	}
+
+	int oldSoundId = 0;
+	nextSoundId = 0;
+	while (1) {
+		nextSoundId = diMUSEGetNextSound(nextSoundId);
+		if (!nextSoundId)
+			break;
+
+		if (diMUSEGetParam(nextSoundId, DIMUSE_P_SND_HAS_STREAM) && (diMUSEGetParam(nextSoundId, DIMUSE_P_STREAM_BUFID) == DIMUSE_BUFFER_MUSIC)) {
+			oldSoundId = nextSoundId;
+			break;
+		}
+	}
+
+	if (!songName) {
+		if (oldSoundId)
+			diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 120);
+		return;
+	}
+
+	switch (table->transitionType) {
+	case 0:
+		debug(5, "IMuseDigital::playComiMusic(): NULL transition, ignored");
+		break;
+	case 1:
+		_filesHandler->openSound(table->soundId);
+		if (table->soundId) {
+			if (diMUSEStartSound(table->soundId, 126))
+				debug(5, "IMuseDigital::playComiMusic(): transition 1, failed to start the sound (%d)", table->soundId);
+			diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 1);
+			diMUSEFadeParam(table->soundId, DIMUSE_P_VOLUME, 127, 120);
+			_filesHandler->closeSound(table->soundId);
+			diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+		} else {
+			debug(5, "IMuseDigital::playComiMusic(): transition 1, empty soundId, ignored");
+		}
+		break;
+	case 2:
+	case 3:
+	case 4:
+	case 12:
+		_filesHandler->openSound(table->soundId);
+		if (table->filename[0] == 0 || table->soundId == 0) {
+			if (oldSoundId)
+				diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 60);
+			break;
+		}
+
+		if (table->transitionType == 4) {
+			_stopSequenceFlag = 0;
+			diMUSESetTrigger(table->soundId, MKTAG('_', 'e', 'n', 'd'), 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1);
+		}
+
+		if (oldSoundId) {
+			fadeDelay = table->fadeOutDelay;
+			if (!fadeDelay)
+				fadeDelay = 1000;
+			else
+				fadeDelay = (fadeDelay * 100) / 6; // Set dimuse_table fade out time to millisecond scale
+
+			if (table->transitionType == 2) {
+				diMUSESwitchStream(oldSoundId, table->soundId, fadeDelay, 0, 0);
+				diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
+				diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+				diMUSESetHook(table->soundId, table->hookId);
+				diMUSEProcessStreams();
+			} else if (oldSoundId != table->soundId) {
+				if ((!sequence) && (table->attribPos != 0) &&
+					(table->attribPos == _comiStateMusicTable[_curMusicState].attribPos)) {
+
+					debug(5, "IMuseDigital::playComiMusic(): Starting new sound (%s) with same attribute as old sound (%s)",
+						table->name, _comiStateMusicTable[_curMusicState].name);
+
+					diMUSESwitchStream(oldSoundId, table->soundId, fadeDelay, 0, 1);
+					diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
+					diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+					diMUSEProcessStreams();
+				} else {
+					switch (table->transitionType) {
+					case 12:
+						diMUSESetHook(oldSoundId, table->hookId);
+						diMUSESetTrigger(oldSoundId, MKTAG('e', 'x', 'i', 't'), 26, oldSoundId, table->soundId, fadeDelay, 1, 0, -1, -1, -1, -1, -1, -1);
+						diMUSESetTrigger(oldSoundId, MKTAG('e', 'x', 'i', 't'), 12, table->soundId, DIMUSE_P_VOLUME, 127, -1, -1, -1, -1, -1, -1, -1, -1);
+						diMUSESetTrigger(oldSoundId, MKTAG('e', 'x', 'i', 't'), 12, table->soundId, DIMUSE_P_GROUP, 4, -1, -1, -1, -1, -1, -1, -1, -1);
+						diMUSESetTrigger(oldSoundId, MKTAG('e', 'x', 'i', 't'), 15, table->soundId, hookId, -1, -1, -1, -1, -1, -1, -1, -1, -1);
+						diMUSEProcessStreams();
+						break;
+					default:
+						diMUSESwitchStream(oldSoundId, table->soundId, fadeDelay, 0, 0);
+						diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
+						diMUSESetParam(table->soundId, DIMUSE_P_GROUP, 4);
+						diMUSESetHook(table->soundId, hookId);
+						diMUSEProcessStreams();
+						break;
+					}
+				}
+			}
+		} else {
+			if (diMUSEStartStream(table->soundId, 126, DIMUSE_BUFFER_MUSIC))
+				debug(5, "IMuseDigital::playComiMusic(): failed to start the stream for sound %d", table->soundId);
+			diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
+			diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+			diMUSESetHook(table->soundId, hookId);
+		}
+		_filesHandler->closeSound(table->soundId);
+		diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
+		break;
+	case 5:
+		debug(5, "IMuseDigital::playComiMusic(): no-op transition type (5), ignored");
+		break;
+	case 6:
+		_stopSequenceFlag = 0;
+		diMUSESetTrigger(DIMUSE_SMUSH_SOUNDID + DIMUSE_BUFFER_MUSIC, MKTAG('_', 'e', 'n', 'd'), 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1);
+		break;
+	case 7:
+		if (oldSoundId)
+			diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 60);
+		break;
+	case 8:
+		if (oldSoundId)
+			diMUSESetHook(oldSoundId, table->hookId);
+		break;
+	case 9:
+		if (oldSoundId)
+			diMUSESetHook(oldSoundId, table->hookId);
+		_stopSequenceFlag = 0;
+		diMUSESetTrigger(oldSoundId, MKTAG('_', 'e', 'n', 'd'), 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1);
+		break;
+	default:
+		debug(5, "IMuseDigital::playComiMusic(): bogus transition type, ignored");
+		break;
+	}
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_sndmgr.cpp b/engines/scumm/imuse_digi/dimuse_sndmgr.cpp
index d3f00e01ee..976c33c24d 100644
--- a/engines/scumm/imuse_digi/dimuse_sndmgr.cpp
+++ b/engines/scumm/imuse_digi/dimuse_sndmgr.cpp
@@ -57,260 +57,11 @@ ImuseDigiSndMgr::~ImuseDigiSndMgr() {
 	BundleCodecs::releaseImcTables();
 }
 
-void ImuseDigiSndMgr::countElements(byte *ptr, int &numRegions, int &numJumps, int &numSyncs, int &numMarkers) {
-	uint32 tag;
-	int32 size = 0;
-
-	do {
-		tag = READ_BE_UINT32(ptr); ptr += 4;
-		switch (tag) {
-		case MKTAG('S','T','O','P'):
-		case MKTAG('F','R','M','T'):
-		case MKTAG('D','A','T','A'):
-			size = READ_BE_UINT32(ptr); ptr += size + 4;
-			break;
-		case MKTAG('T','E','X','T'):
-			if (!scumm_stricmp((const char *)(ptr + 8), "exit") || _vm->_game.id == GID_CMI)
-				numMarkers++;
-			size = READ_BE_UINT32(ptr); ptr += size + 4;
-			break;
-		case MKTAG('R','E','G','N'):
-			numRegions++;
-			size = READ_BE_UINT32(ptr); ptr += size + 4;
-			break;
-		case MKTAG('J','U','M','P'):
-			numJumps++;
-			size = READ_BE_UINT32(ptr); ptr += size + 4;
-			break;
-		case MKTAG('S','Y','N','C'):
-			numSyncs++;
-			size = READ_BE_UINT32(ptr); ptr += size + 4;
-			break;
-		default:
-			error("ImuseDigiSndMgr::countElements() Unknown sfx header '%s'", tag2str(tag));
-		}
-	} while (tag != MKTAG('D','A','T','A'));
-}
-
-void ImuseDigiSndMgr::prepareSoundFromRMAP(Common::SeekableReadStream *file, SoundDesc *sound, int32 offset, int32 size) {
-	int l;
-
-	file->seek(offset, SEEK_SET);
-	uint32 tag = file->readUint32BE();
-	assert(tag == MKTAG('R','M','A','P'));
-	int32 version = file->readUint32BE();
-	if (version != 3) {
-		if (version == 2) {
-			warning("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version of compressed *.bun file, expected 3, but it's 2");
-			warning("Suggested to recompress with latest tool from daily builds");
-		} else
-			error("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version number, expected 3, but it's: %d", version);
-	}
-	sound->bits = file->readUint32BE();
-	sound->freq = file->readUint32BE();
-	sound->channels = file->readUint32BE();
-	sound->numRegions = file->readUint32BE();
-	sound->numJumps = file->readUint32BE();
-	sound->numSyncs = file->readUint32BE();
-	if (version >= 3)
-		sound->numMarkers = file->readUint32BE();
-	else
-		sound->numMarkers = 0;
-
-	sound->region = new Region[sound->numRegions];
-	assert(sound->region);
-	sound->jump = new Jump[sound->numJumps];
-	assert(sound->jump);
-	sound->sync = new Sync[sound->numSyncs];
-	assert(sound->sync);
-	sound->marker = new Marker[sound->numMarkers];
-	assert(sound->marker);
-
-	for (l = 0; l < sound->numRegions; l++) {
-		sound->region[l].offset = file->readUint32BE();
-		sound->region[l].length = file->readUint32BE();
-	}
-	for (l = 0; l < sound->numJumps; l++) {
-		sound->jump[l].offset = file->readUint32BE();
-		sound->jump[l].dest = file->readUint32BE();
-		sound->jump[l].hookId = file->readUint32BE();
-		sound->jump[l].fadeDelay = file->readUint32BE();
-	}
-	for (l = 0; l < sound->numSyncs; l++) {
-		sound->sync[l].size = file->readUint32BE();
-		sound->sync[l].ptr = new byte[sound->sync[l].size];
-		file->read(sound->sync[l].ptr, sound->sync[l].size);
-	}
-	if (version >= 3) {
-		for (l = 0; l < sound->numMarkers; l++) {
-			sound->marker[l].pos = file->readUint32BE();
-			sound->marker[l].length = file->readUint32BE();
-			sound->marker[l].ptr = new char[sound->marker[l].length];
-			file->read(sound->marker[l].ptr, sound->marker[l].length);
-		}
-	}
-}
-
-void ImuseDigiSndMgr::prepareSound(byte *ptr, SoundDesc *sound, bool rawBundle) {
-	if (READ_BE_UINT32(ptr) == MKTAG('C','r','e','a')) {
-		bool quit = false;
-		int len;
-
-		int32 offset = READ_LE_UINT16(ptr + 20);
-		int16 code = READ_LE_UINT16(ptr + 24);
-
-		sound->numRegions = 0;
-		sound->region = new Region[70];
-		assert(sound->region);
-
-		sound->numJumps = 0;
-		sound->jump = new Jump[1];
-		assert(sound->jump);
-
-		sound->numSyncs = 0;
-
-		sound->resPtr = ptr;
-		sound->bits = 8;
-		sound->channels = 1;
-
-		while (!quit) {
-			len = READ_LE_UINT32(ptr + offset);
-			code = len & 0xFF;
-			if ((code != 0) && (code != 1) && (code != 6) && (code != 7)) {
-				// try again with 2 bytes forward (workaround for some FT sounds (ex.362, 363)
-				offset += 2;
-				len = READ_LE_UINT32(ptr + offset);
-				code = len & 0xFF;
-				if ((code != 0) && (code != 1) && (code != 6) && (code != 7)) {
-					error("Invalid code in VOC file : %d", code);
-				}
-			}
-			offset += 4;
-			len >>= 8;
-			switch (code) {
-			case 0:
-				quit = true;
-				break;
-			case 1:
-				{
-					int time_constant = ptr[offset];
-					offset += 2;
-					len -= 2;
-					sound->freq = Audio::getSampleRateFromVOCRate(time_constant);
-					sound->region[sound->numRegions].offset = offset;
-					sound->region[sound->numRegions].length = len;
-					sound->numRegions++;
-				}
-				break;
-			case 6:	// begin of loop
-				sound->jump[0].dest = offset + 8;
-				sound->jump[0].hookId = 0;
-				sound->jump[0].fadeDelay = 0;
-				break;
-			case 7:	// end of loop
-				sound->jump[0].offset = offset - 4;
-				sound->numJumps++;
-				sound->region[sound->numRegions].offset = offset - 4;
-				sound->region[sound->numRegions].length = 0;
-				sound->numRegions++;
-				break;
-			default:
-				error("Invalid code in VOC file : %d", code);
-				quit = true;
-				break;
-			}
-			offset += len;
-		}
-	} else if (READ_BE_UINT32(ptr) == MKTAG('i','M','U','S')) {
-		uint32 tag;
-		int32 size = 0;
-		byte *s_ptr = ptr;
-		ptr += 16;
-
-		int curIndexRegion = 0;
-		int curIndexJump = 0;
-		int curIndexSync = 0;
-		int curIndexMarker = 0;
-
-		sound->numRegions = 0;
-		sound->numJumps = 0;
-		sound->numSyncs = 0;
-		sound->numMarkers = 0;
-		countElements(ptr, sound->numRegions, sound->numJumps, sound->numSyncs, sound->numMarkers);
-		sound->region = new Region[sound->numRegions];
-		assert(sound->region);
-		sound->jump = new Jump[sound->numJumps];
-		assert(sound->jump);
-		sound->sync = new Sync[sound->numSyncs];
-		assert(sound->sync);
-		sound->marker = new Marker[sound->numMarkers];
-		assert(sound->marker);
-
-		do {
-			tag = READ_BE_UINT32(ptr); ptr += 4;
-			switch (tag) {
-			case MKTAG('F','R','M','T'):
-				ptr += 12;
-				sound->littleEndian = READ_BE_UINT32(ptr) == 16 && rawBundle;
-
-				sound->bits = READ_BE_UINT32(ptr); ptr += 4;
-				sound->freq = READ_BE_UINT32(ptr); ptr += 4;
-				sound->channels = READ_BE_UINT32(ptr); ptr += 4;
-				break;
-			case MKTAG('T','E','X','T'):
-				if (!scumm_stricmp((const char *)(ptr + 8), "exit") || _vm->_game.id == GID_CMI) {
-					sound->marker[curIndexMarker].pos = READ_BE_UINT32(ptr + 4);
-					sound->marker[curIndexMarker].length = strlen((const char *)(ptr + 8)) + 1;
-					sound->marker[curIndexMarker].ptr = new char[sound->marker[curIndexMarker].length];
-					assert(sound->marker[curIndexMarker].ptr);
-					strcpy(sound->marker[curIndexMarker].ptr, (const char *)(ptr + 8));
-					curIndexMarker++;
-				}
-				size = READ_BE_UINT32(ptr); ptr += size + 4;
-				break;
-			case MKTAG('S','T','O','P'):
-				size = READ_BE_UINT32(ptr); ptr += size + 4;
-				break;
-			case MKTAG('R','E','G','N'):
-				ptr += 4;
-				sound->region[curIndexRegion].offset = READ_BE_UINT32(ptr); ptr += 4;
-				sound->region[curIndexRegion].length = READ_BE_UINT32(ptr); ptr += 4;
-				curIndexRegion++;
-				break;
-			case MKTAG('J','U','M','P'):
-				ptr += 4;
-				sound->jump[curIndexJump].offset = READ_BE_UINT32(ptr); ptr += 4;
-				sound->jump[curIndexJump].dest = READ_BE_UINT32(ptr); ptr += 4;
-				sound->jump[curIndexJump].hookId = READ_BE_UINT32(ptr); ptr += 4;
-				sound->jump[curIndexJump].fadeDelay = READ_BE_UINT32(ptr); ptr += 4;
-				curIndexJump++;
-				break;
-			case MKTAG('S','Y','N','C'):
-				size = READ_BE_UINT32(ptr); ptr += 4;
-				sound->sync[curIndexSync].size = size;
-				sound->sync[curIndexSync].ptr = new byte[size];
-				assert(sound->sync[curIndexSync].ptr);
-				memcpy(sound->sync[curIndexSync].ptr, ptr, size);
-				curIndexSync++;
-				ptr += size;
-				break;
-			case MKTAG('D','A','T','A'):
-				ptr += 4;
-				break;
-			default:
-				error("ImuseDigiSndMgr::prepareSound(%d/%s) Unknown sfx header '%s'", sound->soundId, sound->name, tag2str(tag));
-			}
-		} while (tag != MKTAG('D','A','T','A'));
-		sound->offsetData = ptr - s_ptr;
-	} else {
-		error("ImuseDigiSndMgr::prepareSound(): Unknown sound format");
-	}
-}
-
 ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::allocSlot() {
 	for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
 		if (!_sounds[l].inUse) {
 			_sounds[l].inUse = true;
+			_sounds[l].scheduledForDealloc = false;
 			return &_sounds[l];
 		}
 	}
@@ -320,31 +71,32 @@ ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::allocSlot() {
 
 bool ImuseDigiSndMgr::openMusicBundle(SoundDesc *sound, int &disk) {
 	bool result = false;
+	bool compressed = false;
 
 	sound->bundle = new BundleMgr(_cacheBundleDir);
 	assert(sound->bundle);
 	if (_vm->_game.id == GID_CMI) {
 		if (_vm->_game.features & GF_DEMO) {
-			result = sound->bundle->open("music.bun", sound->compressed);
+			result = sound->bundle->open("music.bun", compressed);
 		} else {
 			char musicfile[20];
 			if (disk == -1)
 				disk = _vm->VAR(_vm->VAR_CURRENTDISK);
 			sprintf(musicfile, "musdisk%d.bun", disk);
 //			if (_disk != _vm->VAR(_vm->VAR_CURRENTDISK)) {
-//				_vm->_imuseDigital->parseScriptCmds(0x1000, 0, 0, 0, 0, 0, 0, 0);
-//				_vm->_imuseDigital->parseScriptCmds(0x2000, 0, 0, 0, 0, 0, 0, 0);
-//				_vm->_imuseDigital->stopAllSounds();
+//				_vm->_DiMUSE_v1->parseScriptCmds(0x1000, 0, 0, 0, 0, 0, 0, 0);
+//				_vm->_DiMUSE_v1->parseScriptCmds(0x2000, 0, 0, 0, 0, 0, 0, 0);
+//				_vm->_DiMUSE_v1->stopAllSounds();
 //				sound->bundle->closeFile();
 //			}
 
-			result = sound->bundle->open(musicfile, sound->compressed, true);
+			result = sound->bundle->open(musicfile, compressed);
 
 			// FIXME: Shouldn't we only set _disk if result == true?
 			_disk = (byte)_vm->VAR(_vm->VAR_CURRENTDISK);
 		}
 	} else if (_vm->_game.id == GID_DIG)
-		result = sound->bundle->open("digmusic.bun", sound->compressed, true);
+		result = sound->bundle->open("digmusic.bun", compressed);
 	else
 		error("ImuseDigiSndMgr::openMusicBundle() Don't know which bundle file to load");
 
@@ -355,31 +107,32 @@ bool ImuseDigiSndMgr::openMusicBundle(SoundDesc *sound, int &disk) {
 
 bool ImuseDigiSndMgr::openVoiceBundle(SoundDesc *sound, int &disk) {
 	bool result = false;
+	bool compressed = false;
 
 	sound->bundle = new BundleMgr(_cacheBundleDir);
 	assert(sound->bundle);
 	if (_vm->_game.id == GID_CMI) {
 		if (_vm->_game.features & GF_DEMO) {
-			result = sound->bundle->open("voice.bun", sound->compressed);
+			result = sound->bundle->open("voice.bun", compressed);
 		} else {
 			char voxfile[20];
 			if (disk == -1)
 				disk = _vm->VAR(_vm->VAR_CURRENTDISK);
 			sprintf(voxfile, "voxdisk%d.bun", disk);
 //			if (_disk != _vm->VAR(_vm->VAR_CURRENTDISK)) {
-//				_vm->_imuseDigital->parseScriptCmds(0x1000, 0, 0, 0, 0, 0, 0, 0);
-//				_vm->_imuseDigital->parseScriptCmds(0x2000, 0, 0, 0, 0, 0, 0, 0);
-//				_vm->_imuseDigital->stopAllSounds();
+//				_vm->_DiMUSE_v1->parseScriptCmds(0x1000, 0, 0, 0, 0, 0, 0, 0);
+//				_vm->_DiMUSE_v1->parseScriptCmds(0x2000, 0, 0, 0, 0, 0, 0, 0);
+//				_vm->_DiMUSE_v1->stopAllSounds();
 //				sound->bundle->closeFile();
 //			}
 
-			result = sound->bundle->open(voxfile, sound->compressed);
+			result = sound->bundle->open(voxfile, compressed);
 
 			// FIXME: Shouldn't we only set _disk if result == true?
 			_disk = (byte)_vm->VAR(_vm->VAR_CURRENTDISK);
 		}
 	} else if (_vm->_game.id == GID_DIG)
-		result = sound->bundle->open("digvoice.bun", sound->compressed);
+		result = sound->bundle->open("digvoice.bun", compressed);
 	else
 		error("ImuseDigiSndMgr::openVoiceBundle() Don't know which bundle file to load");
 
@@ -391,7 +144,6 @@ bool ImuseDigiSndMgr::openVoiceBundle(SoundDesc *sound, int &disk) {
 ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::openSound(int32 soundId, const char *soundName, int soundType, int volGroupId, int disk) {
 	assert(soundId >= 0);
 	assert(soundType);
-	bool rawBundle = false;
 
 	SoundDesc *sound = allocSlot();
 	if (!sound) {
@@ -406,7 +158,6 @@ ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::openSound(int32 soundId, const char
 	case IMUSE_RESOURCE:
 		assert(soundName[0] == 0);	// Paranoia check
 
-		_vm->ensureResourceLoaded(rtSound, soundId);
 		_vm->_res->lock(rtSound, soundId);
 		ptr = _vm->getResourceAddress(rtSound, soundId);
 		if (ptr == nullptr) {
@@ -414,6 +165,7 @@ ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::openSound(int32 soundId, const char
 			return nullptr;
 		}
 		sound->resPtr = ptr;
+		sound->resSize = _vm->getResourceSize(rtSound, soundId) - 8;
 		break;
 	case IMUSE_BUNDLE:
 		if (volGroupId == IMUSE_VOLGRP_VOICE)
@@ -426,35 +178,15 @@ ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::openSound(int32 soundId, const char
 			closeSound(sound);
 			return nullptr;
 		}
-		if (sound->compressed) {
-			char fileName[24];
-			int32 offset = 0, size = 0;
-			sprintf(fileName, "%s.map", soundName);
-			Common::SeekableReadStream *rmapFile = sound->bundle->getFile(fileName, offset, size);
-			if (!rmapFile) {
-				closeSound(sound);
-				return nullptr;
-			}
-			prepareSoundFromRMAP(rmapFile, sound, offset, size);
-			strcpy(sound->name, soundName);
-			sound->soundId = soundId;
-			sound->type = soundType;
-			sound->volGroupId = volGroupId;
-			sound->disk = disk;
-			return sound;
-		} else if (soundName[0] == 0) {
-			if (sound->bundle->decompressSampleByIndex(soundId, 0, 0x2000, &ptr, 0, header_outside, rawBundle) == 0 || ptr == nullptr) {
-				closeSound(sound);
-				free(ptr);
-				return nullptr;
-			}
-		} else {
-			if (sound->bundle->decompressSampleByName(soundName, 0, 0x2000, &ptr, header_outside, rawBundle) == 0 || ptr == nullptr) {
+
+		if (soundName[0] != 0) {
+			if (sound->bundle->readFile(soundName, 0x2000, &ptr, header_outside) == 0 || ptr == nullptr) {
 				closeSound(sound);
 				free(ptr);
 				return nullptr;
 			}
 		}
+
 		sound->resPtr = nullptr;
 		break;
 	default:
@@ -463,11 +195,8 @@ ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::openSound(int32 soundId, const char
 
 	strcpy(sound->name, soundName);
 	sound->soundId = soundId;
-	sound->type = soundType;
-	sound->volGroupId = volGroupId;
-	sound->disk = _disk;
-	prepareSound(ptr, sound, rawBundle);
-	if ((soundType == IMUSE_BUNDLE) && !sound->compressed) {
+
+	if (soundType == IMUSE_BUNDLE) {
 		free(ptr);
 	}
 	return sound;
@@ -486,278 +215,68 @@ void ImuseDigiSndMgr::closeSound(SoundDesc *soundDesc) {
 			_vm->_res->unlock(rtSound, soundDesc->soundId);
 	}
 
-	delete soundDesc->compressedStream;
 	delete soundDesc->bundle;
 
-	for (int r = 0; r < soundDesc->numSyncs; r++)
-		delete[] soundDesc->sync[r].ptr;
-	for (int r = 0; r < soundDesc->numMarkers; r++)
-		delete[] soundDesc->marker[r].ptr;
-	delete[] soundDesc->region;
-	delete[] soundDesc->jump;
-	delete[] soundDesc->sync;
-	delete[] soundDesc->marker;
 	memset(soundDesc, 0, sizeof(SoundDesc));
 }
 
-ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::cloneSound(SoundDesc *soundDesc) {
-	ImuseDigiSndMgr::SoundDesc *desc;
-	assert(checkForProperHandle(soundDesc));
-
-	desc = openSound(soundDesc->soundId, soundDesc->name, soundDesc->type, soundDesc->volGroupId, soundDesc->disk);
-	if (!desc)
-		desc = openSound(soundDesc->soundId, soundDesc->name, soundDesc->type, soundDesc->volGroupId, 1);
-	if (!desc)
-		desc = openSound(soundDesc->soundId, soundDesc->name, soundDesc->type, soundDesc->volGroupId, 2);
-	return desc;
-}
-
-bool ImuseDigiSndMgr::checkForProperHandle(SoundDesc *soundDesc) {
-	if (!soundDesc)
-		return false;
-	for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
-		if (soundDesc == &_sounds[l])
-			return true;
+ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::findSoundById(int soundId) {
+	SoundDesc *soundDesc = nullptr;
+	for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
+		if (_sounds[i].soundId == soundId) {
+			soundDesc = &_sounds[i];
+			break;
+		}
 	}
-	return false;
+	return soundDesc;
 }
 
-bool ImuseDigiSndMgr::isSndDataExtComp(SoundDesc *soundDesc) {
-	assert(checkForProperHandle(soundDesc));
-	return soundDesc->compressed;
+ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::getSounds() {
+	return _sounds;
 }
 
-int ImuseDigiSndMgr::getFreq(SoundDesc *soundDesc) {
-	assert(checkForProperHandle(soundDesc));
-	return soundDesc->freq;
-}
-
-int ImuseDigiSndMgr::getBits(SoundDesc *soundDesc) {
-	assert(checkForProperHandle(soundDesc));
-	return soundDesc->bits;
-}
-
-int ImuseDigiSndMgr::getChannels(SoundDesc *soundDesc) {
-	assert(checkForProperHandle(soundDesc));
-	return soundDesc->channels;
-}
-
-bool ImuseDigiSndMgr::isEndOfRegion(SoundDesc *soundDesc, int region) {
-	assert(checkForProperHandle(soundDesc));
-	assert(region >= 0 && region < soundDesc->numRegions);
-	return soundDesc->endFlag;
-}
-
-int ImuseDigiSndMgr::getNumRegions(SoundDesc *soundDesc) {
-	assert(checkForProperHandle(soundDesc));
-	return soundDesc->numRegions;
-}
-
-int ImuseDigiSndMgr::getNumJumps(SoundDesc *soundDesc) {
-	assert(checkForProperHandle(soundDesc));
-	return soundDesc->numJumps;
-}
-
-int ImuseDigiSndMgr::getRegionOffset(SoundDesc *soundDesc, int region) {
-	debug(5, "getRegionOffset() region:%d", region);
-	assert(checkForProperHandle(soundDesc));
-	assert(region >= 0 && region < soundDesc->numRegions);
-	return soundDesc->region[region].offset;
-}
-
-int ImuseDigiSndMgr::getJumpIdByRegionAndHookId(SoundDesc *soundDesc, int region, int hookId) {
-	debug(5, "getJumpIdByRegionAndHookId() region:%d, hookId:%d", region, hookId);
-	assert(checkForProperHandle(soundDesc));
-	assert(region >= 0 && region < soundDesc->numRegions);
-	int32 offset = soundDesc->region[region].offset;
-	int jumpIdCandidate = -1;
-	for (int l = 0; l < soundDesc->numJumps; l++) {
-		if (offset == soundDesc->jump[l].offset) {
-			jumpIdCandidate = l;
-			if (soundDesc->jump[l].hookId == hookId)
-				return l;
+void ImuseDigiSndMgr::scheduleSoundForDeallocation(int soundId) {
+	SoundDesc *soundDesc = nullptr;
+	for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
+		if (_sounds[i].soundId == soundId) {
+			soundDesc = &_sounds[i];
 		}
 	}
-	// We missed the jump because we didn't have the right hookId...
-	// ...but if that jump led to a region with a "start" marker we have to enforce it anyway!
-	// This fixes edge-cases (like bug #11956) where a different hookId than the one expected prevents us
-	// from playing the music track from the "start" marker, effectively playing also the
-	// silence (or ambient noise) which precedes it.
-	if (_vm->_game.id == GID_CMI)
-		if (jumpIdCandidate != -1) {
-			offset = soundDesc->jump[jumpIdCandidate].dest;
-			// Element 2 in marker[] should always be "start"
-			if (offset == soundDesc->marker[2].pos && !scumm_stricmp(soundDesc->marker[2].ptr, "start"))
-				return jumpIdCandidate;
-		}
 
-	return -1;
-}
-
-bool ImuseDigiSndMgr::checkForTriggerByRegionAndMarker(SoundDesc *soundDesc, int region, const char *marker) {
-	debug(5, "checkForTriggerByRegionAndMarker() region:%d, marker:%s", region, marker);
 	assert(checkForProperHandle(soundDesc));
-	assert(region >= 0 && region < soundDesc->numRegions);
-	assert(marker);
-	int32 offset = soundDesc->region[region].offset;
-	for (int l = 0; l < soundDesc->numMarkers; l++) {
-		if (offset == soundDesc->marker[l].pos) {
-			if (!scumm_stricmp(soundDesc->marker[l].ptr, marker))
-				return true;
-		}
-	}
-
-	return false;
-}
 
-void ImuseDigiSndMgr::getSyncSizeAndPtrById(SoundDesc *soundDesc, int number, int32 &sync_size, byte **sync_ptr) {
-	assert(checkForProperHandle(soundDesc));
-	assert(number >= 0);
-	if (number < soundDesc->numSyncs) {
-		sync_size = soundDesc->sync[number].size;
-		*sync_ptr = soundDesc->sync[number].ptr;
-	} else {
-		sync_size = 0;
-		*sync_ptr = nullptr;
-	}
+	soundDesc->scheduledForDealloc = true;
 }
 
-int ImuseDigiSndMgr::getRegionIdByJumpId(SoundDesc *soundDesc, int jumpId) {
-	debug(5, "getRegionIdByJumpId() jumpId:%d", jumpId);
-	assert(checkForProperHandle(soundDesc));
-	assert(jumpId >= 0 && jumpId < soundDesc->numJumps);
-	int32 dest = soundDesc->jump[jumpId].dest;
-	for (int l = 0; l < soundDesc->numRegions; l++) {
-		if (dest == soundDesc->region[l].offset) {
-			return l;
+void ImuseDigiSndMgr::closeSoundById(int soundId) {
+	SoundDesc *soundDesc = nullptr;
+	for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
+		if (_sounds[i].soundId == soundId) {
+			soundDesc = &_sounds[i];
 		}
 	}
 
-	return -1;
-}
-
-int ImuseDigiSndMgr::getJumpHookId(SoundDesc *soundDesc, int number) {
-	debug(5, "getJumpHookId() number:%d", number);
-	assert(checkForProperHandle(soundDesc));
-	assert(number >= 0 && number < soundDesc->numJumps);
-	return soundDesc->jump[number].hookId;
-}
+	if (soundDesc) {
+		assert(checkForProperHandle(soundDesc));
 
-int ImuseDigiSndMgr::getJumpFade(SoundDesc *soundDesc, int number) {
-	debug(5, "getJumpFade() number:%d", number);
-	assert(checkForProperHandle(soundDesc));
-	assert(number >= 0 && number < soundDesc->numJumps);
-	return soundDesc->jump[number].fadeDelay;
-}
+		if (soundDesc->resPtr) {
+			_vm->_res->unlock(rtSound, soundDesc->soundId);
+		}
 
-int32 ImuseDigiSndMgr::getDataFromRegion(SoundDesc *soundDesc, int region, byte **buf, int32 offset, int32 size) {
-	debug(6, "getDataFromRegion() region:%d, offset:%d, size:%d, numRegions:%d", region, offset, size, soundDesc->numRegions);
-	assert(checkForProperHandle(soundDesc));
+		delete soundDesc->bundle;
 
-	// In COMI we allow at least -size*2 as offset, since music
-	// tracks need that in order to be realigned after crossfades
-	if (_vm->_game.id == GID_CMI)
-		assert(buf && offset >= -(size * 2) && size >= 0);
-	else
-		assert(buf && offset >= 0 && size >= 0);
-	assert(region >= 0 && region < soundDesc->numRegions);
-
-	int32 region_offset = soundDesc->region[region].offset;
-	int32 region_length = soundDesc->region[region].length;
-	int32 offset_data = soundDesc->offsetData;
-	int32 start = region_offset - offset_data;
-
-	if (offset + size + offset_data > region_length) {
-		size = region_length - offset;
-		soundDesc->endFlag = true;
-	} else {
-		soundDesc->endFlag = false;
+		memset(soundDesc, 0, sizeof(SoundDesc));
 	}
+}
 
-	int header_size = soundDesc->offsetData;
-	bool header_outside = ((_vm->_game.id == GID_CMI) && !(_vm->_game.features & GF_DEMO));
-	if ((soundDesc->bundle) && (!soundDesc->compressed)) {
-		size = soundDesc->bundle->decompressSampleByCurIndex(start + offset, size, buf, header_size, header_outside);
-	} else if (soundDesc->resPtr) {
-		*buf = (byte *)malloc(size);
-		assert(*buf);
-		memcpy(*buf, soundDesc->resPtr + start + offset + header_size, size);
-	} else if ((soundDesc->bundle) && (soundDesc->compressed)) {
-		*buf = (byte *)malloc(size);
-		assert(*buf);
-		char fileName[26];
-		int offsetMs = (((offset * 8 * 10) / soundDesc->bits) / (soundDesc->channels * soundDesc->freq)) * 100;
-		sprintf(fileName, "%s_reg%03d", soundDesc->name, region);
-		if (scumm_stricmp(fileName, soundDesc->lastFileName) != 0) {
-			int32 offs = 0, len = 0;
-			Common::SeekableReadStream *cmpFile;
-#if defined(USE_FLAC) || defined(USE_VORBIS) || defined(USE_MAD)
-			uint8 soundMode = 0;
-#endif
-
-			sprintf(fileName, "%s_reg%03d.fla", soundDesc->name, region);
-			cmpFile = soundDesc->bundle->getFile(fileName, offs, len);
-			if (len) {
-#ifndef USE_FLAC
-				error("FLAC library compiled support needed");
-#else
-				soundMode = 3;
-#endif
-			}
-			if (!len) {
-				sprintf(fileName, "%s_reg%03d.ogg", soundDesc->name, region);
-				cmpFile = soundDesc->bundle->getFile(fileName, offs, len);
-				if (len) {
-#ifndef USE_VORBIS
-					error("Vorbis library compiled support needed");
-#else
-					soundMode = 2;
-#endif
-				}
-			}
-			if (!len) {
-				sprintf(fileName, "%s_reg%03d.mp3", soundDesc->name, region);
-				cmpFile = soundDesc->bundle->getFile(fileName, offs, len);
-				if (len) {
-#ifndef USE_MAD
-					error("Mad library compiled support needed");
-#else
-					soundMode = 1;
-#endif
-				}
-			}
-			assert(len);
-
-			if (!soundDesc->compressedStream) {
-				Common::SeekableReadStream *tmp = cmpFile->readStream(len);
-				assert(tmp);
-#ifdef USE_FLAC
-				if (soundMode == 3)
-					soundDesc->compressedStream = Audio::makeFLACStream(tmp, DisposeAfterUse::YES);
-#endif
-#ifdef USE_VORBIS
-				if (soundMode == 2)
-					soundDesc->compressedStream = Audio::makeVorbisStream(tmp, DisposeAfterUse::YES);
-#endif
-#ifdef USE_MAD
-				if (soundMode == 1)
-					soundDesc->compressedStream = Audio::makeMP3Stream(tmp, DisposeAfterUse::YES);
-#endif
-				assert(soundDesc->compressedStream);
-				soundDesc->compressedStream->seek(offsetMs);
-			}
-			strcpy(soundDesc->lastFileName, fileName);
-		}
-		size = soundDesc->compressedStream->readBuffer((int16 *)*buf, size / 2) * 2;
-		if (soundDesc->compressedStream->endOfData() || soundDesc->endFlag) {
-			delete soundDesc->compressedStream;
-			soundDesc->compressedStream = nullptr;
-			soundDesc->lastFileName[0] = 0;
-			soundDesc->endFlag = true;
-		}
+bool ImuseDigiSndMgr::checkForProperHandle(SoundDesc *soundDesc) {
+	if (!soundDesc)
+		return false;
+	for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
+		if (soundDesc == &_sounds[l])
+			return true;
 	}
-
-	return size;
+	return false;
 }
 
 } // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_sndmgr.h b/engines/scumm/imuse_digi/dimuse_sndmgr.h
index a3f65c194c..6a06a9f240 100644
--- a/engines/scumm/imuse_digi/dimuse_sndmgr.h
+++ b/engines/scumm/imuse_digi/dimuse_sndmgr.h
@@ -52,74 +52,25 @@ public:
 #define IMUSE_VOLGRP_SFX 2
 #define IMUSE_VOLGRP_MUSIC 3
 
-private:
-	struct Region {
-		int32 offset;		// offset of region
-		int32 length;		// length of region
-	};
-
-	struct Jump {
-		int32 offset;		// jump offset position
-		int32 dest;			// jump to dest position
-		byte hookId;		// id of hook
-		int16 fadeDelay;	// fade delay in ms
-	};
-
-	struct Sync {
-		int32 size;			// size of sync
-		byte *ptr;			// pointer to sync
-	};
-
-	struct Marker {
-		int32 pos;			// position Markaer in sound data
-		int32 length;		// length of marker string
-		char *ptr;			// pointer to string
-	};
-
 public:
 
 	struct SoundDesc {
-		uint16 freq;		// frequency
-		byte channels;		// stereo or mono
-		byte bits;			// 8, 12, 16
-		bool littleEndian;      // Endianness: default is big for original files and native for recompressed ones
-
-		int numJumps;		// number of Jumps
-		Region *region;
-
-		int numRegions;		// number of Regions
-		Jump *jump;
-
-		int numSyncs;		// number of Syncs
-		Sync *sync;
-
-		int numMarkers;		// number of Markers
-		Marker *marker;
-
-		bool endFlag;
 		bool inUse;
-		byte *allData;
-		int32 offsetData;
+		bool scheduledForDealloc;
+
 		byte *resPtr;
+		int resSize;
+		int resCurOffset;
 		char name[15];
 		int16 soundId;
 		BundleMgr *bundle;
-		int type;
-		int volGroupId;
-		int disk;
-		Audio::SeekableAudioStream *compressedStream;
-		bool compressed;
-		char lastFileName[24];
 	};
 
 private:
 
 	SoundDesc _sounds[MAX_IMUSE_SOUNDS];
-
 	bool checkForProperHandle(SoundDesc *soundDesc);
 	SoundDesc *allocSlot();
-	void prepareSound(byte *ptr, SoundDesc *sound, bool uncompressedBundle);
-	void prepareSoundFromRMAP(Common::SeekableReadStream *file, SoundDesc *sound, int32 offset, int32 size);
 
 	ScummEngine *_vm;
 	byte _disk;
@@ -128,8 +79,6 @@ private:
 	bool openMusicBundle(SoundDesc *sound, int &disk);
 	bool openVoiceBundle(SoundDesc *sound, int &disk);
 
-	void countElements(byte *ptr, int &numRegions, int &numJumps, int &numSyncs, int &numMarkers);
-
 public:
 
 	ImuseDigiSndMgr(ScummEngine *scumm);
@@ -137,24 +86,11 @@ public:
 
 	SoundDesc *openSound(int32 soundId, const char *soundName, int soundType, int volGroupId, int disk);
 	void closeSound(SoundDesc *soundDesc);
-	SoundDesc *cloneSound(SoundDesc *soundDesc);
-
-	bool isSndDataExtComp(SoundDesc *soundDesc);
-	int getFreq(SoundDesc *soundDesc);
-	int getBits(SoundDesc *soundDesc);
-	int getChannels(SoundDesc *soundDesc);
-	bool isEndOfRegion(SoundDesc *soundDesc, int region);
-	int getNumRegions(SoundDesc *soundDesc);
-	int getNumJumps(SoundDesc *soundDesc);
-	int getRegionOffset(SoundDesc *soundDesc, int region);
-	int getJumpIdByRegionAndHookId(SoundDesc *soundDesc, int region, int hookId);
-	bool checkForTriggerByRegionAndMarker(SoundDesc *soundDesc, int region, const char *marker);
-	int getRegionIdByJumpId(SoundDesc *soundDesc, int jumpId);
-	int getJumpHookId(SoundDesc *soundDesc, int number);
-	int getJumpFade(SoundDesc *soundDesc, int number);
-	void getSyncSizeAndPtrById(SoundDesc *soundDesc, int number, int32 &sync_size, byte **sync_ptr);
-
-	int32 getDataFromRegion(SoundDesc *soundDesc, int region, byte **buf, int32 offset, int32 size);
+	void closeSoundById(int soundId);
+	SoundDesc *findSoundById(int soundId);
+	SoundDesc *getSounds();
+	void scheduleSoundForDeallocation(int soundId);
+
 };
 
 } // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_streamer.cpp b/engines/scumm/imuse_digi/dimuse_streamer.cpp
new file mode 100644
index 0000000000..1050657113
--- /dev/null
+++ b/engines/scumm/imuse_digi/dimuse_streamer.cpp
@@ -0,0 +1,386 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/imuse_digi/dimuse_engine.h"
+
+namespace Scumm {
+
+int IMuseDigital::streamerInit() {
+	for (int l = 0; l < DIMUSE_MAX_STREAMS; l++) {
+		_streams[l].soundId = 0;
+	}
+	_lastStreamLoaded = nullptr;
+	return 0;
+}
+
+IMuseDigiStream *IMuseDigital::streamerAllocateSound(int soundId, int bufId, int32 maxRead) {
+	IMuseDigiSndBuffer *bufInfoPtr = _filesHandler->getBufInfo(bufId);
+	if (!bufInfoPtr) {
+		debug(5, "IMuseDigital::streamerAlloc(): ERROR: couldn't get buffer info");
+		return nullptr;
+	}
+
+	if ((bufInfoPtr->bufSize / 4) <= maxRead) {
+		debug(5, "IMuseDigital::streamerAlloc(): ERROR: maxRead too big for buffer");
+		return nullptr;
+	}
+
+	for (int l = 0; l < DIMUSE_MAX_STREAMS; l++) {
+		if (_streams[l].soundId && _streams[l].bufId == bufId) {
+			debug(5, "IMuseDigital::streamerAlloc(): ERROR: stream bufId %d already in use", bufId);
+			return nullptr;
+		}
+	}
+
+	for (int l = 0; l < DIMUSE_MAX_STREAMS; l++) {
+		if (!_streams[l].soundId) {
+			_streams[l].endOffset = _filesHandler->seek(soundId, 0, SEEK_END, bufId);
+			_streams[l].curOffset = 0;
+			_streams[l].soundId = soundId;
+			_streams[l].bufId = bufId;
+			_streams[l].buf = bufInfoPtr->buffer;
+			_streams[l].bufFreeSize = bufInfoPtr->bufSize - maxRead - (_isEarlyDiMUSE ? 0 : 4);
+			_streams[l].loadSize = bufInfoPtr->loadSize;
+			_streams[l].criticalSize = bufInfoPtr->criticalSize;
+			_streams[l].maxRead = maxRead;
+			_streams[l].loadIndex = 0;
+			_streams[l].readIndex = 0;
+			_streams[l].paused = 0;
+			_streams[l].vocLoopFlag = 0;
+			_streams[l].vocLoopTriggerOffset = 0;
+			return &_streams[l];
+		}
+	}
+	debug(5, "IMuseDigital::streamerAlloc(): ERROR: no spare streams");
+	return nullptr;
+}
+
+int IMuseDigital::streamerClearSoundInStream(IMuseDigiStream *streamPtr) {
+	streamPtr->soundId = 0;
+	if (_lastStreamLoaded == streamPtr) {
+		_lastStreamLoaded = 0;
+	}
+	return 0;
+}
+
+int IMuseDigital::streamerProcessStreams() {
+	if (!_isEarlyDiMUSE)
+		dispatchPredictFirstStream();
+
+	IMuseDigiStream *stream1 = nullptr;
+	IMuseDigiStream *stream2 = nullptr;
+
+	for (int l = 0; l < DIMUSE_MAX_STREAMS; l++) {
+		if ((_streams[l].soundId) && (!_streams[l].paused)) {
+			if (stream2) {
+				if (stream1) {
+					debug(5, "IMuseDigital::streamerProcessStreams(): WARNING: three streams in use");
+				} else {
+					stream1 = &_streams[l];
+				}
+			} else {
+				stream2 = &_streams[l];
+			}
+		}
+	}
+
+	if (!stream1) {
+		if (stream2)
+			streamerFetchData(stream2);
+		return 0;
+	}
+
+	if (!stream2) {
+		if (stream1)
+			streamerFetchData(stream1);
+		return 0;
+	}
+
+	// Check if we've reached or surpassed criticalSize
+	bool critical1 = (streamerGetFreeBufferAmount(stream1) >= stream1->criticalSize);
+	bool critical2 = (streamerGetFreeBufferAmount(stream2) >= stream2->criticalSize);
+
+	if (!critical1) {
+		if (!critical2) {
+			if (stream1 == _lastStreamLoaded) {
+				streamerFetchData(stream1);
+				streamerFetchData(stream2);
+				return 0;
+			} else {
+				streamerFetchData(stream2);
+				streamerFetchData(stream1);
+				return 0;
+			}
+		} else {
+			streamerFetchData(stream1);
+			return 0;
+		}
+	}
+
+	if (!critical2) {
+		streamerFetchData(stream2);
+		return 0;
+	} else {
+		if (stream1 == _lastStreamLoaded) {
+			streamerFetchData(stream1);
+		} else {
+			streamerFetchData(stream2);
+		}
+		return 0;
+	}
+}
+
+uint8 *IMuseDigital::streamerGetStreamBuffer(IMuseDigiStream *streamPtr, int size) {
+	if (size > streamerGetFreeBufferAmount(streamPtr) || streamPtr->maxRead < size)
+		return 0;
+
+	if (size > streamPtr->bufFreeSize - streamPtr->readIndex) {
+		memcpy(&streamPtr->buf[streamPtr->bufFreeSize],
+			streamPtr->buf,
+			size - streamPtr->bufFreeSize + streamPtr->readIndex + (_isEarlyDiMUSE ? 0 : 4));
+	}
+
+	int32 readIndex_tmp = streamPtr->readIndex;
+	uint8 *ptr = &streamPtr->buf[readIndex_tmp];
+	streamPtr->readIndex = readIndex_tmp + size;
+	if (streamPtr->bufFreeSize <= readIndex_tmp + size)
+		streamPtr->readIndex = readIndex_tmp + size - streamPtr->bufFreeSize;
+
+	return ptr;
+}
+
+uint8 *IMuseDigital::streamerGetStreamBufferAtOffset(IMuseDigiStream *streamPtr, int32 offset, int size) {
+	if (offset + size > streamerGetFreeBufferAmount(streamPtr) || streamPtr->maxRead < size)
+		return 0;
+
+	int32 offsetReadIndex = offset + streamPtr->readIndex;
+	if (offsetReadIndex >= streamPtr->bufFreeSize) {
+		offsetReadIndex -= streamPtr->bufFreeSize;
+	}
+
+	if (size > streamPtr->bufFreeSize - offsetReadIndex) {
+		memcpy(&streamPtr->buf[streamPtr->bufFreeSize], streamPtr->buf, size + offsetReadIndex - streamPtr->bufFreeSize);
+	}
+
+	return &streamPtr->buf[offsetReadIndex];
+}
+
+int IMuseDigital::streamerSetReadIndex(IMuseDigiStream *streamPtr, int offset) {
+	_streamerBailFlag = 1;
+	if (offset > streamerGetFreeBufferAmount(streamPtr))
+		return -1;
+
+	streamPtr->readIndex += offset;
+	if (streamPtr->readIndex >= streamPtr->bufFreeSize) {
+		streamPtr->readIndex -= streamPtr->bufFreeSize;
+	}
+	return 0;
+}
+
+int IMuseDigital::streamerSetLoadIndex(IMuseDigiStream *streamPtr, int offset) {
+	_streamerBailFlag = 1;
+	if (offset > streamerGetFreeBufferAmount(streamPtr))
+		return -1;
+
+	streamPtr->loadIndex = streamPtr->readIndex + offset;
+	if (streamPtr->loadIndex >= streamPtr->bufFreeSize) {
+		streamPtr->loadIndex -= streamPtr->bufFreeSize;
+	}
+	return 0;
+}
+
+int IMuseDigital::streamerGetFreeBufferAmount(IMuseDigiStream *streamPtr) {
+	int32 freeBufferSize = streamPtr->loadIndex - streamPtr->readIndex;
+	if (freeBufferSize < 0)
+		freeBufferSize += streamPtr->bufFreeSize;
+	return freeBufferSize;
+}
+
+int IMuseDigital::streamerSetSoundToStreamFromOffset(IMuseDigiStream *streamPtr, int soundId, int32 offset) {
+	_streamerBailFlag = 1;
+	streamPtr->soundId = soundId;
+	streamPtr->curOffset = offset;
+	streamPtr->endOffset = _isEarlyDiMUSE ? _filesHandler->seek(streamPtr->soundId, 0, SEEK_END, streamPtr->bufId) : 0;
+	streamPtr->paused = 0;
+	if (_lastStreamLoaded == streamPtr) {
+		_lastStreamLoaded = nullptr;
+	}
+	return 0;
+}
+
+int IMuseDigital::streamerQueryStream(IMuseDigiStream *streamPtr, int32 &bufSize, int32 &criticalSize, int32 &freeSpace, int &paused) {
+	dispatchPredictFirstStream();
+	bufSize = streamPtr->bufFreeSize;
+	criticalSize = streamPtr->criticalSize;
+	freeSpace = streamerGetFreeBufferAmount(streamPtr);
+	paused = streamPtr->paused;
+	return 0;
+}
+
+int IMuseDigital::streamerFeedStream(IMuseDigiStream *streamPtr, uint8 *srcBuf, int32 sizeToFeed, int paused) {
+	int32 size = streamPtr->readIndex - streamPtr->loadIndex;
+	if (size <= 0)
+		size += streamPtr->bufFreeSize;
+	if (sizeToFeed > (size - 4)) {
+		// Don't worry, judging from the intro of The Dig, this is totally expected
+		// to happen, and when it does, the output matches the EXE behavior
+		debug(5, "IMuseDigital::streamerFeedStream(): WARNING: buffer overflow");
+		_streamerBailFlag = 1;
+
+		int32 newTentativeSize = sizeToFeed - (size - 4) - (sizeToFeed - (size - 4)) % 12 + 12;
+		size = streamPtr->loadIndex - streamPtr->readIndex;
+		if (size < 0)
+			size += streamPtr->bufFreeSize;
+		if (size >= newTentativeSize) {
+			streamPtr->readIndex = newTentativeSize + streamPtr->readIndex;
+			if (streamPtr->bufFreeSize <= streamPtr->readIndex)
+				streamPtr->readIndex -= streamPtr->bufFreeSize;
+		}
+	}
+
+	if (sizeToFeed > 0) {
+		do {
+			size = streamPtr->bufFreeSize - streamPtr->loadIndex;
+			if (size >= sizeToFeed) {
+				size = sizeToFeed;
+			}
+			sizeToFeed -= size;
+			memcpy(&streamPtr->buf[streamPtr->loadIndex], srcBuf, size);
+			srcBuf += size;
+
+			int val = size + streamPtr->loadIndex;
+			streamPtr->curOffset += size;
+			streamPtr->loadIndex += size;
+			if (val >= streamPtr->bufFreeSize) {
+				streamPtr->loadIndex = val - streamPtr->bufFreeSize;
+			}
+		} while (sizeToFeed > 0);
+	}
+
+	streamPtr->paused = paused;
+	return 0;
+}
+
+int IMuseDigital::streamerFetchData(IMuseDigiStream *streamPtr) {
+	if (!_isEarlyDiMUSE && streamPtr->endOffset == 0) {
+		streamPtr->endOffset = _filesHandler->seek(streamPtr->soundId, 0, SEEK_END, streamPtr->bufId);
+	}
+
+	int32 size = streamPtr->readIndex - streamPtr->loadIndex;
+	if (size <= 0)
+		size += streamPtr->bufFreeSize;
+
+	int32 loadSize = streamPtr->loadSize;
+	int32 remainingSize = streamPtr->endOffset - streamPtr->curOffset;
+
+	if (loadSize >= size - (_isEarlyDiMUSE ? 1 : 4)) {
+		loadSize = size - (_isEarlyDiMUSE ? 1 : 4);
+	}
+
+	if (loadSize >= remainingSize) {
+		loadSize = remainingSize;
+	}
+
+	if (remainingSize <= 0) {
+		streamPtr->paused = 1;
+
+		if (!_isEarlyDiMUSE) {
+			// Pad the buffer
+			streamPtr->buf[streamPtr->loadIndex] = 127;
+			streamPtr->loadIndex++;
+			streamPtr->buf[streamPtr->loadIndex] = 127;
+			streamPtr->loadIndex++;
+			streamPtr->buf[streamPtr->loadIndex] = 127;
+			streamPtr->loadIndex++;
+			streamPtr->buf[streamPtr->loadIndex] = 127;
+			streamPtr->loadIndex++;
+		}
+	}
+
+	int32 actualAmount;
+	int32 requestedAmount;
+
+	while (1) {
+		if (!_isEarlyDiMUSE && loadSize <= 0)
+			return 0;
+
+		requestedAmount = streamPtr->bufFreeSize - streamPtr->loadIndex;
+		if (requestedAmount >= loadSize) {
+			requestedAmount = loadSize;
+		}
+
+		if (_filesHandler->seek(streamPtr->soundId, streamPtr->curOffset, SEEK_SET, streamPtr->bufId) != streamPtr->curOffset) {
+			debug(5, "IMuseDigital::streamerFetchData(): ERROR: invalid seek in streamer (%d), pausing stream...", streamPtr->curOffset);
+			streamPtr->paused = 1;
+			return 0;
+		}
+
+		_streamerBailFlag = 0;
+
+		Common::StackLock lock(_mutex);
+		actualAmount = _filesHandler->read(streamPtr->soundId, &streamPtr->buf[streamPtr->loadIndex], requestedAmount, streamPtr->bufId);
+		Common::StackLock unlock(_mutex);
+
+		// FT has no bailFlag
+		if (!_isEarlyDiMUSE && _streamerBailFlag)
+			return 0;
+
+		loadSize -= actualAmount;
+		streamPtr->curOffset += actualAmount;
+		_lastStreamLoaded = streamPtr;
+
+		int32 newLoadIndex = actualAmount + streamPtr->loadIndex;
+		streamPtr->loadIndex = newLoadIndex;
+		if (newLoadIndex >= streamPtr->bufFreeSize) {
+			streamPtr->loadIndex = newLoadIndex - streamPtr->bufFreeSize;
+		}
+
+		if (_isEarlyDiMUSE && streamPtr->vocLoopFlag) {
+			if (streamPtr->vocLoopTriggerOffset <= streamPtr->curOffset) {
+				dispatchVOCLoopCallback(streamPtr->soundId);
+				streamPtr->vocLoopFlag = 0;
+			}
+		}
+
+		if (actualAmount != requestedAmount)
+			break;
+
+		// FT checks for negative or zero loadSize here
+		if (_isEarlyDiMUSE && loadSize <= 0)
+			return 0;
+	}
+
+	debug(5, "IMuseDigital::streamerFetchData(): ERROR: unable to load the correct amount of data (req=%d, act=%d)", requestedAmount, actualAmount);
+	_lastStreamLoaded = nullptr;
+	return 0;
+}
+
+void IMuseDigital::streamerSetLoopFlag(IMuseDigiStream *streamPtr, int offset) {
+	streamPtr->vocLoopFlag = 1;
+	streamPtr->vocLoopTriggerOffset = offset;
+}
+
+void IMuseDigital::streamerRemoveLoopFlag(IMuseDigiStream *streamPtr) {
+	streamPtr->vocLoopFlag = 0;
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_tables.cpp b/engines/scumm/imuse_digi/dimuse_tables.cpp
index fdd91f9f64..966160805d 100644
--- a/engines/scumm/imuse_digi/dimuse_tables.cpp
+++ b/engines/scumm/imuse_digi/dimuse_tables.cpp
@@ -20,8 +20,6 @@
  *
  */
 
-
-//#include "scumm/imuse_digi/dimuse.h"
 #include "scumm/imuse_digi/dimuse_tables.h"
 
 namespace Scumm {
@@ -853,7 +851,7 @@ const imuseFtSeqTable _ftSeqMusicTable[] = {
 	{"",         0,  0  },
 
 	{"funeral",  2,  127},
-	{"",         2,  127},
+	{"",         0,  0  },
 	{"moshop",   3,  64 },
 	{"",         0,  0  },
 
diff --git a/engines/scumm/imuse_digi/dimuse_track.cpp b/engines/scumm/imuse_digi/dimuse_track.cpp
deleted file mode 100644
index 391df09255..0000000000
--- a/engines/scumm/imuse_digi/dimuse_track.cpp
+++ /dev/null
@@ -1,568 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#include "common/config-manager.h"
-#include "common/timer.h"
-
-#include "scumm/actor.h"




More information about the Scummvm-git-logs mailing list