[Scummvm-git-logs] scummvm master -> 6654d1d048b0a7b5d1aa921c8910944a0c592db2

moralrecordings noreply at scummvm.org
Sun Aug 28 10:31:07 UTC 2022


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

Summary:
09cf6d5ada DIRECTOR: LINGO: Prioritise named sprite event handlers
7ee474f070 DIRECTOR: LINGO: Add missing color transforms
ef03dbb92e DIRECTOR: Fix rendering for kInkTypeBackgndTrans and kInkTypeCopy
830cf1cacd DIRECTOR: Only use kCastMemberSprite when cast member exists
f5a0bd236b AUDIO: Add virtual base class for looping audio
f48dbb43b6 DIRECTOR: Use LoopableAudioStream for score audio loops
6654d1d048 DIRECTOR: Fix freeing up cast members


Commit: 09cf6d5ada76d5df8c7db29a3b6048545d8ec55d
    https://github.com/scummvm/scummvm/commit/09cf6d5ada76d5df8c7db29a3b6048545d8ec55d
Author: Scott Percival (code at moral.net.au)
Date: 2022-08-28T10:31:00Z

Commit Message:
DIRECTOR: LINGO: Prioritise named sprite event handlers

In Director 4, with score scripts it is possible for the compiler
to produce a generic handler which is empty, in addition to named
handlers (e.g. mouseUp). The original behaviour for sprite scripts
was to default to the generic handler if it exists, even if there
is a named handler that matches the event. This change makes the
generic handler the fallback choice.

Fixes opening the inventory in Eastern Mind.

Changed paths:
    engines/director/lingo/lingo-events.cpp


diff --git a/engines/director/lingo/lingo-events.cpp b/engines/director/lingo/lingo-events.cpp
index e6a0c2f7857..a7e834d475d 100644
--- a/engines/director/lingo/lingo-events.cpp
+++ b/engines/director/lingo/lingo-events.cpp
@@ -127,16 +127,16 @@ void Movie::queueSpriteEvent(Common::Queue<LingoEvent> &queue, LEvent event, int
 	if (sprite->_scriptId.member) {
 		ScriptContext *script = getScriptContext(kScoreScript, sprite->_scriptId);
 		if (script) {
-			if (script->_eventHandlers.contains(kEventGeneric)) {
+			if (script->_eventHandlers.contains(event)) {
+				// D4-style event handler
+				queue.push(LingoEvent(event, eventId, kScoreScript, sprite->_scriptId, false, spriteId));
+			} else if (script->_eventHandlers.contains(kEventGeneric)) {
 				// D3-style sprite script, not contained in a handler
 				// If sprite is immediate, its script is run on mouseDown, otherwise on mouseUp
 				if ((event == kEventMouseDown && sprite->_immediate) || (event == kEventMouseUp && !sprite->_immediate)) {
 					queue.push(LingoEvent(kEventGeneric, eventId, kScoreScript, sprite->_scriptId, false, spriteId));
 				}
 				return; // Do not execute the cast script if there is a D3-style sprite script
-			} else if (script->_eventHandlers.contains(event)) {
-				// D4-style event handler
-				queue.push(LingoEvent(event, eventId, kScoreScript, sprite->_scriptId, false, spriteId));
 			}
 		}
 	}


Commit: 7ee474f070efebdfd9516e0e89b339fc25714bf6
    https://github.com/scummvm/scummvm/commit/7ee474f070efebdfd9516e0e89b339fc25714bf6
Author: Scott Percival (code at moral.net.au)
Date: 2022-08-28T10:31:00Z

Commit Message:
DIRECTOR: LINGO: Add missing color transforms

Internally, ScummVM refers to palette indices in a reverse order
compared to the Director runtime. As such, any script-based reading or
writing of color indices needs to be transformed to match this.

Fixes the coloring of the inventory in Eastern Mind.

Changed paths:
    engines/director/lingo/lingo-the.cpp


diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index e951b5e114b..110c0b8f876 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -885,7 +885,8 @@ Datum Lingo::getTheEntity(int entity, Datum &id, int field) {
 		break;
 	case kTheStageColor:
 		d.type = INT;
-		d.u.i = g_director->getCurrentWindow()->getStageColor();
+		// TODO: Provide proper reverse transform for non-indexed color
+		d.u.i = (int)g_director->transformColor(g_director->getCurrentWindow()->getStageColor());
 		break;
 	case kTheStageLeft:
 		d.type = INT;
@@ -1204,7 +1205,7 @@ void Lingo::setTheEntity(int entity, Datum &id, int field, Datum &d) {
 		setTheEntitySTUB(kTheStage);
 		break;
 	case kTheStageColor:
-		g_director->getCurrentWindow()->setStageColor(d.asInt());
+		g_director->getCurrentWindow()->setStageColor(g_director->transformColor(d.asInt()));
 
 		// Queue an immediate update of the stage
 		if (!score->getNextFrame())
@@ -1337,7 +1338,8 @@ Datum Lingo::getTheSprite(Datum &id1, int field) {
 
 	switch (field) {
 	case kTheBackColor:
-		d.u.i = sprite->_backColor;
+		// TODO: Provide proper reverse transform for non-indexed color
+		d.u.i = (int)g_director->transformColor(sprite->_backColor);
 		break;
 	case kTheBlend:
 		d.u.i = sprite->_blend;
@@ -1359,7 +1361,8 @@ Datum Lingo::getTheSprite(Datum &id1, int field) {
 		d.u.i = sprite->_editable;
 		break;
 	case kTheForeColor:
-		d.u.i = sprite->_foreColor;
+		// TODO: Provide proper reverse transform for non-indexed color
+		d.u.i = (int)g_director->transformColor(sprite->_foreColor);
 		break;
 	case kTheHeight:
 		d.u.i = channel->_height;
@@ -1491,9 +1494,12 @@ void Lingo::setTheSprite(Datum &id1, int field, Datum &d) {
 
 	switch (field) {
 	case kTheBackColor:
-		if ((uint32)d.asInt() != sprite->_backColor) {
-			sprite->_backColor = d.asInt();
-			channel->_dirty = true;
+		{
+			uint32 newColor = g_director->transformColor(d.asInt());
+			if (newColor != sprite->_backColor) {
+				sprite->_backColor = newColor;
+				channel->_dirty = true;
+			}
 		}
 		break;
 	case kTheBlend:
@@ -1563,9 +1569,12 @@ void Lingo::setTheSprite(Datum &id1, int field, Datum &d) {
 		channel->_sprite->_editable = d.asInt();
 		break;
 	case kTheForeColor:
-		if ((uint32)d.asInt() != sprite->_foreColor) {
-			sprite->_foreColor = d.asInt();
-			channel->_dirty = true;
+		{
+			uint32 newColor = g_director->transformColor(d.asInt());
+			if (newColor != sprite->_foreColor) {
+				sprite->_foreColor = newColor;
+				channel->_dirty = true;
+			}
 		}
 		break;
 	case kTheHeight:


Commit: ef03dbb92ef268b83c1c32a93121e28f55306e6f
    https://github.com/scummvm/scummvm/commit/ef03dbb92ef268b83c1c32a93121e28f55306e6f
Author: Scott Percival (code at moral.net.au)
Date: 2022-08-28T10:31:00Z

Commit Message:
DIRECTOR: Fix rendering for kInkTypeBackgndTrans and kInkTypeCopy

I don't know what scenario was used as a basis for developing the existing
applyColor logic in inkDrawPixel(). As such, I've kept it for the uint32
pathway, and replaced the uint8 pathway with the logic documented in the
Director 4 manual.

Changed paths:
    engines/director/graphics.cpp


diff --git a/engines/director/graphics.cpp b/engines/director/graphics.cpp
index 2a7388f1fe5..b634fd01f2c 100644
--- a/engines/director/graphics.cpp
+++ b/engines/director/graphics.cpp
@@ -197,10 +197,10 @@ void inkDrawPixel(int x, int y, int src, void *data) {
 	if (!p->destRect.contains(x, y))
 		return;
 
-	T dst;
+	T *dst;
 	uint32 tmpDst;
 
-	dst = (T)p->dst->getBasePtr(x, y);
+	dst = (T *)p->dst->getBasePtr(x, y);
 
 	if (p->ms) {
 		if (p->ms->pd->thickness > 1) {
@@ -255,28 +255,32 @@ void inkDrawPixel(int x, int y, int src, void *data) {
 
  	switch (p->ink) {
 	case kInkTypeBackgndTrans:
-		if ((uint32)src == p->backColor)
-			break;
-		// fall through
+		*dst = src == p->backColor ? *dst : src;
+		break;
 	case kInkTypeMatte:
+		// fall through
 	case kInkTypeMask:
 		// Only unmasked pixels make it here, so copy them straight
 	case kInkTypeCopy: {
 		if (p->applyColor) {
-			// TODO: Improve the efficiency of this composition
-			byte rSrc, gSrc, bSrc;
-			byte rDst, gDst, bDst;
-			byte rFor, gFor, bFor;
-			byte rBak, gBak, bBak;
-
-			wm->decomposeColor(src, rSrc, gSrc, bSrc);
-			wm->decomposeColor(*dst, rDst, gDst, bDst);
-			wm->decomposeColor(p->foreColor, rFor, gFor, bFor);
-			wm->decomposeColor(p->backColor, rBak, gBak, bBak);
-
-			*dst = wm->findBestColor((rSrc | rFor) & (~rSrc | rBak),
-									 (gSrc | gFor) & (~gSrc | gBak),
-									 (bSrc | bFor) & (~bSrc | bBak));
+			if (sizeof(T) == 1) {
+				*dst = src == 0x00 ? p->foreColor : (src == 0xff ? p->backColor : *dst);
+			} else {
+				// TODO: Improve the efficiency of this composition
+				byte rSrc, gSrc, bSrc;
+				byte rDst, gDst, bDst;
+				byte rFor, gFor, bFor;
+				byte rBak, gBak, bBak;
+
+				wm->decomposeColor(src, rSrc, gSrc, bSrc);
+				wm->decomposeColor(*dst, rDst, gDst, bDst);
+				wm->decomposeColor(p->foreColor, rFor, gFor, bFor);
+				wm->decomposeColor(p->backColor, rBak, gBak, bBak);
+
+				*dst = wm->findBestColor((rSrc | rFor) & (~rSrc | rBak),
+										(gSrc | gFor) & (~gSrc | gBak),
+										(bSrc | bFor) & (~bSrc | bBak));
+			}
 		} else {
 			*dst = src;
 		}
@@ -284,20 +288,24 @@ void inkDrawPixel(int x, int y, int src, void *data) {
 	}
 	case kInkTypeNotCopy:
 		if (p->applyColor) {
-			// TODO: Improve the efficiency of this composition
-			byte rSrc, gSrc, bSrc;
-			byte rDst, gDst, bDst;
-			byte rFor, gFor, bFor;
-			byte rBak, gBak, bBak;
-
-			wm->decomposeColor(src, rSrc, gSrc, bSrc);
-			wm->decomposeColor(*dst, rDst, gDst, bDst);
-			wm->decomposeColor(p->foreColor, rFor, gFor, bFor);
-			wm->decomposeColor(p->backColor, rBak, gBak, bBak);
-
-			*dst = wm->findBestColor((~rSrc | rFor) & (rSrc | rBak),
-									 (~gSrc | gFor) & (gSrc | gBak),
-									 (~bSrc | bFor) & (bSrc | bBak));
+			if (sizeof(T) == 1) {
+				*dst = src == 0x00 ? p->backColor : (src == 0xff ? p->foreColor : src);
+			} else {
+				// TODO: Improve the efficiency of this composition
+				byte rSrc, gSrc, bSrc;
+				byte rDst, gDst, bDst;
+				byte rFor, gFor, bFor;
+				byte rBak, gBak, bBak;
+
+				wm->decomposeColor(src, rSrc, gSrc, bSrc);
+				wm->decomposeColor(*dst, rDst, gDst, bDst);
+				wm->decomposeColor(p->foreColor, rFor, gFor, bFor);
+				wm->decomposeColor(p->backColor, rBak, gBak, bBak);
+
+				*dst = wm->findBestColor((~rSrc | rFor) & (rSrc | rBak),
+										(~gSrc | gFor) & (gSrc | gBak),
+										(~bSrc | bFor) & (bSrc | bBak));
+			}
 		} else {
 			*dst = src;
 		}
@@ -361,9 +369,9 @@ void inkDrawPixel(int x, int y, int src, void *data) {
 
 Graphics::MacDrawPixPtr DirectorEngine::getInkDrawPixel() {
 	if (_pixelformat.bytesPerPixel == 1)
-		return &inkDrawPixel<byte *>;
+		return &inkDrawPixel<byte>;
 	else
-		return &inkDrawPixel<uint32 *>;
+		return &inkDrawPixel<uint32>;
 }
 
 void DirectorPlotData::setApplyColor() {


Commit: 830cf1cacdcab990a91f3203c19dfbd16374f861
    https://github.com/scummvm/scummvm/commit/830cf1cacdcab990a91f3203c19dfbd16374f861
Author: Scott Percival (code at moral.net.au)
Date: 2022-08-28T10:31:00Z

Commit Message:
DIRECTOR: Only use kCastMemberSprite when cast member exists

The default sprite type for an empty channel in Director is kInactiveSprite;
make sure this is used as some scripts depend on it.

Fixes being unable to summon the drum in Eastern Mind.

Changed paths:
    engines/director/sprite.cpp


diff --git a/engines/director/sprite.cpp b/engines/director/sprite.cpp
index 129d19aec58..bbc0f062370 100644
--- a/engines/director/sprite.cpp
+++ b/engines/director/sprite.cpp
@@ -460,7 +460,7 @@ void Sprite::setCast(CastMemberID memberID) {
 	_castId = memberID;
 	_cast = _movie->getCastMember(_castId);
 	//As QDShapes don't have an associated cast, we must not change their _SpriteType.
-	if (g_director->getVersion() >= 400 && !isQDShape())
+	if (g_director->getVersion() >= 400 && !isQDShape() && _castId != CastMemberID(0, 0))
 		_spriteType = kCastMemberSprite;
 
 	if (_cast) {


Commit: f5a0bd236b6c8c95ff714ff27e28feabfa19ecf4
    https://github.com/scummvm/scummvm/commit/f5a0bd236b6c8c95ff714ff27e28feabfa19ecf4
Author: Scott Percival (code at moral.net.au)
Date: 2022-08-28T10:31:00Z

Commit Message:
AUDIO: Add virtual base class for looping audio

LoopingAudioStream and SubLoopingAudioStream now share a common base
class, and have the methods getCompleteIterations() and
setRemainingIterations().

Changed paths:
    audio/audiostream.cpp
    audio/audiostream.h


diff --git a/audio/audiostream.cpp b/audio/audiostream.cpp
index dce185c6457..182060657ba 100644
--- a/audio/audiostream.cpp
+++ b/audio/audiostream.cpp
@@ -121,12 +121,12 @@ int LoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) {
 
 		if (!_parent->rewind()) {
 			// TODO: Properly indicate error
-			_loops = _completeIterations = 1;
+			_loops = _completeIterations;
 			return samplesRead;
 		}
 		if (_parent->endOfStream()) {
 			// Apparently this is an empty stream
-			_loops = _completeIterations = 1;
+			_loops = _completeIterations;
 		}
 
 		return samplesRead + readBuffer(buffer + samplesRead, remainingSamples);
@@ -176,19 +176,19 @@ SubLoopingAudioStream::SubLoopingAudioStream(SeekableAudioStream *stream,
 											 const Timestamp loopStart,
 											 const Timestamp loopEnd,
 											 DisposeAfterUse::Flag disposeAfterUse)
-	: _parent(stream, disposeAfterUse), _loops(loops),
+	: _parent(stream, disposeAfterUse), _loops(loops), _completeIterations(0),
 	  _pos(0, getRate() * (isStereo() ? 2 : 1)),
 	  _loopStart(convertTimeToStreamPos(loopStart, getRate(), isStereo())),
-	  _loopEnd(convertTimeToStreamPos(loopEnd, getRate(), isStereo())),
-	  _done(false) {
+	  _loopEnd(convertTimeToStreamPos(loopEnd, getRate(), isStereo())) {
 	assert(loopStart < loopEnd);
+	assert(stream);
 
 	if (!_parent->rewind())
-		_done = true;
+		_loops = _completeIterations = 1;
 }
 
 int SubLoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) {
-	if (_done)
+	if ((_loops && _completeIterations == _loops) || !numSamples)
 		return 0;
 
 	int framesLeft = MIN(_loopEnd.frameDiff(_pos), numSamples);
@@ -197,20 +197,18 @@ int SubLoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) {
 
 	if (framesRead < framesLeft && _parent->endOfStream()) {
 		// TODO: Proper error indication.
-		_done = true;
+		if (!_completeIterations)
+			_completeIterations = 1;
+		_loops = _completeIterations;
 		return framesRead;
 	} else if (_pos == _loopEnd) {
-		if (_loops != 0) {
-			--_loops;
-			if (!_loops) {
-				_done = true;
-				return framesRead;
-			}
-		}
+		++_completeIterations;
+		if (_completeIterations == _loops)
+			return framesRead;
 
 		if (!_parent->seek(_loopStart)) {
 			// TODO: Proper error indication.
-			_done = true;
+			_loops = _completeIterations;
 			return framesRead;
 		}
 
@@ -225,13 +223,13 @@ int SubLoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) {
 bool SubLoopingAudioStream::endOfData() const {
 	// We're out of data if this stream is finished or the parent
 	// has run out of data for now.
-	return _done || _parent->endOfData();
+	return (_loops != 0 && _completeIterations == _loops) || _parent->endOfData();
 }
 
 bool SubLoopingAudioStream::endOfStream() const {
 	// The end of the stream has been reached only when we've gone
 	// through all the iterations.
-	return _done;
+	return _loops != 0 && _completeIterations == _loops;
 }
 
 #pragma mark -
diff --git a/audio/audiostream.h b/audio/audiostream.h
index c12db746041..05c165dc25e 100644
--- a/audio/audiostream.h
+++ b/audio/audiostream.h
@@ -116,13 +116,32 @@ public:
 	virtual bool rewind() = 0;
 };
 
+/**
+ * Generic loopable audio stream. Subclasses of this are used to feed
+ * looping sampled audio data into ScummVM's audio mixer.
+ */
+class LoopableAudioStream : public virtual AudioStream {
+public:
+
+	/**
+	 * Return the number of loops that the stream has played.
+	 */
+	virtual uint getCompleteIterations() const = 0;
+
+	/**
+	 * Set the number of remaining loops the stream should play
+	 * before stopping.
+	 */
+	virtual void setRemainingIterations(uint loops) = 0;
+};
+
 /**
  * A looping audio stream.
  *
  * This object does nothing besides using a RewindableAudioStream
  * to play a stream in a loop.
  */
-class LoopingAudioStream : public AudioStream {
+class LoopingAudioStream : public LoopableAudioStream {
 public:
 	/**
 	 * Create a looping audio stream object.
@@ -145,10 +164,8 @@ public:
 	bool isStereo() const { return _parent->isStereo(); }
 	int getRate() const { return _parent->getRate(); }
 
-	/**
-	 * Return the number of loops that the stream has played.
-	 */
 	uint getCompleteIterations() const { return _completeIterations; }
+	void setRemainingIterations(uint loops) { _loops = _completeIterations + loops; }
 private:
 	Common::DisposablePtr<RewindableAudioStream> _parent;
 
@@ -253,7 +270,7 @@ AudioStream *makeLoopingAudioStream(SeekableAudioStream *stream, Timestamp start
  * @b Important: This can be merged with SubSeekableAudioStream for playback purposes.
  *               To do this, it must be extended to accept a start time.
  */
-class SubLoopingAudioStream : public AudioStream {
+class SubLoopingAudioStream : public LoopableAudioStream {
 public:
 	/**
 	 * Constructor for a SubLoopingAudioStream.
@@ -279,14 +296,16 @@ public:
 
 	bool isStereo() const { return _parent->isStereo(); }
 	int getRate() const { return _parent->getRate(); }
+
+	uint getCompleteIterations() const { return _completeIterations; }
+	void setRemainingIterations(uint loops) { _loops = _completeIterations + loops; }
 private:
 	Common::DisposablePtr<SeekableAudioStream> _parent;
 
 	uint _loops;
+	uint _completeIterations;
 	Timestamp _pos;
 	Timestamp _loopStart, _loopEnd;
-
-	bool _done;
 };
 
 


Commit: f48dbb43b6b6483ef2160ae3d087eb2e0f06c7e5
    https://github.com/scummvm/scummvm/commit/f48dbb43b6b6483ef2160ae3d087eb2e0f06c7e5
Author: Scott Percival (code at moral.net.au)
Date: 2022-08-28T10:31:00Z

Commit Message:
DIRECTOR: Use LoopableAudioStream for score audio loops

For looping sounds, the original code polled the score and created a new
AudioStream every time the previous iteration finished playing. The
downside of this approach is the sound needs to be fully resampled and
buffered after each playback, which can be heard as a gap between loops.

To fix this, loops now use a LoopableAudioStream. The reason why the
previous code did not was to allow for a specific edge case.
In Director, when a looping audio channel is set to cast member 0, the
loop is allowed to finish the current iteration before the audio stops.
Originally there wasn't a way of doing that, but now the
LoopableAudioStream classes can stop playing after a number of
iterations. As such, we keep a copy of the LoopableAudioStream handle
and call for the loop to stop when the cast member gets set to 0.

Improves looped audio playback in Eastern Mind.

Fixes the guitar solo between the transition from BLAST.DXR to INTRO.DXR
in Meet MediaBand.

Fixes the audio transition between n.am and s.am in Meet MediaBand.

Changed paths:
    engines/director/sound.cpp
    engines/director/sound.h


diff --git a/engines/director/sound.cpp b/engines/director/sound.cpp
index cb4ea80dbc2..3ba1212fee6 100644
--- a/engines/director/sound.cpp
+++ b/engines/director/sound.cpp
@@ -23,6 +23,7 @@
 // https://github.com/System25/drxtract/blob/master/snd2wav
 // License: GNU GPL v2 (see COPYING file for details)
 
+#include "audio/audiostream.h"
 #include "common/file.h"
 #include "common/macresman.h"
 #include "common/substream.h"
@@ -134,6 +135,13 @@ void DirectorSound::playCastMember(CastMemberID memberID, uint8 soundChannel, bo
 		if (shouldStopOnZero(soundChannel)) {
 			stopSound(soundChannel);
 		} else {
+			// If there is a loopable stream specified, set the loop to expire by itself
+			if (_channels[soundChannel - 1].loopPtr) {
+				debugC(5, kDebugSound, "DirectorSound::playCastMember(): telling loop in channel %d to stop", soundChannel);
+				_channels[soundChannel - 1].loopPtr->setRemainingIterations(1);
+				_channels[soundChannel - 1].loopPtr = nullptr;
+			}
+
 			// Don't stop the currently playing sound, just set the last played sound to 0.
 			setLastPlayedSound(soundChannel, SoundID(), false);
 		}
@@ -146,16 +154,12 @@ void DirectorSound::playCastMember(CastMemberID memberID, uint8 soundChannel, bo
 				bool looping = ((SoundCastMember *)soundCast)->_looping;
 				bool stopOnZero = true;
 
-				if (!forPuppet && isLastPlayedSound(soundChannel, memberID)) {
-					// We just played this sound.
-					// If the sound is not marked "looping", we should not play it again.
-					if (!looping)
-						return;
-
-					// If the sound is not finished yet, we need to wait more before playing it again.
-					if (isChannelActive(soundChannel))
-						return;
+				// For a non-puppet sound, if the sound is the same ID as the last
+				// played, do nothing.
+				if (!forPuppet && isLastPlayedSound(soundChannel, memberID))
+					return;
 
+				if (!forPuppet && looping) {
 					// We know that this is a non-puppet, looping sound.
 					// We don't want to stop it if this channel's cast member changes to 0.
 					stopOnZero = false;
@@ -174,7 +178,11 @@ void DirectorSound::playCastMember(CastMemberID memberID, uint8 soundChannel, bo
 					warning("DirectorSound::playCastMember: audio data failed to load from cast");
 					return;
 				}
-				debugC(5, kDebugSound, "DirectorSound::playCastMember(): playing cast ID %s, channel %d, looping %d, stopOnZero %d", memberID.asString().c_str(), soundChannel, looping, stopOnZero);
+				debugC(5, kDebugSound, "DirectorSound::playCastMember(): playing cast ID %s, channel %d, looping %d, stopOnZero %d, forPuppet %d", memberID.asString().c_str(), soundChannel, looping, stopOnZero, forPuppet);
+				// For looping sounds, keep a copy of the AudioStream so it is
+				// possible to gracefully stop the playback
+				if (looping)
+					_channels[soundChannel - 1].loopPtr = dynamic_cast<Audio::LoopableAudioStream *>(as);
 				playStream(*as, soundChannel);
 				setLastPlayedSound(soundChannel, memberID, stopOnZero);
 			}
@@ -389,9 +397,11 @@ void DirectorSound::playExternalSound(uint16 menu, uint16 submenu, uint8 soundCh
 void DirectorSound::changingMovie() {
 	for (uint i = 1; i < _channels.size(); i++) {
 		_channels[i - 1].movieChanged = true;
-		if (isChannelPuppet(i)) {
-			setPuppetSound(SoundID(), i); // disable puppet sound
-		} else if (isChannelActive(i)) {
+		if (isChannelActive(i)) {
+			if (isChannelPuppet(i)) {
+				setPuppetSound(SoundID(), i); // disable puppet sound
+			}
+
 			// Don't stop this sound until there's a new, non-zero sound in this channel.
 			_channels[i - 1].stopOnZero = false;
 
@@ -428,6 +438,8 @@ void DirectorSound::stopSound(uint8 soundChannel) {
 		return;
 
 	debugC(5, kDebugSound, "DirectorSound::stopSound(): stopping channel %d", soundChannel);
+	if (_channels[soundChannel - 1].loopPtr)
+		_channels[soundChannel - 1].loopPtr = nullptr;
 	cancelFade(soundChannel);
 	_mixer->stopHandle(_channels[soundChannel - 1].handle);
 	setLastPlayedSound(soundChannel, SoundID());
@@ -437,6 +449,8 @@ void DirectorSound::stopSound(uint8 soundChannel) {
 void DirectorSound::stopSound() {
 	debugC(5, kDebugSound, "DirectorSound::stopSound(): stopping all channels");
 	for (uint i = 0; i < _channels.size(); i++) {
+		if (_channels[i].loopPtr)
+			_channels[i].loopPtr = nullptr;
 		cancelFade(i + 1);
 
 		_mixer->stopHandle(_channels[i].handle);
@@ -469,6 +483,7 @@ void DirectorSound::setPuppetSound(SoundID soundId, uint8 soundChannel) {
 
 	_channels[soundChannel - 1].newPuppet = true;
 	_channels[soundChannel - 1].puppet = soundId;
+	_channels[soundChannel - 1].stopOnZero = true;
 }
 
 void DirectorSound::playPuppetSound(uint8 soundChannel) {
@@ -749,20 +764,13 @@ Audio::AudioStream *SNDDecoder::getAudioStream(bool looping, bool forPuppet, Dis
 
 	if (looping) {
 		if (hasLoopBounds()) {
-			// If this is for a puppet, return an automatically looping stream.
-			// Otherwise, the sound will be looped by the score.
-			if (forPuppet)
-				return new Audio::SubLoopingAudioStream(stream, 0, Audio::Timestamp(0, _loopStart, _rate), Audio::Timestamp(0, _loopEnd, _rate));
-			else
-				return new Audio::SubSeekableAudioStream(stream, Audio::Timestamp(0, _loopStart, _rate), Audio::Timestamp(0, _loopEnd, _rate));
+			// Return an automatically looping stream.
+			return new Audio::SubLoopingAudioStream(stream, 0, Audio::Timestamp(0, _loopStart, _rate), Audio::Timestamp(0, _loopEnd, _rate));
 		} else {
 			// Not sure if looping sounds can appear without loop bounds.
 			// Let's just log a warning and loop the entire sound...
 			warning("SNDDecoder::getAudioStream: Looping sound has no loop bounds");
-			if (forPuppet)
-				return new Audio::LoopingAudioStream(stream, 0);
-			else
-				return stream;
+			return new Audio::LoopingAudioStream(stream, 0);
 		}
 	}
 
@@ -814,9 +822,7 @@ Audio::AudioStream *AudioFileDecoder::getAudioStream(bool looping, bool forPuppe
 	}
 
 	if (stream) {
-		if (looping && forPuppet) {
-			// If this is for a puppet, return an automatically looping stream.
-			// Otherwise, the sound will be looped by the score
+		if (looping) {
 			return new Audio::LoopingAudioStream(stream, 0);
 		}
 		return stream;
diff --git a/engines/director/sound.h b/engines/director/sound.h
index a16b4adff01..5e323319aba 100644
--- a/engines/director/sound.h
+++ b/engines/director/sound.h
@@ -29,6 +29,7 @@ namespace Audio {
 	class SoundHandle;
 	class PCSpeaker;
 	class RewindableAudioStream;
+	class LoopableAudioStream;
 }
 
 namespace Common {
@@ -145,7 +146,11 @@ struct SoundChannel {
 	// And we will override the sound when ever the sound is changing. thus we use a flag to indicate whether the movie is changed.
 	bool movieChanged;
 
-	SoundChannel(): handle(), lastPlayedSound(SoundID()), stopOnZero(true), volume(255), fade(nullptr), puppet(SoundID()), newPuppet(false), movieChanged(false) {}
+	// Pointer for keeping track of a looping sound, used to signal
+	// a stop at the end of a loop.
+	Audio::LoopableAudioStream *loopPtr;
+
+	SoundChannel(): handle(), lastPlayedSound(SoundID()), stopOnZero(true), volume(255), fade(nullptr), puppet(SoundID()), newPuppet(false), movieChanged(false), loopPtr(nullptr) {}
 };
 
 class DirectorSound {


Commit: 6654d1d048b0a7b5d1aa921c8910944a0c592db2
    https://github.com/scummvm/scummvm/commit/6654d1d048b0a7b5d1aa921c8910944a0c592db2
Author: Scott Percival (code at moral.net.au)
Date: 2022-08-28T10:31:00Z

Commit Message:
DIRECTOR: Fix freeing up cast members

Changed paths:
    engines/director/cast.cpp


diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index 353c653c31c..472cb7a127e 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -103,8 +103,8 @@ Cast::~Cast() {
 	if (_loadedCast)
 		for (Common::HashMap<int, CastMember *>::iterator it = _loadedCast->begin(); it != _loadedCast->end(); ++it)
 			if (it->_value) {
-				it->_value = nullptr;
 				delete it->_value;
+				it->_value = nullptr;
 			}
 
 	for (Common::HashMap<uint16, CastMemberInfo *>::iterator it = _castsInfo.begin(); it != _castsInfo.end(); ++it)




More information about the Scummvm-git-logs mailing list