Skip to content

Commit e9a04da

Browse files
authored
Merge pull request #539 from scratchcpp/fix_sound_clone_playback
Fix #538: Fix playback of sound clones
2 parents d41aad0 + 4ec3f1f commit e9a04da

File tree

9 files changed

+128
-22
lines changed

9 files changed

+128
-22
lines changed

include/scratchcpp/sound.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
namespace libscratchcpp
1010
{
1111

12+
class Target;
1213
class SoundPrivate;
1314

1415
/*! \brief The Sound class represents a Scratch sound. */
@@ -32,12 +33,17 @@ class LIBSCRATCHCPP_EXPORT Sound : public Asset
3233

3334
virtual bool isPlaying();
3435

36+
Target *target() const;
37+
void setTarget(Target *target);
38+
3539
std::shared_ptr<Sound> clone() const;
3640

3741
protected:
3842
void processData(unsigned int size, void *data) override;
3943

4044
private:
45+
void stopCloneSounds();
46+
4147
spimpl::unique_impl_ptr<SoundPrivate> impl;
4248
};
4349

src/engine/internal/engine.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -459,14 +459,11 @@ void Engine::deinitClone(std::shared_ptr<Sprite> clone)
459459

460460
void Engine::stopSounds()
461461
{
462-
// TODO: Loop through clones
463462
for (auto target : m_targets) {
464463
const auto &sounds = target->sounds();
465464

466-
for (auto sound : sounds) {
467-
if (sound->isPlaying())
468-
sound->stop();
469-
}
465+
for (auto sound : sounds)
466+
sound->stop();
470467
}
471468
}
472469

src/scratch/sound.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: Apache-2.0
22

33
#include <scratchcpp/sound.h>
4+
#include <scratchcpp/sprite.h>
45
#include <iostream>
56

67
#include "sound_p.h"
@@ -47,12 +48,16 @@ void Sound::setVolume(double volume)
4748
/*! Starts the playback of the sound. */
4849
void Sound::start()
4950
{
51+
// Stop sounds in clones (#538)
52+
stopCloneSounds();
5053
impl->player->start();
5154
}
5255

5356
/*! Stops the playback of the sound. */
5457
void Sound::stop()
5558
{
59+
// Stop sounds in clones (#538)
60+
stopCloneSounds();
5661
impl->player->stop();
5762
}
5863

@@ -62,6 +67,18 @@ bool Sound::isPlaying()
6267
return impl->player->isPlaying();
6368
}
6469

70+
/*! Returns the sprite or stage this variable belongs to. */
71+
Target *Sound::target() const
72+
{
73+
return impl->target;
74+
}
75+
76+
/*! Sets the sprite or stage this variable belongs to. */
77+
void Sound::setTarget(Target *target)
78+
{
79+
impl->target = target;
80+
}
81+
6582
/*! Returns an independent copy of the sound which is valid for as long as the original sound exists. */
6683
std::shared_ptr<Sound> Sound::clone() const
6784
{
@@ -83,3 +100,30 @@ void Sound::processData(unsigned int size, void *data)
83100
if (!impl->player->load(size, data, impl->rate))
84101
std::cerr << "Failed to load sound " << name() << std::endl;
85102
}
103+
104+
void Sound::stopCloneSounds()
105+
{
106+
if (impl->target && !impl->target->isStage()) {
107+
Sprite *sprite = static_cast<Sprite *>(impl->target);
108+
109+
if (sprite->isClone())
110+
sprite = sprite->cloneSprite();
111+
112+
// Stop the sound in the sprite (clone root)
113+
auto sound = sprite->soundAt(sprite->findSound(name()));
114+
115+
if (sound && sound.get() != this)
116+
sound->impl->player->stop();
117+
118+
// Stop the sound in clones
119+
const auto &clones = sprite->clones();
120+
121+
for (auto clone : clones) {
122+
auto sound = clone->soundAt(clone->findSound(name()));
123+
assert(sound);
124+
125+
if (sound && sound.get() != this)
126+
sound->impl->player->stop();
127+
}
128+
}
129+
}

src/scratch/sound_p.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
namespace libscratchcpp
1111
{
1212

13+
class Target;
14+
1315
struct SoundPrivate
1416
{
1517
SoundPrivate();
@@ -19,6 +21,7 @@ struct SoundPrivate
1921
int sampleCount = 0;
2022
static IAudioOutput *audioOutput;
2123
std::shared_ptr<IAudioPlayer> player = nullptr;
24+
Target *target = nullptr;
2225
};
2326

2427
} // namespace libscratchcpp

src/scratch/target.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,10 @@ int Target::addSound(std::shared_ptr<Sound> sound)
344344

345345
assert(sound);
346346

347-
if (sound)
347+
if (sound) {
348+
sound->setTarget(this);
348349
sound->setVolume(impl->volume);
350+
}
349351

350352
impl->sounds.push_back(sound);
351353
return impl->sounds.size() - 1;

test/assets/sound_test.cpp

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#include <scratchcpp/sound.h>
2+
#include <scratchcpp/sprite.h>
23
#include <scratch/sound_p.h>
34
#include <audiooutputmock.h>
45
#include <audioplayermock.h>
6+
#include <enginemock.h>
57

68
#include "../common.h"
79

@@ -106,30 +108,78 @@ TEST_F(SoundTest, IsPlaying)
106108
SoundPrivate::audioOutput = nullptr;
107109
}
108110

109-
TEST_F(SoundTest, Clone)
111+
TEST_F(SoundTest, Target)
110112
{
111113
Sound sound("sound1", "a", "wav");
112-
sound.setRate(44100);
113-
sound.setSampleCount(10000);
114+
ASSERT_EQ(sound.target(), nullptr);
115+
116+
Target target;
117+
sound.setTarget(&target);
118+
ASSERT_EQ(sound.target(), &target);
119+
}
120+
121+
TEST_F(SoundTest, Clone)
122+
{
123+
auto sound = std::make_shared<Sound>("sound1", "a", "wav");
124+
sound->setRate(44100);
125+
sound->setSampleCount(10000);
114126

115127
const char *data = "abc";
116128
void *dataPtr = const_cast<void *>(static_cast<const void *>(data));
117129

118130
EXPECT_CALL(*m_player, isLoaded()).WillOnce(Return(false));
119131
EXPECT_CALL(*m_player, load(3, dataPtr, 44100)).WillOnce(Return(true));
120-
sound.setData(3, dataPtr);
132+
sound->setData(3, dataPtr);
121133

122134
auto clonePlayer = std::make_shared<AudioPlayerMock>();
123135
EXPECT_CALL(m_playerFactory, createAudioPlayer()).WillOnce(Return(clonePlayer));
124136
EXPECT_CALL(*clonePlayer, loadCopy(m_player.get())).WillOnce(Return(true));
125137
EXPECT_CALL(*m_player, volume()).WillOnce(Return(0.45));
126138
EXPECT_CALL(*clonePlayer, setVolume(0.45));
127139
EXPECT_CALL(*clonePlayer, isLoaded()).WillOnce(Return(true));
128-
auto clone = sound.clone();
140+
auto clone = sound->clone();
129141
ASSERT_TRUE(clone);
130-
ASSERT_EQ(clone->name(), sound.name());
131-
ASSERT_EQ(clone->id(), sound.id());
132-
ASSERT_EQ(clone->dataFormat(), sound.dataFormat());
133-
ASSERT_EQ(clone->rate(), sound.rate());
134-
ASSERT_EQ(clone->sampleCount(), sound.sampleCount());
142+
ASSERT_EQ(clone->name(), sound->name());
143+
ASSERT_EQ(clone->id(), sound->id());
144+
ASSERT_EQ(clone->dataFormat(), sound->dataFormat());
145+
ASSERT_EQ(clone->rate(), sound->rate());
146+
ASSERT_EQ(clone->sampleCount(), sound->sampleCount());
147+
148+
// Stopping/starting the sound should stop its clones
149+
auto anotherPlayer = std::make_shared<AudioPlayerMock>();
150+
EXPECT_CALL(m_playerFactory, createAudioPlayer()).WillOnce(Return(anotherPlayer));
151+
auto another = std::make_shared<Sound>("another", "c", "mp3");
152+
Sprite sprite;
153+
EngineMock engine;
154+
sprite.setEngine(&engine);
155+
156+
EXPECT_CALL(engine, cloneLimit()).WillOnce(Return(-1));
157+
EXPECT_CALL(engine, initClone);
158+
EXPECT_CALL(engine, requestRedraw);
159+
EXPECT_CALL(engine, moveSpriteBehindOther);
160+
auto spriteClone = sprite.clone();
161+
162+
EXPECT_CALL(*anotherPlayer, setVolume).Times(2);
163+
EXPECT_CALL(*m_player, setVolume);
164+
EXPECT_CALL(*clonePlayer, setVolume);
165+
sprite.addSound(another);
166+
sprite.addSound(sound);
167+
spriteClone->addSound(another);
168+
spriteClone->addSound(clone);
169+
170+
EXPECT_CALL(*m_player, stop());
171+
EXPECT_CALL(*clonePlayer, stop());
172+
sound->stop();
173+
174+
EXPECT_CALL(*m_player, stop());
175+
EXPECT_CALL(*clonePlayer, stop());
176+
clone->stop();
177+
178+
EXPECT_CALL(*m_player, start());
179+
EXPECT_CALL(*clonePlayer, stop());
180+
sound->start();
181+
182+
EXPECT_CALL(*m_player, stop());
183+
EXPECT_CALL(*clonePlayer, start());
184+
clone->start();
135185
}

test/blocks/sound_blocks_test.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
#include <scratchcpp/block.h>
33
#include <scratchcpp/input.h>
44
#include <scratchcpp/field.h>
5-
#include <scratchcpp/target.h>
65
#include <scratchcpp/sound.h>
76
#include <scratch/sound_p.h>
87
#include <enginemock.h>
98
#include <audiooutputmock.h>
109
#include <audioplayermock.h>
10+
#include <targetmock.h>
1111

1212
#include "../common.h"
1313
#include "blocks/soundblocks.h"
@@ -212,7 +212,8 @@ TEST_F(SoundBlocksTest, PlayImpl)
212212
EXPECT_CALL(*player1, setVolume);
213213
EXPECT_CALL(*player2, setVolume);
214214
EXPECT_CALL(*player3, setVolume);
215-
Target target;
215+
TargetMock target;
216+
EXPECT_CALL(target, isStage()).WillRepeatedly(Return(true));
216217
target.addSound(std::make_shared<Sound>("some sound", "", ""));
217218
target.addSound(std::make_shared<Sound>("test", "", ""));
218219
target.addSound(std::make_shared<Sound>("another sound", "", ""));
@@ -412,7 +413,8 @@ TEST_F(SoundBlocksTest, PlayUntilDoneImpl)
412413
EXPECT_CALL(*player1, setVolume);
413414
EXPECT_CALL(*player2, setVolume);
414415
EXPECT_CALL(*player3, setVolume);
415-
Target target;
416+
TargetMock target;
417+
EXPECT_CALL(target, isStage()).WillRepeatedly(Return(true));
416418
target.addSound(std::make_shared<Sound>("some sound", "", ""));
417419
target.addSound(std::make_shared<Sound>("test", "", ""));
418420
target.addSound(std::make_shared<Sound>("another sound", "", ""));

test/engine/engine_test.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,7 @@ TEST(EngineTest, StopSounds)
275275

276276
auto engine = p.engine();
277277

278-
EXPECT_CALL(*player1, isPlaying()).WillOnce(Return(false));
279-
EXPECT_CALL(*player2, isPlaying()).WillOnce(Return(true));
280-
EXPECT_CALL(*player3, isPlaying()).WillOnce(Return(true));
278+
EXPECT_CALL(*player1, stop());
281279
EXPECT_CALL(*player2, stop());
282280
EXPECT_CALL(*player3, stop());
283281
engine->stopSounds();

test/scratch_classes/target_test.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,10 @@ TEST(TargetTest, Sounds)
442442
ASSERT_EQ(target.findSound("sound2"), 1);
443443
ASSERT_EQ(target.findSound("sound3"), 2);
444444

445+
ASSERT_EQ(s1->target(), &target);
446+
ASSERT_EQ(s2->target(), &target);
447+
ASSERT_EQ(s3->target(), &target);
448+
445449
SoundPrivate::audioOutput = nullptr;
446450
}
447451

0 commit comments

Comments
 (0)