Skip to content

Commit 5f83cd5

Browse files
authored
Merge pull request #534 from scratchcpp/sound_clone
Fix #411: Implement sound cloning
2 parents 9ec931a + 04baedf commit 5f83cd5

16 files changed

+180
-72
lines changed

include/scratchcpp/sound.h

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class LIBSCRATCHCPP_EXPORT Sound : public Asset
3232

3333
virtual bool isPlaying();
3434

35+
std::shared_ptr<Sound> clone() const;
36+
3537
protected:
3638
void processData(unsigned int size, void *data) override;
3739

src/audio/iaudioplayer.h

+5
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@ class IAudioPlayer
1111
virtual ~IAudioPlayer() { }
1212

1313
virtual bool load(unsigned int size, const void *data, unsigned long sampleRate) = 0;
14+
virtual bool loadCopy(IAudioPlayer *player) = 0;
15+
16+
virtual float volume() const = 0;
1417
virtual void setVolume(float volume) = 0;
1518

19+
virtual bool isLoaded() const = 0;
20+
1621
virtual void start() = 0;
1722
virtual void stop() = 0;
1823

src/audio/internal/audioplayer.cpp

+34-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: Apache-2.0
22

33
#include <iostream>
4+
#include <cassert>
45

56
#include "audioplayer.h"
67
#include "audioengine.h"
@@ -26,7 +27,7 @@ AudioPlayer::~AudioPlayer()
2627

2728
bool AudioPlayer::load(unsigned int size, const void *data, unsigned long sampleRate)
2829
{
29-
if (!AudioEngine::initialized())
30+
if (!AudioEngine::initialized() || m_loaded)
3031
return false;
3132

3233
ma_engine *engine = AudioEngine::engine();
@@ -55,6 +56,33 @@ bool AudioPlayer::load(unsigned int size, const void *data, unsigned long sample
5556
return true;
5657
}
5758

59+
bool AudioPlayer::loadCopy(IAudioPlayer *player)
60+
{
61+
assert(player && dynamic_cast<AudioPlayer *>(player));
62+
63+
if (!AudioEngine::initialized() || !player || !player->isLoaded())
64+
return false;
65+
66+
ma_engine *engine = AudioEngine::engine();
67+
68+
AudioPlayer *playerPtr = static_cast<AudioPlayer *>(player);
69+
ma_result initResult = ma_sound_init_from_data_source(engine, playerPtr->m_decoder, MA_SOUND_FLAG_DECODE, NULL, m_sound);
70+
71+
if (initResult != MA_SUCCESS) {
72+
std::cerr << "Failed to init sound copy." << std::endl;
73+
return false;
74+
}
75+
76+
m_loaded = true;
77+
ma_sound_set_volume(m_sound, m_volume);
78+
return true;
79+
}
80+
81+
float AudioPlayer::volume() const
82+
{
83+
return m_volume;
84+
}
85+
5886
void AudioPlayer::setVolume(float volume)
5987
{
6088
m_volume = volume;
@@ -65,6 +93,11 @@ void AudioPlayer::setVolume(float volume)
6593
ma_sound_set_volume(m_sound, volume);
6694
}
6795

96+
bool AudioPlayer::isLoaded() const
97+
{
98+
return m_loaded;
99+
}
100+
68101
void AudioPlayer::start()
69102
{
70103
if (!m_loaded)

src/audio/internal/audioplayer.h

+5
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@ class AudioPlayer : public IAudioPlayer
1717
~AudioPlayer();
1818

1919
bool load(unsigned int size, const void *data, unsigned long sampleRate) override;
20+
bool loadCopy(IAudioPlayer *player) override;
21+
22+
float volume() const override;
2023
void setVolume(float volume) override;
2124

25+
bool isLoaded() const override;
26+
2227
void start() override;
2328
void stop() override;
2429

src/audio/internal/audioplayerstub.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,24 @@ bool AudioPlayerStub::load(unsigned int size, const void *data, unsigned long sa
1313
return true;
1414
}
1515

16+
bool AudioPlayerStub::loadCopy(IAudioPlayer *player)
17+
{
18+
return true;
19+
}
20+
21+
float AudioPlayerStub::volume() const
22+
{
23+
return m_volume;
24+
}
25+
1626
void AudioPlayerStub::setVolume(float volume)
1727
{
28+
m_volume = volume;
29+
}
30+
31+
bool AudioPlayerStub::isLoaded() const
32+
{
33+
return true;
1834
}
1935

2036
void AudioPlayerStub::start()

src/audio/internal/audioplayerstub.h

+8
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,20 @@ class AudioPlayerStub : public IAudioPlayer
1313
AudioPlayerStub();
1414

1515
bool load(unsigned int size, const void *data, unsigned long sampleRate) override;
16+
bool loadCopy(IAudioPlayer *player) override;
17+
18+
float volume() const override;
1619
void setVolume(float volume) override;
1720

21+
bool isLoaded() const override;
22+
1823
void start() override;
1924
void stop() override;
2025

2126
bool isPlaying() const override;
27+
28+
private:
29+
float m_volume = 1;
2230
};
2331

2432
} // namespace libscratchcpp

src/scratch/sound.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,24 @@ bool Sound::isPlaying()
6262
return impl->player->isPlaying();
6363
}
6464

65+
/*! Returns an independent copy of the sound which is valid for as long as the original sound exists. */
66+
std::shared_ptr<Sound> Sound::clone() const
67+
{
68+
auto sound = std::make_shared<Sound>(name(), id(), dataFormat());
69+
sound->setRate(rate());
70+
sound->setSampleCount(sampleCount());
71+
sound->impl->player->loadCopy(impl->player.get());
72+
sound->impl->player->setVolume(impl->player->volume());
73+
sound->setData(dataSize(), const_cast<void *>(data()));
74+
75+
return sound;
76+
}
77+
6578
void Sound::processData(unsigned int size, void *data)
6679
{
80+
if (impl->player->isLoaded())
81+
return;
82+
6783
if (!impl->player->load(size, data, impl->rate))
6884
std::cerr << "Failed to load sound " << name() << std::endl;
6985
}

src/scratch/sprite.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <scratchcpp/variable.h>
77
#include <scratchcpp/list.h>
88
#include <scratchcpp/costume.h>
9+
#include <scratchcpp/sound.h>
910
#include <scratchcpp/rect.h>
1011
#include <cassert>
1112
#include <iostream>
@@ -64,6 +65,11 @@ std::shared_ptr<Sprite> Sprite::clone()
6465
for (auto list : l)
6566
clone->addList(list->clone());
6667

68+
const auto &sounds = this->sounds();
69+
70+
for (auto sound : sounds)
71+
clone->addSound(sound->clone());
72+
6773
clone->setCostumeIndex(costumeIndex());
6874
clone->setLayerOrder(layerOrder());
6975
clone->setVolume(volume());

src/scratch/target.cpp

-12
Original file line numberDiff line numberDiff line change
@@ -331,18 +331,12 @@ int Target::findCostume(const std::string &costumeName) const
331331
/*! Returns the list of sounds. */
332332
const std::vector<std::shared_ptr<Sound>> &Target::sounds() const
333333
{
334-
if (Target *source = dataSource())
335-
return source->sounds();
336-
337334
return impl->sounds;
338335
}
339336

340337
/*! Adds a sound and returns its index. */
341338
int Target::addSound(std::shared_ptr<Sound> sound)
342339
{
343-
if (Target *source = dataSource())
344-
return source->addSound(sound);
345-
346340
auto it = std::find(impl->sounds.begin(), impl->sounds.end(), sound);
347341

348342
if (it != impl->sounds.end())
@@ -360,9 +354,6 @@ int Target::addSound(std::shared_ptr<Sound> sound)
360354
/*! Returns the sound at index. */
361355
std::shared_ptr<Sound> Target::soundAt(int index) const
362356
{
363-
if (Target *source = dataSource())
364-
return source->soundAt(index);
365-
366357
if (index < 0 || index >= impl->sounds.size())
367358
return nullptr;
368359

@@ -372,9 +363,6 @@ std::shared_ptr<Sound> Target::soundAt(int index) const
372363
/*! Returns the index of the sound with the given name. */
373364
int Target::findSound(const std::string &soundName) const
374365
{
375-
if (Target *source = dataSource())
376-
return source->findSound(soundName);
377-
378366
auto it = std::find_if(impl->sounds.begin(), impl->sounds.end(), [soundName](std::shared_ptr<Sound> sound) { return sound->name() == soundName; });
379367

380368
if (it == impl->sounds.end())

test/assets/sound_test.cpp

+34-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,12 @@ TEST_F(SoundTest, ProcessData)
6060
const char *data = "abc";
6161
void *dataPtr = const_cast<void *>(static_cast<const void *>(data));
6262

63-
EXPECT_CALL(*m_player, load(3, dataPtr, 44100));
63+
EXPECT_CALL(*m_player, isLoaded()).WillOnce(Return(false));
64+
EXPECT_CALL(*m_player, load(3, dataPtr, 44100)).WillOnce(Return(true));
65+
sound.setData(3, dataPtr);
66+
67+
EXPECT_CALL(*m_player, isLoaded()).WillOnce(Return(true));
68+
EXPECT_CALL(*m_player, load).Times(0);
6469
sound.setData(3, dataPtr);
6570
}
6671

@@ -100,3 +105,31 @@ TEST_F(SoundTest, IsPlaying)
100105

101106
SoundPrivate::audioOutput = nullptr;
102107
}
108+
109+
TEST_F(SoundTest, Clone)
110+
{
111+
Sound sound("sound1", "a", "wav");
112+
sound.setRate(44100);
113+
sound.setSampleCount(10000);
114+
115+
const char *data = "abc";
116+
void *dataPtr = const_cast<void *>(static_cast<const void *>(data));
117+
118+
EXPECT_CALL(*m_player, isLoaded()).WillOnce(Return(false));
119+
EXPECT_CALL(*m_player, load(3, dataPtr, 44100)).WillOnce(Return(true));
120+
sound.setData(3, dataPtr);
121+
122+
auto clonePlayer = std::make_shared<AudioPlayerMock>();
123+
EXPECT_CALL(m_playerFactory, createAudioPlayer()).WillOnce(Return(clonePlayer));
124+
EXPECT_CALL(*clonePlayer, loadCopy(m_player.get())).WillOnce(Return(true));
125+
EXPECT_CALL(*m_player, volume()).WillOnce(Return(0.45));
126+
EXPECT_CALL(*clonePlayer, setVolume(0.45));
127+
EXPECT_CALL(*clonePlayer, isLoaded()).WillOnce(Return(true));
128+
auto clone = sound.clone();
129+
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());
135+
}

test/audio/CMakeLists.txt

+17
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,20 @@ gtest_discover_tests(audioinput_test)
3333
if (LIBSCRATCHCPP_AUDIO_SUPPORT)
3434
target_compile_definitions(audioinput_test PRIVATE LIBSCRATCHCPP_AUDIO_SUPPORT)
3535
endif()
36+
37+
if(LIBSCRATCHCPP_AUDIO_SUPPORT)
38+
# audioplayer_test
39+
add_executable(
40+
audioplayer_test
41+
audioplayer_test.cpp
42+
)
43+
44+
target_link_libraries(
45+
audioplayer_test
46+
GTest::gtest_main
47+
scratchcpp
48+
)
49+
50+
gtest_discover_tests(audioplayer_test)
51+
target_compile_definitions(audioplayer_test PRIVATE LIBSCRATCHCPP_AUDIO_SUPPORT)
52+
endif()

test/audio/audioplayer_test.cpp

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include <audio/internal/audioplayer.h>
2+
3+
#include "../common.h"
4+
5+
using namespace libscratchcpp;
6+
7+
TEST(AudioPlayerTest, Volume)
8+
{
9+
AudioPlayer player;
10+
ASSERT_EQ(player.volume(), 1);
11+
12+
player.setVolume(0.86f);
13+
ASSERT_EQ(player.volume(), 0.86f);
14+
}

test/engine/engine_test.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,9 @@ TEST(EngineTest, StopSounds)
261261
auto player3 = std::make_shared<AudioPlayerMock>();
262262
SoundPrivate::audioOutput = &factory;
263263
EXPECT_CALL(factory, createAudioPlayer()).WillOnce(Return(player1)).WillOnce(Return(player2)).WillOnce(Return(player3));
264+
EXPECT_CALL(*player1, isLoaded).WillOnce(Return(false));
265+
EXPECT_CALL(*player2, isLoaded).WillOnce(Return(false));
266+
EXPECT_CALL(*player3, isLoaded).WillOnce(Return(false));
264267
EXPECT_CALL(*player1, load).WillOnce(Return(true));
265268
EXPECT_CALL(*player2, load).WillOnce(Return(true));
266269
EXPECT_CALL(*player3, load).WillOnce(Return(true));

test/mocks/audioplayermock.h

+5
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ class AudioPlayerMock : public IAudioPlayer
99
{
1010
public:
1111
MOCK_METHOD(bool, load, (unsigned int, const void *, unsigned long), (override));
12+
MOCK_METHOD(bool, loadCopy, (IAudioPlayer *), (override));
13+
14+
MOCK_METHOD(float, volume, (), (const, override));
1215
MOCK_METHOD(void, setVolume, (float), (override));
1316

17+
MOCK_METHOD(bool, isLoaded, (), (const, override));
18+
1419
MOCK_METHOD(void, start, (), (override));
1520
MOCK_METHOD(void, stop, (), (override));
1621

test/scratch_classes/sprite_test.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <scratchcpp/variable.h>
44
#include <scratchcpp/list.h>
55
#include <scratchcpp/costume.h>
6+
#include <scratchcpp/sound.h>
67
#include <scratchcpp/rect.h>
78
#include <scratchcpp/project.h>
89
#include <enginemock.h>
@@ -63,6 +64,12 @@ TEST(SpriteTest, Clone)
6364
sprite.addVariable(var2);
6465
auto c1 = std::make_shared<Costume>("costume1", "", "svg");
6566
sprite.addCostume(c1);
67+
auto s1 = std::make_shared<Sound>("sound1", "a", "wav");
68+
auto s2 = std::make_shared<Sound>("sound2", "b", "wav");
69+
auto s3 = std::make_shared<Sound>("sound3", "c", "wav");
70+
sprite.addSound(s1);
71+
sprite.addSound(s2);
72+
sprite.addSound(s3);
6673

6774
auto list1 = std::make_shared<List>("c", "list1");
6875
list1->push_back("item1");
@@ -111,6 +118,14 @@ TEST(SpriteTest, Clone)
111118
ASSERT_EQ(*clone->listAt(1), std::deque<Value>({ "test" }));
112119
ASSERT_EQ(clone->listAt(1)->target(), clone);
113120

121+
ASSERT_EQ(clone->sounds().size(), 3);
122+
ASSERT_EQ(clone->soundAt(0)->id(), "a");
123+
ASSERT_EQ(clone->soundAt(0)->name(), "sound1");
124+
ASSERT_EQ(clone->soundAt(0)->dataFormat(), "wav");
125+
ASSERT_EQ(clone->soundAt(2)->id(), "c");
126+
ASSERT_EQ(clone->soundAt(2)->name(), "sound3");
127+
ASSERT_EQ(clone->soundAt(2)->dataFormat(), "wav");
128+
114129
ASSERT_EQ(clone->costumeIndex(), 1);
115130
ASSERT_EQ(clone->layerOrder(), 5);
116131
ASSERT_EQ(clone->volume(), 50);

0 commit comments

Comments
 (0)