[Scummvm-git-logs] scummvm master -> 0da18320af79b6ca8a47248442d106674b5ab860

peterkohaut peterkohaut at users.noreply.github.com
Thu Aug 24 23:45:58 CEST 2017


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:
0da18320af BLADERUNNER: Added audio mixer & various fixes


Commit: 0da18320af79b6ca8a47248442d106674b5ab860
    https://github.com/scummvm/scummvm/commit/0da18320af79b6ca8a47248442d106674b5ab860
Author: Peter Kohaut (peter.kohaut at gmail.com)
Date: 2017-08-24T23:43:47+02:00

Commit Message:
BLADERUNNER: Added audio mixer & various fixes

Audio mixer is supporting fading and pan animation
Added support for skipping speech by pressing Return
Added proper support for ambient sounds
Added more code to the dialogue menu
Added tooltips to the Spinner
Fixed calculation of volume and pan of walk steps
Code cleanup & formatting

Changed paths:
  A engines/bladerunner/audio_mixer.cpp
  A engines/bladerunner/audio_mixer.h
    engines/bladerunner/actor.cpp
    engines/bladerunner/actor_clues.cpp
    engines/bladerunner/actor_clues.h
    engines/bladerunner/actor_combat.h
    engines/bladerunner/ambient_sounds.cpp
    engines/bladerunner/ambient_sounds.h
    engines/bladerunner/archive.cpp
    engines/bladerunner/aud_stream.cpp
    engines/bladerunner/aud_stream.h
    engines/bladerunner/audio_player.cpp
    engines/bladerunner/audio_player.h
    engines/bladerunner/audio_speech.cpp
    engines/bladerunner/audio_speech.h
    engines/bladerunner/bladerunner.cpp
    engines/bladerunner/bladerunner.h
    engines/bladerunner/color.h
    engines/bladerunner/dialogue_menu.cpp
    engines/bladerunner/dialogue_menu.h
    engines/bladerunner/elevator.cpp
    engines/bladerunner/font.cpp
    engines/bladerunner/font.h
    engines/bladerunner/gameflags.cpp
    engines/bladerunner/module.mk
    engines/bladerunner/mouse.cpp
    engines/bladerunner/scene.cpp
    engines/bladerunner/scene.h
    engines/bladerunner/script/init.cpp
    engines/bladerunner/script/script.cpp
    engines/bladerunner/script/script.h
    engines/bladerunner/settings.h
    engines/bladerunner/spinner.cpp
    engines/bladerunner/spinner.h
    engines/bladerunner/ui_image_picker.cpp
    engines/bladerunner/ui_image_picker.h
    engines/bladerunner/view.cpp
    engines/bladerunner/vqa_player.cpp


diff --git a/engines/bladerunner/actor.cpp b/engines/bladerunner/actor.cpp
index 85c5d89..c29956c 100644
--- a/engines/bladerunner/actor.cpp
+++ b/engines/bladerunner/actor.cpp
@@ -23,7 +23,6 @@
 #include "bladerunner/actor.h"
 
 #include "bladerunner/bladerunner.h"
-
 #include "bladerunner/actor_clues.h"
 #include "bladerunner/actor_combat.h"
 #include "bladerunner/actor_walk.h"
@@ -37,6 +36,7 @@
 #include "bladerunner/scene_objects.h"
 #include "bladerunner/script/scene.h"
 #include "bladerunner/script/ai.h"
+#include "bladerunner/set.h"
 #include "bladerunner/slice_animations.h"
 #include "bladerunner/slice_renderer.h"
 #include "bladerunner/waypoints.h"
@@ -767,7 +767,7 @@ void Actor::setBoundingBox(const Vector3 &position, bool retired) {
 
 float Actor::distanceFromView(View *view) const{
 	float xDist = this->_position.x - view->_cameraPosition.x;
-	float zDist = this->_position.z - view->_cameraPosition.z;
+	float zDist = this->_position.z + view->_cameraPosition.z;
 	return sqrt(xDist * xDist + zDist * zDist);
 }
 
@@ -863,7 +863,7 @@ void Actor::faceHeading(int heading, bool animate) {
 }
 
 void Actor::modifyFriendlinessToOther(int otherActorId, signed int change) {
-	_friendlinessToOther[otherActorId] = MIN(MAX(_friendlinessToOther[otherActorId] + change, 0), 100);
+	_friendlinessToOther[otherActorId] = CLIP(_friendlinessToOther[otherActorId] + change, 0, 100);
 }
 
 void Actor::setFriendlinessToOther(int otherActorId, int friendliness) {
@@ -895,29 +895,29 @@ void Actor::setImmunityToObstacles(bool isImmune) {
 }
 
 void Actor::modifyCurrentHP(signed int change) {
-	_currentHP = MIN(MAX(_currentHP + change, 0), 100);
+	_currentHP = CLIP(_currentHP + change, 0, 100);
 	if (_currentHP > 0)
 		retire(false, 0, 0, -1);
 }
 
 void Actor::modifyMaxHP(signed int change) {
-	_maxHP = MIN(MAX(_maxHP + change, 0), 100);
+	_maxHP = CLIP(_maxHP + change, 0, 100);
 }
 
 void Actor::modifyCombatAggressiveness(signed int change) {
-	_combatAggressiveness = MIN(MAX(_combatAggressiveness + change, 0), 100);
+	_combatAggressiveness = CLIP(_combatAggressiveness + change, 0, 100);
 }
 
 void Actor::modifyHonesty(signed int change) {
-	_honesty = MIN(MAX(_honesty + change, 0), 100);
+	_honesty = CLIP(_honesty + change, 0, 100);
 }
 
 void Actor::modifyIntelligence(signed int change) {
-	_intelligence = MIN(MAX(_intelligence + change, 0), 100);
+	_intelligence = CLIP(_intelligence + change, 0, 100);
 }
 
 void Actor::modifyStability(signed int change) {
-	_stability = MIN(MAX(_stability + change, 0), 100);
+	_stability = CLIP(_stability + change, 0, 100);
 }
 
 void Actor::setFlagDamageAnimIfMoving(bool value) {
@@ -1043,7 +1043,7 @@ void Actor::speechPlay(int sentenceId, bool voiceOver) {
 		int screenX = 320; //, screenY = 0;
 		//TODO: transform to screen space using fov;
 		balance = 127 * (2 * screenX - 640) / 640;
-		balance = MIN(127, MAX(-127, balance));
+		balance = CLIP<int>(balance, -127, 127);
 	}
 
 	_vm->_audioSpeech->playSpeech(name, balance);
@@ -1092,12 +1092,12 @@ void Actor::copyClues(int actorId) {
 
 int Actor::soundVolume() const {
 	float dist = distanceFromView(_vm->_view);
-	return 255.0f * MAX(MIN(dist / 1200.0f, 1.0f), 0.0f);
+	return 35.0f * CLIP(1.0f - (dist / 1200.0f), 0.0f, 1.0f);
 }
 
 int Actor::soundBalance() const {
 	Vector3 screenPosition = _vm->_view->calculateScreenPosition(_position);
-	return 127.0f * (MAX(MIN(screenPosition.x / 640.0f, 1.0f), 0.0f) * 2.0f - 1.0f);
+	return 35.0f * (CLIP(screenPosition.x / 640.0f, 0.0f, 1.0f) * 2.0f - 1.0f);
 }
 
 bool Actor::walkFindU1(const Vector3 &startPosition, const Vector3 &targetPosition, float size, Vector3 *newDestination) {
diff --git a/engines/bladerunner/actor_clues.cpp b/engines/bladerunner/actor_clues.cpp
index 749dda2..cd848f9 100644
--- a/engines/bladerunner/actor_clues.cpp
+++ b/engines/bladerunner/actor_clues.cpp
@@ -22,6 +22,8 @@
 
 #include "bladerunner/actor_clues.h"
 
+#include "bladerunner/bladerunner.h"
+#include "bladerunner/gameinfo.h"
 #include "bladerunner/crimes_database.h"
 
 #include "common/debug.h"
diff --git a/engines/bladerunner/actor_clues.h b/engines/bladerunner/actor_clues.h
index f7ec877..d053ef5 100644
--- a/engines/bladerunner/actor_clues.h
+++ b/engines/bladerunner/actor_clues.h
@@ -23,12 +23,10 @@
 #ifndef BLADERUNNER_ACTOR_CLUES_H
 #define BLADERUNNER_ACTOR_CLUES_H
 
-#include "bladerunner/bladerunner.h"
-
-#include "bladerunner/gameinfo.h"
-
 namespace BladeRunner {
 
+class BladeRunnerEngine;
+
 struct ActorClue {
 	int _clueId;
 	int _weight;
diff --git a/engines/bladerunner/actor_combat.h b/engines/bladerunner/actor_combat.h
index ee29bf4..3763f46 100644
--- a/engines/bladerunner/actor_combat.h
+++ b/engines/bladerunner/actor_combat.h
@@ -23,12 +23,12 @@
 #ifndef BLADERUNNER_ACTOR_COMBAT_H
 #define BLADERUNNER_ACTOR_COMBAT_H
 
-#include "bladerunner/bladerunner.h"
-
 #include "bladerunner/vector.h"
 
 namespace BladeRunner {
 
+class BladeRunnerEngine;
+
 class ActorCombat {
 	BladeRunnerEngine *_vm;
 
diff --git a/engines/bladerunner/ambient_sounds.cpp b/engines/bladerunner/ambient_sounds.cpp
index aaf6c01..7790b03 100644
--- a/engines/bladerunner/ambient_sounds.cpp
+++ b/engines/bladerunner/ambient_sounds.cpp
@@ -34,7 +34,7 @@ namespace BladeRunner {
 #define NON_LOOPING_SOUNDS 25
 #define LOOPING_SOUNDS      3
 
-AmbientSounds::AmbientSounds(BladeRunnerEngine *vm) : _vm(vm) {
+AmbientSounds::AmbientSounds(BladeRunnerEngine *vm)	: _vm(vm) {
 	_nonLoopingSounds = new NonLoopingSound[NON_LOOPING_SOUNDS];
 	_loopingSounds = new LoopingSound[LOOPING_SOUNDS];
 	_ambientVolume = 65;
@@ -64,45 +64,133 @@ static inline void sort(int &a, int &b) {
 }
 
 void AmbientSounds::addSound(
-		int id,
-		int timeRangeBegin, int timeRangeEnd,
-		int volumeRangeBegin, int volumeRangeEnd,
-		int unk1RangeBegin, int unk1RangeEnd,
-		int unk2RangeBegin, int unk2RangeEnd,
-		int priority, int unk3) {
-	const char *name = _vm->_gameInfo->getSfxTrack(id);
-
-	sort(volumeRangeBegin, volumeRangeEnd);
-	sort(unk1RangeBegin, unk1RangeEnd);
-	sort(unk2RangeBegin, unk2RangeEnd);
+	int sfxId,
+	int timeMin, int timeMax,
+	int volumeMin, int volumeMax,
+	int panStartMin, int panStartMax,
+	int panEndMin, int panEndMax,
+	int priority, int unk) {
+	const char *name = _vm->_gameInfo->getSfxTrack(sfxId);
+
+	sort(volumeMin, volumeMax);
+	sort(panStartMin, panStartMax);
+	sort(panEndMin, panEndMax);
 
 	addSoundByName(
-		name,
-		timeRangeBegin, timeRangeEnd,
-		volumeRangeBegin, volumeRangeEnd,
-		unk1RangeBegin, unk1RangeEnd,
-		unk2RangeBegin, unk2RangeEnd,
-		priority, unk3
-	);
+				 name,
+				 timeMin, timeMax,
+				 volumeMin, volumeMax,
+				 panStartMin, panStartMax,
+				 panEndMin, panEndMax,
+				 priority, unk
+				);
 }
 
-void AmbientSounds::addLoopingSound(int sfx_id, int volume, int unk, int fadeInTime) {
-	const char *name = _vm->_gameInfo->getSfxTrack(sfx_id);
+void AmbientSounds::removeNonLoopingSound(int sfxId, bool stopPlaying) {
+	const char *name = _vm->_gameInfo->getSfxTrack(sfxId);
+	int32 hash = mix_id(name);
+	int index = findNonLoopingTrackByHash(hash);
+	if (index >= 0) {
+		removeNonLoopingSoundByIndex(index, stopPlaying);
+	}
+}
+
+void AmbientSounds::removeAllNonLoopingSounds(bool stopPlaying) {
+	for (int i = 0; i < NON_LOOPING_SOUNDS; i++) {
+		removeNonLoopingSoundByIndex(i, stopPlaying);
+	}
+}
+
+void AmbientSounds::addSpeech(int actorId, int sentenceId, int timeMin, int timeMax, int volumeMin, int volumeMax, int panStartMin, int panStartMax, int panEndMin, int panEndMax, int priority, int unk) {
+	sort(volumeMin, volumeMax);
+	sort(panStartMin, panStartMax);
+	sort(panEndMin, panEndMax);
+
+	char name[13];
+	sprintf(name, "%02d-%04d.AUD", actorId, sentenceId); //TODO somewhere here should be also language code
+	addSoundByName(name,
+					timeMin, timeMax,
+					volumeMin, volumeMax,
+					panStartMin, panStartMax,
+					panEndMin, panEndMax,
+					priority, unk);
+}
+
+void AmbientSounds::playSound(int sfxId, int volume, int panStart, int panEnd, int priority) {
+	const char *name = _vm->_gameInfo->getSfxTrack(sfxId);
+
+	_vm->_audioPlayer->playAud(name, volume * _ambientVolume / 100, panStart, panEnd, priority, AudioPlayer::OVERRIDE_VOLUME);
+}
+
+void AmbientSounds::addLoopingSound(int sfxId, int volume, int pan, int delay) {
+	const char *name = _vm->_gameInfo->getSfxTrack(sfxId);
 
 	int32 hash = mix_id(name);
 
-	if (findLoopingTrackByHash(hash) >= 0)
+	if (findLoopingTrackByHash(hash) >= 0) {
 		return;
+	}
 
 	int i = findAvailableLoopingTrack();
-	if (i == -1)
+	if (i == -1) {
 		return;
+	}
+	LoopingSound &track = _loopingSounds[i];
+
+	track.isActive = true;
+	strcpy(track.name, name);
+	track.hash = hash;
+	track.pan = pan;
+	track.volume = volume;
+
+	int actualVolumeStart = volume * _ambientVolume / 100;
+	int actualVolumeEnd = actualVolumeStart;
+
+	if (delay > 0) {
+		actualVolumeStart = 0;
+	}
+
+	track.audioPlayerTrack = _vm->_audioPlayer->playAud(name, actualVolumeStart, pan, pan, 99, AudioPlayer::LOOP | AudioPlayer::OVERRIDE_VOLUME);
+
+	if (track.audioPlayerTrack == -1) {
+		removeLoopingSoundByIndex(i, 0);
+	} else {
+		if (delay) {
+			_vm->_audioPlayer->adjustVolume(track.audioPlayerTrack, actualVolumeEnd, delay, false);
+		}
+	}
+}
+
+void AmbientSounds::adjustLoopingSound(int sfxId, int volume, int pan, int delay) {
+	const char *name = _vm->_gameInfo->getSfxTrack(sfxId);
+	int32 hash = mix_id(name);
+	int index = findLoopingTrackByHash(hash);
 
-	int actualVolume = volume * _ambientVolume / 100;
+	if (index >= 0 && _loopingSounds[index].audioPlayerTrack != -1 && _vm->_audioPlayer->isActive(_loopingSounds[index].audioPlayerTrack)) {
+		if (volume != -1) {
+			_loopingSounds[index].volume = volume;
+			_vm->_audioPlayer->adjustVolume(_loopingSounds[index].audioPlayerTrack, _ambientVolume * volume / 100, delay, false);
+		}
+		if (pan != -101) {
+			_loopingSounds[index].pan = pan;
+			_vm->_audioPlayer->adjustPan(_loopingSounds[index].audioPlayerTrack, pan, delay);
+		}
+	}
+}
 
-	int balance = 0;
+void AmbientSounds::removeLoopingSound(int sfxId, int delay) {
+	const char *name = _vm->_gameInfo->getSfxTrack(sfxId);
+	int32 hash = mix_id(name);
+	int index = findLoopingTrackByHash(hash);
+	if (index >= 0) {
+		removeLoopingSoundByIndex(index, delay);
+	}
+}
 
-	_vm->_audioPlayer->playAud(name, actualVolume, balance, balance, 100, AudioPlayer::LOOP | AudioPlayer::OVERRIDE_VOLUME);
+void AmbientSounds::removeAllLoopingSounds(int delay) {
+	for (int i = 0; i < LOOPING_SOUNDS; i++) {
+		removeLoopingSoundByIndex(i, delay);
+	}
 }
 
 void AmbientSounds::tick() {
@@ -111,37 +199,38 @@ void AmbientSounds::tick() {
 	for (int i = 0; i != NON_LOOPING_SOUNDS; ++i) {
 		NonLoopingSound &track = _nonLoopingSounds[i];
 
-		if (!track.isActive || track.nextPlayTime > now)
+		if (!track.isActive || track.nextPlayTime > now) {
 			continue;
+		}
 
-		int pan1, pan2;
-
-		pan1 = _vm->_rnd.getRandomNumberRng(track.pan1begin, track.pan1end);
-		if (track.pan2begin == -101) {
-			pan2 = pan1;
+		int panEnd;
+		int panStart = _vm->_rnd.getRandomNumberRng(track.panStartMin, track.panStartMax);
+		if (track.panEndMin == -101) {
+			panEnd = panStart;
 		} else {
-			pan2 = _vm->_rnd.getRandomNumberRng(track.pan2begin, track.pan2end);
+			panEnd = _vm->_rnd.getRandomNumberRng(track.panEndMin, track.panEndMax);
 		}
 
-		track.volume = _vm->_rnd.getRandomNumberRng(track.volume1, track.volume2);
+		track.volume = _vm->_rnd.getRandomNumberRng(track.volumeMin, track.volumeMax);
 
-		track.audio_player_track = _vm->_audioPlayer->playAud(
-			track.name,
-			track.volume * _ambientVolume / 100,
-			pan1, pan2,
-			track.priority,
-			AudioPlayer::OVERRIDE_VOLUME
-		);
-
-		track.nextPlayTime = now + _vm->_rnd.getRandomNumberRng(track.time1, track.time2);
+		track.audioPlayerTrack = _vm->_audioPlayer->playAud(
+															 track.name,
+															 track.volume * _ambientVolume / 100,
+															 panStart,
+															 panEnd,
+															 track.priority,
+															 AudioPlayer::OVERRIDE_VOLUME
+															);
 
+		track.nextPlayTime = now + _vm->_rnd.getRandomNumberRng(track.timeMin, track.timeMax);
 	}
 }
 
 int AmbientSounds::findAvailableNonLoopingTrack() {
 	for (int i = 0; i != NON_LOOPING_SOUNDS; ++i) {
-		if (!_nonLoopingSounds[i].isActive)
+		if (!_nonLoopingSounds[i].isActive) {
 			return i;
+		}
 	}
 
 	return -1;
@@ -151,8 +240,9 @@ int AmbientSounds::findNonLoopingTrackByHash(int32 hash) {
 	for (int i = 0; i != NON_LOOPING_SOUNDS; ++i) {
 		NonLoopingSound &track = _nonLoopingSounds[i];
 
-		if (track.isActive && track.hash == hash)
+		if (track.isActive && track.hash == hash) {
 			return i;
+		}
 	}
 
 	return -1;
@@ -160,8 +250,9 @@ int AmbientSounds::findNonLoopingTrackByHash(int32 hash) {
 
 int AmbientSounds::findAvailableLoopingTrack() {
 	for (int i = 0; i != LOOPING_SOUNDS; ++i) {
-		if (!_loopingSounds[i].isActive)
+		if (!_loopingSounds[i].isActive) {
 			return i;
+		}
 	}
 
 	return -1;
@@ -171,46 +262,77 @@ int AmbientSounds::findLoopingTrackByHash(int32 hash) {
 	for (int i = 0; i != LOOPING_SOUNDS; ++i) {
 		LoopingSound &track = _loopingSounds[i];
 
-		if (track.isActive && track.hash == hash)
+		if (track.isActive && track.hash == hash) {
 			return i;
+		}
 	}
 
 	return -1;
 }
 
 void AmbientSounds::addSoundByName(
-		const char *name,
-		int timeRangeBegin, int timeRangeEnd,
-		int volumeRangeBegin, int volumeRangeEnd,
-		int pan1begin, int pan1end,
-		int pan2begin, int pan2end,
-		int priority, int unk3) {
+	const char *name,
+	int timeMin, int timeMax,
+	int volumeMin, int volumeMax,
+	int panStartMin, int panStartMax,
+	int panEndMin, int panEndMax,
+	int priority, int unk) {
 	if (strlen(name) > 12) {
 		error("AmbientSounds::addSoundByName: Overlong name '%s'", name);
 	}
 
 	int i = findAvailableNonLoopingTrack();
-	if (i < 0)
+	if (i < 0) {
 		return;
+	}
 
 	NonLoopingSound &track = _nonLoopingSounds[i];
 
-	uint32 now = g_system->getMillis();
+	uint32 now = _vm->getTotalPlayTime();
 
-	track.isActive      = true;
+	track.isActive = true;
 	strcpy(track.name, name);
-	track.hash          = mix_id(name);
-	track.time1         = 1000 * timeRangeBegin;
-	track.time2         = 1000 * timeRangeEnd;
-	track.nextPlayTime  = now + _vm->_rnd.getRandomNumberRng(track.time1, track.time2);
-	track.volume1       = volumeRangeBegin;
-	track.volume2       = volumeRangeEnd;
-	track.volume        = 0;
-	track.pan1begin     = pan1begin;
-	track.pan1end       = pan1end;
-	track.pan2begin     = pan2begin;
-	track.pan2end       = pan2end;
-	track.priority      = priority;
+	track.hash = mix_id(name);
+	track.timeMin = 1000 * timeMin;
+	track.timeMax = 1000 * timeMax;
+	track.nextPlayTime = now + _vm->_rnd.getRandomNumberRng(track.timeMin, track.timeMax);
+	track.volumeMin = volumeMin;
+	track.volumeMax = volumeMax;
+	track.volume = 0;
+	track.panStartMin = panStartMin;
+	track.panStartMax = panStartMax;
+	track.panEndMin = panEndMin;
+	track.panEndMax = panEndMax;
+	track.priority = priority;
+}
+
+void AmbientSounds::removeNonLoopingSoundByIndex(int index, bool stopPlaying) {
+	NonLoopingSound &track = _nonLoopingSounds[index];
+	if (stopPlaying) {
+		if (track.isActive && track.audioPlayerTrack != -1 && _vm->_audioPlayer->isActive(track.audioPlayerTrack)) {
+			_vm->_audioPlayer->stop(track.audioPlayerTrack, stopPlaying);
+		}
+	}
+	track.isActive = false;
+	track.audioPlayerTrack = -1;
+	//	track.field_45 = 0;
+}
+
+void AmbientSounds::removeLoopingSoundByIndex(int index, int delay) {
+	LoopingSound &track = _loopingSounds[index];
+	if (track.isActive && track.audioPlayerTrack != -1 && _vm->_audioPlayer->isActive(track.audioPlayerTrack)) {
+		if (delay > 0) {
+			_vm->_audioPlayer->adjustVolume(track.audioPlayerTrack, 0, delay, false);
+		} else {
+			_vm->_audioPlayer->stop(track.audioPlayerTrack, false);
+		}
+	}
+	track.isActive = false;
+	track.name[0] = 0;
+	track.hash = 0;
+	track.audioPlayerTrack = -1;
+	track.volume = 0;
+	track.pan = 0;
 }
 
 } // End of namespace BladeRunner
diff --git a/engines/bladerunner/ambient_sounds.h b/engines/bladerunner/ambient_sounds.h
index e5428e9..292d817 100644
--- a/engines/bladerunner/ambient_sounds.h
+++ b/engines/bladerunner/ambient_sounds.h
@@ -30,23 +30,21 @@ namespace BladeRunner {
 class BladeRunnerEngine;
 
 class AmbientSounds {
-	BladeRunnerEngine *_vm;
-
 	struct NonLoopingSound {
 		bool   isActive;
 		char   name[13];
 		int32  hash;
-		int32  audio_player_track;
-		int32  time1;
-		int32  time2;
+		int32  audioPlayerTrack;
+		int32  timeMin;
+		int32  timeMax;
 		uint32 nextPlayTime;
-		int32  volume1;
-		int32  volume2;
+		int32  volumeMin;
+		int32  volumeMax;
 		int32  volume;
-		int32  pan1begin;
-		int32  pan1end;
-		int32  pan2begin;
-		int32  pan2end;
+		int32  panStartMin;
+		int32  panStartMax;
+		int32  panEndMin;
+		int32  panEndMax;
 		int32  priority;
 	};
 
@@ -54,9 +52,13 @@ class AmbientSounds {
 		bool  isActive;
 		char  name[13];
 		int32 hash;
+		int   audioPlayerTrack;
 		int32 volume;
+		int   pan;
 	};
 
+	BladeRunnerEngine *_vm;
+
 	NonLoopingSound *_nonLoopingSounds;
 	LoopingSound    *_loopingSounds;
 	int              _ambientVolume;
@@ -66,25 +68,31 @@ public:
 	~AmbientSounds();
 
 	void addSound(
-		int id,
-		int timeRangeBegin, int timeRangeEnd,
-		int volumeRangeBegin, int volumeRangeEnd,
-		int pan1begin, int pan1end,
-		int pan2begin, int pan2end,
-		int priority, int unk3
+		int sfxId,
+		int timeMin, int timeMax,
+		int volumeMin, int volumeMax,
+		int panStartMin, int panStartMax,
+		int panEndMin, int panEndMax,
+		int priority, int unk
 	);
-	// removeSound
-	// addSpeechSound
-	// removeSpeechSound
-	// playSound
-	// playSpeech
-	// removeAllNonLoopingSounds
-
-	// addLoopingSound
-	void addLoopingSound(int sfx_id, int volume, int unk, int fadeInTime);
-	// adjustLoopingSound
-	// removeLoopingSound
-	// removeAllLoopingSounds
+	void removeNonLoopingSound(int sfxId, bool stopPlaying);
+	void removeAllNonLoopingSounds(bool stopPlaying);
+
+	void addSpeech(
+		int actorId, int sentenceId,
+		int timeMin, int timeMax,
+		int volumeMin, int volumeMax,
+		int panStartMin, int panStartMax,
+		int panEndMin, int panEndMax,
+		int priority, int unk);
+	void playSound(int sfxId, int volume, int panStart, int panEnd, int priority);
+
+	void addLoopingSound(int sfxId, int volume, int pan, int delay);
+	void adjustLoopingSound(int sfxId, int volume, int pan, int delay);
+	// it seems there is little confusion in original code about delay parameter,
+	// sometimes it is used as boolean in same way as stopPlaying from non looping
+	void removeLoopingSound(int sfxId, int delay);
+	void removeAllLoopingSounds(int delay);
 
 	void tick();
 
@@ -108,11 +116,14 @@ private:
 
 	void addSoundByName(
 		const char *name,
-		int timeRangeBegin, int timeRangeEnd,
-		int volumeRangeBegin, int volumeRangeEnd,
-		int unk1RangeBegin, int unk1RangeEnd,
-		int unk2RangeBegin, int unk2RangeEnd,
-		int priority, int unk3);
+		int timeMin, int timeMax,
+		int volumeMin, int volumeMax,
+		int panStartMin, int panStartMax,
+		int panEndMin, int panEndMax,
+		int priority, int unk);
+
+	void removeNonLoopingSoundByIndex(int index, bool stopPlaying);
+	void removeLoopingSoundByIndex(int index, int delay);
 };
 
 } // End of namespace BladeRunner
diff --git a/engines/bladerunner/archive.cpp b/engines/bladerunner/archive.cpp
index 0051455..9c19ced 100644
--- a/engines/bladerunner/archive.cpp
+++ b/engines/bladerunner/archive.cpp
@@ -145,7 +145,7 @@ Common::SeekableReadStream *MIXArchive::createReadStreamForMember(const Common::
 	uint32 i = indexForId(id);
 
 	if (i == _entry_count)
-		return NULL;
+		return nullptr;
 
 	uint32 start = _entries[i].offset + 6 + 12 * _entry_count;
 	uint32 end   = _entries[i].length + start;
diff --git a/engines/bladerunner/aud_stream.cpp b/engines/bladerunner/aud_stream.cpp
index e859b33..c74421b 100644
--- a/engines/bladerunner/aud_stream.cpp
+++ b/engines/bladerunner/aud_stream.cpp
@@ -41,11 +41,15 @@ AudStream::AudStream(AudioCache *cache, int32 hash)
 
 void AudStream::init(byte *data) {
 	_data = data;
-	_end = _data + READ_LE_UINT32(_data + 2) + 12;
-	assert(_end - _data >= 12);
-
+	_frequency = READ_LE_UINT16(_data);
+	_size = READ_LE_UINT32(_data + 2);
+	_sizeDecompressed = READ_LE_UINT32(_data + 6);
+	_flags = *(_data + 10);
 	_compressionType = *(_data + 11);
 
+	_end = _data + _size + 12;
+	assert(_end - _data >= 12);
+
 	_deafBlockRemain = 0;
 	_p = _data + 12;
 }
@@ -113,4 +117,16 @@ bool AudStream::rewind() {
 	return true;
 }
 
+int AudStream::getLength()
+{
+	int bytesPerSecond = _frequency;
+	if (_flags & 1) { // 16 bit
+		bytesPerSecond *= 2;
+	}
+	if (_flags & 2) { // stereo
+		bytesPerSecond *= 2;
+	}
+	return (1000 * _sizeDecompressed) / bytesPerSecond;
+}
+
 } // End of namespace BladeRunner
diff --git a/engines/bladerunner/aud_stream.h b/engines/bladerunner/aud_stream.h
index 3279bda..f3117d7 100644
--- a/engines/bladerunner/aud_stream.h
+++ b/engines/bladerunner/aud_stream.h
@@ -39,8 +39,12 @@ class AudStream : public Audio::RewindableAudioStream {
 	byte       *_end;
 	AudioCache *_cache;
 	int32       _hash;
-	byte        _compressionType;
 	uint16      _deafBlockRemain;
+	uint16      _frequency;
+	uint32      _size;
+	uint32      _sizeDecompressed;
+	byte        _flags;
+	byte        _compressionType;
 
 	ADPCMWestwoodDecoder _decoder;
 
@@ -53,9 +57,10 @@ public:
 
 	int readBuffer(int16 *buffer, const int numSamples);
 	bool isStereo() const { return false; }
-	int getRate() const { return READ_LE_UINT16(_data); };
+	int getRate() const { return _frequency; };
 	bool endOfData() const { return _p == _end; }
 	bool rewind();
+	int getLength();
 };
 
 } // End of namespace BladeRunner
diff --git a/engines/bladerunner/audio_mixer.cpp b/engines/bladerunner/audio_mixer.cpp
new file mode 100644
index 0000000..0bd7b04
--- /dev/null
+++ b/engines/bladerunner/audio_mixer.cpp
@@ -0,0 +1,191 @@
+/* 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 "bladerunner/audio_mixer.h"
+
+#include "bladerunner/bladerunner.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+#include "common/timer.h"
+
+namespace BladeRunner {
+AudioMixer::AudioMixer(BladeRunnerEngine *vm):
+	_vm(vm)
+{
+	for (int i = 0; i < kAudioMixerChannels; i++) {
+		_channels[i].isPresent = false;
+	}
+	_vm->getTimerManager()->installTimerProc(timerCallback, 25 * 1000 , this, "BladeRunnerAudioMixerTimer");
+}
+
+AudioMixer::~AudioMixer() {
+	for (int i = 0; i < kAudioMixerChannels; i++) {
+		stop(i, 0);
+	}
+	_vm->getTimerManager()->removeTimerProc(timerCallback);
+}
+
+int AudioMixer::playStream(Audio::Mixer::SoundType type, Audio::RewindableAudioStream *stream, int priority, bool loop, int volume, int pan, void (*endCallback)(int, void*), void *callbackData) {
+	Common::StackLock lock(_mutex);
+
+	int channel = -1;
+	int lowestPriority = 1000000;
+	int lowestPriorityChannel = -1;
+	for (int i = 0; i < kAudioMixerChannels; i++) {
+		if (!_channels[i].isPresent) {
+			channel = i;
+			break;
+		}
+		if (_channels[i].priority < lowestPriority) {
+			lowestPriority = _channels[i].priority;
+			lowestPriorityChannel = i;
+		}
+	}
+	if (channel == -1) {
+		if (priority < lowestPriority) {
+			return -1;
+		}
+		stop(lowestPriorityChannel, 0);
+		channel = lowestPriorityChannel;
+	}
+
+	_channels[channel].isPresent = true;
+	_channels[channel].stream = stream;
+	_channels[channel].priority = priority;
+	_channels[channel].loop = loop;
+	_channels[channel].volume = volume;
+	_channels[channel].volumeTarget = 0;
+	_channels[channel].volumeDelta = 0;
+	_channels[channel].pan = pan;
+	_channels[channel].panTarget = 0;
+	_channels[channel].panDelta = 0;
+	_channels[channel].endCallback = endCallback;
+	_channels[channel].callbackData = callbackData;
+
+
+	Audio::AudioStream* audioStream = stream;
+
+	if (loop) {
+		audioStream = new Audio::LoopingAudioStream(stream, 0, DisposeAfterUse::YES);
+	}
+
+	_vm->_mixer->playStream(
+		type,
+		&_channels[channel].handle,
+		audioStream,
+		-1,
+		volume * 255 / 100,
+		pan * 127 / 100);
+
+	return channel;
+}
+
+void AudioMixer::stop(int channel, int time) {
+	Common::StackLock lock(_mutex);
+
+	if (_channels[channel].isPresent) {
+		if (time) {
+			adjustVolume(channel, 0, time);
+		} else {
+			_channels[channel].isPresent = false;
+			_vm->_mixer->stopHandle(_channels[channel].handle);
+			if (_channels[channel].endCallback != nullptr) {
+				_channels[channel].endCallback(channel, _channels[channel].callbackData);
+			}
+		}
+	}
+}
+
+bool AudioMixer::isActive(int channel) {
+	Common::StackLock lock(_mutex);
+
+	return _channels[channel].isPresent && _vm->_mixer->isSoundHandleActive(_channels[channel].handle);
+}
+
+void AudioMixer::timerCallback(void *self) {
+	((AudioMixer*)self)->tick();
+}
+
+void AudioMixer::adjustVolume(int channel, int newVolume, int time)
+{
+	Common::StackLock lock(_mutex);
+
+	if (_channels[channel].isPresent) {
+		_channels[channel].volumeTarget = newVolume;
+		_channels[channel].volumeDelta = ((newVolume - _channels[channel].volume) / (time / 60.0f)) / 40.0f;
+	}
+}
+
+void AudioMixer::adjustPan(int channel, int newPan, int time)
+{
+	Common::StackLock lock(_mutex);
+
+	if (_channels[channel].isPresent) {
+		newPan = CLIP(newPan, -100, 100);
+		_channels[channel].panTarget = newPan;
+		_channels[channel].panDelta = ((newPan - _channels[channel].pan) / (time / 60.0f)) / 40.0f;
+	}
+}
+
+void AudioMixer::tick()
+{
+	Common::StackLock lock(_mutex);
+
+	for (int i = 0; i < kAudioMixerChannels; i++) {
+		Channel *channel = &_channels[i];
+		if (!channel->isPresent) {
+			continue;
+		}
+
+		if (channel->volumeDelta != 0.0f) {
+			channel->volume = CLIP(channel->volume + channel->volumeDelta, 0.0f, 100.0f);
+
+			if ((channel->volumeDelta < 0 && channel->volume <= channel->volumeTarget) || (channel->volumeDelta > 0 && channel->volume >= channel->volumeTarget)) {
+				channel->volumeDelta = 0.0f;
+			}
+
+			_vm->_mixer->setChannelVolume(channel->handle, channel->volume * 255 / 100);
+
+			if (channel->volume <= 0.0f) {
+				stop(i, 0);
+			}
+		}
+
+		if (channel->panDelta != 0.0) {
+			channel->pan = CLIP(channel->pan + channel->panDelta, -100.0f, 100.0f);
+
+			if ((channel->panDelta < 0 && channel->pan <= channel->panTarget) || (channel->panDelta > 0 && channel->pan >= channel->panTarget)) {
+				channel->panDelta = 0.0f;
+			}
+
+			_vm->_mixer->setChannelBalance(channel->handle, channel->pan * 127 / 100);
+		}
+
+		if (!_vm->_mixer->isSoundHandleActive(channel->handle) || channel->stream->endOfStream()) {
+			stop(i, 0);
+		}
+	}
+}
+
+} // End of namespace BladeRunner
diff --git a/engines/bladerunner/audio_mixer.h b/engines/bladerunner/audio_mixer.h
new file mode 100644
index 0000000..75a0f08
--- /dev/null
+++ b/engines/bladerunner/audio_mixer.h
@@ -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.
+ *
+ */
+
+#ifndef BLADERUNNER_AUDIO_MIXER_H
+#define BLADERUNNER_AUDIO_MIXER_H
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+#include "common/mutex.h"
+
+namespace BladeRunner {
+
+class BladeRunnerEngine;
+
+class AudioMixer {
+	static const int kAudioMixerChannels = 9;
+
+	struct Channel {
+		bool isPresent;
+		int  priority;
+		bool loop;
+		Audio::SoundHandle  handle;
+		Audio::AudioStream *stream;
+		float  volume;
+		float  volumeDelta;
+		float  volumeTarget;
+		float  pan;
+		float  panDelta;
+		float  panTarget;
+		void (*endCallback)(int channel, void *data);
+		void  *callbackData;
+	};
+
+	BladeRunnerEngine *_vm;
+
+	Channel       _channels[kAudioMixerChannels];
+	Common::Mutex _mutex;
+
+public:
+	AudioMixer(BladeRunnerEngine *vm);
+	~AudioMixer();
+
+	int playStream(Audio::Mixer::SoundType type, Audio::RewindableAudioStream *stream, int priority, bool loop, int volume, int pan, void(*onEndCallback)(int, void *), void *callbackData);
+	void stop(int channel, int delay);
+
+	void adjustVolume(int channel, int newVolume, int time);
+	void adjustPan(int channel, int newPan, int time);
+
+	void resume(int channel, int delay);
+	void pause(int channel, int delay);
+
+private:
+	bool isActive(int channel);
+	void tick();
+	static void timerCallback(void *refCon);
+};
+
+} // End of namespace BladeRunner
+
+#endif
diff --git a/engines/bladerunner/audio_player.cpp b/engines/bladerunner/audio_player.cpp
index 0c2b747..f5ba83c 100644
--- a/engines/bladerunner/audio_player.cpp
+++ b/engines/bladerunner/audio_player.cpp
@@ -20,15 +20,13 @@
  *
  */
 
-#include "audio_player.h"
-
-#include "bladerunner/archive.h"
-#include "bladerunner/aud_stream.h"
+#include "bladerunner/audio_player.h"
 
 #include "bladerunner/bladerunner.h"
 
-#include "audio/audiostream.h"
-#include "audio/mixer.h"
+#include "bladerunner/archive.h"
+#include "bladerunner/aud_stream.h"
+#include "bladerunner/audio_mixer.h"
 
 #include "common/debug.h"
 #include "common/stream.h"
@@ -79,7 +77,7 @@ byte *AudioCache::findByHash(int32 hash) {
 		}
 	}
 
-	return NULL;
+	return nullptr;
 }
 
 void AudioCache::storeByHash(int32 hash, Common::SeekableReadStream *stream) {
@@ -110,7 +108,7 @@ void AudioCache::incRef(int32 hash) {
 			return;
 		}
 	}
-	assert(0 && "AudioCache::incRef: hash not found");
+	assert(false && "AudioCache::incRef: hash not found");
 }
 
 void AudioCache::decRef(int32 hash) {
@@ -123,7 +121,7 @@ void AudioCache::decRef(int32 hash) {
 			return;
 		}
 	}
-	assert(0 && "AudioCache::decRef: hash not found");
+	assert(false && "AudioCache::decRef: hash not found");
 }
 
 AudioPlayer::AudioPlayer(BladeRunnerEngine *vm) : _vm(vm) {
@@ -133,68 +131,111 @@ AudioPlayer::AudioPlayer(BladeRunnerEngine *vm) : _vm(vm) {
 		_tracks[i].hash = 0;
 		_tracks[i].priority = 0;
 	}
+
+	_sfxVolume = 65;
 }
 
 AudioPlayer::~AudioPlayer() {
+	stopAll();
 	delete _cache;
 }
 
-bool AudioPlayer::isTrackActive(Track *track) {
-	if (!track->isMaybeActive)
-		return false;
+void AudioPlayer::stopAll() {
+	for (int i = 0; i != kTracks; ++i) {
+		stop(i, false);
+	}
+	for (int i = 0; i != kTracks; ++i) {
+		while (isActive(i)) {
+			// wait for all tracks to finish
+		}
+	}
+}
+
+void AudioPlayer::adjustVolume(int track, int volume, int delay, bool overrideVolume) {
+	if (track < 0 || track >= kTracks || !_tracks[track].isActive || _tracks[track].channel == -1) {
+		return;
+	}
+
+	int actualVolume = volume;
+	if (!overrideVolume) {
+		actualVolume = actualVolume * _sfxVolume / 100;
+	}
 
-	return track->isMaybeActive = _vm->_mixer->isSoundHandleActive(track->soundHandle);
+	_tracks[track].volume = volume;
+	_vm->_audioMixer->adjustVolume(_tracks[track].channel, volume, 60 * delay);
 }
 
-void AudioPlayer::stopAll() {
-	for (int i = 0; i != TRACKS; ++i) {
-		_vm->_mixer->stopHandle(_tracks[i].soundHandle);
+void AudioPlayer::adjustPan(int track, int pan, int delay) {
+	if (track < 0 || track >= kTracks || !_tracks[track].isActive || _tracks[track].channel == -1) {
+		return;
 	}
+
+	_tracks[track].pan = pan;
+	_vm->_audioMixer->adjustPan(_tracks[track].channel, pan, 60 * delay);
 }
 
-void AudioPlayer::fadeAndStopTrack(Track *track, int time) {
-	(void)time;
+//void AudioPlayer::tick() {
+//	for (int i = 0; i != 6; ++i) {
+//		Track *ti = &_tracks[i];
+//	}
+//}
 
-	_vm->_mixer->stopHandle(track->soundHandle);
+void AudioPlayer::remove(int channel) {
+	Common::StackLock lock(_mutex);
+	for (int i = 0; i != kTracks; ++i) {
+		if (_tracks[i].channel == channel) {
+			_tracks[i].isActive = false;
+			_tracks[i].priority = 0;
+			_tracks[i].channel = -1;
+			//_cache->decRef(_tracks[i].hash);
+			break;
+		}
+	}
+}
+
+void AudioPlayer::mixerChannelEnded(int channel, void *data) {
+	AudioPlayer *audioPlayer = (AudioPlayer *)data;
+	audioPlayer->remove(channel);
 }
 
 int AudioPlayer::playAud(const Common::String &name, int volume, int panFrom, int panTo, int priority, byte flags) {
 	/* Find first available track or, alternatively, the lowest priority playing track */
-	Track *track = NULL;
-	int    lowestPriority = 1000000;
-	Track *lowestPriorityTrack = NULL;
+	int track = -1;
+	int lowestPriority = 1000000;
+	int lowestPriorityTrack = -1;
 
 	for (int i = 0; i != 6; ++i) {
-		Track *ti = &_tracks[i];
-		if (!isTrackActive(ti)) {
-			track = ti;
+		if (!isActive(i)) {
+			track = i;
 			break;
 		}
 
-		if (lowestPriorityTrack == NULL || ti->priority < lowestPriority) {
-			lowestPriority = ti->priority;
-			lowestPriorityTrack = ti;
+		if (lowestPriorityTrack == -1 || _tracks[i].priority < lowestPriority) {
+			lowestPriority = _tracks[i].priority;
+			lowestPriorityTrack = i;
 		}
 	}
 
 	/* If there's no available track, stop the lowest priority track if it's lower than
 	 * the new priority
 	 */
-	if (track == NULL && lowestPriority < priority) {
-		fadeAndStopTrack(lowestPriorityTrack, 1);
+	if (track == -1 && lowestPriority < priority) {
+		stop(lowestPriorityTrack, true);
 		track = lowestPriorityTrack;
 	}
 
 	/* If there's still no available track, give up */
-	if (track == NULL)
+	if (track == -1) {
 		return -1;
+	}
 
 	/* Load audio resource and store in cache. Playback will happen directly from there. */
 	int32 hash = mix_id(name);
 	if (!_cache->findByHash(hash)) {
 		Common::SeekableReadStream *r = _vm->getResourceStream(name);
-		if (!r)
+		if (!r) {
 			return -1;
+		}
 
 		int32 size = r->size();
 		while (!_cache->canAllocate(size)) {
@@ -207,34 +248,55 @@ int AudioPlayer::playAud(const Common::String &name, int volume, int panFrom, in
 		delete r;
 	}
 
-	AudStream *audStream = new AudStream(_cache, hash);
+	AudStream *audioStream = new AudStream(_cache, hash);
 
-	Audio::AudioStream *audioStream = audStream;
-	if (flags & LOOP) {
-		audioStream = new Audio::LoopingAudioStream(audStream, 0, DisposeAfterUse::YES);
+	int actualVolume = volume;
+	if (!(flags & OVERRIDE_VOLUME)) {
+		actualVolume = _sfxVolume * volume / 100;
 	}
 
-	Audio::SoundHandle soundHandle;
-
 	// debug("PlayStream: %s", name.c_str());
 
-	int balance = panFrom;
-
-	_vm->_mixer->playStream(
+	int channel = _vm->_audioMixer->playStream(
 		Audio::Mixer::kPlainSoundType,
-		&soundHandle,
 		audioStream,
-		-1,
-		volume * 255 / 100,
-		balance);
+		priority,
+		flags & LOOP,
+		actualVolume,
+		panFrom,
+		mixerChannelEnded,
+		this);
+
+	if (channel == -1) {
+		return -1;
+	}
+
+	if (panFrom != panTo) {
+		_vm->_audioMixer->adjustPan(channel, panTo, (60 * audioStream->getLength()) / 1000);
+	}
+
+	_tracks[track].isActive = true;
+	_tracks[track].channel  = channel;
+	_tracks[track].priority = priority;
+	_tracks[track].hash     = hash;
+	_tracks[track].volume   = actualVolume;
+
+	return track;
+}
+
+bool AudioPlayer::isActive(int track) {
+	Common::StackLock lock(_mutex);
+	if (track < 0 || track >= kTracks) {
+		return false;
+	}
 
-	track->isMaybeActive = true;
-	track->soundHandle   = soundHandle;
-	track->priority      = priority;
-	track->hash          = hash;
-	track->volume        = volume;
+	return _tracks[track].isActive;
+}
 
-	return track - &_tracks[0];
+void AudioPlayer::stop(int track, bool immediately) {
+	if (isActive(track)) {
+		_vm->_audioMixer->stop(_tracks[track].channel, immediately ? 0 : 60);
+	}
 }
 
 } // End of namespace BladeRunner
diff --git a/engines/bladerunner/audio_player.h b/engines/bladerunner/audio_player.h
index 4260a7b..c573c64 100644
--- a/engines/bladerunner/audio_player.h
+++ b/engines/bladerunner/audio_player.h
@@ -20,22 +20,18 @@
  *
  */
 
-#ifndef BLADERUNNER_AUDIO_H
-#define BLADERUNNER_AUDIO_H
+#ifndef BLADERUNNER_AUDIO_PLAYER_H
+#define BLADERUNNER_AUDIO_PLAYER_H
 
-#include "audio/mixer.h"
 #include "common/array.h"
 #include "common/mutex.h"
 #include "common/str.h"
-#include "common/types.h"
 
 namespace BladeRunner {
 
 class BladeRunnerEngine;
 class AudioCache;
 
-#define TRACKS 6
-
 /*
  * This is a poor imitation of Bladerunner's resource cache
  */
@@ -54,6 +50,7 @@ class AudioCache {
 	uint32 _totalSize;
 	uint32 _maxSize;
 	uint32 _accessCounter;
+
 public:
 	AudioCache() :
 		_totalSize(0),
@@ -72,23 +69,25 @@ public:
 };
 
 class AudioPlayer {
-	BladeRunnerEngine *_vm;
-	AudioCache *_cache;
+	static const int kTracks = 6;
 
 	struct Track {
-		bool               isMaybeActive;
-		Audio::SoundHandle soundHandle;
+		bool               isActive;
+		int                channel;
 		int                priority;
 		int32              hash;
 		int                volume;
+		int                pan;
 
-		Track() : isMaybeActive(false) {}
+		Track() : isActive(false) {}
 	};
 
-	Track _tracks[TRACKS];
+	BladeRunnerEngine *_vm;
 
-	bool isTrackActive(Track *track);
-	void fadeAndStopTrack(Track *track, int time);
+	Common::Mutex _mutex;
+	AudioCache   *_cache;
+	Track         _tracks[kTracks];
+	int           _sfxVolume;
 
 public:
 	AudioPlayer(BladeRunnerEngine *vm);
@@ -99,9 +98,16 @@ public:
 		OVERRIDE_VOLUME = 2
 	};
 
-	int playAud(const Common::String &name, int volume, int panFrom, int panTo, int priority, byte flags = 0);
-
+	int playAud(const Common::String &name, int volume, int panStart, int panEnd, int priority, byte flags = 0);
+	bool isActive(int track);
+	void stop(int track, bool immediately);
 	void stopAll();
+	void adjustVolume(int track, int volume, int delay, bool overrideVolume);
+	void adjustPan(int track, int pan, int delay);
+
+private:
+	void remove(int channel);
+	static void mixerChannelEnded(int channel, void *data);
 };
 
 } // End of namespace BladeRunner
diff --git a/engines/bladerunner/audio_speech.cpp b/engines/bladerunner/audio_speech.cpp
index 2d06025..7177cf0 100644
--- a/engines/bladerunner/audio_speech.cpp
+++ b/engines/bladerunner/audio_speech.cpp
@@ -23,6 +23,7 @@
 #include "bladerunner/audio_speech.h"
 
 #include "bladerunner/aud_stream.h"
+#include "bladerunner/audio_mixer.h"
 #include "bladerunner/bladerunner.h"
 
 #include "common/debug.h"
@@ -31,17 +32,29 @@ namespace BladeRunner {
 
 #define BUFFER_SIZE 200000
 
+void AudioSpeech::ended() {
+	//Common::StackLock lock(_mutex);
+	_isActive = false;
+	_channel = -1;
+}
+
+void AudioSpeech::mixerChannelEnded(int channel, void *data) {
+	AudioSpeech *audioSpeech = (AudioSpeech*)data;
+	audioSpeech->ended();
+}
+
 AudioSpeech::AudioSpeech(BladeRunnerEngine *vm) : _vm(vm) {
 	_volume = 50;
-	_isMaybeActive = false;
+	_isActive = false;
 	_data = new byte[BUFFER_SIZE];
+	_channel = -1;
 }
 
 AudioSpeech::~AudioSpeech() {
 	delete[] _data;
 }
 
-bool AudioSpeech::playSpeech(const char *name, int balance) {
+bool AudioSpeech::playSpeech(const char *name, int pan) {
 	// debug("AudioSpeech::playSpeech(\"%s\")", name);
 	Common::ScopedPtr<Common::SeekableReadStream> r(_vm->getResourceStream(name));
 
@@ -67,28 +80,35 @@ bool AudioSpeech::playSpeech(const char *name, int balance) {
 
 	AudStream *audioStream = new AudStream(_data);
 
-	_vm->_mixer->playStream(
-		Audio::Mixer::kPlainSoundType,
-		&_soundHandle,
+	// TODO: shorty mode - set rate of sound to 33khz
+
+	_channel = _vm->_audioMixer->playStream(
+		Audio::Mixer::kSpeechSoundType,
 		audioStream,
-		-1,
-		_volume * 255 / 100,
-		balance);
+		100,
+		false,
+		_volume,
+		pan,
+		mixerChannelEnded,
+		this);
 
-	_isMaybeActive = true;
+	_isActive = true;
 
 	return true;
 }
 
 void AudioSpeech::stopSpeech() {
-	_vm->_mixer->stopHandle(_soundHandle);
+	//Common::StackLock lock(_mutex);
+	if (_channel != -1) {
+		_vm->_audioMixer->stop(_channel, 0);
+	}
 }
 
 bool AudioSpeech::isPlaying() {
-	if (!_isMaybeActive)
-		return false;
-
-	return _isMaybeActive = _vm->_mixer->isSoundHandleActive(_soundHandle);
+	if (_channel == -1) {
+		return  false;
+	}
+	return _isActive;
 }
 
 } // End of namespace BladeRunner
diff --git a/engines/bladerunner/audio_speech.h b/engines/bladerunner/audio_speech.h
index ae70658..f3e4395 100644
--- a/engines/bladerunner/audio_speech.h
+++ b/engines/bladerunner/audio_speech.h
@@ -23,18 +23,18 @@
 #ifndef BLADERUNNER_AUDIO_SPEECH_H
 #define BLADERUNNER_AUDIO_SPEECH_H
 
-#include "audio/mixer.h"
+#include "common/types.h"
 
 namespace BladeRunner {
 
 class BladeRunnerEngine;
 
 class AudioSpeech {
-private:
 	BladeRunnerEngine  *_vm;
+
 	int                 _volume;
-	bool                _isMaybeActive;
-	Audio::SoundHandle  _soundHandle;
+	bool                _isActive;
+	int                 _channel;
 	byte               *_data;
 
 public:
@@ -45,6 +45,10 @@ public:
 	void stopSpeech();
 	bool isPlaying();
 	void setVolume(int volume) { _volume = volume; }
+
+private:
+	void ended();
+	static void mixerChannelEnded(int channel, void *data);
 };
 
 } // End of namespace BladeRunner
diff --git a/engines/bladerunner/bladerunner.cpp b/engines/bladerunner/bladerunner.cpp
index a3ac82d..7fe3f9e 100644
--- a/engines/bladerunner/bladerunner.cpp
+++ b/engines/bladerunner/bladerunner.cpp
@@ -25,6 +25,7 @@
 #include "bladerunner/actor.h"
 #include "bladerunner/adq.h"
 #include "bladerunner/ambient_sounds.h"
+#include "bladerunner/audio_mixer.h"
 #include "bladerunner/audio_player.h"
 #include "bladerunner/audio_speech.h"
 #include "bladerunner/chapters.h"
@@ -42,6 +43,7 @@
 #include "bladerunner/mouse.h"
 #include "bladerunner/outtake.h"
 #include "bladerunner/obstacles.h"
+#include "bladerunner/regions.h"
 #include "bladerunner/scene.h"
 #include "bladerunner/scene_objects.h"
 #include "bladerunner/script/init.h"
@@ -52,6 +54,7 @@
 #include "bladerunner/slice_animations.h"
 #include "bladerunner/slice_renderer.h"
 #include "bladerunner/spinner.h"
+#include "bladerunner/suspects_database.h"
 #include "bladerunner/text_resource.h"
 #include "bladerunner/vqa_decoder.h"
 #include "bladerunner/waypoints.h"
@@ -65,7 +68,7 @@
 #include "engines/util.h"
 
 #include "graphics/pixelformat.h"
-#include "suspects_database.h"
+
 
 namespace BladeRunner {
 
@@ -207,7 +210,7 @@ bool BladeRunnerEngine::startup(bool hasSavegames) {
 
 	_items = new Items(this);
 
-	// Setup sound output
+	_audioMixer = new AudioMixer(this);
 
 	_audioPlayer = new AudioPlayer(this);
 
@@ -290,7 +293,6 @@ bool BladeRunnerEngine::startup(bool hasSavegames) {
 
 	// TODO: KIA
 
-	// TODO: Spinner Interface
 	_spinner = new Spinner(this);
 
 	_elevator = new Elevator(this);
@@ -436,7 +438,7 @@ void BladeRunnerEngine::shutdown() {
 
 	delete _audioPlayer;
 
-	// Shutdown sound output
+	delete _audioMixer;
 
 	if (isArchiveOpen("MUSIC.MIX"))
 		closeArchive("MUSIC.MIX");
@@ -485,7 +487,8 @@ void BladeRunnerEngine::shutdown() {
 
 	// TODO: Delete Elevators
 
-	// TODO: Delete Spinner Interface
+	delete _spinner;
+	_spinner = nullptr;
 
 	// TODO: Delete KIA
 
@@ -586,9 +589,15 @@ void BladeRunnerEngine::gameTick() {
 
 	if (_gameIsRunning && _windowIsActive) {
 		// TODO: Only run if not in Kia, script, nor AI
-		_settings->openNewScene();
+		if (!_sceneScript->IsInsideScript() && !_aiScripts->IsInsideScript()) {
+			_settings->openNewScene();
+		}
 
 		// TODO: Autosave
+
+		//probably not needed, this version of tick is just loading data from buffer
+		//_audioMixer->tick();
+
 		// TODO: Kia
 
 		if (_spinner->isOpen()) {
@@ -664,14 +673,13 @@ void BladeRunnerEngine::gameTick() {
 
 			if (_dialogueMenu->isVisible()) {
 				_dialogueMenu->tick(p.x, p.y);
-				_dialogueMenu->draw();
+				_dialogueMenu->draw(_surfaceGame);
 			}
 
 			_mouse->tick(p.x, p.y);
 			_mouse->draw(_surfaceGame, p.x, p.y);
 
 			// TODO: Process AUD
-			// TODO: Footstep sound
 
 			if (_walkSoundId >= 0) {
 				const char *name = _gameInfo->getSfxTrack(_walkSoundId);
@@ -824,21 +832,41 @@ void BladeRunnerEngine::handleEvents() {
 	Common::EventManager *eventMan = _system->getEventManager();
 	while (eventMan->pollEvent(event)) {
 		switch (event.type) {
+		case Common::EVENT_KEYUP:
+			handleKeyUp(event);
+			break;
+		case Common::EVENT_KEYDOWN:
+			handleKeyDown(event);
+			break;
+		case Common::EVENT_LBUTTONUP:
+			handleMouseAction(event.mouse.x, event.mouse.y, true, false);
+			break;
+		case Common::EVENT_RBUTTONUP:
+		case Common::EVENT_MBUTTONUP:
+			handleMouseAction(event.mouse.x, event.mouse.y, false, false);
+			break;
 		case Common::EVENT_LBUTTONDOWN:
+			handleMouseAction(event.mouse.x, event.mouse.y, true, true);
+			break;
 		case Common::EVENT_RBUTTONDOWN:
-		case Common::EVENT_LBUTTONUP:
-		case Common::EVENT_RBUTTONUP: {
-			bool buttonLeft = event.type == Common::EVENT_LBUTTONDOWN || event.type == Common::EVENT_LBUTTONUP;
-			bool buttonDown = event.type == Common::EVENT_LBUTTONDOWN || event.type == Common::EVENT_RBUTTONDOWN;
-
-			handleMouseAction(event.mouse.x, event.mouse.y, buttonLeft, buttonDown);
-		}
+		case Common::EVENT_MBUTTONDOWN:
+			handleMouseAction(event.mouse.x, event.mouse.y, false, true);
+			break;
 		default:
-			;
+			; // nothing to do
 		}
 	}
 }
 
+void BladeRunnerEngine::handleKeyUp(Common::Event &event) {
+	if (event.kbd.keycode == Common::KEYCODE_RETURN) {
+		_speechSkipped = true;
+	}
+}
+
+void BladeRunnerEngine::handleKeyDown(Common::Event &event) {
+}
+
 void BladeRunnerEngine::handleMouseAction(int x, int y, bool buttonLeft, bool buttonDown) {
 	if (!playerHasControl() || _mouse->isDisabled())
 		return;
@@ -868,40 +896,42 @@ void BladeRunnerEngine::handleMouseAction(int x, int y, bool buttonLeft, bool bu
 		return;
 	}
 
-	Vector3 mousePosition = _mouse->getXYZ(x, y);
+	if (buttonLeft && !buttonDown) {
+		Vector3 mousePosition = _mouse->getXYZ(x, y);
 
-	int isClickable;
-	int isObstacle;
-	int isTarget;
+		int isClickable;
+		int isObstacle;
+		int isTarget;
 
-	int sceneObjectId = _sceneObjects->findByXYZ(&isClickable, &isObstacle, &isTarget, mousePosition.x, mousePosition.y, mousePosition.z, 1, 0, 1);
-	int exitIndex = _scene->_exits->getRegionAtXY(x, y);
+		int sceneObjectId = _sceneObjects->findByXYZ(&isClickable, &isObstacle, &isTarget, mousePosition.x, mousePosition.y, mousePosition.z, true, false, true);
+		int exitIndex = _scene->_exits->getRegionAtXY(x, y);
 
-	if ((sceneObjectId < 0 || sceneObjectId > 73) && exitIndex >= 0) {
-		handleMouseClickExit(x, y, exitIndex);
-		return;
-	}
+		if ((sceneObjectId < 0 || sceneObjectId > 73) && exitIndex >= 0) {
+			handleMouseClickExit(x, y, exitIndex);
+			return;
+		}
 
-	int regionIndex = _scene->_regions->getRegionAtXY(x, y);
-	if (regionIndex >= 0) {
-		handleMouseClickRegion(x, y, regionIndex);
-		return;
-	}
+		int regionIndex = _scene->_regions->getRegionAtXY(x, y);
+		if (regionIndex >= 0) {
+			handleMouseClickRegion(x, y, regionIndex);
+			return;
+		}
 
-	if (sceneObjectId == -1) {
-		bool isRunning;
-		_playerActor->loopWalkToXYZ(mousePosition, 0, false, false, false, &isRunning);
-		debug("Clicked on nothing %f, %f, %f", mousePosition.x, mousePosition.y, mousePosition.z);
-		return;
-	} else if (sceneObjectId >= 0 && sceneObjectId <= 73) {
-		handleMouseClickActor(x, y, sceneObjectId);
-		return;
-	} else if (sceneObjectId >= 74 && sceneObjectId <= 197) {
-		handleMouseClickItem(x, y, sceneObjectId - 74);
-		return;
-	} else if (sceneObjectId >= 198 && sceneObjectId <= 293) {
-		handleMouseClick3DObject(x, y, sceneObjectId - 198, isClickable, isTarget);
-		return;
+		if (sceneObjectId == -1) {
+			bool isRunning;
+			_playerActor->loopWalkToXYZ(mousePosition, 0, false, false, false, &isRunning);
+			debug("Clicked on nothing %f, %f, %f", mousePosition.x, mousePosition.y, mousePosition.z);
+			return;
+		} else if (sceneObjectId >= 0 && sceneObjectId <= 73) {
+			handleMouseClickActor(x, y, sceneObjectId);
+			return;
+		} else if (sceneObjectId >= 74 && sceneObjectId <= 197) {
+			handleMouseClickItem(x, y, sceneObjectId - 74);
+			return;
+		} else if (sceneObjectId >= 198 && sceneObjectId <= 293) {
+			handleMouseClick3DObject(x, y, sceneObjectId - 198, isClickable, isTarget);
+			return;
+		}
 	}
 }
 
@@ -939,8 +969,9 @@ void BladeRunnerEngine::gameWaitForActive() {
 }
 
 void BladeRunnerEngine::loopActorSpeaking() {
-	if (!_audioSpeech->isPlaying())
+	if (!_audioSpeech->isPlaying()) {
 		return;
+	}
 
 	playerLosesControl();
 
@@ -1019,7 +1050,7 @@ Common::SeekableReadStream *BladeRunnerEngine::getResourceStream(const Common::S
 	}
 
 	debug("getResource: Resource %s not found.", name.c_str());
-	return 0;
+	return nullptr;
 }
 
 bool BladeRunnerEngine::playerHasControl() {
diff --git a/engines/bladerunner/bladerunner.h b/engines/bladerunner/bladerunner.h
index d58f9a5..5164f35 100644
--- a/engines/bladerunner/bladerunner.h
+++ b/engines/bladerunner/bladerunner.h
@@ -32,7 +32,10 @@
 #include "engines/engine.h"
 
 #include "graphics/surface.h"
-#include "suspects_database.h"
+
+namespace Common {
+struct Event;
+}
 
 namespace BladeRunner {
 
@@ -49,6 +52,7 @@ class Actor;
 class ADQ;
 class AIScripts;
 class AmbientSounds;
+class AudioMixer;
 class AudioPlayer;
 class AudioSpeech;
 class Chapters;
@@ -72,6 +76,7 @@ class Shape;
 class SliceAnimations;
 class SliceRenderer;
 class Spinner;
+class SuspectsDatabase;
 class TextResource;
 class View;
 class Waypoints;
@@ -89,6 +94,7 @@ public:
 	ADQ              *_adq;
 	AIScripts        *_aiScripts;
 	AmbientSounds    *_ambientSounds;
+	AudioMixer       *_audioMixer;
 	AudioPlayer      *_audioPlayer;
 	AudioSpeech      *_audioSpeech;
 	Chapters         *_chapters;
@@ -176,6 +182,8 @@ public:
 	void gameTick();
 	void actorsUpdate();
 	void handleEvents();
+	void handleKeyUp(Common::Event &event);
+	void handleKeyDown(Common::Event &event);
 	void handleMouseAction(int x, int y, bool buttonLeft, bool buttonDown);
 	void handleMouseClickExit(int x, int y, int exitIndex);
 	void handleMouseClickRegion(int x, int y, int regionIndex);
diff --git a/engines/bladerunner/color.h b/engines/bladerunner/color.h
index a93d79b..f979652 100644
--- a/engines/bladerunner/color.h
+++ b/engines/bladerunner/color.h
@@ -32,7 +32,6 @@ struct Color {
 	float g;
 	float b;
 
-
 	Color() {
 	}
 
diff --git a/engines/bladerunner/dialogue_menu.cpp b/engines/bladerunner/dialogue_menu.cpp
index 2ef137f..40ec3ae 100644
--- a/engines/bladerunner/dialogue_menu.cpp
+++ b/engines/bladerunner/dialogue_menu.cpp
@@ -23,13 +23,14 @@
 #include "bladerunner/dialogue_menu.h"
 
 #include "bladerunner/bladerunner.h"
-
 #include "bladerunner/font.h"
 #include "bladerunner/mouse.h"
+#include "bladerunner/settings.h"
 #include "bladerunner/shape.h"
 #include "bladerunner/text_resource.h"
 
 #include "common/debug.h"
+#include "common/rect.h"
 #include "common/util.h"
 
 #define LINE_HEIGHT  9
@@ -87,7 +88,7 @@ bool DialogueMenu::showAt(int x, int y) {
 }
 
 bool DialogueMenu::hide() {
-	_waitingForInput = 0;
+	_waitingForInput = false;
 	if (!_isVisible) {
 		return false;
 	}
@@ -102,24 +103,27 @@ bool DialogueMenu::clearList() {
 	return true;
 }
 
-bool DialogueMenu::addToList(int answer, int a3, int a4, int a5, int a6) {
-	if (_listSize >= 10)
+bool DialogueMenu::addToList(int answer, bool done, int priorityPolite, int priorityNormal, int prioritySurly) {
+	if (_listSize >= 10) {
 		return false;
-	if (getAnswerIndex(answer) != -1)
+	}
+	if (getAnswerIndex(answer) != -1) {
 		return false;
+	}
 
 	const char *text = _textResource->getText(answer);
-	if (!text || strlen(text) >= 50)
+	if (!text || strlen(text) >= 50) {
 		return false;
+	}
 
 	int index = _listSize++;
-	strcpy(_items[index].text, text);
+	_items[index].text = text;
 	_items[index].answerValue = answer;
-	_items[index].field_36 = 0;
-	_items[index].field_46 = a3;
-	_items[index].field_3A = a4;
-	_items[index].field_3E = a5;
-	_items[index].field_42 = a6;
+	_items[index].colorIntensity = 0;
+	_items[index].isDone = done;
+	_items[index].priorityPolite = priorityPolite;
+	_items[index].priorityNormal = priorityNormal;
+	_items[index].prioritySurly = prioritySurly;
 
 	// CHECK(madmoose): BLADE.EXE calls this needlessly
 	// calculatePosition();
@@ -127,7 +131,7 @@ bool DialogueMenu::addToList(int answer, int a3, int a4, int a5, int a6) {
 	return true;
 }
 
-bool DialogueMenu::addToListNeverRepeatOnceSelected(int answer, int a3, int a4, int a5) {
+bool DialogueMenu::addToListNeverRepeatOnceSelected(int answer, int priorityPolite, int priorityNormal, int prioritySurly) {
 	for (int i = 0; i != _neverRepeatListSize; ++i) {
 		if (answer == _neverRepeatValues[i] && _neverRepeatWasSelected[i]) {
 			return true;
@@ -137,7 +141,7 @@ bool DialogueMenu::addToListNeverRepeatOnceSelected(int answer, int a3, int a4,
 	_neverRepeatValues[_neverRepeatListSize] = answer;
 	_neverRepeatWasSelected[_neverRepeatListSize] = false;
 	++_neverRepeatListSize;
-	return addToList(answer, 0, a3, a4, a5);
+	return addToList(answer, false, priorityPolite, priorityNormal, prioritySurly);
 }
 
 int DialogueMenu::queryInput() {
@@ -149,20 +153,22 @@ int DialogueMenu::queryInput() {
 		_selectedItemIndex = 0;
 		answer = _items[0].answerValue;
 	} else if (_listSize == 2) {
-		if (_items[0].field_46) {
+		if (_items[0].isDone) {
 			_selectedItemIndex = 1;
 			answer = _items[0].answerValue;
-		} else if (_items[0].field_46) {
+		} else if (_items[0].isDone) {
 			_selectedItemIndex = 0;
 			answer = _items[1].answerValue;
 		}
 	}
 
 	if (answer == -1) {
-		int agenda = 4; //_vm->_settings.getPlayerAgenda();
-		if (agenda == 4) {
+		int agenda = _vm->_settings->getPlayerAgenda();
+		agenda = kPlayerAgendaUserChoice;
+		if (agenda == kPlayerAgendaUserChoice) {
 			_waitingForInput = true;
 			do {
+				// TODO: game resuming
 				// if (!_vm->_gameRunning)
 				// 	break;
 
@@ -176,14 +182,13 @@ int DialogueMenu::queryInput() {
 
 				_vm->gameTick();
 			} while (_waitingForInput);
-
-		} else if (agenda == 3) {
+		} else if (agenda == kPlayerAgendaErratic) {
 			int tries = 0;
 			bool searching = true;
 			int i;
 			do {
 				i = _vm->_rnd.getRandomNumber(_listSize - 1);
-				if (!_items[i].field_46) {
+				if (!_items[i].isDone) {
 					searching = false;
 				} else if (++tries > 1000) {
 					searching = false;
@@ -192,7 +197,21 @@ int DialogueMenu::queryInput() {
 			} while (searching);
 			_selectedItemIndex = i;
 		} else {
-			error("unimplemented...");
+			int priority = -1;
+			for (int i = 0; i < _listSize; i++) {
+				int priorityCompare = -1;
+				if (agenda == kPlayerAgendaPolite) {
+					priorityCompare = _items[i].priorityPolite;
+				} else if (agenda == kPlayerAgendaNormal) {
+					priorityCompare = _items[i].priorityNormal;
+				} else if (agenda == kPlayerAgendaSurly) {
+					priorityCompare = _items[i].prioritySurly;
+				}
+				if (priority < priorityCompare) {
+					priority = priorityCompare;
+					_selectedItemIndex = i;
+				}
+			}
 		}
 	}
 
@@ -205,7 +224,7 @@ int DialogueMenu::queryInput() {
 	}
 
 	if (_selectedItemIndex >= 0) {
-		debug("DM Query Input: %d %s", answer, _items[_selectedItemIndex].text);
+		debug("DM Query Input: %d %s", answer, _items[_selectedItemIndex].text.c_str());
 	}
 
 	return answer;
@@ -234,18 +253,38 @@ void DialogueMenu::tick(int x, int y) {
 	_selectedItemIndex = line;
 }
 
-void DialogueMenu::draw() {
-	if (!_isVisible || _listSize == 0)
+void DialogueMenu::draw(Graphics::Surface &s) {
+	if (!_isVisible || _listSize == 0) {
 		return;
+	}
+
+	int fadeInItemIndex = _fadeInItemIndex;
+	if (fadeInItemIndex < listSize()) {
+		++_fadeInItemIndex;
+	}
 
 	for (int i = 0; i != _listSize; ++i) {
+		int targetColorIntensity = 0;
 		if (i == _selectedItemIndex) {
-			_items[i].field_36 = 31;
+			targetColorIntensity = 31;
 		} else {
-			_items[i].field_36 = 16;
+			targetColorIntensity = 16;
+		}
+		if (i > fadeInItemIndex) {
+			targetColorIntensity = 0;
 		}
 
-		// TODO(madmoose): There's more logic here
+		if (_items[i].colorIntensity < targetColorIntensity) {
+			_items[i].colorIntensity += 4;
+			if(_items[i].colorIntensity > targetColorIntensity) {
+				_items[i].colorIntensity = targetColorIntensity;
+			}
+		} else if (_items[i].colorIntensity > targetColorIntensity) {
+			_items[i].colorIntensity -= 2;
+			if (_items[i].colorIntensity < targetColorIntensity) {
+				_items[i].colorIntensity = targetColorIntensity;
+			}
+		}
 	}
 
 	const int x1 = _screenX;
@@ -253,21 +292,28 @@ void DialogueMenu::draw() {
 	const int x2 = _screenX + BORDER_SIZE + _maxItemWidth;
 	const int y2 = _screenY + BORDER_SIZE + _listSize * LINE_HEIGHT;
 
-	Graphics::Surface &s = _vm->_surfaceGame;
-
 	darkenRect(s, x1 + 8, y1 + 8, x2 + 2, y2 + 2);
 
+	int x = x1 + BORDER_SIZE;
+	int y = y1 + BORDER_SIZE;
+
+	Common::Point mouse = _vm->getMousePos();
+	if (mouse.x >= x && mouse.x < x2) {
+		s.vLine(mouse.x, y1 + 8, y2 + 2, 0x2108);
+	}
+	if (mouse.y >= y && mouse.y < y2) {
+		s.hLine(x1 + 8, mouse.y, x2 + 2, 0x2108);
+	}
+
 	_shapes[0].draw(s, x1, y1);
 	_shapes[3].draw(s, x2, y1);
 	_shapes[2].draw(s, x1, y2);
 	_shapes[5].draw(s, x2, y2);
 
-	int x = x1 + BORDER_SIZE;
-	int y = y1 + BORDER_SIZE;
 	for (int i = 0; i != _listSize; ++i) {
 		_shapes[1].draw(s, x1, y);
 		_shapes[4].draw(s, x2, y);
-		uint16 color = ((_items[i].field_36 >> 1) << 10) | ((_items[i].field_36 >> 1) << 6) | _items[i].field_36;
+		uint16 color = ((_items[i].colorIntensity >> 1) << 10) | ((_items[i].colorIntensity >> 1) << 6) | _items[i].colorIntensity;
 		_vm->_mainFont->drawColor(_items[i].text, s, x, y, color);
 		y += LINE_HEIGHT;
 	}
@@ -307,6 +353,7 @@ void DialogueMenu::calculatePosition(int unusedX, int unusedY) {
 	_screenX = CLIP(_screenX, 0, 640 - w);
 	_screenY = CLIP(_screenY, 0, 480 - h);
 
+	_fadeInItemIndex = 0;
 	debug("calculatePosition: %d %d %d %d %d", _screenX, _screenY, _centerX, _centerY, _maxItemWidth);
 }
 
@@ -324,13 +371,13 @@ void DialogueMenu::clear() {
 	_selectedItemIndex = 0;
 	_listSize = 0;
 	for (int i = 0; i != 10; ++i) {
-		_items[0].text[0] = '\0';
-		_items[0].answerValue = -1;
-		_items[0].field_36 = 0;
-		_items[0].field_42 = -1;
-		_items[0].field_3A = -1;
-		_items[0].field_3E = -1;
-		_items[0].field_46 = 0;
+		_items[i].text.clear();
+		_items[i].answerValue = -1;
+		_items[i].isDone = 0;
+		_items[i].priorityPolite = -1;
+		_items[i].priorityNormal = -1;
+		_items[i].prioritySurly = -1;
+		_items[i].colorIntensity = 0;
 	}
 	_neverRepeatListSize = 0;
 	for (int i = 0; i != 100; ++i) {
diff --git a/engines/bladerunner/dialogue_menu.h b/engines/bladerunner/dialogue_menu.h
index 7a6b99e..1ab90fd 100644
--- a/engines/bladerunner/dialogue_menu.h
+++ b/engines/bladerunner/dialogue_menu.h
@@ -26,7 +26,7 @@
 #include "bladerunner/shape.h"
 
 #include "common/array.h"
-
+#include "common/str.h"
 #include "graphics/surface.h"
 
 namespace BladeRunner {
@@ -35,13 +35,13 @@ class BladeRunnerEngine;
 class TextResource;
 
 struct DialogueItem {
-	char text[50];
+	Common::String text;
 	int  answerValue;
-	int  field_36;
-	int  field_3A;
-	int  field_3E;
-	int  field_42;
-	int  field_46;
+	int  colorIntensity;
+	int  priorityPolite;
+	int  priorityNormal;
+	int  prioritySurly;
+	int  isDone;
 };
 
 class DialogueMenu {
@@ -67,6 +67,8 @@ class DialogueMenu {
 	int                   _maxItemWidth;
 	DialogueItem          _items[10];
 
+	int                   _fadeInItemIndex;
+
 public:
 	DialogueMenu(BladeRunnerEngine *vm);
 	~DialogueMenu();
@@ -74,23 +76,26 @@ public:
 	bool loadText(const char *name);
 
 	bool show();
-	bool showAt(int x, int y);
 	bool hide();
+	bool addToList(int answer, bool done, int priorityPolite, int priorityNormal, int prioritySurly);
+	bool addToListNeverRepeatOnceSelected(int answer, int priorityPolite, int priorityNormal, int prioritySurly);
 	bool clearList();
-	bool addToList(int answer, int a3, int a4, int a5, int a6);
-	bool addToListNeverRepeatOnceSelected(int answer, int a3, int a4, int a5);
 	int  queryInput();
 	int  listSize();
 	bool isVisible();
 	bool isOpen();
 	void tick(int x, int y);
-	void draw();
+	void draw(Graphics::Surface &s);
+
+	void mouseUp();
+	bool waitingForInput();
+
+private:
+	bool showAt(int x, int y);
 	int  getAnswerIndex(int answer);
 	const char *getText(int id);
 	void calculatePosition(int unusedX = 0, int unusedY = 0);
 
-	void mouseUp();
-	bool waitingForInput();
 
 	void clear();
 	void reset();
diff --git a/engines/bladerunner/elevator.cpp b/engines/bladerunner/elevator.cpp
index e9b731e..99f3160 100644
--- a/engines/bladerunner/elevator.cpp
+++ b/engines/bladerunner/elevator.cpp
@@ -29,10 +29,12 @@
 #include "bladerunner/gameinfo.h"
 #include "bladerunner/mouse.h"
 #include "bladerunner/shape.h"
+#include "bladerunner/script/script.h"
 #include "bladerunner/ui_image_picker.h"
 #include "bladerunner/vqa_player.h"
 
 #include "common/rect.h"
+#include "common/str.h"
 #include "common/system.h"
 
 namespace BladeRunner {
@@ -73,7 +75,7 @@ int Elevator::activate(int elevatorId) {
 		return 0;
 	}
 
-	_vqaPlayer->setLoop(1, -1, 0, nullptr, nullptr);
+	_vqaPlayer->setLoop(1, -1, kLoopSetModeJustStart, nullptr, nullptr);
 	_vm->_mouse->setCursor(0);
 
 	for (int i = 0; i != 16; ++i) {
@@ -86,21 +88,21 @@ int Elevator::activate(int elevatorId) {
 	if (elevatorId == 1) {
 		_imagePicker->defineImage(
 			0,
-			220, 298, 308, 392,
+			Common::Rect(220, 298, 308, 392),
 			nullptr,
 			_shapes[11],
 			_shapes[14],
 			nullptr);
 		_imagePicker->defineImage(
 			1,
-			259, 259, 302, 292,
+			Common::Rect(259, 259, 302, 292),
 			nullptr,
 			_shapes[10],
 			_shapes[13],
 			nullptr);
 		_imagePicker->defineImage(
 			2,
-			227, 398, 301, 434,
+			Common::Rect(227, 398, 301, 434),
 			nullptr,
 			_shapes[12],
 			_shapes[15],
@@ -108,7 +110,7 @@ int Elevator::activate(int elevatorId) {
 	} else {
 		_imagePicker->defineImage(
 			4,
-			395, 131, 448, 164,
+			Common::Rect(395, 131, 448, 164),
 			nullptr,
 			_shapes[0],
 			_shapes[5],
@@ -116,7 +118,7 @@ int Elevator::activate(int elevatorId) {
 		);
 		_imagePicker->defineImage(
 			3,
-			395, 165, 448, 198,
+			Common::Rect(395, 165, 448, 198),
 			nullptr,
 			_shapes[1],
 			_shapes[6],
@@ -124,7 +126,7 @@ int Elevator::activate(int elevatorId) {
 		);
 		_imagePicker->defineImage(
 			5,
-			395, 199, 448, 232,
+			Common::Rect(395, 199, 448, 232),
 			nullptr,
 			_shapes[2],
 			_shapes[7],
@@ -132,7 +134,7 @@ int Elevator::activate(int elevatorId) {
 		);
 		_imagePicker->defineImage(
 			6,
-			395, 233, 448, 264,
+			Common::Rect(395, 233, 448, 264),
 			nullptr,
 			_shapes[3],
 			_shapes[8],
@@ -140,7 +142,7 @@ int Elevator::activate(int elevatorId) {
 		);
 		_imagePicker->defineImage(
 			7,
-			395, 265, 448, 295,
+			Common::Rect(395, 265, 448, 295),
 			nullptr,
 			_shapes[4],
 			_shapes[9],
@@ -148,7 +150,7 @@ int Elevator::activate(int elevatorId) {
 		);
 	}
 
-	_imagePicker->setCallbacks(
+	_imagePicker->activate(
 		elevator_mouseInCallback,
 		elevator_mouseOutCallback,
 		elevator_mouseDownCallback,
@@ -165,11 +167,14 @@ int Elevator::activate(int elevatorId) {
 		_vm->gameTick();
 	} while (_buttonClicked == -1);
 
+	_imagePicker->deactivate();
+
 	_vqaPlayer->close();
 	delete _vqaPlayer;
 
-	for (int i = 0; i != (int)_shapes.size(); ++i)
+	for (int i = 0; i != (int)_shapes.size(); ++i) {
 		delete _shapes[i];
+	}
 	_shapes.clear();
 
 	_vm->closeArchive("MODE.MIX");
@@ -191,18 +196,19 @@ bool Elevator::isOpen() const {
 }
 
 int Elevator::handleMouseUp(int x, int y) {
-	_imagePicker->handleMouseAction(x, y, false, true, 0);
+	_imagePicker->handleMouseAction(x, y, false, true, false);
 	return false;
 }
 
 int Elevator::handleMouseDown(int x, int y) {
-	_imagePicker->handleMouseAction(x, y, true, false, 0);
+	_imagePicker->handleMouseAction(x, y, true, false, false);
 	return false;
 }
 
 void Elevator::tick() {
-	if (!_vm->_gameIsRunning)
+	if (!_vm->_gameIsRunning) {
 		return;
+	}
 
 	int frame = _vqaPlayer->update();
 	assert(frame >= -1);
@@ -234,8 +240,8 @@ void Elevator::buttonClick(int buttonId) {
 
 void Elevator::reset() {
 	_isOpen = false;
-	_vqaPlayer = 0;
-	_imagePicker = 0;
+	_vqaPlayer = nullptr;
+	_imagePicker = nullptr;
 	_actorId = -1;
 	_sentenceId = -1;
 	_timeSpeakDescription = 0;
@@ -243,33 +249,33 @@ void Elevator::reset() {
 
 void Elevator::buttonFocus(int buttonId) {
 	switch (buttonId) {
-		case 7:
-			setupDescription(39, 140);
-			break;
-		case 6:
-			setupDescription(39, 130);
-			break;
-		case 5:
-			setupDescription(39, 120);
-			break;
-		case 4:
-			setupDescription(39, 100);
-			break;
-		case 3:
-			setupDescription(39, 110);
-			break;
-		case 2:
-			setupDescription(39, 130);
-			break;
-		case 1:
-			setupDescription(39, 100);
-			break;
-		case 0:
-			setupDescription(39, 150);
-			break;
-		default:
-			resetDescription();
-			break;
+	case 7:
+		setupDescription(kActorAnsweringMachine, 140);
+		break;
+	case 6:
+		setupDescription(kActorAnsweringMachine, 130);
+		break;
+	case 5:
+		setupDescription(kActorAnsweringMachine, 120);
+		break;
+	case 4:
+		setupDescription(kActorAnsweringMachine, 100);
+		break;
+	case 3:
+		setupDescription(kActorAnsweringMachine, 110);
+		break;
+	case 2:
+		setupDescription(kActorAnsweringMachine, 130);
+		break;
+	case 1:
+		setupDescription(kActorAnsweringMachine, 100);
+		break;
+	case 0:
+		setupDescription(kActorAnsweringMachine, 150);
+		break;
+	default:
+		resetDescription();
+		break;
 	}
 }
 
@@ -289,8 +295,9 @@ void Elevator::resetDescription() {
 
 void Elevator::tickDescription() {
 	int now = _vm->getTotalPlayTime();
-	if (_actorId <= 0 || now < _timeSpeakDescription)
+	if (_actorId <= 0 || now < _timeSpeakDescription) {
 		return;
+	}
 
 	_vm->_actors[_actorId]->speechPlay(_sentenceId, false);
 	_actorId = -1;
diff --git a/engines/bladerunner/font.cpp b/engines/bladerunner/font.cpp
index 8ab5cfa..2f56b3f 100644
--- a/engines/bladerunner/font.cpp
+++ b/engines/bladerunner/font.cpp
@@ -136,6 +136,10 @@ int Font::getTextWidth(const Common::String &text) {
 	return totalWidth - _spacing1;
 }
 
+int Font::getTextHeight(const Common::String &text) {
+	return _maxHeight;
+}
+
 void Font::reset() {
 	_maxWidth = 0;
 	_maxHeight = 0;
diff --git a/engines/bladerunner/font.h b/engines/bladerunner/font.h
index de790b0..91a8184 100644
--- a/engines/bladerunner/font.h
+++ b/engines/bladerunner/font.h
@@ -71,6 +71,7 @@ public:
 	void drawColor(const Common::String &text, Graphics::Surface &surface, int x, int y, uint16 color);
 
 	int getTextWidth(const Common::String &text);
+	int getTextHeight(const Common::String &text);
 
 private:
 	void reset();
diff --git a/engines/bladerunner/gameflags.cpp b/engines/bladerunner/gameflags.cpp
index 0e04a1c..261eff9 100644
--- a/engines/bladerunner/gameflags.cpp
+++ b/engines/bladerunner/gameflags.cpp
@@ -27,7 +27,7 @@
 namespace BladeRunner {
 
 GameFlags::GameFlags()
-	: flags(NULL), flagCount(0) {
+	: flags(nullptr), flagCount(0) {
 }
 
 GameFlags::~GameFlags() {
diff --git a/engines/bladerunner/module.mk b/engines/bladerunner/module.mk
index 5c99598..61a9535 100644
--- a/engines/bladerunner/module.mk
+++ b/engines/bladerunner/module.mk
@@ -10,6 +10,7 @@ MODULE_OBJS = \
 	ambient_sounds.o \
 	archive.o \
 	aud_stream.o \
+	audio_mixer.o \
 	audio_player.o \
 	audio_speech.o \
 	bladerunner.o \
diff --git a/engines/bladerunner/mouse.cpp b/engines/bladerunner/mouse.cpp
index 4c5a9b9..42d6ffe 100644
--- a/engines/bladerunner/mouse.cpp
+++ b/engines/bladerunner/mouse.cpp
@@ -23,9 +23,11 @@
 #include "bladerunner/mouse.h"
 
 #include "bladerunner/bladerunner.h"
+#include "bladerunner/regions.h"
 #include "bladerunner/scene.h"
 #include "bladerunner/scene_objects.h"
 #include "bladerunner/shape.h"
+#include "bladerunner/view.h"
 #include "bladerunner/zbuffer.h"
 
 #include "graphics/surface.h"
diff --git a/engines/bladerunner/scene.cpp b/engines/bladerunner/scene.cpp
index f73d0d9..54bc97a 100644
--- a/engines/bladerunner/scene.cpp
+++ b/engines/bladerunner/scene.cpp
@@ -22,23 +22,53 @@
 
 #include "bladerunner/scene.h"
 
-#include "bladerunner/bladerunner.h"
-
 #include "bladerunner/actor.h"
 #include "bladerunner/adq.h"
+#include "bladerunner/bladerunner.h"
 #include "bladerunner/chapters.h"
 #include "bladerunner/gameinfo.h"
 #include "bladerunner/items.h"
-#include "bladerunner/settings.h"
 #include "bladerunner/scene_objects.h"
-#include "bladerunner/script/scene.h"
+#include "bladerunner/set.h"
+#include "bladerunner/settings.h"
 #include "bladerunner/slice_renderer.h"
+#include "bladerunner/regions.h"
+#include "bladerunner/vqa_player.h"
+#include "bladerunner/script/scene.h"
 #include "bladerunner/spinner.h"
 
 #include "common/str.h"
 
 namespace BladeRunner {
 
+Scene::Scene(BladeRunnerEngine *vm)
+	: _vm(vm),
+	_setId(-1),
+	_sceneId(-1),
+	_vqaPlayer(nullptr),
+	_defaultLoop(0),
+	_defaultLoopSet(false),
+	_specialLoopMode(0),
+	_specialLoop(0),
+	_specialLoopAtEnd(false),
+	_introFinished(false),
+	_nextSetId(-1),
+	_nextSceneId(-1),
+	_frame(0),
+	_actorStartFacing(0),
+	_playerWalkedIn(false),
+	_set(new Set(vm)),
+	_regions(new Regions()),
+	_exits(new Regions()) {
+}
+
+Scene::~Scene() {
+	delete _set;
+	delete _regions;
+	delete _exits;
+	delete _vqaPlayer;
+}
+
 bool Scene::open(int setId, int sceneId, bool isLoadingGame) {
 	if (!isLoadingGame) {
 		_vm->_adq->flush(1, false);
@@ -73,21 +103,25 @@ bool Scene::open(int setId, int sceneId, bool isLoadingGame) {
 		vqaName = Common::String::format("%s_%d.VQA", setName.c_str(), MIN(currentResourceId, 3));
 	}
 
-	if (_vqaPlayer != nullptr)
+	if (_vqaPlayer != nullptr) {
 		delete _vqaPlayer;
+	}
 
 	_vqaPlayer = new VQAPlayer(_vm, &_vm->_surfaceInterface);
 
 	Common::String sceneName = _vm->_gameInfo->getSceneName(sceneId);
-	if (!_vm->_sceneScript->Open(sceneName))
+	if (!_vm->_sceneScript->Open(sceneName)) {
 		return false;
+	}
 
-	if (!isLoadingGame)
+	if (!isLoadingGame) {
 		_vm->_sceneScript->InitializeScene();
+	}
 
 	Common::String setResourceName = Common::String::format("%s-MIN.SET", sceneName.c_str());
-	if (!_set->open(setResourceName))
+	if (!_set->open(setResourceName)) {
 		return false;
+	}
 
 	_vm->_sliceRenderer->setView(*_vm->_view);
 
@@ -98,11 +132,12 @@ bool Scene::open(int setId, int sceneId, bool isLoadingGame) {
 		return true;
 	}
 
-	if (!_vqaPlayer->open(vqaName))
+	if (!_vqaPlayer->open(vqaName)) {
 		return false;
+	}
 
 	if (_specialLoop == -1) {
-		_vqaPlayer->setLoop(_defaultLoop, -1, 2, nullptr, nullptr);
+		_vqaPlayer->setLoop(_defaultLoop, -1, kLoopSetModeImmediate, nullptr, nullptr);
 		_defaultLoopSet = true;
 		_specialLoopAtEnd = false;
 	}
@@ -177,21 +212,20 @@ int Scene::advanceFrame() {
 		_vqaPlayer->updateView(_vm->_view);
 		_vqaPlayer->updateLights(_vm->_lights);
 	}
-
-	if (_specialLoopMode && _specialLoopMode != 2 && _specialLoopMode != 3) {
-		if (_specialLoopMode == 1) {
+	if (_specialLoopMode && _specialLoopMode != kSceneLoopMode2 && _specialLoopMode != kSceneLoopModeSpinner) {
+		if (_specialLoopMode == kSceneLoopModeChangeSet) {
 			if (frame == -3) { // TODO: when will this happen? bad data in/eof of vqa
 				_vm->_settings->setNewSetAndScene(_nextSetId, _nextSceneId);
 				_vm->playerGainsControl();
 			}
 		} else if (!_specialLoopAtEnd) {
-			_vqaPlayer->setLoop(_defaultLoop + 1, -1, 0, &Scene::loopEndedStatic, this);
+			_vqaPlayer->setLoop(_defaultLoop + 1, -1, kLoopSetModeJustStart, &Scene::loopEndedStatic, this);
 			_specialLoopAtEnd = true;
 		}
-	} else if (!this->_defaultLoopSet) {
-		_vqaPlayer->setLoop(_defaultLoop, -1, 1, &Scene::loopEndedStatic, this);
+	} else if (!_defaultLoopSet) {
+		_vqaPlayer->setLoop(_defaultLoop, -1, kLoopSetModeEnqueue, &Scene::loopEndedStatic, this);
 		_defaultLoopSet = true;
-		if (_specialLoopMode == 0) {
+		if (_specialLoopMode == kSceneLoopModeLoseControl) {
 			_vm->playerLosesControl();
 		}
 	}
@@ -212,27 +246,27 @@ void Scene::loopSetDefault(int loopId) {
 	_defaultLoop = loopId;
 }
 
-void Scene::loopStartSpecial(int specialLoopMode, int loopId, int flags) {
+void Scene::loopStartSpecial(int specialLoopMode, int loopId, bool immediately) {
 	_specialLoopMode = specialLoopMode;
 	_specialLoop = loopId;
 
-	int unknown = -1;
-	if (_specialLoopMode == 1) {
-		unknown = 0;
+	int repeats = -1;
+	if (_specialLoopMode == kSceneLoopModeChangeSet) {
+		repeats = 0;
 	}
 
-	int loopMode = 1;
-	if (flags) {
-		loopMode = 2;
+	int loopMode = kLoopSetModeEnqueue;
+	if (immediately) {
+		loopMode = kLoopSetModeImmediate;
 	}
 
-	_vqaPlayer->setLoop(_specialLoop, unknown, loopMode, &Scene::loopEndedStatic, this);
-	if (_specialLoopMode == 1) {
-		this->_nextSetId = _vm->_settings->getNewSet();
-		this->_nextSceneId = _vm->_settings->getNewScene();
+	_vqaPlayer->setLoop(_specialLoop, repeats, loopMode, &Scene::loopEndedStatic, this);
+	if (_specialLoopMode == kSceneLoopModeChangeSet) {
+		_nextSetId = _vm->_settings->getNewSet();
+		_nextSceneId = _vm->_settings->getNewScene();
 	}
-	if (flags) {
-		this->_specialLoopAtEnd = true;
+	if (immediately) {
+		_specialLoopAtEnd = true;
 		loopEnded(0, _specialLoop);
 	}
 }
@@ -288,30 +322,30 @@ const char *Scene::objectGetName(int objectId) {
 }
 
 void Scene::loopEnded(int frame, int loopId) {
-	if (_specialLoopMode && _specialLoopMode != 2 && _specialLoopMode != 3) {
-		if (_specialLoopMode == 1) {
+	if (_specialLoopMode && _specialLoopMode != kSceneLoopMode2 && _specialLoopMode != kSceneLoopModeSpinner) {
+		if (_specialLoopMode == kSceneLoopModeChangeSet) {
 			_defaultLoopSet = true;
 			_specialLoopAtEnd = false;
 			_vm->playerLosesControl();
 		}
 	} else if (_specialLoopAtEnd) {
-		_vqaPlayer->setLoop(_defaultLoop, -1, 1, &Scene::loopEndedStatic, this);
+		_vqaPlayer->setLoop(_defaultLoop, -1, kLoopSetModeEnqueue, &Scene::loopEndedStatic, this);
 		_defaultLoopSet = true;
 		_specialLoopAtEnd = false;
-		if (_specialLoopMode == 0) {
+		if (_specialLoopMode == kSceneLoopModeLoseControl) {
 			_vm->playerLosesControl();
 		}
 	} else {
-		if (_specialLoopMode == 0) {
+		if (_specialLoopMode == kSceneLoopModeLoseControl) {
 			_vm->playerGainsControl();
 			_playerWalkedIn = true;
 		}
-		if (_specialLoopMode == 3) {
-			_vm->_spinner->setIsOpen();
+		if (_specialLoopMode == kSceneLoopModeSpinner) {
+			_vm->_spinner->open();
 		}
 		_specialLoopMode = -1;
 		_specialLoop = -1;
-		_vqaPlayer->setLoop(_defaultLoop + 1, -1, 0, nullptr, nullptr);
+		_vqaPlayer->setLoop(_defaultLoop + 1, -1, kLoopSetModeJustStart, nullptr, nullptr);
 		_specialLoopAtEnd = true;
 	}
 }
diff --git a/engines/bladerunner/scene.h b/engines/bladerunner/scene.h
index c90258c..65ad5bf 100644
--- a/engines/bladerunner/scene.h
+++ b/engines/bladerunner/scene.h
@@ -23,16 +23,22 @@
 #ifndef BLADERUNNER_SCENE_H
 #define BLADERUNNER_SCENE_H
 
-#include "bladerunner/bladerunner.h"
-
-#include "bladerunner/regions.h"
-#include "bladerunner/set.h"
-#include "bladerunner/view.h"
-#include "bladerunner/vqa_player.h"
+#include "bladerunner/vector.h"
 
 namespace BladeRunner {
 
 class BladeRunnerEngine;
+class BoundingBox;
+class Regions;
+class Set;
+class VQAPlayer;
+
+enum SceneLoopMode {
+	kSceneLoopModeLoseControl = 0,
+	kSceneLoopModeChangeSet = 1,
+	kSceneLoopMode2 = 2,
+	kSceneLoopModeSpinner = 3
+};
 
 class Scene {
 	BladeRunnerEngine *_vm;
@@ -58,42 +64,23 @@ private:
 
 public:
 	Set        *_set;
-	Regions*    _regions;
-	Regions*    _exits;
+	Regions    *_regions;
+	Regions    *_exits;
 
 	// _default_loop_id = 0;
 	// _scene_vqa_frame_number = -1;
 
 public:
-	Scene(BladeRunnerEngine *vm)
-		: _vm(vm),
-		  _setId(-1),
-		  _sceneId(-1),
-		  _vqaPlayer(nullptr),
-		  _defaultLoop(0),
-		  _introFinished(false),
-		  _nextSetId(-1),
-		  _nextSceneId(-1),
-		  _playerWalkedIn(false),
-		  _set(new Set(vm)),
-		  _regions(new Regions()),
-		  _exits(new Regions())
-	{}
-
-	~Scene() {
-		delete _set;
-		delete _regions;
-		delete _exits;
-		delete _vqaPlayer;
-	}
+	Scene(BladeRunnerEngine *vm);
+	~Scene();
 
 	bool open(int setId, int sceneId, bool isLoadingGame);
 	bool close(bool isLoadingGame);
 	int  advanceFrame();
 	void setActorStart(Vector3 position, int facing);
 
-	void loopSetDefault(int a);
-	void loopStartSpecial(int a, int b, int c);
+	void loopSetDefault(int loopId);
+	void loopStartSpecial(int specialLoopMode, int loopId, bool immediately);
 
 	int getSetId() const { return _setId; }
 	int getSceneId() const { return _sceneId; }
diff --git a/engines/bladerunner/script/init.cpp b/engines/bladerunner/script/init.cpp
index 76f68ed..ed11405 100644
--- a/engines/bladerunner/script/init.cpp
+++ b/engines/bladerunner/script/init.cpp
@@ -2642,16 +2642,16 @@ void ScriptInit::Init_CDB() {
 }
 
 void ScriptInit::Init_Spinner() {
-	Spinner_Set_Selectable_Destination_Flag(0, 1);
-	Spinner_Set_Selectable_Destination_Flag(1, 1);
-	Spinner_Set_Selectable_Destination_Flag(2, 1);
-	Spinner_Set_Selectable_Destination_Flag(3, 0);
-	Spinner_Set_Selectable_Destination_Flag(4, 0);
-	Spinner_Set_Selectable_Destination_Flag(5, 0);
-	Spinner_Set_Selectable_Destination_Flag(6, 0);
-	Spinner_Set_Selectable_Destination_Flag(7, 0);
-	Spinner_Set_Selectable_Destination_Flag(8, 0);
-	Spinner_Set_Selectable_Destination_Flag(9, 0);
+	Spinner_Set_Selectable_Destination_Flag(kSpinnerDestinationPoliceStation, true);
+	Spinner_Set_Selectable_Destination_Flag(kSpinnerDestinationMcCoysApartment, true);
+	Spinner_Set_Selectable_Destination_Flag(kSpinnerDestinationRuncitersAnimals, true);
+	Spinner_Set_Selectable_Destination_Flag(kSpinnerDestinationChinatown, false);
+	Spinner_Set_Selectable_Destination_Flag(kSpinnerDestinationAnimoidRow, false);
+	Spinner_Set_Selectable_Destination_Flag(kSpinnerDestinationTyrellBuilding, false);
+	Spinner_Set_Selectable_Destination_Flag(kSpinnerDestinationDNARow, false);
+	Spinner_Set_Selectable_Destination_Flag(kSpinnerDestinationBradburyBuilding, false);
+	Spinner_Set_Selectable_Destination_Flag(kSpinnerDestinationNightclubRow, false);
+	Spinner_Set_Selectable_Destination_Flag(kSpinnerDestinationHysteriaHall, false);
 }
 
 void ScriptInit::Init_Actor_Friendliness() {
diff --git a/engines/bladerunner/script/script.cpp b/engines/bladerunner/script/script.cpp
index c435270..72f4e50 100644
--- a/engines/bladerunner/script/script.cpp
+++ b/engines/bladerunner/script/script.cpp
@@ -22,14 +22,13 @@
 
 #include "bladerunner/script/script.h"
 
-#include "bladerunner/bladerunner.h"
-
 #include "bladerunner/actor.h"
 #include "bladerunner/actor_combat.h"
 #include "bladerunner/adq.h"
 #include "bladerunner/ambient_sounds.h"
 #include "bladerunner/audio_player.h"
 #include "bladerunner/audio_speech.h"
+#include "bladerunner/bladerunner.h"
 #include "bladerunner/crimes_database.h"
 #include "bladerunner/combat.h"
 #include "bladerunner/dialogue_menu.h"
@@ -39,6 +38,8 @@
 #include "bladerunner/items.h"
 #include "bladerunner/item_pickup.h"
 #include "bladerunner/movement_track.h"
+#include "bladerunner/regions.h"
+#include "bladerunner/set.h"
 #include "bladerunner/settings.h"
 #include "bladerunner/set_effects.h"
 #include "bladerunner/scene.h"
@@ -46,6 +47,7 @@
 #include "bladerunner/slice_animations.h"
 #include "bladerunner/slice_renderer.h"
 #include "bladerunner/spinner.h"
+#include "bladerunner/suspects_database.h"
 #include "bladerunner/text_resource.h"
 #include "bladerunner/vector.h"
 #include "bladerunner/waypoints.h"
@@ -499,7 +501,7 @@ bool ScriptBase::Loop_Actor_Walk_To_XYZ(int actorId, float x, float y, float z,
 	//TODO:
 	//PlayerActorIdle = 0;
 	bool isRunning;
-	bool result = _vm->_actors[actorId]->loopWalkToXYZ(Vector3(x, y, z), destinationOffset, a5, run, 1, &isRunning);
+	bool result = _vm->_actors[actorId]->loopWalkToXYZ(Vector3(x, y, z), destinationOffset, a5, run, true, &isRunning);
 
 //	if (PlayerActorIdle == 1) {
 //		result = 1;
@@ -829,12 +831,12 @@ void ScriptBase::Scene_Loop_Set_Default(int loopId) {
 	_vm->_scene->loopSetDefault(loopId);
 }
 
-void ScriptBase::Scene_Loop_Start_Special(int sceneLoopMode, int loopId, int c) {
-	if (sceneLoopMode == 1) {
-		c = 1;
+void ScriptBase::Scene_Loop_Start_Special(int sceneLoopMode, int loopId, bool immediately) {
+	if (sceneLoopMode == kSceneLoopModeChangeSet) {
+		immediately = true;
 	}
-	_vm->_scene->loopStartSpecial(sceneLoopMode, loopId, c);
-	if (sceneLoopMode == 1) {
+	_vm->_scene->loopStartSpecial(sceneLoopMode, loopId, immediately);
+	if (sceneLoopMode == kSceneLoopModeChangeSet) {
 		_vm->_settings->clearNewSetAndScene();
 	}
 }
@@ -843,54 +845,44 @@ void ScriptBase::Outtake_Play(int id, int noLocalization, int container) {
 	_vm->outtakePlay(id, noLocalization, container);
 }
 
-void ScriptBase::Ambient_Sounds_Add_Sound(int id, int time1, int time2, int volume1, int volume2, int pan1begin, int pan1end, int pan2begin, int pan2end, int priority, int unk) {
-	_vm->_ambientSounds->addSound(id, time1, time2, volume1, volume2, pan1begin, pan1end, pan2begin, pan2end, priority, unk);
+void ScriptBase::Ambient_Sounds_Add_Sound(int sfxId, int timeMin, int timeMax, int volumeMin, int volumeMax, int panStartMin, int panStartMax, int panEndMin, int panEndMax, int priority, int unk) {
+	_vm->_ambientSounds->addSound(sfxId, timeMin, timeMax, volumeMin, volumeMax, panStartMin, panStartMax, panEndMin, panEndMax, priority, unk);
 }
 
-void  ScriptBase::Ambient_Sounds_Remove_Sound(int id, bool a2) {
-	//TODO
-	warning("Ambient_Sounds_Remove_Sound(%d, %d)", id, a2);
+void  ScriptBase::Ambient_Sounds_Remove_Sound(int sfxId, bool stopPlaying) {
+	_vm->_ambientSounds->removeNonLoopingSound(sfxId,  stopPlaying);
 }
 
-void ScriptBase::Ambient_Sounds_Add_Speech_Sound(int id, int unk1, int time1, int time2, int volume1, int volume2, int pan1begin, int pan1end, int pan2begin, int pan2end, int priority, int unk2){
-	//TODO
-	warning("Ambient_Sounds_Add_Speech_Sound(%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", id, unk1, time1, time2, volume1, volume2, pan1begin, pan1end, pan2begin, pan2end, priority, unk2);
+void ScriptBase::Ambient_Sounds_Add_Speech_Sound(int actorId, int sentenceId, int timeMin, int timeMax, int volumeMin, int volumeMax, int panStartMin, int panStartMax, int panEndMin, int panEndMax, int priority, int unk){
+	_vm->_ambientSounds->addSpeech(actorId, sentenceId, timeMin, timeMax, volumeMin, volumeMax, panStartMin, panStartMax, panEndMin, panEndMax, priority, unk);
 }
 
 // ScriptBase::Ambient_Sounds_Remove_Speech_Sound
 
-int ScriptBase::Ambient_Sounds_Play_Sound(int a1, int a2, int a3, int a4, int a5) {
-	//TODO
-	warning("Ambient_Sounds_Remove_Sound(%d, %d, %d, %d, %d)", a1, a2, a3, a4, a5);
-	return -1;
+void ScriptBase::Ambient_Sounds_Play_Sound(int sfxId, int volume, int panStart, int panEnd, int priority) {
+	_vm->_ambientSounds->playSound(sfxId, volume, panStart, panEnd, priority);
 }
 
 // ScriptBase::Ambient_Sounds_Play_Speech_Sound
 
-void ScriptBase::Ambient_Sounds_Remove_All_Non_Looping_Sounds(int time) {
-	//TODO
-	warning("Ambient_Sounds_Remove_All_Non_Looping_Sounds(%d)", time);
-	// _vm->_ambientSounds->removeAllNonLoopingSounds(time);
+void ScriptBase::Ambient_Sounds_Remove_All_Non_Looping_Sounds(bool stopPlaying) {
+	_vm->_ambientSounds->removeAllNonLoopingSounds(stopPlaying);
 }
 
-void ScriptBase::Ambient_Sounds_Add_Looping_Sound(int id, int volume, int pan, int fadeInTime) {
-	_vm->_ambientSounds->addLoopingSound(id, volume, pan, fadeInTime);
+void ScriptBase::Ambient_Sounds_Add_Looping_Sound(int sfxId, int volume, int pan, int delay) {
+	_vm->_ambientSounds->addLoopingSound(sfxId, volume, pan, delay);
 }
 
-void ScriptBase::Ambient_Sounds_Adjust_Looping_Sound(int id, int panBegin, int panEnd, int a4) {
-	//TODO
-	warning("Ambient_Sounds_Adjust_Looping_Sound(%d, %d, %d, %d)", id, panBegin, panEnd, a4);
+void ScriptBase::Ambient_Sounds_Adjust_Looping_Sound(int sfxId, int volume, int pan, int delay) {
+	_vm->_ambientSounds->adjustLoopingSound(sfxId, volume, pan, delay);
 }
 
-void ScriptBase::Ambient_Sounds_Remove_Looping_Sound(int id, bool a2){
-	//TODO
-	warning("Ambient_Sounds_Remove_Looping_Sound(%d, %d)", id, a2);
+void ScriptBase::Ambient_Sounds_Remove_Looping_Sound(int sfxId, int delay){
+	_vm->_ambientSounds->removeLoopingSound(sfxId, delay);
 }
 
-void ScriptBase::Ambient_Sounds_Remove_All_Looping_Sounds(int time) {
-	//TODO
-	warning("Ambient_Sounds_Remove_All_Looping_Sounds(%d)", time);
-	// _vm->_ambientSounds->removeAllLoopingSounds(time);
+void ScriptBase::Ambient_Sounds_Remove_All_Looping_Sounds(int delay) {
+	_vm->_ambientSounds->removeAllLoopingSounds(delay);
 }
 
 void ScriptBase::Setup_Scene_Information(float actorX, float actorY, float actorZ, int actorFacing) {
@@ -1067,14 +1059,14 @@ bool ScriptBase::SDB_Add_Other_Clue(int suspectId, int clueId) {
 	return _vm->_suspectsDatabase->get(suspectId)->addOtherClue(clueId);
 }
 
-void ScriptBase::Spinner_Set_Selectable_Destination_Flag(int a1, int a2) {
-	_vm->_spinner->setSelectableDestinationFlag(a1, a2);
+void ScriptBase::Spinner_Set_Selectable_Destination_Flag(int destination, bool selectable) {
+	_vm->_spinner->setSelectableDestinationFlag(destination, selectable);
 }
 
 // ScriptBase::Spinner_Query_Selectable_Destination_Flag
 
-int ScriptBase::Spinner_Interface_Choose_Dest(int a1, int a2) {
-	return _vm->_spinner->interfaceChooseDest(a1, a2);
+int ScriptBase::Spinner_Interface_Choose_Dest(int loopId, bool immediately) {
+	return _vm->_spinner->chooseDestination(loopId, immediately);
 }
 
 void ScriptBase::ESPER_Flag_To_Activate() {
@@ -1131,7 +1123,7 @@ void ScriptBase::Actor_Retired_Here(int actorId, int width, int height, int reti
 	Vector3 actorPosition;
 	actor->getXYZ(&actorPosition.x, &actorPosition.y, &actorPosition.z);
 	actor->retire(retired, width, height, retiredByActorId);
-	actor->setAtXYZ(actorPosition, actor->getFacing(), true, 0, true);
+	actor->setAtXYZ(actorPosition, actor->getFacing(), true, false, true);
 	_vm->_sceneObjects->setRetired(actorId + SCENE_OBJECTS_ACTORS_OFFSET, true);
 }
 
diff --git a/engines/bladerunner/script/script.h b/engines/bladerunner/script/script.h
index 61cee3a..c282bcc 100644
--- a/engines/bladerunner/script/script.h
+++ b/engines/bladerunner/script/script.h
@@ -567,20 +567,20 @@ protected:
 	bool Music_Is_Playing();
 	void Overlay_Play(const char *overlay, int a2, int a3, int a4, int a5);
 	void Overlay_Remove(const char *overlay);
-	void Scene_Loop_Set_Default(int);
-	void Scene_Loop_Start_Special(int, int, int);
+	void Scene_Loop_Set_Default(int loopId);
+	void Scene_Loop_Start_Special(int sceneLoopMode, int loopId, bool immediately);
 	void Outtake_Play(int id, int noLocalization = false, int container = -1);
-	void Ambient_Sounds_Add_Sound(int id, int time1, int time2, int volume1, int volume2, int pan1begin, int pan1end, int pan2begin, int pan2end, int priority, int unk);
-	void Ambient_Sounds_Remove_Sound(int id, bool a2);
-	void Ambient_Sounds_Add_Speech_Sound(int id, int unk1, int time1, int time2, int volume1, int volume2, int pan1begin, int pan1end, int pan2begin, int pan2end, int priority, int unk2);
+	void Ambient_Sounds_Add_Sound(int sfxId, int timeMin, int timeMax, int volumeMin, int volumeMax, int panStartMin, int panStartMax, int panEndMin, int panEndMax, int priority, int unk);
+	void Ambient_Sounds_Remove_Sound(int sfxId, bool stopPlaying);
+	void Ambient_Sounds_Add_Speech_Sound(int actorId, int sentenceId, int timeMin, int timeMax, int volumeMin, int volumeMax, int panStartMin, int panStartMax, int panEndMin, int panEndMax, int priority, int unk);
 	// Ambient_Sounds_Remove_Speech_Sound
-	int Ambient_Sounds_Play_Sound(int a1, int a2, int a3, int a4, int a5);
+	void Ambient_Sounds_Play_Sound(int sfxId, int volume, int panStart, int panEnd, int priority);
 	// Ambient_Sounds_Play_Speech_Sound
-	void Ambient_Sounds_Remove_All_Non_Looping_Sounds(int time);
-	void Ambient_Sounds_Add_Looping_Sound(int id, int volume, int pan, int fadeInTime);
-	void Ambient_Sounds_Adjust_Looping_Sound(int id, int panBegin, int panEnd, int a4);
-	void Ambient_Sounds_Remove_Looping_Sound(int id, bool a2);
-	void Ambient_Sounds_Remove_All_Looping_Sounds(int time);
+	void Ambient_Sounds_Remove_All_Non_Looping_Sounds(bool stopPlaying);
+	void Ambient_Sounds_Add_Looping_Sound(int sfxId, int volume, int pan, int delay);
+	void Ambient_Sounds_Adjust_Looping_Sound(int sfxId, int volume, int pan, int delay);
+	void Ambient_Sounds_Remove_Looping_Sound(int sfxId, int delay);
+	void Ambient_Sounds_Remove_All_Looping_Sounds(int delay);
 	void Setup_Scene_Information(float actorX, float actorY, float actorZ, int actorFacing);
 	bool Dialogue_Menu_Appear(int x, int y);
 	bool Dialogue_Menu_Disappear();
@@ -625,9 +625,9 @@ protected:
 	bool SDB_Add_Replicant_Clue(int suspectId, int clueId);
 	bool SDB_Add_Non_Replicant_Clue(int suspectId, int clueId);
 	bool SDB_Add_Other_Clue(int suspectId, int clueId);
-	void Spinner_Set_Selectable_Destination_Flag(int a1, int a2);
-	// Spinner_Query_Selectable_Destination_Flag
-	int Spinner_Interface_Choose_Dest(int a1, int a2);
+	void Spinner_Set_Selectable_Destination_Flag(int destination, bool selectable);
+	// Spinner_Query_Selectable_Destination_Flag(int destination);
+	int Spinner_Interface_Choose_Dest(int loopId, bool immediately);
 	void ESPER_Flag_To_Activate();
 	bool Voight_Kampff_Activate(int a1, int a2);
 	int Elevator_Activate(int elevatorId);
diff --git a/engines/bladerunner/settings.h b/engines/bladerunner/settings.h
index de9846a..105c8fc 100644
--- a/engines/bladerunner/settings.h
+++ b/engines/bladerunner/settings.h
@@ -27,6 +27,14 @@ namespace BladeRunner {
 
 class BladeRunnerEngine;
 
+enum PlayerAgenda {
+	kPlayerAgendaPolite = 0,
+	kPlayerAgendaNormal = 1,
+	kPlayerAgendaSurly = 2,
+	kPlayerAgendaErratic = 3,
+	kPlayerAgendaUserChoice = 4,
+};
+
 class Settings {
 	BladeRunnerEngine *_vm;
 
diff --git a/engines/bladerunner/spinner.cpp b/engines/bladerunner/spinner.cpp
index 40650cb..acee0cb 100644
--- a/engines/bladerunner/spinner.cpp
+++ b/engines/bladerunner/spinner.cpp
@@ -38,12 +38,19 @@ namespace BladeRunner {
 
 Spinner::Spinner(BladeRunnerEngine *vm) : _vm(vm) {
 	reset();
-	_imagePicker = new UIImagePicker(vm, SPINNER_DESTINATIONS);
+	_imagePicker = new UIImagePicker(vm, kSpinnerDestinations);
+	_vqaPlayer = nullptr;
 }
 
 Spinner::~Spinner() {
 	delete _imagePicker;
+
 	reset();
+
+	if (_vqaPlayer != nullptr) {
+		_vqaPlayer->close();
+		delete _vqaPlayer;
+	}
 }
 
 void Spinner::setSelectableDestinationFlag(int destination, bool selectable) {
@@ -55,38 +62,39 @@ bool Spinner::querySelectableDestinationFlag(int destination) const {
 }
 
 SpinnerDestination SpinnerDestinationsNear[] = {
-	{ 0, 0x0D2, 0x107, 0x107, 0x14C },
-	{ 1, 0x133, 0x14A, 0x169, 0x17D },
-	{ 2, 0x152, 0x089, 0x16A, 0x0A9 },
-	{ 3, 0x0F8, 0x087, 0x121, 0x0A8 },
-	{ 4, 0x160, 0x0DE, 0x17B, 0x0EE },
-	{ -1, -1, -1, -1, -1 }
+	{ 0, { 210, 263, 263, 332 } },
+	{ 1, { 307, 330, 361, 381 } },
+	{ 2, { 338, 137, 362, 169 } },
+	{ 3, { 248, 135, 289, 168 } },
+	{ 4, { 352, 222, 379, 238 } },
+	{ -1, { -1,-1,-1,-1 } }
+
 };
 
 SpinnerDestination SpinnerDestinationsMedium[] = {
-	{ 0, 0x0FC, 0x0F2, 0x117, 0x11B },
-	{ 1, 0x12D, 0x111, 0x148, 0x130 },
-	{ 2, 0x13F, 0x0B6, 0x150, 0x0C8 },
-	{ 3, 0x10D, 0x0B5, 0x125, 0x0C8 },
-	{ 4, 0x145, 0x0E3, 0x159, 0x0F0 },
-	{ 5, 0x103, 0x04A, 0x17C, 0x077 },
-	{ 6, 0x0CB, 0x07C, 0x0E0, 0x088 },
-	{ 7, 0x0C8, 0x093, 0x0DE, 0x0AA },
-	{ -1, -1, -1, -1, -1 }
+	{ 0, { 252, 242, 279, 283 } },
+	{ 1, { 301, 273, 328, 304 } },
+	{ 2, { 319, 182, 336, 200 } },
+	{ 3, { 269, 181, 293, 200 } },
+	{ 4, { 325, 227, 345, 240 } },
+	{ 5, { 259,  74, 380, 119 } },
+	{ 6, { 203, 124, 224, 136 } },
+	{ 7, { 200, 147, 222, 170 } },
+	{ -1, { -1,-1,-1,-1 } }
 };
 
 SpinnerDestination SpinnerDestinationsFar[] = {
-	{ 0, 0x0DC, 0x0E3, 0x0F6, 0x106 },
-	{ 1, 0x104, 0x0FC, 0x11E, 0x117 },
-	{ 2, 0x11E, 0x0B2, 0x12E, 0x0C4 },
-	{ 3, 0x0F4, 0x0B2, 0x107, 0x0C3 },
-	{ 4, 0x120, 0x0D8, 0x132, 0x0E4 },
-	{ 5, 0x0F9, 0x04D, 0x161, 0x07C },
-	{ 6, 0x0BE, 0x07F, 0x0D0, 0x08A },
-	{ 7, 0x0B9, 0x095, 0x0CE, 0x0AA },
-	{ 8, 0x18E, 0x0F9, 0x1A3, 0x10C },
-	{ 9, 0x186, 0x0DA, 0x1A3, 0x0EC },
-	{ -1, -1, -1, -1, -1 }
+	{ 0, { 220, 227, 246, 262 } },
+	{ 1, { 260, 252, 286, 279 } },
+	{ 2, { 286, 178, 302, 196 } },
+	{ 3, { 244, 178, 263, 195 } },
+	{ 4, { 288, 216, 306, 228 } },
+	{ 5, { 249,  77, 353, 124 } },
+	{ 6, { 190, 127, 208, 138 } },
+	{ 7, { 185, 149, 206, 170 } },
+	{ 8, { 398, 249, 419, 268 } },
+	{ 9, { 390, 218, 419, 236 } },
+	{ -1, { -1, -1, -1, -1 } }
 };
 
 static void spinner_mouseInCallback(int, void*);
@@ -94,16 +102,17 @@ static void spinner_mouseOutCallback(int, void*);
 static void spinner_mouseDownCallback(int, void*);
 static void spinner_mouseUpCallback(int, void*);
 
-int Spinner::interfaceChooseDest(int loopId, int loopFlag) {
+int Spinner::chooseDestination(int loopId, bool immediately) {
 	_selectedDestination = 0;
-	if (!_vm->openArchive("MODE.MIX"))
+	if (!_vm->openArchive("MODE.MIX")) {
 		return 0;
+	}
 
 	if (loopId < 0) {
 		_isOpen = true;
 	} else {
 		_vm->playerLosesControl();
-		_vm->_scene->loopStartSpecial(3, loopId, loopFlag);
+		_vm->_scene->loopStartSpecial(kSceneLoopModeSpinner, loopId, immediately);
 		while (!_isOpen) {
 			_vm->gameTick();
 		}
@@ -119,10 +128,11 @@ int Spinner::interfaceChooseDest(int loopId, int loopFlag) {
 
 	// Determine which map we need to show to include the active destinations
 	uint8 mapmask = 0;
-	uint8 mapmaskv[SPINNER_DESTINATIONS] = { 1, 1, 1, 1, 1, 3, 3, 3, 7, 7 };
-	for (int i = 0; i != SPINNER_DESTINATIONS; ++i) {
-		if (_isDestinationSelectable[i])
+	uint8 mapmaskv[kSpinnerDestinations] = { 1, 1, 1, 1, 1, 3, 3, 3, 7, 7 };
+	for (int i = 0; i != kSpinnerDestinations; ++i) {
+		if (_isDestinationSelectable[i]) {
 			mapmask |= mapmaskv[i];
+		}
 	}
 
 	_destinations = nullptr;
@@ -151,8 +161,8 @@ int Spinner::interfaceChooseDest(int loopId, int loopFlag) {
 		return -1;
 	}
 
-	_vqaPlayer->setLoop(spinnerLoopId,     -1, 2, nullptr, nullptr);
-	_vqaPlayer->setLoop(spinnerLoopId + 1, -1, 0, nullptr, nullptr);
+	_vqaPlayer->setLoop(spinnerLoopId,     -1, kLoopSetModeImmediate, nullptr, nullptr);
+	_vqaPlayer->setLoop(spinnerLoopId + 1, -1, kLoopSetModeJustStart, nullptr, nullptr);
 
 	for (int j = 0; j != shapeCount; ++j) {
 		_shapes.push_back(new Shape(_vm));
@@ -162,17 +172,15 @@ int Spinner::interfaceChooseDest(int loopId, int loopFlag) {
 	_imagePicker->resetImages();
 
 	for (SpinnerDestination *dest = _destinations; dest->id != -1; ++dest) {
-		if (!_isDestinationSelectable[dest->id])
+		if (!_isDestinationSelectable[dest->id]) {
 			continue;
+		}
 
 		const char *tooltip = _vm->_textSpinnerDestinations->getText(dest->id);
 
 		_imagePicker->defineImage(
 			dest->id,
-			dest->left,
-			dest->top,
-			dest->right,
-			dest->bottom,
+			dest->rect,
 			_shapes[dest->id],
 			_shapes[dest->id + _shapes.size() / 2],
 			_shapes[dest->id + _shapes.size() / 2],
@@ -180,7 +188,7 @@ int Spinner::interfaceChooseDest(int loopId, int loopFlag) {
 		);
 	}
 
-	_imagePicker->setCallbacks(
+	_imagePicker->activate(
 		spinner_mouseInCallback,
 		spinner_mouseOutCallback,
 		spinner_mouseDownCallback,
@@ -194,14 +202,26 @@ int Spinner::interfaceChooseDest(int loopId, int loopFlag) {
 		_vm->gameTick();
 	} while (_selectedDestination == -1);
 
-	// TODO: Unfreeze game time
-	_isOpen = false;
-	// TODO: _vm->_scene->resume();
+	_imagePicker->deactivate();
 
-	for (int i = 0; i != (int)_shapes.size(); ++i)
+	for (int i = 0; i != (int)_shapes.size(); ++i) {
 		delete _shapes[i];
+	}
 	_shapes.clear();
 
+	if (_vqaPlayer != nullptr) {
+		_vqaPlayer->close();
+		delete _vqaPlayer;
+		_vqaPlayer = nullptr;
+	}
+
+	_vm->closeArchive("MODE.MIX");
+
+	_isOpen = false;
+
+	// TODO: Unfreeze game time
+	// TODO: _vm->_scene->resume();
+
 	return _selectedDestination;
 }
 
@@ -214,14 +234,13 @@ static void spinner_mouseOutCallback(int, void*) {
 static void spinner_mouseDownCallback(int, void*) {
 }
 
-static void spinner_mouseUpCallback(int image, void *data) {
+static void spinner_mouseUpCallback(int image, void *self) {
 	if (image >= 0 && image < 10) {
-		Spinner *spinner = (Spinner *)data;
-		spinner->setSelectedDestination(image);
+		((Spinner *)self)->setSelectedDestination(image);
 	}
 }
 
-void Spinner::setIsOpen() {
+void Spinner::open() {
 	_isOpen = true;
 }
 
@@ -230,18 +249,19 @@ bool Spinner::isOpen() const {
 }
 
 int Spinner::handleMouseUp(int x, int y) {
-	_imagePicker->handleMouseAction(x, y, false, true, 0);
+	_imagePicker->handleMouseAction(x, y, false, true, false);
 	return false;
 }
 
 int Spinner::handleMouseDown(int x, int y) {
-	_imagePicker->handleMouseAction(x, y, true, false, 0);
+	_imagePicker->handleMouseAction(x, y, true, false, false);
 	return false;
 }
 
 void Spinner::tick() {
-	if (!_vm->_gameIsRunning)
+	if (!_vm->_gameIsRunning) {
 		return;
+	}
 
 	int frame = _vqaPlayer->update();
 	assert(frame >= -1);
@@ -249,8 +269,6 @@ void Spinner::tick() {
 	// vqaPlayer renders to _surfaceInterface
 	blit(_vm->_surfaceInterface, _vm->_surfaceGame);
 
-	_imagePicker->draw(_vm->_surfaceGame);
-
 	Common::Point p = _vm->getMousePos();
 	_imagePicker->handleMouseAction(p.x, p.y, false, false, false);
 	if (_imagePicker->hasHoveredImage()) {
@@ -258,7 +276,9 @@ void Spinner::tick() {
 	} else {
 		_vm->_mouse->setCursor(0);
 	}
+	_imagePicker->draw(_vm->_surfaceGame);
 	_vm->_mouse->draw(_vm->_surfaceGame, p.x, p.y);
+	_imagePicker->drawTooltip(_vm->_surfaceGame, p.x, p.y);
 
 	_vm->blitToScreen(_vm->_surfaceGame);
 	_vm->_system->delayMillis(10);
@@ -269,8 +289,8 @@ void Spinner::setSelectedDestination(int destination) {
 }
 
 void Spinner::reset() {
-	for (int i = 0; i != SPINNER_DESTINATIONS; ++i) {
-		_isDestinationSelectable[i] = 0;
+	for (int i = 0; i != kSpinnerDestinations; ++i) {
+		_isDestinationSelectable[i] = false;
 	}
 
 	_isOpen = false;
@@ -278,13 +298,21 @@ void Spinner::reset() {
 	_selectedDestination = -1;
 	_imagePicker = nullptr;
 
-	for (int i = 0; i != (int)_shapes.size(); ++i)
+	for (int i = 0; i != (int)_shapes.size(); ++i) {
 		delete _shapes[i];
+	}
 	_shapes.clear();
 }
 
 void Spinner::resume() {
-	// TODO
+	if(_vqaPlayer == nullptr) {
+		return;
+	}
+
+	//_vqa->clear();
+	_vqaPlayer->setLoop(0, -1, kLoopSetModeImmediate, nullptr, nullptr);
+	tick();
+	_vqaPlayer->setLoop(1, -1, kLoopSetModeJustStart, nullptr, nullptr);
 }
 
 } // End of namespace BladeRunner
diff --git a/engines/bladerunner/spinner.h b/engines/bladerunner/spinner.h
index 19021fa..cfa593b 100644
--- a/engines/bladerunner/spinner.h
+++ b/engines/bladerunner/spinner.h
@@ -24,6 +24,7 @@
 #define BLADERUNNER_SPINNER_H
 
 #include "common/array.h"
+#include "common/rect.h"
 
 namespace BladeRunner {
 
@@ -32,19 +33,16 @@ class Shape;
 class VQAPlayer;
 class UIImagePicker;
 
-#define SPINNER_DESTINATIONS 10
-
 struct SpinnerDestination {
-	int id;
-	int left;
-	int top;
-	int right;
-	int bottom;
+	int          id;
+	Common::Rect rect;
 };
 
 class Spinner {
+	static const int kSpinnerDestinations = 10;
+
 	BladeRunnerEngine     *_vm;
-	bool                   _isDestinationSelectable[SPINNER_DESTINATIONS];
+	bool                   _isDestinationSelectable[kSpinnerDestinations];
 	bool                   _isOpen;
 	VQAPlayer             *_vqaPlayer;
 	SpinnerDestination    *_destinations;
@@ -59,9 +57,9 @@ public:
 	void setSelectableDestinationFlag(int destination, bool selectable);
 	bool querySelectableDestinationFlag(int destination) const;
 
-	int interfaceChooseDest(int vqaLoopId, int loopFlag);
+	int chooseDestination(int vqaLoopId, bool immediately);
 
-	void setIsOpen();
+	void open();
 	bool isOpen() const;
 
 	int handleMouseUp(int x, int y);
diff --git a/engines/bladerunner/ui_image_picker.cpp b/engines/bladerunner/ui_image_picker.cpp
index 6541297..eb0b575 100644
--- a/engines/bladerunner/ui_image_picker.cpp
+++ b/engines/bladerunner/ui_image_picker.cpp
@@ -23,33 +23,25 @@
 #include "bladerunner/ui_image_picker.h"
 
 #include "bladerunner/bladerunner.h"
-
+#include "bladerunner/font.h"
+#include "bladerunner/mouse.h"
 #include "bladerunner/shape.h"
 
 #include "common/rect.h"
+#include "common/str.h"
 #include "graphics/surface.h"
 
 namespace BladeRunner {
 
-struct UIImagePickerImage {
-	int          active;
-	Common::Rect rect;
-	Shape       *shapeUp;
-	Shape       *shapeHovered;
-	Shape       *shapeDown;
-	const char  *tooltip;
-};
-
 UIImagePicker::UIImagePicker(BladeRunnerEngine *vm, int imageCount) : _vm(vm) {
 	reset();
-	_images = new UIImagePickerImage[imageCount];
+	_images.resize(imageCount);
 	_imageCount = imageCount;
 	resetImages();
 }
 
 UIImagePicker::~UIImagePicker() {
-	delete[] _images;
-	_images = nullptr;
+	_images.clear();
 	reset();
 }
 
@@ -59,28 +51,35 @@ void UIImagePicker::resetImages() {
 	}
 }
 
-bool UIImagePicker::defineImage(int i, int left, int top, int right, int bottom, Shape *shapeUp, Shape *shapeHovered, Shape *shapeDown, const char *tooltip) {
-	if (i < 0 || i >= _imageCount || _images[i].active)
+bool UIImagePicker::defineImage(int i, Common::Rect rect, Shape *shapeUp, Shape *shapeHovered, Shape *shapeDown, const char *tooltip) {
+	if (i < 0 || i >= _imageCount || _images[i].active) {
 		return false;
+	}
 
 	UIImagePickerImage &img = _images[i];
 
-	img.rect.left = left;
-	img.rect.top = top;
-	img.rect.right = right + 1;
-	img.rect.bottom = bottom + 1;
+	img.rect = rect;
+	// for rect to be inclusive
+	img.rect.right += 1;
+	img.rect.bottom += 1;
 	img.shapeUp = shapeUp;
 	img.shapeHovered = shapeHovered;
 	img.shapeDown = shapeDown;
 	img.active = true;
-	img.tooltip = tooltip;
+
+	if (tooltip != nullptr) {
+		img.tooltip = tooltip;
+	} else {
+		img.tooltip.clear();
+	}
 
 	return true;
 }
 
 bool UIImagePicker::setImageTop(int i, int top) {
-	if (i < 0 || i >= _imageCount || !_images[i].active)
+	if (i < 0 || i >= _imageCount || !_images[i].active) {
 		return false;
+	}
 
 	UIImagePickerImage &img = _images[i];
 
@@ -90,8 +89,9 @@ bool UIImagePicker::setImageTop(int i, int top) {
 }
 
 bool UIImagePicker::setImageLeft(int i, int left) {
-	if (i < 0 || i >= _imageCount || !_images[i].active)
+	if (i < 0 || i >= _imageCount || !_images[i].active) {
 		return false;
+	}
 
 	UIImagePickerImage &img = _images[i];
 
@@ -101,8 +101,9 @@ bool UIImagePicker::setImageLeft(int i, int left) {
 }
 
 bool UIImagePicker::setImageShapeUp(int i, Shape *shapeUp) {
-	if (i < 0 || i >= _imageCount || !_images[i].active)
+	if (i < 0 || i >= _imageCount || !_images[i].active) {
 		return false;
+	}
 
 	_images[i].shapeUp = shapeUp;
 
@@ -110,8 +111,9 @@ bool UIImagePicker::setImageShapeUp(int i, Shape *shapeUp) {
 }
 
 bool UIImagePicker::setImageShapeHovered(int i, Shape *shapeHovered) {
-	if (i < 0 || i >= _imageCount || !_images[i].active)
+	if (i < 0 || i >= _imageCount || !_images[i].active) {
 		return false;
+	}
 
 	_images[i].shapeHovered = shapeHovered;
 
@@ -119,8 +121,9 @@ bool UIImagePicker::setImageShapeHovered(int i, Shape *shapeHovered) {
 }
 
 bool UIImagePicker::setImageShapeDown(int i, Shape *shapeDown) {
-	if (i < 0 || i >= _imageCount || !_images[i].active)
+	if (i < 0 || i >= _imageCount || !_images[i].active) {
 		return false;
+	}
 
 	_images[i].shapeDown = shapeDown;
 
@@ -128,8 +131,9 @@ bool UIImagePicker::setImageShapeDown(int i, Shape *shapeDown) {
 }
 
 bool UIImagePicker::setImageTooltip(int i, const char *tooltip) {
-	if (i < 0 || i >= _imageCount || !_images[i].active)
+	if (i < 0 || i >= _imageCount || !_images[i].active) {
 		return false;
+	}
 
 	_images[i].tooltip = tooltip;
 
@@ -137,18 +141,19 @@ bool UIImagePicker::setImageTooltip(int i, const char *tooltip) {
 }
 
 bool UIImagePicker::resetActiveImage(int i) {
-	if (i < 0 || i >= _imageCount || !_images[i].active)
+	if (i < 0 || i >= _imageCount || !_images[i].active) {
 		return false;
+	}
 
 	resetImage(i);
 	return true;
 }
 
-void UIImagePicker::setCallbacks(UIImagePickerCallback *mouseInCallback,
-                                 UIImagePickerCallback *mouseOutCallback,
-                                 UIImagePickerCallback *mouseDownCallback,
-                                 UIImagePickerCallback *mouseUpCallback,
-                                 void *callbackData)
+void UIImagePicker::activate(UIImagePickerCallback *mouseInCallback,
+                             UIImagePickerCallback *mouseOutCallback,
+                             UIImagePickerCallback *mouseDownCallback,
+                             UIImagePickerCallback *mouseUpCallback,
+                             void *callbackData)
 {
 	_isButtonDown = false;
 	_mouseInCallback   = mouseInCallback;
@@ -162,19 +167,29 @@ void UIImagePicker::setCallbacks(UIImagePickerCallback *mouseInCallback,
 	_pressedImageIndex = -1;
 }
 
-void UIImagePicker::resetCallbacks() {}
-
-// TODO
-void UIImagePicker::drawTooltip() {}
+void UIImagePicker::deactivate() {
+	_isButtonDown = false;
+	_mouseInCallback   = nullptr;
+	_mouseOutCallback  = nullptr;
+	_mouseDownCallback = nullptr;
+	_mouseUpCallback   = nullptr;
+	_callbackData      = nullptr;
+	_hoverStartTimestamp = 0;
+	_isVisible = false;
+	_hoveredImageIndex = -1;
+	_pressedImageIndex = -1;
+}
 
 void UIImagePicker::draw(Graphics::Surface &surface) {
-	if (!_isVisible)
+	if (!_isVisible) {
 		return;
+	}
 
 	for (int i = 0; i != _imageCount; ++i) {
 		UIImagePickerImage &img = _images[i];
-		if (!img.active)
+		if (!img.active) {
 			continue;
+		}
 
 		// TODO: Check interaction with Mouse::isDisabled
 		if (i == _hoveredImageIndex && i == _pressedImageIndex && _isButtonDown) {
@@ -193,6 +208,47 @@ void UIImagePicker::draw(Graphics::Surface &surface) {
 	}
 }
 
+void UIImagePicker::drawTooltip(Graphics::Surface &surface, int x, int y) {
+	if (!_isVisible) {
+		return;
+	}
+
+	if (_hoveredImageIndex == -1 || _vm->_mouse->isDisabled() || !_images[_hoveredImageIndex].active || _vm->getTotalPlayTime() < _hoverStartTimestamp + 1000) {
+		return;
+	}
+
+	Common::String &tooltip = _images[_hoveredImageIndex].tooltip;
+	int width = _vm->_mainFont->getTextWidth(tooltip) + 1;
+	int height = _vm->_mainFont->getTextHeight(tooltip) + 1;
+
+	Common::Rect rect;
+	rect.left = x - ((width / 2) + 1);
+	if (rect.left < 0) {
+		rect.left = 0;
+	}
+
+	rect.top = y - 10;
+	if (rect.top < 0) {
+		rect.top = 0;
+	}
+
+	rect.right = width + rect.left + 3;
+	if (rect.right >= 640) {
+		rect.right = 639;
+		rect.left = 636 - width;
+	}
+
+	rect.bottom = height + rect.top + 1;
+	if (rect.bottom >= 480) {
+		rect.bottom = 479;
+		rect.top = 478 - height;
+	}
+
+	surface.fillRect(rect, 0);
+	surface.frameRect(rect, 0x7FFF);
+	_vm->_mainFont->drawColor(tooltip, surface, rect.left + 2, rect.top, 0x7FFF);
+}
+
 void UIImagePicker::handleMouseAction(int x, int y, bool down, bool up, bool ignore) {
 	if (!_isVisible || ignore) {
 		return;
@@ -210,13 +266,16 @@ void UIImagePicker::handleMouseAction(int x, int y, bool down, bool up, bool ign
 	if (hoveredImageIndex != _hoveredImageIndex) {
 		if (!_isButtonDown) {
 			if (hoveredImageIndex == -1) {
-				if (_mouseOutCallback)
+				if (_mouseOutCallback) {
 					_mouseOutCallback(hoveredImageIndex, _callbackData);
+				}
 			} else {
-				if (_mouseInCallback)
+				if (_mouseInCallback) {
 					_mouseInCallback(hoveredImageIndex, _callbackData);
+				}
 			}
 		}
+		_hoverStartTimestamp = _vm->getTotalPlayTime();
 		_hoveredImageIndex = hoveredImageIndex;
 	}
 
@@ -224,8 +283,11 @@ void UIImagePicker::handleMouseAction(int x, int y, bool down, bool up, bool ign
 	if (down && !_isButtonDown) {
 		_isButtonDown = true;
 		_pressedImageIndex = _hoveredImageIndex;
-		if (_mouseDownCallback)
-			_mouseDownCallback(_hoveredImageIndex, _callbackData);
+		if (_hoveredImageIndex != 1) {
+			if (_mouseDownCallback) {
+				_mouseDownCallback(_hoveredImageIndex, _callbackData);
+			}
+		}
 	}
 
 	// If mouse button changed to released
@@ -253,7 +315,7 @@ void UIImagePicker::resetImage(int i) {
 	img.shapeUp = nullptr;
 	img.shapeHovered = nullptr;
 	img.shapeDown = nullptr;
-	img.tooltip = nullptr;
+	img.tooltip.clear();
 }
 
 bool UIImagePicker::hasHoveredImage() {
diff --git a/engines/bladerunner/ui_image_picker.h b/engines/bladerunner/ui_image_picker.h
index c55aa48..c06b556 100644
--- a/engines/bladerunner/ui_image_picker.h
+++ b/engines/bladerunner/ui_image_picker.h
@@ -23,6 +23,10 @@
 #ifndef BLADERUNNER_UI_IMAGE_PICKER_H
 #define BLADERUNNER_UI_IMAGE_PICKER_H
 
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/str.h"
+
 namespace Graphics {
 struct Surface;
 }
@@ -31,10 +35,18 @@ namespace BladeRunner {
 
 class BladeRunnerEngine;
 class Shape;
-struct UIImagePickerImage;
 
 typedef void UIImagePickerCallback(int, void*);
 
+struct UIImagePickerImage {
+	int             active;
+	Common::Rect    rect;
+	Shape          *shapeUp;
+	Shape          *shapeHovered;
+	Shape          *shapeDown;
+	Common::String  tooltip;
+};
+
 class UIImagePicker {
 	BladeRunnerEngine *_vm;
 
@@ -42,9 +54,9 @@ class UIImagePicker {
 	int _imageCount;
 	int _hoveredImageIndex;
 	int _pressedImageIndex;
-	int _hoverStartTimestamp;
+	uint32 _hoverStartTimestamp;
 	int _isButtonDown;
-	UIImagePickerImage *_images;
+	Common::Array<UIImagePickerImage> _images;
 
 	UIImagePickerCallback *_mouseInCallback;
 	UIImagePickerCallback *_mouseOutCallback;
@@ -57,7 +69,7 @@ public:
 	~UIImagePicker();
 
 	void resetImages();
-	bool defineImage(int i, int left, int top, int right, int bottom, Shape *shapeUp, Shape *shapeHovered, Shape *shapeDown, const char *tooltip);
+	bool defineImage(int i, Common::Rect rect, Shape *shapeUp, Shape *shapeHovered, Shape *shapeDown, const char *tooltip);
 
 	bool setImageTop(int i, int top);
 	bool setImageLeft(int i, int left);
@@ -68,16 +80,16 @@ public:
 
 	bool resetActiveImage(int i);
 
-	void setCallbacks(UIImagePickerCallback *mouseInCallback,
-	                  UIImagePickerCallback *mouseOutCallback,
-	                  UIImagePickerCallback *mouseDownCallback,
-	                  UIImagePickerCallback *mouseUpCallback,
-	                  void *callbackData);
+	void activate(UIImagePickerCallback *mouseInCallback,
+	              UIImagePickerCallback *mouseOutCallback,
+	              UIImagePickerCallback *mouseDownCallback,
+	              UIImagePickerCallback *mouseUpCallback,
+	              void *callbackData);
 
-	void resetCallbacks();
+	void deactivate();
 
-	void drawTooltip();
 	void draw(Graphics::Surface &surface);
+	void drawTooltip(Graphics::Surface &surface, int x, int y);
 
 	void handleMouseAction(int x, int y, bool down, bool up, bool ignore = false);
 
diff --git a/engines/bladerunner/view.cpp b/engines/bladerunner/view.cpp
index ab9eb3e..ed5cef9 100644
--- a/engines/bladerunner/view.cpp
+++ b/engines/bladerunner/view.cpp
@@ -72,8 +72,8 @@ void View::calculateCameraPosition() {
 	Matrix4x3 invertedMatrix = invertMatrix(_sliceViewMatrix);
 
 	_cameraPosition.x = invertedMatrix(0, 3);
-	_cameraPosition.y = invertedMatrix(1, 3);
-	_cameraPosition.z = invertedMatrix(2, 3);
+	_cameraPosition.z = invertedMatrix(1, 3); // this is not a bug, it Z & Y are inverted in original source
+	_cameraPosition.y = invertedMatrix(2, 3);
 }
 
 Vector3 View::calculateScreenPosition(Vector3 worldPosition) {
diff --git a/engines/bladerunner/vqa_player.cpp b/engines/bladerunner/vqa_player.cpp
index f6eecc8..12baa26 100644
--- a/engines/bladerunner/vqa_player.cpp
+++ b/engines/bladerunner/vqa_player.cpp
@@ -108,42 +108,40 @@ int VQAPlayer::update() {
 		return -3;
 	}
 
+	if (now < _frameNextTime) {
+		return -1;
+	}
+
+	int frame = _frameNext;
+	_decoder.readFrame(_frameNext, 0x2);
+	_decoder.decodeVideoFrame();
 
-//	TODO: preload audio in better way
-	int audioPreloadFrames = 3;
-
-	if (now >= _frameNextTime) {
-		int frame = _frameNext;
-		_decoder.readFrame(_frameNext, 0x2);
-		_decoder.decodeVideoFrame();
-
-		if (_hasAudio) {
-			if (!_audioStarted) {
-				for (int i = 0; i < audioPreloadFrames; i++) {
-					if (_frameNext + i < _frameEnd) {
-						_decoder.readFrame(_frameNext + i, 0x1);
-						queueAudioFrame(_decoder.decodeAudioFrame());
-					}
+	int audioPreloadFrames = 14;
+
+	if (_hasAudio) {
+		if (!_audioStarted) {
+			for (int i = 0; i < audioPreloadFrames; i++) {
+				if (_frameNext + i < _frameEnd) {
+					_decoder.readFrame(_frameNext + i, 0x1);
+					queueAudioFrame(_decoder.decodeAudioFrame());
 				}
-				_vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, _audioStream);
-				_audioStarted = true;
-			}
-			if (_frameNext + audioPreloadFrames < _frameEnd) {
-				_decoder.readFrame(_frameNext + audioPreloadFrames, 0x1);
-				queueAudioFrame(_decoder.decodeAudioFrame());
 			}
+			_vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, _audioStream);
+			_audioStarted = true;
 		}
-		if (_frameNextTime == 0) {
-			_frameNextTime = now + 60000 / 15;
-		} else {
-			_frameNextTime += 60000 / 15;
+		if (_frameNext + audioPreloadFrames < _frameEnd) {
+			_decoder.readFrame(_frameNext + audioPreloadFrames, 0x1);
+			queueAudioFrame(_decoder.decodeAudioFrame());
 		}
-
-		_frameNext++;
-		return frame;
+	}
+	if (_frameNextTime == 0) {
+		_frameNextTime = now + 60000 / 15;
+	} else {
+		_frameNextTime += 60000 / 15;
 	}
 
-	return -1;
+	_frameNext++;
+	return frame;
 }
 
 void VQAPlayer::updateZBuffer(ZBuffer *zbuffer) {
@@ -159,7 +157,9 @@ void VQAPlayer::updateLights(Lights *lights) {
 }
 
 bool VQAPlayer::setLoop(int loop, int repeatsCount, int loopSetMode, void (*callback)(void *, int, int), void *callbackData) {
+#if 0
 	debug("VQAPlayer::setBeginAndEndFrameFromLoop(%i, %i, %i), streamLoaded = %i", loop, repeatsCount, loopSetMode, _s != nullptr);
+#endif
 	if (_s == nullptr) {
 		_loopInitial = loop;
 		_repeatsCountInitial = repeatsCount;
@@ -178,7 +178,9 @@ bool VQAPlayer::setLoop(int loop, int repeatsCount, int loopSetMode, void (*call
 }
 
 bool VQAPlayer::setBeginAndEndFrame(int begin, int end, int repeatsCount, int loopSetMode, void (*callback)(void *, int, int), void *callbackData) {
+#if 0
 	debug("VQAPlayer::setBeginAndEndFrame(%i, %i, %i, %i), streamLoaded = %i", begin, end, repeatsCount, loopSetMode, _s != nullptr);
+#endif
 
 	if (repeatsCount < 0) {
 		repeatsCount = -1;





More information about the Scummvm-git-logs mailing list