[Scummvm-git-logs] scummvm branch-2-9 -> a8930572186fa7d9fef2fc48e983898912265fd6

mgerhardy noreply at scummvm.org
Wed Apr 30 05:01:53 UTC 2025


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

Summary:
3eced3c6a4 TWINE: comments to mention the original source names
49e3fdf9e9 TWINE: added pitchbend values to playSample (they are still unused)
067722311f TWINE: fixed pitch handling for samples
9ca0e8ed64 TWINE: Fix compilation
1b16313b96 TWINE: the holomap rotation is slow and location interaction is weird
68e7353686 TWINE: The movement of the meca penguin is different from dos version
4ce8dbf73b TWINE: fixed memory leak in fla sample handling
f0fa6416ec TWINE: fixed Audio::makeVOCStream call and fixed memory leak in vox samples
6defd5c324 TWINE: fixed missing pitchbend parameter for environmental sounds
20d9634db0 TWINE: started to support mixer balance changing for some samples
a893057218 TWINE: Skipping the text intro with Escape also skips the subsequent FMV


Commit: 3eced3c6a422db3b124aa9e3b3878f9c00847d48
    https://github.com/scummvm/scummvm/commit/3eced3c6a422db3b124aa9e3b3878f9c00847d48
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2025-04-29T21:39:43+02:00

Commit Message:
TWINE: comments to mention the original source names

Changed paths:
    engines/twine/shared.h


diff --git a/engines/twine/shared.h b/engines/twine/shared.h
index ce4b5e7cf77..86c46c48aa4 100644
--- a/engines/twine/shared.h
+++ b/engines/twine/shared.h
@@ -287,38 +287,38 @@ enum class ControlMode {
 
 enum class AnimationTypes {
 	kAnimNone = -1,
-	kStanding = 0,  // GEN_ANIM_RIEN
-	kForward = 1,   // GEN_ANIM_MARCHE
-	kBackward = 2,  // GEN_ANIM_RECULE
-	kTurnLeft = 3,  // GEN_ANIM_GAUCHE
-	kTurnRight = 4, // GEN_ANIM_DROITE
-	kHit = 5,
-	kBigHit = 6,
-	kFall = 7,
-	kLanding = 8,
-	kLandingHit = 9,
-	kLandDeath = 10,
-	kAction = 11,
-	kClimbLadder = 12,
-	kTopLadder = 13,
-	kJump = 14,
-	kThrowBall = 15,
-	kHide = 16,
-	kKick = 17,
-	kRightPunch = 18,
-	kLeftPunch = 19,
-	kFoundItem = 20,
-	kDrawn = 21,
-	kHit2 = 22,
-	kSabreAttack = 23,
-	kPush = 27, // GEN_ANIM_POUSSE
-	kSabreUnknown = 24,
+	kStanding = 0,      // GEN_ANIM_RIEN
+	kForward = 1,       // GEN_ANIM_MARCHE
+	kBackward = 2,      // GEN_ANIM_RECULE
+	kTurnLeft = 3,      // GEN_ANIM_GAUCHE
+	kTurnRight = 4,     // GEN_ANIM_DROITE
+	kHit = 5,           // GEN_ANIM_ENCAISSE
+	kBigHit = 6,        // GEN_ANIM_CHOC
+	kFall = 7,          // GEN_ANIM_TOMBE
+	kLanding = 8,       // GEN_ANIM_RECEPTION
+	kLandingHit = 9,    // GEN_ANIM_RECEPTION_2
+	kLandDeath = 10,    // GEN_ANIM_MORT
+	kAction = 11,       // GEN_ANIM_ACTION
+	kClimbLadder = 12,  // GEN_ANIM_MONTE
+	kTopLadder = 13,    // GEN_ANIM_ECHELLE
+	kJump = 14,         // GEN_ANIM_SAUTE
+	kThrowBall = 15,    // GEN_ANIM_LANCE
+	kHide = 16,         // GEN_ANIM_CACHE
+	kKick = 17,         // GEN_ANIM_COUP_1
+	kRightPunch = 18,   // GEN_ANIM_COUP_2
+	kLeftPunch = 19,    // GEN_ANIM_COUP_3
+	kFoundItem = 20,    // GEN_ANIM_TROUVE
+	kDrawn = 21,        // GEN_ANIM_NOYADE
+	kHit2 = 22,         // GEN_ANIM_CHOC2
+	kSabreAttack = 23,  // GEN_ANIM_SABRE
+	kSabreUnknown = 24, // GEN_ANIM_DEGAINE
+	kPush = 27,         // GEN_ANIM_POUSSE
 	kCarStarting = 303,
 	kCarDriving = 304,
 	kCarDrivingBackwards = 305,
 	kCarStopping = 306,
 	kCarFrozen = 307,
-	kAnimInvalid = 255
+	kAnimInvalid = 255 // NO_ANIM
 };
 
 enum class AnimType {


Commit: 49e3fdf9e9fae436bbe7232b756d91f16dfdea9c
    https://github.com/scummvm/scummvm/commit/49e3fdf9e9fae436bbe7232b756d91f16dfdea9c
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2025-04-29T21:39:43+02:00

Commit Message:
TWINE: added pitchbend values to playSample (they are still unused)

see issue https://bugs.scummvm.org/ticket/15735

Changed paths:
    engines/twine/audio/sound.cpp
    engines/twine/audio/sound.h
    engines/twine/renderer/redraw.cpp
    engines/twine/scene/actor.cpp
    engines/twine/scene/animations.cpp
    engines/twine/scene/extra.cpp
    engines/twine/script/script_move.cpp
    engines/twine/script/script_move.h
    engines/twine/twine.cpp


diff --git a/engines/twine/audio/sound.cpp b/engines/twine/audio/sound.cpp
index b8d758e2cb1..19a72209cfb 100644
--- a/engines/twine/audio/sound.cpp
+++ b/engines/twine/audio/sound.cpp
@@ -112,7 +112,7 @@ void Sound::playFlaSample(int32 index, int32 repeat, uint8 balance, int32 volume
 	playSample(channelIdx, index, audioStream, repeat, Resources::HQR_FLASAMP_FILE);
 }
 
-void Sound::playSample(int32 index, int32 repeat, int32 x, int32 y, int32 z, int32 actorIdx) {
+void Sound::playSample(int32 index, uint16 pitchbend, int32 repeat, int32 x, int32 y, int32 z, int32 actorIdx) {
 	if (!_engine->_cfgfile.Sound) {
 		return;
 	}
@@ -131,6 +131,9 @@ void Sound::playSample(int32 index, int32 repeat, int32 x, int32 y, int32 z, int
 		samplesPlayingActors[channelIdx] = -1;
 	}
 
+	// TODO: implement pitchbend - see https://bugs.scummvm.org/ticket/15735
+	// frequency would be 11025 + (pitchbend - 0x1000);
+
 	uint8 *sampPtr = _engine->_resources->_samplesTable[index];
 	uint32 sampSize = _engine->_resources->_samplesSizeTable[index];
 	Common::MemoryReadStream *stream = new Common::MemoryReadStream(sampPtr, sampSize, DisposeAfterUse::NO);
diff --git a/engines/twine/audio/sound.h b/engines/twine/audio/sound.h
index 08d73da772b..3db1bd056f2 100644
--- a/engines/twine/audio/sound.h
+++ b/engines/twine/audio/sound.h
@@ -44,7 +44,7 @@ enum _Samples {
 	TaskCompleted = 41,
 	Hit = 86,
 	ItemFound = 97,
-	WalkFloorBegin = 126,
+	WalkFloorBegin = 126, // BASE_STEP_SOUND
 	WalkFloorRightBegin = 141
 };
 }
@@ -100,11 +100,10 @@ public:
 	 * @param z sound generating entity z position
 	 * @param actorIdx
 	 */
-	void playSample(int32 index, int32 repeat = 1, int32 x = 128, int32 y = 128, int32 z = 128, int32 actorIdx = -1); // HQ_3D_MixSample
-	void playSample(int32 index, int32 repeat, const IVec3 &pos, int32 actorIdx = -1) { // HQ_MixSample
-		playSample(index, repeat, pos.x, pos.y, pos.z, actorIdx);
+	void playSample(int32 index, uint16 pitchbend = 0x1000, int32 repeat = 1, int32 x = 128, int32 y = 128, int32 z = 128, int32 actorIdx = -1); // HQ_3D_MixSample
+	void playSample(int32 index, uint16 pitchbend, int32 repeat, const IVec3 &pos, int32 actorIdx = -1) { // HQ_MixSample
+		playSample(index, pitchbend, repeat, pos.x, pos.y, pos.z, actorIdx);
 	}
-
 	/** Pause samples */
 	void pauseSamples();
 
diff --git a/engines/twine/renderer/redraw.cpp b/engines/twine/renderer/redraw.cpp
index 8c617f4b4e1..cfff95a7a80 100644
--- a/engines/twine/renderer/redraw.cpp
+++ b/engines/twine/renderer/redraw.cpp
@@ -332,7 +332,7 @@ int32 Redraw::fillExtraDrawingList(DrawListStruct *drawList, int32 drawListPos)
 			if (_engine->timerRef - extra->spawnTime > 35) {
 				extra->spawnTime = _engine->timerRef;
 				extra->type &= ~ExtraType::TIME_IN;
-				_engine->_sound->playSample(Samples::ItemPopup, 1, extra->pos);
+				_engine->_sound->playSample(Samples::ItemPopup, 0x1000, 1, extra->pos);
 			}
 			continue;
 		}
diff --git a/engines/twine/scene/actor.cpp b/engines/twine/scene/actor.cpp
index 8c92373bc2c..ad3c67f46b6 100644
--- a/engines/twine/scene/actor.cpp
+++ b/engines/twine/scene/actor.cpp
@@ -436,13 +436,13 @@ void Actor::giveExtraBonus(int32 actorIdx) {
 	}
 	if (actor->_workFlags.bIsDead) {
 		_engine->_extra->addExtraBonus(actor->posObj(), LBAAngles::ANGLE_90, LBAAngles::ANGLE_0, bonusSprite, actor->_bonusAmount);
-		_engine->_sound->playSample(Samples::ItemPopup, 1, actor->posObj(), actorIdx);
+		_engine->_sound->playSample(Samples::ItemPopup, 0x1000, 1, actor->posObj(), actorIdx);
 	} else {
 		const ActorStruct *sceneHero = _engine->_scene->_sceneHero;
 		const int32 angle = _engine->_movements->getAngle(actor->posObj(), sceneHero->posObj());
 		const IVec3 pos(actor->_posObj.x, actor->_posObj.y + actor->_boundingBox.maxs.y, actor->_posObj.z);
 		_engine->_extra->addExtraBonus(pos, LBAAngles::ANGLE_70, angle, bonusSprite, actor->_bonusAmount);
-		_engine->_sound->playSample(Samples::ItemPopup, 1, pos, actorIdx);
+		_engine->_sound->playSample(Samples::ItemPopup, 0x1000, 1, pos, actorIdx);
 	}
 }
 
diff --git a/engines/twine/scene/animations.cpp b/engines/twine/scene/animations.cpp
index 962592c8cff..e5ef81e0449 100644
--- a/engines/twine/scene/animations.cpp
+++ b/engines/twine/scene/animations.cpp
@@ -294,9 +294,14 @@ void Animations::processAnimActions(int32 actorIdx) { // GereAnimAction
 			}
 			break;
 		case ActionType::ACTION_SAMPLE:
+			if (action.animFrame == actor->_frame) {
+				_engine->_sound->playSample(action.sampleIndex, 0x1000, 1, actor->posObj(), actorIdx);
+			}
+			break;
 		case ActionType::ACTION_SAMPLE_FREQ:
 			if (action.animFrame == actor->_frame) {
-				_engine->_sound->playSample(action.sampleIndex, 1, actor->posObj(), actorIdx);
+				const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(action.frequency) - (action.frequency / 2);
+				_engine->_sound->playSample(action.sampleIndex, pitchBend, 1, actor->posObj(), actorIdx);
 			}
 			break;
 		case ActionType::ACTION_THROW_EXTRA_BONUS:
@@ -311,7 +316,7 @@ void Animations::processAnimActions(int32 actorIdx) { // GereAnimAction
 			break;
 		case ActionType::ACTION_SAMPLE_REPEAT:
 			if (action.animFrame == actor->_frame) {
-				_engine->_sound->playSample(action.sampleIndex, action.repeat, actor->posObj(), actorIdx);
+				_engine->_sound->playSample(action.sampleIndex, 0x1000, action.repeat, actor->posObj(), actorIdx);
 			}
 			break;
 		case ActionType::ACTION_THROW_SEARCH:
@@ -332,13 +337,15 @@ void Animations::processAnimActions(int32 actorIdx) { // GereAnimAction
 		case ActionType::ACTION_LEFT_STEP:
 			if (action.animFrame == actor->_frame && (actor->_brickSound & 0xF0U) != 0xF0U) {
 				const int16 sampleIdx = (actor->_brickSound & 0x0FU) + Samples::WalkFloorBegin;
-				_engine->_sound->playSample(sampleIdx, 1, actor->posObj(), actorIdx);
+				const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(1000) - 500;
+				_engine->_sound->playSample(sampleIdx, pitchBend, 1, actor->posObj(), actorIdx);
 			}
 			break;
 		case ActionType::ACTION_RIGHT_STEP:
 			if (action.animFrame == actor->_frame && (actor->_brickSound & 0xF0U) != 0xF0U) {
 				const int16 sampleIdx = (actor->_brickSound & 0x0FU) + Samples::WalkFloorRightBegin;
-				_engine->_sound->playSample(sampleIdx, 1, actor->posObj(), actorIdx);
+				const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(1000) - 500;
+				_engine->_sound->playSample(sampleIdx, pitchBend, 1, actor->posObj(), actorIdx);
 			}
 			break;
 		case ActionType::ACTION_HERO_HITTING:
@@ -400,7 +407,7 @@ void Animations::processAnimActions(int32 actorIdx) { // GereAnimAction
 	}
 }
 
-bool Animations::initAnim(AnimationTypes newAnim, AnimType flag, AnimationTypes genNextAnim, int32 actorIdx) {
+bool Animations::initAnim(AnimationTypes genNewAnim, AnimType flag, AnimationTypes genNextAnim, int32 actorIdx) {
 	ActorStruct *actor = _engine->_scene->getActor(actorIdx);
 	if (actor->_body == -1) {
 		return false;
@@ -410,7 +417,7 @@ bool Animations::initAnim(AnimationTypes newAnim, AnimType flag, AnimationTypes
 		return false;
 	}
 
-	if (newAnim == actor->_genAnim && actor->_anim != -1) {
+	if (genNewAnim == actor->_genAnim && actor->_anim != -1) {
 		return true;
 	}
 
@@ -418,7 +425,7 @@ bool Animations::initAnim(AnimationTypes newAnim, AnimType flag, AnimationTypes
 		genNextAnim = actor->_genAnim;
 	}
 
-	int32 newanim = searchAnim(newAnim, actorIdx);
+	int32 newanim = searchAnim(genNewAnim, actorIdx);
 
 	if (newanim == -1) {
 		newanim = searchAnim(AnimationTypes::kStanding, actorIdx);
@@ -428,7 +435,7 @@ bool Animations::initAnim(AnimationTypes newAnim, AnimType flag, AnimationTypes
 	}
 
 	if (flag != AnimType::kAnimationSet && actor->_flagAnim == AnimType::kAnimationAllThen) {
-		actor->_nextGenAnim = newAnim;
+		actor->_nextGenAnim = genNewAnim;
 		return false;
 	}
 
@@ -455,7 +462,7 @@ bool Animations::initAnim(AnimationTypes newAnim, AnimType flag, AnimationTypes
 	}
 
 	actor->_anim = newanim;
-	actor->_genAnim = newAnim;
+	actor->_genAnim = genNewAnim;
 	actor->_nextGenAnim = genNextAnim;
 	actor->_ptrAnimAction = _currentActorAnimExtraPtr;
 
diff --git a/engines/twine/scene/extra.cpp b/engines/twine/scene/extra.cpp
index 258a9365fb7..32631a1cb52 100644
--- a/engines/twine/scene/extra.cpp
+++ b/engines/twine/scene/extra.cpp
@@ -610,7 +610,7 @@ void Extra::gereExtras() {
 			const int32 angle = ClampAngle(tmpAngle - extra->angle);
 
 			if (angle > LBAAngles::ANGLE_140 && angle < LBAAngles::ANGLE_210) {
-				_engine->_sound->playSample(Samples::ItemFound, 1, _engine->_scene->_sceneHero->posObj(), OWN_ACTOR_SCENE_INDEX);
+				_engine->_sound->playSample(Samples::ItemFound, 0x1000, 1, _engine->_scene->_sceneHero->posObj(), OWN_ACTOR_SCENE_INDEX);
 
 				if (extraKey->info1 > 1) {
 					const IVec3 &projPos = _engine->_renderer->projectPoint(extraKey->pos - _engine->_grid->_worldCube);
@@ -643,7 +643,7 @@ void Extra::gereExtras() {
 			_engine->_movements->initRealValue(LBAAngles::ANGLE_0, extra->destPos.z, LBAAngles::ANGLE_17, &extra->trackActorMove);
 
 			if (extraIdx == _engine->_collision->extraCheckExtraCol(extra, _engine->_gameState->_magicBall)) {
-				_engine->_sound->playSample(Samples::ItemFound, 1, _engine->_scene->_sceneHero->posObj(), OWN_ACTOR_SCENE_INDEX);
+				_engine->_sound->playSample(Samples::ItemFound, 0x1000, 1, _engine->_scene->_sceneHero->posObj(), OWN_ACTOR_SCENE_INDEX);
 
 				if (extraKey->info1 > 1) {
 					const IVec3 &projPos = _engine->_renderer->projectPoint(extraKey->pos - _engine->_grid->_worldCube);
@@ -720,7 +720,8 @@ void Extra::gereExtras() {
 				}
 				// if extra is magic ball
 				if (i == _engine->_gameState->_magicBall) {
-					_engine->_sound->playSample(Samples::Hit, 1, extra->pos);
+					const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(300) - 150;
+					_engine->_sound->playSample(Samples::Hit, pitchBend, 1, extra->pos);
 
 					// can't bounce with not magic points
 					if (_engine->_gameState->_magicBallType <= 0) {
@@ -791,7 +792,7 @@ void Extra::gereExtras() {
 		if ((extra->type & ExtraType::TAKABLE) && !(extra->type & ExtraType::FLY)) {
 			// if hero touch extra
 			if (_engine->_collision->extraCheckObjCol(extra, -1) == 0) {
-				_engine->_sound->playSample(Samples::ItemFound, 1, extra->pos);
+				_engine->_sound->playSample(Samples::ItemFound, 0x1000, 1, extra->pos);
 
 				if (extra->info1 > 1) {
 					const IVec3 &projPos = _engine->_renderer->projectPoint(extra->pos - _engine->_grid->_worldCube);
diff --git a/engines/twine/script/script_move.cpp b/engines/twine/script/script_move.cpp
index 1021b905bfa..8737bbb00d7 100644
--- a/engines/twine/script/script_move.cpp
+++ b/engines/twine/script/script_move.cpp
@@ -280,7 +280,7 @@ int32 ScriptMove::mWAIT_NUM_ANIM(TwinEEngine *engine, MoveScriptContext &ctx) {
 int32 ScriptMove::mSAMPLE(TwinEEngine *engine, MoveScriptContext &ctx) {
 	int32 sampleIdx = ctx.stream.readSint16LE();
 	debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SAMPLE(%i)", (int)sampleIdx);
-	engine->_sound->playSample(sampleIdx, 1, ctx.actor->posObj(), ctx.actorIdx);
+	engine->_sound->playSample(sampleIdx, 0x1000, 1, ctx.actor->posObj(), ctx.actorIdx);
 	return 0;
 }
 
@@ -491,7 +491,8 @@ int32 ScriptMove::mWAIT_DOOR(TwinEEngine *engine, MoveScriptContext &ctx) {
 int32 ScriptMove::mSAMPLE_RND(TwinEEngine *engine, MoveScriptContext &ctx) {
 	int32 sampleIdx = ctx.stream.readSint16LE();
 	debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SAMPLE_RND(%i)", (int)sampleIdx);
-	engine->_sound->playSample(sampleIdx, 1, ctx.actor->posObj(), ctx.actorIdx);
+	const uint16 pitchbend = 0x800 + engine->getRandomNumber(0x800);
+	engine->_sound->playSample(sampleIdx, pitchbend, 1, ctx.actor->posObj(), ctx.actorIdx);
 	return 0;
 }
 
@@ -503,7 +504,7 @@ int32 ScriptMove::mSAMPLE_ALWAYS(TwinEEngine *engine, MoveScriptContext &ctx) {
 	int32 sampleIdx = ctx.stream.readSint16LE();
 	debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SAMPLE_ALWAYS(%i)", (int)sampleIdx);
 	if (!engine->_sound->isSamplePlaying(sampleIdx)) { // if its not playing
-		engine->_sound->playSample(sampleIdx, -1, ctx.actor->posObj(), ctx.actorIdx);
+		engine->_sound->playSample(sampleIdx, 0x1000, 0, ctx.actor->posObj(), ctx.actorIdx);
 	}
 	return 0;
 }
@@ -552,8 +553,8 @@ int32 ScriptMove::mPLAY_FLA(TwinEEngine *engine, MoveScriptContext &ctx) {
  * @note Opcode @c 0x1F
  */
 int32 ScriptMove::mREPEAT_SAMPLE(TwinEEngine *engine, MoveScriptContext &ctx) {
-	ctx.numRepeatSample = ctx.stream.readSint16LE();
-	debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::REPEAT_SAMPLE(%i)", (int)ctx.numRepeatSample);
+	ctx.bigSampleRepeat = ctx.stream.readSint16LE();
+	debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::REPEAT_SAMPLE(%i)", (int)ctx.bigSampleRepeat);
 	return 0;
 }
 
@@ -564,8 +565,8 @@ int32 ScriptMove::mREPEAT_SAMPLE(TwinEEngine *engine, MoveScriptContext &ctx) {
 int32 ScriptMove::mSIMPLE_SAMPLE(TwinEEngine *engine, MoveScriptContext &ctx) {
 	int32 sampleIdx = ctx.stream.readSint16LE();
 	debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SIMPLE_SAMPLE(%i)", (int)sampleIdx);
-	engine->_sound->playSample(sampleIdx, ctx.numRepeatSample, ctx.actor->posObj(), ctx.actorIdx);
-	ctx.numRepeatSample = 1;
+	engine->_sound->playSample(sampleIdx, 0x1000, ctx.bigSampleRepeat, ctx.actor->posObj(), ctx.actorIdx);
+	ctx.bigSampleRepeat = 1;
 	return 0;
 }
 
diff --git a/engines/twine/script/script_move.h b/engines/twine/script/script_move.h
index e77da56ab47..d52a35e2c5a 100644
--- a/engines/twine/script/script_move.h
+++ b/engines/twine/script/script_move.h
@@ -30,7 +30,7 @@ namespace TwinE {
 struct MoveScriptContext {
 	int32 actorIdx;
 	ActorStruct *actor;
-	int32 numRepeatSample = 1;
+	int32 bigSampleRepeat = 1;
 
 	Common::MemorySeekableReadWriteStream stream;
 
diff --git a/engines/twine/twine.cpp b/engines/twine/twine.cpp
index 78912b5e46c..06509cbb91e 100644
--- a/engines/twine/twine.cpp
+++ b/engines/twine/twine.cpp
@@ -1096,7 +1096,8 @@ bool TwinEEngine::runGameEngine() { // mainLoopInteration
 				actor->_workFlags.bIsHitting = 0;
 #endif
 			} else {
-				_sound->playSample(Samples::Explode, 1, actor->posObj(), a);
+				const uint16 pitchBend = 0x1000 + getRandomNumber(2000) - (2000 / 2);
+				_sound->playSample(Samples::Explode, pitchBend, 1, actor->posObj(), a);
 
 				if (a == _scene->_mecaPenguinIdx) {
 					_extra->extraExplo(actor->posObj());
@@ -1160,7 +1161,8 @@ bool TwinEEngine::runGameEngine() { // mainLoopInteration
 						actor->_flags.bNoShadow = 1;
 					}
 				} else {
-					_sound->playSample(Samples::Explode, 1, actor->posObj(), a);
+					const uint16 pitchBend = 0x1000 + getRandomNumber(2000) - (2000 / 2);
+					_sound->playSample(Samples::Explode, pitchBend, 1, actor->posObj(), a);
 					if (actor->_bonusParameter.cloverleaf || actor->_bonusParameter.kashes || actor->_bonusParameter.key || actor->_bonusParameter.lifepoints || actor->_bonusParameter.magicpoints) {
 						if (!actor->_bonusParameter.givenNothing) {
 							_actor->giveExtraBonus(a);


Commit: 067722311fe248354ecbec5bfa60710463360b2f
    https://github.com/scummvm/scummvm/commit/067722311fe248354ecbec5bfa60710463360b2f
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2025-04-29T21:39:48+02:00

Commit Message:
TWINE: fixed pitch handling for samples

see https://bugs.scummvm.org/ticket/15735

Changed paths:
    engines/twine/audio/sound.cpp


diff --git a/engines/twine/audio/sound.cpp b/engines/twine/audio/sound.cpp
index 19a72209cfb..6dcf4d2e8a6 100644
--- a/engines/twine/audio/sound.cpp
+++ b/engines/twine/audio/sound.cpp
@@ -131,14 +131,13 @@ void Sound::playSample(int32 index, uint16 pitchbend, int32 repeat, int32 x, int
 		samplesPlayingActors[channelIdx] = -1;
 	}
 
-	// TODO: implement pitchbend - see https://bugs.scummvm.org/ticket/15735
-	// frequency would be 11025 + (pitchbend - 0x1000);
-
 	uint8 *sampPtr = _engine->_resources->_samplesTable[index];
 	uint32 sampSize = _engine->_resources->_samplesSizeTable[index];
 	Common::MemoryReadStream *stream = new Common::MemoryReadStream(sampPtr, sampSize, DisposeAfterUse::NO);
-	Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(stream, DisposeAfterUse::YES);
+	Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
 	playSample(channelIdx, index, audioStream, repeat, Resources::HQR_SAMPLES_FILE, Audio::Mixer::kSFXSoundType);
+	uint16 frequency = 11025 + (pitchbend - 0x1000);
+	_engine->_system->getMixer()->setChannelRate(samplesPlaying[channelIdx], frequency);
 }
 
 bool Sound::playVoxSample(const TextEntry *text) {


Commit: 9ca0e8ed64850059c902913bac8c244fee6a792b
    https://github.com/scummvm/scummvm/commit/9ca0e8ed64850059c902913bac8c244fee6a792b
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2025-04-29T21:39:51+02:00

Commit Message:
TWINE: Fix compilation

Changed paths:
    engines/twine/audio/sound.cpp


diff --git a/engines/twine/audio/sound.cpp b/engines/twine/audio/sound.cpp
index 6dcf4d2e8a6..cbac347ffb6 100644
--- a/engines/twine/audio/sound.cpp
+++ b/engines/twine/audio/sound.cpp
@@ -21,6 +21,7 @@
 
 #include "twine/audio/sound.h"
 #include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
 #include "audio/decoders/voc.h"
 #include "common/config-manager.h"
 #include "common/memstream.h"


Commit: 1b16313b96c9a5f5accf1852934c2780d1e18add
    https://github.com/scummvm/scummvm/commit/1b16313b96c9a5f5accf1852934c2780d1e18add
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2025-04-29T21:40:55+02:00

Commit Message:
TWINE: the holomap rotation is slow and location interaction is weird

See issue https://bugs.scummvm.org/ticket/15409 and https://bugs.scummvm.org/ticket/12074

Changed paths:
    engines/twine/holomap_v1.cpp
    engines/twine/holomap_v1.h
    engines/twine/text.h


diff --git a/engines/twine/holomap_v1.cpp b/engines/twine/holomap_v1.cpp
index 6daef4931ae..84ab8b9def6 100644
--- a/engines/twine/holomap_v1.cpp
+++ b/engines/twine/holomap_v1.cpp
@@ -690,10 +690,24 @@ void HolomapV1::holoMap() {
 		}
 
 		if (_dialstat) {
-			_engine->_text->drawHolomapLocation(_listHoloPos[_current].mess);
+			_engine->_text->normalWinDial();
+			_engine->_text->setFontCrossColor(COLOR_WHITE);
+			_engine->_interface->box(_engine->_text->_dialTextBox, COLOR_BLACK);
+			_engine->saveFrontBuffer();
+			_engine->_text->commonOpenDial(_listHoloPos[_current].mess);
+			_engine->_text->initDialWindow();
+			_textState = ProgressiveTextState::ContinueRunning;
 			_dialstat = false;
 		}
 
+		if (_textState == ProgressiveTextState::ContinueRunning) {
+			_textState = _engine->_text->nextDialChar();
+			if (_textState != ProgressiveTextState::ContinueRunning) {
+				_engine->_text->fadeInRemainingChars();
+				_engine->_text->closeDial();
+			}
+		}
+
 		++_engine->timerRef;
 		debugC(3, kDebugLevels::kDebugTimers, "Holomap time: %i", _engine->timerRef);
 
diff --git a/engines/twine/holomap_v1.h b/engines/twine/holomap_v1.h
index 6c3f1671897..e003c6e59f8 100644
--- a/engines/twine/holomap_v1.h
+++ b/engines/twine/holomap_v1.h
@@ -24,6 +24,7 @@
 
 #include "twine/holomap.h"
 #include "twine/shared.h"
+#include "twine/text.h"
 
 #define NUM_HOLOMAPCOLORS 32
 #define HOLOMAP_PALETTE_INDEX (12*16)
@@ -111,6 +112,7 @@ public:
 	bool _flagredraw = false;
 	bool _dialstat = false;
 	bool _flagpal = false;
+	ProgressiveTextState _textState = ProgressiveTextState::End;
 
 	/**
 	 * Set Holomap location position
diff --git a/engines/twine/text.h b/engines/twine/text.h
index 576ad826500..958efc69a2c 100644
--- a/engines/twine/text.h
+++ b/engines/twine/text.h
@@ -143,20 +143,20 @@ private:
 	/** Dialogue text buffer size for cross coloring dialogues */
 	int32 _nbDegrade = 0;
 
-	// Dial_X1, Dial_Y1
-	Common::Rect _dialTextBox { 0, 0, 0, 0};
-
 	int32 _maxLineDial = 0;
 	int32 _dialMaxSize = 0;
 
 	bool _isShiftJIS = false;
 	bool _isVisualRTL = false;
 
-	bool displayText(TextId index, bool showText, bool playVox, bool loop); // MyDial
 public:
 	Text(TwinEEngine *engine);
 	~Text();
 
+	// Dial_X1, Dial_Y1
+	Common::Rect _dialTextBox { 0, 0, 0, 0};
+	bool displayText(TextId index, bool showText, bool playVox, bool loop); // MyDial
+
 	static const int32 lineHeight = INTER_LINE;
 
 	bool _flagRunningDial = false;


Commit: 68e73536862950585ec5de17509f070890b73685
    https://github.com/scummvm/scummvm/commit/68e73536862950585ec5de17509f070890b73685
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2025-04-29T21:44:22+02:00

Commit Message:
TWINE: The movement of the meca penguin is different from dos version

fixed issue 13731 (https://bugs.scummvm.org/ticket/13731)

Changed paths:
    engines/twine/debugger/debugtools.cpp
    engines/twine/parser/body.h
    engines/twine/renderer/redraw.cpp
    engines/twine/scene/actor.cpp
    engines/twine/scene/actor.h
    engines/twine/scene/animations.cpp
    engines/twine/scene/collision.cpp
    engines/twine/scene/gamestate.cpp
    engines/twine/scene/movements.cpp
    engines/twine/scene/scene.cpp
    engines/twine/script/script_life.cpp
    engines/twine/shared.h


diff --git a/engines/twine/debugger/debugtools.cpp b/engines/twine/debugger/debugtools.cpp
index b3ee08aafc6..4e7523e2b13 100644
--- a/engines/twine/debugger/debugtools.cpp
+++ b/engines/twine/debugger/debugtools.cpp
@@ -539,7 +539,7 @@ static void actorDetailsWindow(int &actorIdx, TwinEEngine *engine) {
 				ImGui::TableNextColumn();
 				ImGui::Text("Strength");
 				ImGui::TableNextColumn();
-				ImGui::Text("%i", actor->_strengthOfHit);
+				ImGui::Text("%i", actor->_hitForce);
 				ImGui::TableNextColumn();
 				ImGui::Text("Hit by");
 				ImGui::TableNextColumn();
diff --git a/engines/twine/parser/body.h b/engines/twine/parser/body.h
index 76b10c7d49e..685d5eff664 100644
--- a/engines/twine/parser/body.h
+++ b/engines/twine/parser/body.h
@@ -32,6 +32,12 @@
 
 namespace TwinE {
 
+/** Actors animation timer structure */
+struct AnimTimerDataStruct {
+	const KeyFrame *ptr = nullptr;
+	int32 time = 0; // keyframe time
+};
+
 class BodyData : public Parser {
 private:
 	void loadVertices(Common::SeekableReadStream &stream);
@@ -55,6 +61,7 @@ protected:
 
 public:
 	bool animated = false;
+	AnimTimerDataStruct _animTimerData;
 
 	BoundingBox bbox;
 	int16 offsetToData = 0;
diff --git a/engines/twine/renderer/redraw.cpp b/engines/twine/renderer/redraw.cpp
index cfff95a7a80..084bb8f7bb3 100644
--- a/engines/twine/renderer/redraw.cpp
+++ b/engines/twine/renderer/redraw.cpp
@@ -30,6 +30,7 @@
 #include "twine/input.h"
 #include "twine/menu/interface.h"
 #include "twine/menu/menu.h"
+#include "twine/parser/body.h"
 #include "twine/parser/sprite.h"
 #include "twine/renderer/renderer.h"
 #include "twine/renderer/screens.h"
@@ -404,7 +405,8 @@ void Redraw::processDrawListActors(const DrawListStruct &drawCmd, bool flagflip)
 	ActorStruct *actor = _engine->_scene->getActor(actorIdx);
 	if (actor->_anim >= 0) {
 		const AnimData &animData = _engine->_resources->_animData[actor->_anim];
-		_engine->_animations->setInterAnimObjet2(actor->_frame, animData, actor->_entityDataPtr->getBody(actor->_body), &actor->_animTimerData);
+		BodyData &bodyData = actor->_entityDataPtr->getBody(actor->_body);
+		_engine->_animations->setInterAnimObjet2(actor->_frame, animData, bodyData, &bodyData._animTimerData);
 	}
 
 	const IVec3 &delta = actor->posObj() - _engine->_grid->_worldCube;
diff --git a/engines/twine/scene/actor.cpp b/engines/twine/scene/actor.cpp
index ad3c67f46b6..61bc56a24ab 100644
--- a/engines/twine/scene/actor.cpp
+++ b/engines/twine/scene/actor.cpp
@@ -125,7 +125,7 @@ void Actor::setBehaviour(HeroBehaviourType behaviour) {
 	sceneHero->_genAnim = AnimationTypes::kAnimNone;
 	sceneHero->_flagAnim = AnimType::kAnimationTypeRepeat;
 
-	_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, OWN_ACTOR_SCENE_INDEX);
+	_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, OWN_ACTOR_SCENE_INDEX);
 }
 
 void Actor::setFrame(int32 actorIdx, uint32 frame) {
@@ -207,7 +207,8 @@ void Actor::setFrame(int32 actorIdx, uint32 frame) {
 void Actor::initSprite(int32 spriteNum, int32 actorIdx) {
 	ActorStruct *localActor = _engine->_scene->getActor(actorIdx);
 
-	localActor->_sprite = spriteNum;
+	localActor->_sprite = spriteNum; // lba2
+
 	if (!localActor->_flags.bSprite3D) {
 		return;
 	}
@@ -240,25 +241,25 @@ int32 Actor::searchBody(BodyType bodyIdx, int32 actorIdx, ActorBoundingBox &acto
 	return (int)bodyIdx;
 }
 
-void Actor::initBody(BodyType bodyIdx, int16 actorIdx) {
+void Actor::initBody(BodyType gennewbody, int16 actorIdx) {
 	ActorStruct *localActor = _engine->_scene->getActor(actorIdx);
 	if (localActor->_flags.bSprite3D) {
 		return;
 	}
 
-	debug(1, "Load body %i for actor %i", (int)bodyIdx, actorIdx);
+	debug(1, "Load body %i for actor %i", (int)gennewbody, actorIdx);
 
-	if (IS_HERO(actorIdx) && _heroBehaviour == HeroBehaviourType::kProtoPack && bodyIdx != BodyType::btTunic && bodyIdx != BodyType::btNormal) {
+	if (IS_HERO(actorIdx) && _heroBehaviour == HeroBehaviourType::kProtoPack && gennewbody != BodyType::btTunic && gennewbody != BodyType::btNormal) {
 		setBehaviour(HeroBehaviourType::kNormal);
 	}
 
 	ActorBoundingBox actorBoundingBox;
-	const int32 newBody = searchBody(bodyIdx, actorIdx, actorBoundingBox);
+	const int32 newBody = searchBody(gennewbody, actorIdx, actorBoundingBox);
 	if (newBody == -1) {
 		localActor->_genBody = BodyType::btNone;
 		localActor->_body = -1;
 		localActor->_boundingBox = BoundingBox();
-		debug("Failed to initialize body %i for actor %i", (int)bodyIdx, actorIdx);
+		debug("Failed to initialize body %i for actor %i", (int)gennewbody, actorIdx);
 		return;
 	}
 
@@ -268,7 +269,7 @@ void Actor::initBody(BodyType bodyIdx, int16 actorIdx) {
 
 	const int32 oldBody = localActor->_body;
 	localActor->_body = newBody;
-	localActor->_genBody = bodyIdx;
+	localActor->_genBody = gennewbody;
 
 	if (actorBoundingBox.hasBoundingBox) {
 		localActor->_boundingBox = actorBoundingBox.bbox;
@@ -305,6 +306,8 @@ void Actor::copyInterAnim(const BodyData &src, BodyData &dest) {
 		return;
 	}
 
+	dest._animTimerData = src._animTimerData;
+
 	const int16 numBones = MIN<int16>((int16)src.getNumBones(), (int16)dest.getNumBones());
 	for (int16 i = 0; i < numBones; ++i) {
 		const BoneFrame *srcBoneFrame = src.getBoneState(i);
@@ -317,7 +320,7 @@ void Actor::startInitObj(int16 actorIdx) {
 	ActorStruct *actor = _engine->_scene->getActor(actorIdx);
 
 	if (actor->_flags.bSprite3D) {
-		if (actor->_strengthOfHit != 0) {
+		if (actor->_hitForce != 0) {
 			actor->_workFlags.bIsHitting = 1;
 		}
 
@@ -340,7 +343,7 @@ void Actor::startInitObj(int16 actorIdx) {
 		actor->_flagAnim = AnimType::kAnimationTypeRepeat;
 
 		if (actor->_body != -1) {
-			_engine->_animations->initAnim(actor->_genAnim, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, actorIdx);
+			_engine->_animations->initAnim(actor->_genAnim, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
 		}
 
 		_engine->_movements->initRealAngle(actor->_beta, actor->_beta, LBAAngles::ANGLE_0, &actor->realAngle);
@@ -393,9 +396,9 @@ void Actor::hitObj(int32 actorIdx, int32 actorIdxAttacked, int32 hitforce, int32
 			}
 
 			if (_engine->getRandomNumber() & 1) {
-				_engine->_animations->initAnim(AnimationTypes::kHit2, AnimType::kAnimationInsert, AnimationTypes::kAnimInvalid, actorIdxAttacked);
+				_engine->_animations->initAnim(AnimationTypes::kHit2, AnimType::kAnimationInsert, AnimationTypes::kNoAnim, actorIdxAttacked);
 			} else {
-				_engine->_animations->initAnim(AnimationTypes::kBigHit, AnimType::kAnimationInsert, AnimationTypes::kAnimInvalid, actorIdxAttacked);
+				_engine->_animations->initAnim(AnimationTypes::kBigHit, AnimType::kAnimationInsert, AnimationTypes::kNoAnim, actorIdxAttacked);
 			}
 		}
 
@@ -410,7 +413,7 @@ void Actor::hitObj(int32 actorIdx, int32 actorIdxAttacked, int32 hitforce, int32
 			actor->_lifePoint = 0;
 		}
 	} else {
-		_engine->_animations->initAnim(AnimationTypes::kHit, AnimType::kAnimationInsert, AnimationTypes::kAnimInvalid, actorIdxAttacked);
+		_engine->_animations->initAnim(AnimationTypes::kHit, AnimType::kAnimationInsert, AnimationTypes::kNoAnim, actorIdxAttacked);
 	}
 }
 
diff --git a/engines/twine/scene/actor.h b/engines/twine/scene/actor.h
index a806b4819e8..72e5d550dab 100644
--- a/engines/twine/scene/actor.h
+++ b/engines/twine/scene/actor.h
@@ -51,12 +51,6 @@ struct RealValue {
 	int16 getRealAngle(int32 time);
 };
 
-/** Actors animation timer structure */
-struct AnimTimerDataStruct {
-	const KeyFrame *ptr = nullptr;
-	int32 time = 0;
-};
-
 /** Actors static flags structure */
 struct StaticFlagsStruct {
 	uint32 bComputeCollisionWithObj : 1;    // 0x000001 CHECK_OBJ_COL
@@ -189,7 +183,7 @@ public:
 		int32 Fin;
 	} A3DS;
 
-	int32 _strengthOfHit = 0;
+	int32 _hitForce = 0;
 	int32 _hitBy = -1;
 	BonusParameter _bonusParameter;
 	int32 _beta = 0; // facing angle of actor. Minumum is 0 (SW). Going counter clock wise
@@ -251,7 +245,6 @@ public:
 
 	BoundingBox _boundingBox; // Xmin, YMin, Zmin, Xmax, Ymax, Zmax
 	RealValue realAngle;
-	AnimTimerDataStruct _animTimerData;
 };
 
 inline const IVec3 &ActorStruct::posObj() const {
diff --git a/engines/twine/scene/animations.cpp b/engines/twine/scene/animations.cpp
index e5ef81e0449..44d335c7992 100644
--- a/engines/twine/scene/animations.cpp
+++ b/engines/twine/scene/animations.cpp
@@ -289,7 +289,7 @@ void Animations::processAnimActions(int32 actorIdx) { // GereAnimAction
 		switch (action.type) {
 		case ActionType::ACTION_HITTING:
 			if (action.animFrame - 1 == actor->_frame) {
-				actor->_strengthOfHit = action.strength;
+				actor->_hitForce = action.strength;
 				actor->_workFlags.bIsHitting = 1;
 			}
 			break;
@@ -350,7 +350,7 @@ void Animations::processAnimActions(int32 actorIdx) { // GereAnimAction
 			break;
 		case ActionType::ACTION_HERO_HITTING:
 			if (action.animFrame - 1 == actor->_frame) {
-				actor->_strengthOfHit = magicLevelStrengthOfHit[_engine->_gameState->_magicLevelIdx];
+				actor->_hitForce = magicLevelStrengthOfHit[_engine->_gameState->_magicLevelIdx];
 				actor->_workFlags.bIsHitting = 1;
 			}
 			break;
@@ -421,7 +421,7 @@ bool Animations::initAnim(AnimationTypes genNewAnim, AnimType flag, AnimationTyp
 		return true;
 	}
 
-	if (genNextAnim == AnimationTypes::kAnimInvalid && actor->_flagAnim != AnimType::kAnimationAllThen) {
+	if (genNextAnim == AnimationTypes::kNoAnim && actor->_flagAnim != AnimType::kAnimationAllThen) {
 		genNextAnim = actor->_genAnim;
 	}
 
@@ -453,12 +453,13 @@ bool Animations::initAnim(AnimationTypes genNewAnim, AnimType flag, AnimationTyp
 		flag = AnimType::kAnimationAllThen;
 	}
 
+	BodyData &bodyData = actor->_entityDataPtr->getBody(actor->_body);
 	if (actor->_anim == -1) {
 		// if no previous animation
-		setAnimObjet(0, _engine->_resources->_animData[newanim], actor->_entityDataPtr->getBody(actor->_body), &actor->_animTimerData);
+		setAnimObjet(0, _engine->_resources->_animData[newanim], bodyData, &bodyData._animTimerData);
 	} else {
 		// interpolation between animations
-		stockInterAnim(actor->_entityDataPtr->getBody(actor->_body), &actor->_animTimerData);
+		stockInterAnim(bodyData, &bodyData._animTimerData);
 	}
 
 	actor->_anim = newanim;
@@ -494,7 +495,7 @@ void Animations::doAnim(int32 actorIdx) {
 
 	IVec3 &processActor = actor->_processActor;
 	if (actor->_flags.bSprite3D) {
-		if (actor->_strengthOfHit) {
+		if (actor->_hitForce) {
 			actor->_workFlags.bIsHitting = 1;
 		}
 
@@ -585,8 +586,9 @@ void Animations::doAnim(int32 actorIdx) {
 			const AnimData &animData = _engine->_resources->_animData[actor->_anim];
 
 			bool keyFramePassed = false;
-			if (actor->_entityDataPtr->getBody(actor->_body).isAnimated()) {
-				keyFramePassed = setInterDepObjet(actor->_frame, animData, &actor->_animTimerData);
+			BodyData &bodyData = actor->_entityDataPtr->getBody(actor->_body);
+			if (bodyData.isAnimated()) {
+				keyFramePassed = setInterDepObjet(actor->_frame, animData, &bodyData._animTimerData);
 			}
 
 			if (_animMasterRot) {
@@ -636,7 +638,7 @@ void Animations::doAnim(int32 actorIdx) {
 
 						actor->_flagAnim = AnimType::kAnimationTypeRepeat;
 						actor->_frame = 0;
-						actor->_strengthOfHit = 0;
+						actor->_hitForce = 0;
 					}
 
 					processAnimActions(actorIdx);
@@ -795,10 +797,10 @@ void Animations::doAnim(int32 actorIdx) {
 							if (fallHeight <= (2 * SIZE_BRICK_Y) && actor->_genAnim == AnimationTypes::kForward) {
 								actor->_workFlags.bWasWalkingBeforeFalling = 1;
 							} else {
-								initAnim(AnimationTypes::kFall, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, actorIdx);
+								initAnim(AnimationTypes::kFall, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
 							}
 						} else {
-							initAnim(AnimationTypes::kFall, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, actorIdx);
+							initAnim(AnimationTypes::kFall, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
 						}
 					}
 				}
diff --git a/engines/twine/scene/collision.cpp b/engines/twine/scene/collision.cpp
index 736205fc3a6..e12e7de8db9 100644
--- a/engines/twine/scene/collision.cpp
+++ b/engines/twine/scene/collision.cpp
@@ -328,7 +328,7 @@ int32 Collision::checkObjCol(int32 actorIdx) {
 				const IVec3 minsTest = actorTest->posObj() + actorTest->_boundingBox.mins;
 				const IVec3 maxsTest = actorTest->posObj() + actorTest->_boundingBox.maxs;
 				if (mins.x < maxsTest.x && maxs.x > minsTest.x && mins.y < maxsTest.y && maxs.y > minsTest.y && mins.z < maxsTest.z && maxs.z > minsTest.z) {
-					_engine->_actor->hitObj(actorIdx, a, ptrobj->_strengthOfHit, ptrobj->_beta + LBAAngles::ANGLE_180);
+					_engine->_actor->hitObj(actorIdx, a, ptrobj->_hitForce, ptrobj->_beta + LBAAngles::ANGLE_180);
 					ptrobj->_workFlags.bIsHitting = 0;
 				}
 			}
diff --git a/engines/twine/scene/gamestate.cpp b/engines/twine/scene/gamestate.cpp
index 3e61d50a99b..3dcb783f3f5 100644
--- a/engines/twine/scene/gamestate.cpp
+++ b/engines/twine/scene/gamestate.cpp
@@ -383,7 +383,7 @@ void GameState::doFoundObj(InventoryItems item) {
 	const int32 bodyAnimIdx = _engine->_animations->searchAnim(AnimationTypes::kFoundItem, OWN_ACTOR_SCENE_INDEX);
 	const AnimData &ptranim = _engine->_resources->_animData[bodyAnimIdx];
 
-	_engine->_animations->stockInterAnim(bodyData, &_engine->_scene->_sceneHero->_animTimerData);
+	_engine->_animations->stockInterAnim(bodyData, &bodyData._animTimerData);
 
 	uint frameanim = 0;
 
diff --git a/engines/twine/scene/movements.cpp b/engines/twine/scene/movements.cpp
index 358d25852ab..40158bac237 100644
--- a/engines/twine/scene/movements.cpp
+++ b/engines/twine/scene/movements.cpp
@@ -285,7 +285,7 @@ void Movements::processBehaviourExecution(int actorIdx) {
 		}
 		break;
 	case HeroBehaviourType::kDiscrete:
-		_engine->_animations->initAnim(AnimationTypes::kHide, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, actorIdx);
+		_engine->_animations->initAnim(AnimationTypes::kHide, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
 		break;
 	case HeroBehaviourType::kProtoPack:
 	case HeroBehaviourType::kMax:
@@ -333,7 +333,7 @@ void Movements::processManualMovementExecution(int actorIdx) {
 		// if walking should get stopped
 		if (!_engine->_input->isActionActive(TwinEActionType::MoveForward) && !_engine->_input->isActionActive(TwinEActionType::MoveBackward)) {
 			if (_lastJoyFlag && (_heroActionKey != _previousLoopActionKey || _changedCursorKeys != _previousChangedCursorKeys)) {
-				_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, actorIdx);
+				_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
 			}
 		}
 
@@ -341,17 +341,17 @@ void Movements::processManualMovementExecution(int actorIdx) {
 
 		if (_engine->_input->isActionActive(TwinEActionType::MoveForward)) {
 			if (!_engine->_scene->_flagClimbing) {
-				_engine->_animations->initAnim(AnimationTypes::kForward, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, actorIdx);
+				_engine->_animations->initAnim(AnimationTypes::kForward, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
 			}
 			_lastJoyFlag = true;
 		} else if (_engine->_input->isActionActive(TwinEActionType::MoveBackward)) {
-			_engine->_animations->initAnim(AnimationTypes::kBackward, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, actorIdx);
+			_engine->_animations->initAnim(AnimationTypes::kBackward, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
 			_lastJoyFlag = true;
 		}
 
 		if (_engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
 			if (actor->_genAnim == AnimationTypes::kStanding) {
-				_engine->_animations->initAnim(AnimationTypes::kTurnLeft, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, actorIdx);
+				_engine->_animations->initAnim(AnimationTypes::kTurnLeft, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
 			} else {
 				if (!actor->_workFlags.bIsRotationByAnim) {
 					actor->_beta = actor->realAngle.getRealAngle(_engine->timerRef);
@@ -360,7 +360,7 @@ void Movements::processManualMovementExecution(int actorIdx) {
 			_lastJoyFlag = true;
 		} else if (_engine->_input->isActionActive(TwinEActionType::TurnRight)) {
 			if (actor->_genAnim == AnimationTypes::kStanding) {
-				_engine->_animations->initAnim(AnimationTypes::kTurnRight, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, actorIdx);
+				_engine->_animations->initAnim(AnimationTypes::kTurnRight, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
 			} else {
 				if (!actor->_workFlags.bIsRotationByAnim) {
 					actor->_beta = actor->realAngle.getRealAngle(_engine->timerRef);
@@ -433,11 +433,11 @@ void Movements::processRandomAction(int actorIdx) {
 		const int32 angle = ClampAngle(actor->_beta + (_engine->getRandomNumber() & (LBAAngles::ANGLE_180 - 1)) - LBAAngles::ANGLE_90 + LBAAngles::ANGLE_180);
 		initRealAngleConst(actor->_beta, angle, actor->_srot, &actor->realAngle);
 		actor->_delayInMillis = _engine->timerRef + _engine->getRandomNumber(_engine->toSeconds(6)) + _engine->toSeconds(6);
-		_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, actorIdx);
+		_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
 	}
 
 	if (!actor->realAngle.timeValue) {
-		_engine->_animations->initAnim(AnimationTypes::kForward, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, actorIdx);
+		_engine->_animations->initAnim(AnimationTypes::kForward, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
 		if (_engine->timerRef > actor->_delayInMillis) {
 			const int32 angle = ClampAngle(actor->_beta + (_engine->getRandomNumber() & (LBAAngles::ANGLE_180 - 1)) - LBAAngles::ANGLE_90);
 			initRealAngleConst(actor->_beta, angle, actor->_srot, &actor->realAngle);
diff --git a/engines/twine/scene/scene.cpp b/engines/twine/scene/scene.cpp
index 018888eccc9..57009d82a5f 100644
--- a/engines/twine/scene/scene.cpp
+++ b/engines/twine/scene/scene.cpp
@@ -247,7 +247,7 @@ bool Scene::loadSceneLBA2() {
 		act->_posObj.y = (int16)stream.readUint16LE();
 		act->_posObj.z = (int16)stream.readUint16LE();
 		act->_oldPos = act->posObj();
-		act->_strengthOfHit = stream.readByte();
+		act->_hitForce = stream.readByte();
 		setBonusParameterFlags(act, stream.readUint16LE());
 		act->_beta = (int16)stream.readUint16LE();
 		act->_srot = (int16)stream.readUint16LE();
@@ -381,7 +381,7 @@ bool Scene::loadSceneLBA1() {
 		act->_posObj.y = (int16)stream.readUint16LE();
 		act->_posObj.z = (int16)stream.readUint16LE();
 		act->_oldPos = act->posObj();
-		act->_strengthOfHit = stream.readByte();
+		act->_hitForce = stream.readByte();
 		setBonusParameterFlags(act, stream.readUint16LE());
 		act->_bonusParameter.givenNothing = 0;
 		act->_beta = (int16)stream.readUint16LE();
@@ -866,7 +866,7 @@ void Scene::checkZoneSce(int32 actorIdx) {
 							if (actor->_posObj.y >= (zone->mins.y + zone->maxs.y) / 2) {
 								_engine->_animations->initAnim(AnimationTypes::kTopLadder, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx); // reached end of ladder
 							} else {
-								_engine->_animations->initAnim(AnimationTypes::kClimbLadder, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, actorIdx); // go up in ladder
+								_engine->_animations->initAnim(AnimationTypes::kClimbLadder, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx); // go up in ladder
 							}
 						}
 					}
diff --git a/engines/twine/script/script_life.cpp b/engines/twine/script/script_life.cpp
index f32db9c7a0a..4433ce8b733 100644
--- a/engines/twine/script/script_life.cpp
+++ b/engines/twine/script/script_life.cpp
@@ -926,7 +926,7 @@ int32 ScriptLife::lMESSAGE(TwinEEngine *engine, LifeScriptContext &ctx) {
 	// if we are in sporty mode, we might have triggered a jump with the special action binding
 	// see https://bugs.scummvm.org/ticket/13676 for more details.
 	if (ctx.actor->isJumpAnimationActive()) {
-		engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, OWN_ACTOR_SCENE_INDEX);
+		engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, OWN_ACTOR_SCENE_INDEX);
 	}
 
 	engine->_text->drawTextProgressive(textIdx);
@@ -1014,7 +1014,7 @@ int32 ScriptLife::lSET_BEHAVIOUR(TwinEEngine *engine, LifeScriptContext &ctx) {
 	const HeroBehaviourType behavior = (HeroBehaviourType)ctx.stream.readByte();
 	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::SET_BEHAVIOUR(%i)", (int)behavior);
 
-	engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kAnimInvalid, OWN_ACTOR_SCENE_INDEX);
+	engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, OWN_ACTOR_SCENE_INDEX);
 	engine->_actor->setBehaviour(behavior);
 
 	return 0;
diff --git a/engines/twine/shared.h b/engines/twine/shared.h
index 86c46c48aa4..cf67d9ed4e5 100644
--- a/engines/twine/shared.h
+++ b/engines/twine/shared.h
@@ -318,7 +318,7 @@ enum class AnimationTypes {
 	kCarDrivingBackwards = 305,
 	kCarStopping = 306,
 	kCarFrozen = 307,
-	kAnimInvalid = 255 // NO_ANIM
+	kNoAnim = 255 // NO_ANIM
 };
 
 enum class AnimType {


Commit: 4ce8dbf73b91805b2ce437e1f5a199ea5f3ed807
    https://github.com/scummvm/scummvm/commit/4ce8dbf73b91805b2ce437e1f5a199ea5f3ed807
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2025-04-29T21:44:27+02:00

Commit Message:
TWINE: fixed memory leak in fla sample handling

Changed paths:
    engines/twine/audio/sound.cpp


diff --git a/engines/twine/audio/sound.cpp b/engines/twine/audio/sound.cpp
index cbac347ffb6..0119d177096 100644
--- a/engines/twine/audio/sound.cpp
+++ b/engines/twine/audio/sound.cpp
@@ -109,7 +109,7 @@ void Sound::playFlaSample(int32 index, int32 repeat, uint8 balance, int32 volume
 	}
 
 	Common::MemoryReadStream *stream = new Common::MemoryReadStream(sampPtr, sampSize, DisposeAfterUse::YES);
-	Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(stream, DisposeAfterUse::YES);
+	Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
 	playSample(channelIdx, index, audioStream, repeat, Resources::HQR_FLASAMP_FILE);
 }
 


Commit: f0fa6416ec5968429c14471e99ddcc8a6c6a00ac
    https://github.com/scummvm/scummvm/commit/f0fa6416ec5968429c14471e99ddcc8a6c6a00ac
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2025-04-29T21:44:31+02:00

Commit Message:
TWINE: fixed Audio::makeVOCStream call and fixed memory leak in vox samples

Changed paths:
    engines/twine/audio/sound.cpp


diff --git a/engines/twine/audio/sound.cpp b/engines/twine/audio/sound.cpp
index 0119d177096..210d71ee1b2 100644
--- a/engines/twine/audio/sound.cpp
+++ b/engines/twine/audio/sound.cpp
@@ -183,7 +183,7 @@ bool Sound::playVoxSample(const TextEntry *text) {
 		*sampPtr = 'C';
 	}
 	Common::MemoryReadStream *stream = new Common::MemoryReadStream(sampPtr, sampSize, DisposeAfterUse::YES);
-	Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(stream, DisposeAfterUse::YES);
+	Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
 	return playSample(channelIdx, text->index, audioStream, 1, _engine->_text->_currentVoxBankFile.c_str(), Audio::Mixer::kSpeechSoundType);
 }
 


Commit: 6defd5c324674e2306488d4ed9bf7122e8243226
    https://github.com/scummvm/scummvm/commit/6defd5c324674e2306488d4ed9bf7122e8243226
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2025-04-29T21:44:35+02:00

Commit Message:
TWINE: fixed missing pitchbend parameter for environmental sounds

Changed paths:
    engines/twine/audio/sound.cpp
    engines/twine/scene/scene.cpp


diff --git a/engines/twine/audio/sound.cpp b/engines/twine/audio/sound.cpp
index 210d71ee1b2..09a6beaaf7b 100644
--- a/engines/twine/audio/sound.cpp
+++ b/engines/twine/audio/sound.cpp
@@ -192,6 +192,8 @@ bool Sound::playSample(int channelIdx, int index, Audio::SeekableAudioStream *au
 		warning("Failed to create audio stream for %s: %i", name, index);
 		return false;
 	}
+
+	// infinite loop
 	if (loop == -1) {
 		loop = 0;
 	}
diff --git a/engines/twine/scene/scene.cpp b/engines/twine/scene/scene.cpp
index 57009d82a5f..7222c2db85b 100644
--- a/engines/twine/scene/scene.cpp
+++ b/engines/twine/scene/scene.cpp
@@ -736,10 +736,11 @@ void Scene::processEnvironmentSound() {
 
 			const int16 sampleIdx = _sampleAmbiance[currentAmb];
 			if (sampleIdx != -1) {
-				/*int16 decal = _sampleRound[currentAmb];*/
+				int16 decal = _sampleRound[currentAmb];
 				int16 repeat = _sampleRepeat[currentAmb];
 
-				_engine->_sound->playSample(sampleIdx, repeat, 110, -1, 110);
+				const uint16 pitchbend = 0x1000 + _engine->getRandomNumber(decal) - (decal / 2);
+				_engine->_sound->playSample(sampleIdx, pitchbend, repeat, 110, 110);
 				break;
 			}
 		}


Commit: 20d9634db0cda3ea3ad6536d294f366e9c7b38a6
    https://github.com/scummvm/scummvm/commit/20d9634db0cda3ea3ad6536d294f366e9c7b38a6
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2025-04-29T21:45:18+02:00

Commit Message:
TWINE: started to support mixer balance changing for some samples

also renamed methods to match better the original sources

Changed paths:
    engines/twine/audio/sound.cpp
    engines/twine/audio/sound.h
    engines/twine/movies.cpp
    engines/twine/renderer/redraw.cpp
    engines/twine/scene/actor.cpp
    engines/twine/scene/animations.cpp
    engines/twine/scene/extra.cpp
    engines/twine/scene/gamestate.cpp
    engines/twine/scene/scene.cpp
    engines/twine/script/script_move.cpp
    engines/twine/twine.cpp


diff --git a/engines/twine/audio/sound.cpp b/engines/twine/audio/sound.cpp
index 09a6beaaf7b..57f5b49c875 100644
--- a/engines/twine/audio/sound.cpp
+++ b/engines/twine/audio/sound.cpp
@@ -23,6 +23,7 @@
 #include "audio/audiostream.h"
 #include "audio/decoders/raw.h"
 #include "audio/decoders/voc.h"
+#include "audio/mixer.h"
 #include "common/config-manager.h"
 #include "common/memstream.h"
 #include "common/system.h"
@@ -36,6 +37,7 @@
 #include "twine/resources/hqr.h"
 #include "twine/scene/movements.h"
 #include "twine/resources/resources.h"
+#include "twine/shared.h"
 #include "twine/text.h"
 #include "twine/twine.h"
 
@@ -55,19 +57,37 @@ void Sound::startRainSample() {
 #if 0
 	const int sample = SAMPLE_RAIN;
 	if (CubeMode == CUBE_EXTERIEUR && !TEMPETE_FINIE && !isSamplePlaying(sample)) {
-		//const int frequency = 0x1000;
+		const int rate = 0x1000;
 		const int offset = 300;
 		const int repeat = 0;
 		const int panning = 64;
 		const int volumeRain = 70;
-		// TODO: playSample(sample, /*frequency,*/ offset, repeat, panning, volumeRain);
+		// TODO: mixSample(sample, rate, offset, repeat, panning, volumeRain);
 	}
 
 	RestartRainSample = false;
 #endif
 }
 
-void Sound::setSamplePosition(int32 channelIdx, int32 x, int32 y, int32 z) {
+void Sound::setChannelRate(int32 channelIdx, uint32 rate) {
+	if (channelIdx < 0 || channelIdx >= NUM_CHANNELS) {
+		return;
+	}
+	_engine->_system->getMixer()->setChannelRate(_samplesPlaying[channelIdx], rate);
+}
+
+void Sound::setChannelBalance(int32 channelIdx, uint8 volumeLeft, uint8 volumeRight) {
+	if (channelIdx < 0 || channelIdx >= NUM_CHANNELS) {
+		return;
+	}
+	Audio::Mixer *mixer = _engine->_system->getMixer();
+	const Audio::SoundHandle &handle = _samplesPlaying[channelIdx];
+	// balance value ranges from -127 to 127
+	int8 balance = (int8)(((int16)volumeRight - (int16)volumeLeft) * 127 / 255);
+	mixer->setChannelBalance(handle, balance);
+}
+
+void Sound::setChannelPosition(int32 channelIdx, int32 x, int32 y, int32 z) {
 	if (channelIdx < 0 || channelIdx >= NUM_CHANNELS) {
 		return;
 	}
@@ -77,13 +97,13 @@ void Sound::setSamplePosition(int32 channelIdx, int32 x, int32 y, int32 z) {
 	int32 distance = getDistance3D(camX, camY, camZ, x, y, z);
 	distance = boundRuleThree(0, distance, 10000, 255);
 	byte targetVolume = 0;
-	if (distance < 255) {
-		targetVolume = 255 - distance;
+	if (distance < Audio::Mixer::kMaxChannelVolume) {
+		targetVolume = Audio::Mixer::kMaxChannelVolume - distance;
 	}
-	_engine->_system->getMixer()->setChannelVolume(samplesPlaying[channelIdx], targetVolume);
+	_engine->_system->getMixer()->setChannelVolume(_samplesPlaying[channelIdx], targetVolume);
 }
 
-void Sound::playFlaSample(int32 index, int32 repeat, uint8 balance, int32 volumeLeft, int32 volumeRight) {
+void Sound::playFlaSample(int32 index, int16 rate, int32 repeat, uint8 volumeLeft, uint8 volumeRight) {
 	if (!_engine->_cfgfile.Sound) {
 		return;
 	}
@@ -110,10 +130,19 @@ void Sound::playFlaSample(int32 index, int32 repeat, uint8 balance, int32 volume
 
 	Common::MemoryReadStream *stream = new Common::MemoryReadStream(sampPtr, sampSize, DisposeAfterUse::YES);
 	Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
-	playSample(channelIdx, index, audioStream, repeat, Resources::HQR_FLASAMP_FILE);
+	if (playSample(channelIdx, index, audioStream, repeat, Resources::HQR_FLASAMP_FILE)) {
+		setChannelRate(channelIdx, rate);
+		setChannelBalance(channelIdx, volumeLeft, volumeRight);
+	}
+}
+
+void Sound::mixSample(int32 index, uint16 pitchbend, int32 repeat, uint8 volumeLeft, uint8 volumeRight) {
+	mixSample3D(index, pitchbend, repeat, {}, -1);
+	int channelIdx = getSampleChannel(index);
+	setChannelBalance(channelIdx, volumeLeft, volumeRight);
 }
 
-void Sound::playSample(int32 index, uint16 pitchbend, int32 repeat, int32 x, int32 y, int32 z, int32 actorIdx) {
+void Sound::mixSample3D(int32 index, uint16 pitchbend, int32 repeat, const IVec3 &pos, int32 actorIdx) {
 	if (!_engine->_cfgfile.Sound) {
 		return;
 	}
@@ -125,11 +154,12 @@ void Sound::playSample(int32 index, uint16 pitchbend, int32 repeat, int32 x, int
 	}
 
 	if (actorIdx != -1) {
-		setSamplePosition(channelIdx, x, y, z);
+		// TODO: implement balance
+		setChannelPosition(channelIdx, pos.x, pos.y, pos.z);
 		// save the actor index for the channel so we can check the position
-		samplesPlayingActors[channelIdx] = actorIdx;
+		_samplesPlayingActors[channelIdx] = actorIdx;
 	} else {
-		samplesPlayingActors[channelIdx] = -1;
+		_samplesPlayingActors[channelIdx] = -1;
 	}
 
 	uint8 *sampPtr = _engine->_resources->_samplesTable[index];
@@ -137,8 +167,8 @@ void Sound::playSample(int32 index, uint16 pitchbend, int32 repeat, int32 x, int
 	Common::MemoryReadStream *stream = new Common::MemoryReadStream(sampPtr, sampSize, DisposeAfterUse::NO);
 	Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
 	playSample(channelIdx, index, audioStream, repeat, Resources::HQR_SAMPLES_FILE, Audio::Mixer::kSFXSoundType);
-	uint16 frequency = 11025 + (pitchbend - 0x1000);
-	_engine->_system->getMixer()->setChannelRate(samplesPlaying[channelIdx], frequency);
+	uint32 rate = 11025 + (pitchbend - 0x1000);
+	setChannelRate(channelIdx, rate);
 }
 
 bool Sound::playVoxSample(const TextEntry *text) {
@@ -187,7 +217,7 @@ bool Sound::playVoxSample(const TextEntry *text) {
 	return playSample(channelIdx, text->index, audioStream, 1, _engine->_text->_currentVoxBankFile.c_str(), Audio::Mixer::kSpeechSoundType);
 }
 
-bool Sound::playSample(int channelIdx, int index, Audio::SeekableAudioStream *audioStream, int32 loop, const char *name, Audio::Mixer::SoundType soundType) {
+bool Sound::playSample(int32 channelIdx, int32 index, Audio::SeekableAudioStream *audioStream, int32 loop, const char *name, Audio::Mixer::SoundType soundType) {
 	if (audioStream == nullptr) {
 		warning("Failed to create audio stream for %s: %i", name, index);
 		return false;
@@ -198,9 +228,8 @@ bool Sound::playSample(int channelIdx, int index, Audio::SeekableAudioStream *au
 		loop = 0;
 	}
 	Audio::AudioStream *loopStream = Audio::makeLoopingAudioStream(audioStream, loop);
-	Audio::SoundHandle *handle = &samplesPlaying[channelIdx];
+	Audio::SoundHandle *handle = &_samplesPlaying[channelIdx];
 	const byte volume = Audio::Mixer::kMaxChannelVolume;
-	// TODO: implement balance
 	_engine->_system->getMixer()->playStream(soundType, handle, loopStream, index, volume);
 	return true;
 }
@@ -224,64 +253,64 @@ void Sound::stopSamples() { // HQ_StopSample
 		return;
 	}
 
-	for (int i = 0; i < NUM_CHANNELS; i++) {
-		_engine->_system->getMixer()->stopHandle(samplesPlaying[i]);
+	for (int channelIdx = 0; channelIdx < NUM_CHANNELS; channelIdx++) {
+		_engine->_system->getMixer()->stopHandle(_samplesPlaying[channelIdx]);
 	}
-	memset(samplesPlayingActors, -1, sizeof(samplesPlayingActors));
+	memset(_samplesPlayingActors, -1, sizeof(_samplesPlayingActors));
 }
 
-int32 Sound::getActorChannel(int32 index) {
-	for (int32 c = 0; c < NUM_CHANNELS; c++) {
-		if (samplesPlayingActors[c] == index) {
-			return c;
+int32 Sound::getActorChannel(int32 actorIdx) {
+	for (int32 channelIdx = 0; channelIdx < NUM_CHANNELS; channelIdx++) {
+		if (_samplesPlayingActors[channelIdx] == actorIdx) {
+			return channelIdx;
 		}
 	}
 	return -1;
 }
 
 int32 Sound::getSampleChannel(int32 index) {
-	for (int32 c = 0; c < NUM_CHANNELS; c++) {
-		if (_engine->_system->getMixer()->getSoundID(samplesPlaying[c]) == index) {
-			return c;
+	for (int32 channelIdx = 0; channelIdx < NUM_CHANNELS; channelIdx++) {
+		if (_engine->_system->getMixer()->getSoundID(_samplesPlaying[channelIdx]) == index) {
+			return channelIdx;
 		}
 	}
 	return -1;
 }
 
-void Sound::removeSampleChannel(int32 c) {
-	samplesPlayingActors[c] = -1;
+void Sound::removeChannelWatch(int32 channelIdx) {
+	_samplesPlayingActors[channelIdx] = -1;
 }
 
 void Sound::stopSample(int32 index) {
 	if (!_engine->_cfgfile.Sound) {
 		return;
 	}
-	const int32 stopChannel = getSampleChannel(index);
-	if (stopChannel != -1) {
+	const int32 channelIdx = getSampleChannel(index);
+	if (channelIdx != -1) {
 		_engine->_system->getMixer()->stopID(index);
-		removeSampleChannel(stopChannel);
+		removeChannelWatch(channelIdx);
 	}
 }
 
-bool Sound::isChannelPlaying(int32 chan) {
-	if (chan >= 0 && chan < ARRAYSIZE(samplesPlaying)) {
-		if (_engine->_system->getMixer()->isSoundHandleActive(samplesPlaying[chan])) {
+bool Sound::isChannelPlaying(int32 channelIdx) {
+	if (channelIdx >= 0 && channelIdx < ARRAYSIZE(_samplesPlaying)) {
+		if (_engine->_system->getMixer()->isSoundHandleActive(_samplesPlaying[channelIdx])) {
 			return true;
 		}
-		removeSampleChannel(chan);
+		removeChannelWatch(channelIdx);
 	}
 	return false;
 }
 
 int32 Sound::isSamplePlaying(int32 index) {
-	const int32 chan = getSampleChannel(index);
-	return isChannelPlaying(chan);
+	const int32 channelIdx = getSampleChannel(index);
+	return isChannelPlaying(channelIdx);
 }
 
 int32 Sound::getFreeSampleChannelIndex() {
-	for (int i = 0; i < NUM_CHANNELS; i++) {
-		if (!_engine->_system->getMixer()->isSoundHandleActive(samplesPlaying[i])) {
-			return i;
+	for (int channelIdx = 0; channelIdx < NUM_CHANNELS; channelIdx++) {
+		if (!_engine->_system->getMixer()->isSoundHandleActive(_samplesPlaying[channelIdx])) {
+			return channelIdx;
 		}
 	}
 	return -1;
diff --git a/engines/twine/audio/sound.h b/engines/twine/audio/sound.h
index 3db1bd056f2..21172090dc0 100644
--- a/engines/twine/audio/sound.h
+++ b/engines/twine/audio/sound.h
@@ -54,56 +54,57 @@ class Sound {
 private:
 	TwinEEngine *_engine;
 
-	/** Get the channel where the sample is playing */
-	int32 getSampleChannel(int32 index);
-
 	/** Samples playing at the same time */
-	Audio::SoundHandle samplesPlaying[NUM_CHANNELS];
+	Audio::SoundHandle _samplesPlaying[NUM_CHANNELS];
 
 	/** Samples playing at a actors position */
-	int32 samplesPlayingActors[NUM_CHANNELS]{0};
+	int32 _samplesPlayingActors[NUM_CHANNELS]{0};
 
-	bool playSample(int channelIdx, int index, Audio::SeekableAudioStream *audioStream, int32 loop, const char *name, Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType);
+	bool playSample(int32 channelIdx, int32 index, Audio::SeekableAudioStream *audioStream, int32 loop, const char *name, Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType);
 
-	bool isChannelPlaying(int32 channel);
+	bool isChannelPlaying(int32 channelIdx);
 
 	/** Find a free channel slot to use */
 	int32 getFreeSampleChannelIndex();
 
 	/** Remove a sample from the channel usage list */
-	void removeSampleChannel(int32 index);
-
+	void removeChannelWatch(int32 channelIdx);
 public:
 	Sound(TwinEEngine *engine);
 	~Sound();
 
+	/** Get the channel where the sample is playing */
+	int32 getSampleChannel(int32 index);
+
 	/**
 	 * Play FLA movie samples
 	 * @param index sample index under flasamp.hqr file
 	 * @param repeat number of times to repeat the sample
 	 */
-	void playFlaSample(int32 index, int32 repeat, uint8 balance, int32 volumeLeft, int32 volumeRight);
+	void playFlaSample(int32 index, int16 rate, int32 repeat, uint8 volumeLeft, uint8 volumeRight);
+
+	void setChannelBalance(int32 channelIdx, uint8 volumeLeft, uint8 volumeRight);
+
+	void setChannelRate(int32 channelIdx, uint32 rate);
 
 	/** Update sample position in channel */
-	void setSamplePosition(int32 channelIdx, int32 x, int32 y, int32 z);
+	void setChannelPosition(int32 channelIdx, int32 x, int32 y, int32 z);
 
-	inline void setSamplePosition(int32 channelIdx, const IVec3 &pos) {
-		setSamplePosition(channelIdx, pos.x, pos.y, pos.z);
+	inline void setChannelPosition(int32 channelIdx, const IVec3 &pos) {
+		setChannelPosition(channelIdx, pos.x, pos.y, pos.z);
 	}
 
 	/**
 	 * Play samples
 	 * @param index sample index under flasamp.hqr file
 	 * @param repeat number of times to repeat the sample
-	 * @param x sound generating entity x position
-	 * @param y sound generating entity y position
-	 * @param z sound generating entity z position
+	 * @param pos sound generating entity position
 	 * @param actorIdx
 	 */
-	void playSample(int32 index, uint16 pitchbend = 0x1000, int32 repeat = 1, int32 x = 128, int32 y = 128, int32 z = 128, int32 actorIdx = -1); // HQ_3D_MixSample
-	void playSample(int32 index, uint16 pitchbend, int32 repeat, const IVec3 &pos, int32 actorIdx = -1) { // HQ_MixSample
-		playSample(index, pitchbend, repeat, pos.x, pos.y, pos.z, actorIdx);
-	}
+	void mixSample3D(int32 index, uint16 pitchbend, int32 repeat, const IVec3 &pos, int32 actorIdx); // HQ_3D_MixSample
+
+	void mixSample(int32 index, uint16 pitchbend, int32 repeat, uint8 volumeLeft, uint8 volumeRight); // HQ_MixSample
+
 	/** Pause samples */
 	void pauseSamples();
 
@@ -116,13 +117,13 @@ public:
 	void stopSamples();
 
 	/** Get the channel where the actor sample is playing */
-	int32 getActorChannel(int32 index);
+	int32 getActorChannel(int32 actorIdx);
 
 	/** Stops a specific sample */
 	void stopSample(int32 index); // HQ_StopOneSample
 
 	/** Check if a sample is playing */
-	int32 isSamplePlaying(int32 index); // IsSamplePlaying
+	int32 isSamplePlaying(int32 index);
 
 	/** Play VOX sample */
 	bool playVoxSample(const TextEntry *text);
diff --git a/engines/twine/movies.cpp b/engines/twine/movies.cpp
index 547e3b8c5bd..b566b923759 100644
--- a/engines/twine/movies.cpp
+++ b/engines/twine/movies.cpp
@@ -234,7 +234,7 @@ void Movies::drawNextFrameFla() {
 			sample.balance = stream.readByte();
 			sample.volumeLeft = stream.readByte();
 			sample.volumeRight = stream.readByte();
-			_engine->_sound->playFlaSample(sample.sampleNum, sample.repeat, sample.balance, sample.volumeLeft, sample.volumeRight);
+			_engine->_sound->playFlaSample(sample.sampleNum, sample.freq, sample.repeat, sample.volumeLeft, sample.volumeRight);
 			break;
 		}
 		case kStopSample: {
@@ -276,13 +276,14 @@ void Movies::drawNextFrameFla() {
 			break;
 		}
 		case kSampleBalance: {
-			/* int16 num = */ stream.readSint16LE();
-			/* uint8 offset = */ stream.readByte();
+			const int16 sampleNum = stream.readSint16LE();
+			/* const uint8 offset = */ stream.readByte();
 			stream.skip(1); // padding
-			/* int16 balance = */ stream.readSint16LE();
-			/* uint8 volumeLeft = */ stream.readByte();
-			/* uint8 volumeRight = */ stream.readByte();
-			// TODO: change balance
+			/* const int16 balance = */ stream.readSint16LE();
+			const uint8 volumeLeft = stream.readByte();
+			const uint8 volumeRight = stream.readByte();
+			const int32 channelIdx = _engine->_sound->getSampleChannel(sampleNum);
+			_engine->_sound->setChannelBalance(channelIdx, volumeLeft, volumeRight);
 			break;
 		}
 		default: {
diff --git a/engines/twine/renderer/redraw.cpp b/engines/twine/renderer/redraw.cpp
index 084bb8f7bb3..39c172164da 100644
--- a/engines/twine/renderer/redraw.cpp
+++ b/engines/twine/renderer/redraw.cpp
@@ -333,7 +333,7 @@ int32 Redraw::fillExtraDrawingList(DrawListStruct *drawList, int32 drawListPos)
 			if (_engine->timerRef - extra->spawnTime > 35) {
 				extra->spawnTime = _engine->timerRef;
 				extra->type &= ~ExtraType::TIME_IN;
-				_engine->_sound->playSample(Samples::ItemPopup, 0x1000, 1, extra->pos);
+				_engine->_sound->mixSample3D(Samples::ItemPopup, 0x1000, 1, extra->pos, -1);
 			}
 			continue;
 		}
diff --git a/engines/twine/scene/actor.cpp b/engines/twine/scene/actor.cpp
index 61bc56a24ab..f5a12edbac8 100644
--- a/engines/twine/scene/actor.cpp
+++ b/engines/twine/scene/actor.cpp
@@ -439,13 +439,13 @@ void Actor::giveExtraBonus(int32 actorIdx) {
 	}
 	if (actor->_workFlags.bIsDead) {
 		_engine->_extra->addExtraBonus(actor->posObj(), LBAAngles::ANGLE_90, LBAAngles::ANGLE_0, bonusSprite, actor->_bonusAmount);
-		_engine->_sound->playSample(Samples::ItemPopup, 0x1000, 1, actor->posObj(), actorIdx);
+		_engine->_sound->mixSample3D(Samples::ItemPopup, 0x1000, 1, actor->posObj(), actorIdx);
 	} else {
 		const ActorStruct *sceneHero = _engine->_scene->_sceneHero;
 		const int32 angle = _engine->_movements->getAngle(actor->posObj(), sceneHero->posObj());
 		const IVec3 pos(actor->_posObj.x, actor->_posObj.y + actor->_boundingBox.maxs.y, actor->_posObj.z);
 		_engine->_extra->addExtraBonus(pos, LBAAngles::ANGLE_70, angle, bonusSprite, actor->_bonusAmount);
-		_engine->_sound->playSample(Samples::ItemPopup, 0x1000, 1, pos, actorIdx);
+		_engine->_sound->mixSample3D(Samples::ItemPopup, 0x1000, 1, pos, actorIdx);
 	}
 }
 
diff --git a/engines/twine/scene/animations.cpp b/engines/twine/scene/animations.cpp
index 44d335c7992..f8950a8483d 100644
--- a/engines/twine/scene/animations.cpp
+++ b/engines/twine/scene/animations.cpp
@@ -295,13 +295,13 @@ void Animations::processAnimActions(int32 actorIdx) { // GereAnimAction
 			break;
 		case ActionType::ACTION_SAMPLE:
 			if (action.animFrame == actor->_frame) {
-				_engine->_sound->playSample(action.sampleIndex, 0x1000, 1, actor->posObj(), actorIdx);
+				_engine->_sound->mixSample3D(action.sampleIndex, 0x1000, 1, actor->posObj(), actorIdx);
 			}
 			break;
 		case ActionType::ACTION_SAMPLE_FREQ:
 			if (action.animFrame == actor->_frame) {
 				const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(action.frequency) - (action.frequency / 2);
-				_engine->_sound->playSample(action.sampleIndex, pitchBend, 1, actor->posObj(), actorIdx);
+				_engine->_sound->mixSample3D(action.sampleIndex, pitchBend, 1, actor->posObj(), actorIdx);
 			}
 			break;
 		case ActionType::ACTION_THROW_EXTRA_BONUS:
@@ -316,7 +316,7 @@ void Animations::processAnimActions(int32 actorIdx) { // GereAnimAction
 			break;
 		case ActionType::ACTION_SAMPLE_REPEAT:
 			if (action.animFrame == actor->_frame) {
-				_engine->_sound->playSample(action.sampleIndex, 0x1000, action.repeat, actor->posObj(), actorIdx);
+				_engine->_sound->mixSample3D(action.sampleIndex, 0x1000, action.repeat, actor->posObj(), actorIdx);
 			}
 			break;
 		case ActionType::ACTION_THROW_SEARCH:
@@ -338,14 +338,14 @@ void Animations::processAnimActions(int32 actorIdx) { // GereAnimAction
 			if (action.animFrame == actor->_frame && (actor->_brickSound & 0xF0U) != 0xF0U) {
 				const int16 sampleIdx = (actor->_brickSound & 0x0FU) + Samples::WalkFloorBegin;
 				const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(1000) - 500;
-				_engine->_sound->playSample(sampleIdx, pitchBend, 1, actor->posObj(), actorIdx);
+				_engine->_sound->mixSample3D(sampleIdx, pitchBend, 1, actor->posObj(), actorIdx);
 			}
 			break;
 		case ActionType::ACTION_RIGHT_STEP:
 			if (action.animFrame == actor->_frame && (actor->_brickSound & 0xF0U) != 0xF0U) {
 				const int16 sampleIdx = (actor->_brickSound & 0x0FU) + Samples::WalkFloorRightBegin;
 				const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(1000) - 500;
-				_engine->_sound->playSample(sampleIdx, pitchBend, 1, actor->posObj(), actorIdx);
+				_engine->_sound->mixSample3D(sampleIdx, pitchBend, 1, actor->posObj(), actorIdx);
 			}
 			break;
 		case ActionType::ACTION_HERO_HITTING:
diff --git a/engines/twine/scene/extra.cpp b/engines/twine/scene/extra.cpp
index 32631a1cb52..0b93bd2c3b4 100644
--- a/engines/twine/scene/extra.cpp
+++ b/engines/twine/scene/extra.cpp
@@ -610,7 +610,7 @@ void Extra::gereExtras() {
 			const int32 angle = ClampAngle(tmpAngle - extra->angle);
 
 			if (angle > LBAAngles::ANGLE_140 && angle < LBAAngles::ANGLE_210) {
-				_engine->_sound->playSample(Samples::ItemFound, 0x1000, 1, _engine->_scene->_sceneHero->posObj(), OWN_ACTOR_SCENE_INDEX);
+				_engine->_sound->mixSample3D(Samples::ItemFound, 0x1000, 1, _engine->_scene->_sceneHero->posObj(), OWN_ACTOR_SCENE_INDEX);
 
 				if (extraKey->info1 > 1) {
 					const IVec3 &projPos = _engine->_renderer->projectPoint(extraKey->pos - _engine->_grid->_worldCube);
@@ -643,7 +643,7 @@ void Extra::gereExtras() {
 			_engine->_movements->initRealValue(LBAAngles::ANGLE_0, extra->destPos.z, LBAAngles::ANGLE_17, &extra->trackActorMove);
 
 			if (extraIdx == _engine->_collision->extraCheckExtraCol(extra, _engine->_gameState->_magicBall)) {
-				_engine->_sound->playSample(Samples::ItemFound, 0x1000, 1, _engine->_scene->_sceneHero->posObj(), OWN_ACTOR_SCENE_INDEX);
+				_engine->_sound->mixSample3D(Samples::ItemFound, 0x1000, 1, _engine->_scene->_sceneHero->posObj(), OWN_ACTOR_SCENE_INDEX);
 
 				if (extraKey->info1 > 1) {
 					const IVec3 &projPos = _engine->_renderer->projectPoint(extraKey->pos - _engine->_grid->_worldCube);
@@ -721,7 +721,7 @@ void Extra::gereExtras() {
 				// if extra is magic ball
 				if (i == _engine->_gameState->_magicBall) {
 					const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(300) - 150;
-					_engine->_sound->playSample(Samples::Hit, pitchBend, 1, extra->pos);
+					_engine->_sound->mixSample3D(Samples::Hit, pitchBend, 1, extra->pos, -1);
 
 					// can't bounce with not magic points
 					if (_engine->_gameState->_magicBallType <= 0) {
@@ -792,7 +792,7 @@ void Extra::gereExtras() {
 		if ((extra->type & ExtraType::TAKABLE) && !(extra->type & ExtraType::FLY)) {
 			// if hero touch extra
 			if (_engine->_collision->extraCheckObjCol(extra, -1) == 0) {
-				_engine->_sound->playSample(Samples::ItemFound, 0x1000, 1, extra->pos);
+				_engine->_sound->mixSample3D(Samples::ItemFound, 0x1000, 1, extra->pos, -1);
 
 				if (extra->info1 > 1) {
 					const IVec3 &projPos = _engine->_renderer->projectPoint(extra->pos - _engine->_grid->_worldCube);
diff --git a/engines/twine/scene/gamestate.cpp b/engines/twine/scene/gamestate.cpp
index 3dcb783f3f5..9d22bd4248a 100644
--- a/engines/twine/scene/gamestate.cpp
+++ b/engines/twine/scene/gamestate.cpp
@@ -366,7 +366,7 @@ void GameState::doFoundObj(InventoryItems item) {
 	const int32 boxBottomRightX = projPos.x + (SIZE_FOUND_OBJ / 2);
 	const int32 boxBottomRightY = projPos.y + (SIZE_FOUND_OBJ / 2);
 	const Common::Rect boxRect(boxTopLeftX, boxTopLeftY, boxBottomRightX, boxBottomRightY);
-	_engine->_sound->playSample(Samples::BigItemFound);
+	_engine->_sound->mixSample(Samples::BigItemFound, 0x1000, 1, 128, 128);
 
 	// process vox play
 	_engine->_music->stopMusic();
@@ -543,7 +543,8 @@ void GameState::processGameoverAnimation() {
 		debugC(3, kDebugLevels::kDebugTimers, "GameOver time: %i", _engine->timerRef);
 	}
 
-	_engine->_sound->playSample(Samples::Explode);
+	const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(2000) - (2000 / 2);
+	_engine->_sound->mixSample(Samples::Explode, pitchBend, 1, 128, 128);
 	_engine->blitWorkToFront(rect);
 	_engine->_renderer->setFollowCamera(0, 0, 0, 0, 0, 0, zoom);
 	_engine->_renderer->affObjetIso(0, 0, 0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, gameOverPtr, dummy);
diff --git a/engines/twine/scene/scene.cpp b/engines/twine/scene/scene.cpp
index 7222c2db85b..768d1e55b0d 100644
--- a/engines/twine/scene/scene.cpp
+++ b/engines/twine/scene/scene.cpp
@@ -740,7 +740,7 @@ void Scene::processEnvironmentSound() {
 				int16 repeat = _sampleRepeat[currentAmb];
 
 				const uint16 pitchbend = 0x1000 + _engine->getRandomNumber(decal) - (decal / 2);
-				_engine->_sound->playSample(sampleIdx, pitchbend, repeat, 110, 110);
+				_engine->_sound->mixSample(sampleIdx, pitchbend, repeat, 110, 110);
 				break;
 			}
 		}
diff --git a/engines/twine/script/script_move.cpp b/engines/twine/script/script_move.cpp
index 8737bbb00d7..d1a09a12f49 100644
--- a/engines/twine/script/script_move.cpp
+++ b/engines/twine/script/script_move.cpp
@@ -280,7 +280,7 @@ int32 ScriptMove::mWAIT_NUM_ANIM(TwinEEngine *engine, MoveScriptContext &ctx) {
 int32 ScriptMove::mSAMPLE(TwinEEngine *engine, MoveScriptContext &ctx) {
 	int32 sampleIdx = ctx.stream.readSint16LE();
 	debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SAMPLE(%i)", (int)sampleIdx);
-	engine->_sound->playSample(sampleIdx, 0x1000, 1, ctx.actor->posObj(), ctx.actorIdx);
+	engine->_sound->mixSample3D(sampleIdx, 0x1000, 1, ctx.actor->posObj(), ctx.actorIdx);
 	return 0;
 }
 
@@ -492,7 +492,7 @@ int32 ScriptMove::mSAMPLE_RND(TwinEEngine *engine, MoveScriptContext &ctx) {
 	int32 sampleIdx = ctx.stream.readSint16LE();
 	debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SAMPLE_RND(%i)", (int)sampleIdx);
 	const uint16 pitchbend = 0x800 + engine->getRandomNumber(0x800);
-	engine->_sound->playSample(sampleIdx, pitchbend, 1, ctx.actor->posObj(), ctx.actorIdx);
+	engine->_sound->mixSample3D(sampleIdx, pitchbend, 1, ctx.actor->posObj(), ctx.actorIdx);
 	return 0;
 }
 
@@ -504,7 +504,7 @@ int32 ScriptMove::mSAMPLE_ALWAYS(TwinEEngine *engine, MoveScriptContext &ctx) {
 	int32 sampleIdx = ctx.stream.readSint16LE();
 	debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SAMPLE_ALWAYS(%i)", (int)sampleIdx);
 	if (!engine->_sound->isSamplePlaying(sampleIdx)) { // if its not playing
-		engine->_sound->playSample(sampleIdx, 0x1000, 0, ctx.actor->posObj(), ctx.actorIdx);
+		engine->_sound->mixSample3D(sampleIdx, 0x1000, 0, ctx.actor->posObj(), ctx.actorIdx);
 	}
 	return 0;
 }
@@ -565,7 +565,7 @@ int32 ScriptMove::mREPEAT_SAMPLE(TwinEEngine *engine, MoveScriptContext &ctx) {
 int32 ScriptMove::mSIMPLE_SAMPLE(TwinEEngine *engine, MoveScriptContext &ctx) {
 	int32 sampleIdx = ctx.stream.readSint16LE();
 	debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SIMPLE_SAMPLE(%i)", (int)sampleIdx);
-	engine->_sound->playSample(sampleIdx, 0x1000, ctx.bigSampleRepeat, ctx.actor->posObj(), ctx.actorIdx);
+	engine->_sound->mixSample(sampleIdx, 0x1000, ctx.bigSampleRepeat, 128, 128);
 	ctx.bigSampleRepeat = 1;
 	return 0;
 }
diff --git a/engines/twine/twine.cpp b/engines/twine/twine.cpp
index 06509cbb91e..bbc2d3342c9 100644
--- a/engines/twine/twine.cpp
+++ b/engines/twine/twine.cpp
@@ -735,7 +735,7 @@ void TwinEEngine::restoreTimer() {
 void TwinEEngine::processActorSamplePosition(int32 actorIdx) {
 	const ActorStruct *actor = _scene->getActor(actorIdx);
 	const int32 channelIdx = _sound->getActorChannel(actorIdx);
-	_sound->setSamplePosition(channelIdx, actor->posObj());
+	_sound->setChannelPosition(channelIdx, actor->posObj());
 }
 
 void TwinEEngine::processBookOfBu() {
@@ -1097,7 +1097,7 @@ bool TwinEEngine::runGameEngine() { // mainLoopInteration
 #endif
 			} else {
 				const uint16 pitchBend = 0x1000 + getRandomNumber(2000) - (2000 / 2);
-				_sound->playSample(Samples::Explode, pitchBend, 1, actor->posObj(), a);
+				_sound->mixSample3D(Samples::Explode, pitchBend, 1, actor->posObj(), a);
 
 				if (a == _scene->_mecaPenguinIdx) {
 					_extra->extraExplo(actor->posObj());
@@ -1162,7 +1162,7 @@ bool TwinEEngine::runGameEngine() { // mainLoopInteration
 					}
 				} else {
 					const uint16 pitchBend = 0x1000 + getRandomNumber(2000) - (2000 / 2);
-					_sound->playSample(Samples::Explode, pitchBend, 1, actor->posObj(), a);
+					_sound->mixSample3D(Samples::Explode, pitchBend, 1, actor->posObj(), a);
 					if (actor->_bonusParameter.cloverleaf || actor->_bonusParameter.kashes || actor->_bonusParameter.key || actor->_bonusParameter.lifepoints || actor->_bonusParameter.magicpoints) {
 						if (!actor->_bonusParameter.givenNothing) {
 							_actor->giveExtraBonus(a);


Commit: a8930572186fa7d9fef2fc48e983898912265fd6
    https://github.com/scummvm/scummvm/commit/a8930572186fa7d9fef2fc48e983898912265fd6
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2025-04-29T21:45:49+02:00

Commit Message:
TWINE: Skipping the text intro with Escape also skips the subsequent FMV

fixes 15734 (https://bugs.scummvm.org/ticket/15734)

Changed paths:
    engines/twine/twine.cpp


diff --git a/engines/twine/twine.cpp b/engines/twine/twine.cpp
index bbc2d3342c9..61bc2e1ae68 100644
--- a/engines/twine/twine.cpp
+++ b/engines/twine/twine.cpp
@@ -649,12 +649,10 @@ void TwinEEngine::introduction() {
 		}
 	}
 
-	if (!abort) {
-		if (isLBA1()) {
-			_movie->playMovie(FLA_DRAGON3);
-		} else {
-			_movie->playMovie("INTRO");
-		}
+	if (isLBA1()) {
+		_movie->playMovie(FLA_DRAGON3);
+	} else {
+		_movie->playMovie("INTRO");
 	}
 }
 




More information about the Scummvm-git-logs mailing list