From 7d4f11b94ad32ff5474fe7a5a238cd83d1e4a59d Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 15 Apr 2025 12:20:37 +0200 Subject: [PATCH 01/11] Implement sound_play block --- src/blocks/soundblocks.cpp | 64 ++++++ src/blocks/soundblocks.h | 5 + test/blocks/sound_blocks_test.cpp | 323 +++++++++++++++++++++++++++++- 3 files changed, 391 insertions(+), 1 deletion(-) diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index df7611ab0..b01d2a60c 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -1,5 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include + #include "soundblocks.h" using namespace libscratchcpp; @@ -21,4 +26,63 @@ Rgb SoundBlocks::color() const void SoundBlocks::registerBlocks(IEngine *engine) { + engine->addCompileFunction(this, "sound_play", &compilePlay); +} + +CompilerValue *SoundBlocks::compilePlay(Compiler *compiler) +{ + auto sound = compiler->addInput("SOUND_MENU"); + compiler->addTargetFunctionCall("sound_play", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { sound }); + return nullptr; +} + +int sound_wrap_clamp_index(Target *target, int index) +{ + const long soundCount = target->sounds().size(); + + if (index < 0) + return (soundCount + index % (-soundCount)) % soundCount; + else if (index >= soundCount) + return index % soundCount; + else + return index; +} + +int sound_get_index(Target *target, const ValueData *sound) +{ + if (!value_isString(sound)) { + // Numbers should be treated as sound indices + if (value_isNaN(sound) || value_isInfinity(sound) || value_isNegativeInfinity(sound)) + return -1; + else + return sound_wrap_clamp_index(target, value_toLong(sound) - 1); + } else { + // Strings should be treated as sound names, where possible + // TODO: Use UTF-16 in Target + // StringPtr *nameStr = value_toStringPtr(sound); + std::string nameStr; + value_toString(sound, &nameStr); + const int soundIndex = target->findSound(nameStr); + + auto it = std::find_if(nameStr.begin(), nameStr.end(), [](char c) { return !std::isspace(c); }); + bool isWhiteSpace = (it == nameStr.end()); + + if (soundIndex != -1) { + return soundIndex; + // Try to cast the string to a number (and treat it as a costume index) + // Pure whitespace should not be treated as a number + } else if (value_isValidNumber(sound) && !isWhiteSpace) + return sound_wrap_clamp_index(target, value_toLong(sound) - 1); + } + + return -1; +} + +extern "C" void sound_play(Target *target, const ValueData *soundName) +{ + int index = sound_get_index(target, soundName); + auto sound = target->soundAt(index); + + if (sound) + sound->start(); } diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h index f605c7b64..a005f8428 100644 --- a/src/blocks/soundblocks.h +++ b/src/blocks/soundblocks.h @@ -7,6 +7,8 @@ namespace libscratchcpp { +class IAudioOutput; + class SoundBlocks : public IExtension { public: @@ -15,6 +17,9 @@ class SoundBlocks : public IExtension Rgb color() const override; void registerBlocks(IEngine *engine) override; + + private: + static CompilerValue *compilePlay(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/sound_blocks_test.cpp b/test/blocks/sound_blocks_test.cpp index 06406a519..955924041 100644 --- a/test/blocks/sound_blocks_test.cpp +++ b/test/blocks/sound_blocks_test.cpp @@ -1,15 +1,336 @@ +#include +#include +#include +#include +#include #include +#include +#include #include "../common.h" #include "blocks/soundblocks.h" using namespace libscratchcpp; +using namespace libscratchcpp::test; + +using ::testing::Return; class SoundBlocksTest : public testing::Test { public: - void SetUp() override { m_extension = std::make_unique(); } + void SetUp() override + { + m_extension = std::make_unique(); + m_engine = m_project.engine().get(); + m_extension->registerBlocks(m_engine); + + SoundPrivate::audioOutput = &m_outputMock; + } + + void TearDown() override { SoundPrivate::audioOutput = nullptr; } std::unique_ptr m_extension; + Project m_project; + IEngine *m_engine = nullptr; EngineMock m_engineMock; + AudioOutputMock m_outputMock; }; + +TEST_F(SoundBlocksTest, Play_NoSounds) +{ + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("sound_play"); + builder.addDropdownInput("SOUND_MENU", "pop"); + builder.build(); + builder.run(); +} + +TEST_F(SoundBlocksTest, Play_InvalidSoundName) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("test", "b", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_play"); + builder.addDropdownInput("SOUND_MENU", "meow"); + builder.build(); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start).Times(0); + builder.run(); +} + +TEST_F(SoundBlocksTest, Play_SoundName) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("meow", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_play"); + builder.addDropdownInput("SOUND_MENU", "meow"); + builder.build(); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start()); + EXPECT_CALL(*player3, start).Times(0); + builder.run(); +} + +TEST_F(SoundBlocksTest, Play_NumberIndex) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("3", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_play"); + builder.addValueInput("SOUND_MENU", 3); + builder.build(); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start).Times(0); + EXPECT_CALL(*player3, start()); + builder.run(); +} + +TEST_F(SoundBlocksTest, Play_PositiveOutOfRangeNumberIndex) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("4", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_play"); + builder.addValueInput("SOUND_MENU", 4); + builder.build(); + + EXPECT_CALL(*player1, start()); + EXPECT_CALL(*player2, start).Times(0); + EXPECT_CALL(*player3, start).Times(0); + builder.run(); +} + +TEST_F(SoundBlocksTest, Play_NegativeOutOfRangeNumberIndex) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("-4", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("meow", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_play"); + builder.addValueInput("SOUND_MENU", -4); + builder.build(); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start()); + EXPECT_CALL(*player3, start).Times(0); + builder.run(); +} + +TEST_F(SoundBlocksTest, Play_InvalidNumberIndex) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("meow", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_play"); + builder.addValueInput("SOUND_MENU", std::numeric_limits::quiet_NaN()); + builder.addBlock("sound_play"); + builder.addValueInput("SOUND_MENU", std::numeric_limits::infinity()); + builder.addBlock("sound_play"); + builder.addValueInput("SOUND_MENU", -std::numeric_limits::infinity()); + builder.build(); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start).Times(0); + EXPECT_CALL(*player3, start).Times(0); + builder.run(); +} + +TEST_F(SoundBlocksTest, Play_StringIndex) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("meow", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_play"); + builder.addValueInput("SOUND_MENU", "3"); + builder.build(); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start).Times(0); + EXPECT_CALL(*player3, start()); + builder.run(); +} + +TEST_F(SoundBlocksTest, Play_StringIndexExists) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("3", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_play"); + builder.addValueInput("SOUND_MENU", "3"); + builder.build(); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start()); + EXPECT_CALL(*player3, start).Times(0); + builder.run(); +} + +TEST_F(SoundBlocksTest, Play_OutOfRangeStringIndex) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("3", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_play"); + builder.addValueInput("SOUND_MENU", "+7.0"); + builder.build(); + + EXPECT_CALL(*player1, start()); + EXPECT_CALL(*player2, start).Times(0); + EXPECT_CALL(*player3, start).Times(0); + builder.run(); +} + +TEST_F(SoundBlocksTest, Play_WhitespaceString) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("test", "a", "mp3")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("pop", "b", "wav")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_play"); + builder.addValueInput("SOUND_MENU", ""); + builder.addBlock("sound_play"); + builder.addValueInput("SOUND_MENU", " "); + builder.build(); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start).Times(0); + builder.run(); +} + +TEST_F(SoundBlocksTest, Play_Stage) +{ + auto stage = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + stage->addSound(std::make_shared("test", "a", "mp3")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + stage->addSound(std::make_shared("pop", "b", "wav")); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("sound_play"); + builder.addDropdownInput("SOUND_MENU", "test"); + builder.build(); + + EXPECT_CALL(*player1, start()); + EXPECT_CALL(*player2, start).Times(0); + builder.run(); +} From 5635768e7e6f0fc258eb16d265d39a1f2236713d Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 15 Apr 2025 19:39:04 +0200 Subject: [PATCH 02/11] Implement sound_playuntildone block --- src/blocks/soundblocks.cpp | 33 ++- src/blocks/soundblocks.h | 1 + test/blocks/sound_blocks_test.cpp | 430 ++++++++++++++++++++++++++++++ 3 files changed, 461 insertions(+), 3 deletions(-) diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index b01d2a60c..d64a13ab5 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -27,12 +27,27 @@ Rgb SoundBlocks::color() const void SoundBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "sound_play", &compilePlay); + engine->addCompileFunction(this, "sound_playuntildone", &compilePlayUntilDone); } CompilerValue *SoundBlocks::compilePlay(Compiler *compiler) { auto sound = compiler->addInput("SOUND_MENU"); - compiler->addTargetFunctionCall("sound_play", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { sound }); + compiler->addTargetFunctionCall("sound_play", Compiler::StaticType::Pointer, { Compiler::StaticType::Unknown }, { sound }); + return nullptr; +} + +CompilerValue *SoundBlocks::compilePlayUntilDone(Compiler *compiler) +{ + auto sound = compiler->addInput("SOUND_MENU"); + auto soundPtr = compiler->addTargetFunctionCall("sound_play", Compiler::StaticType::Pointer, { Compiler::StaticType::Unknown }, { sound }); + + compiler->beginLoopCondition(); + auto numType = Compiler::StaticType::Number; + auto playing = compiler->addFunctionCall("sound_is_playing", Compiler::StaticType::Bool, { Compiler::StaticType::Pointer }, { soundPtr }); + compiler->beginWhileLoop(playing); + compiler->endLoop(); + return nullptr; } @@ -78,11 +93,23 @@ int sound_get_index(Target *target, const ValueData *sound) return -1; } -extern "C" void sound_play(Target *target, const ValueData *soundName) +extern "C" Sound *sound_play(Target *target, const ValueData *soundName) { int index = sound_get_index(target, soundName); auto sound = target->soundAt(index); - if (sound) + if (sound) { sound->start(); + return sound.get(); + } + + return nullptr; +} + +extern "C" bool sound_is_playing(Sound *sound) +{ + if (!sound) + return false; + + return sound->isPlaying(); } diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h index a005f8428..2837ed51e 100644 --- a/src/blocks/soundblocks.h +++ b/src/blocks/soundblocks.h @@ -20,6 +20,7 @@ class SoundBlocks : public IExtension private: static CompilerValue *compilePlay(Compiler *compiler); + static CompilerValue *compilePlayUntilDone(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/sound_blocks_test.cpp b/test/blocks/sound_blocks_test.cpp index 955924041..a706e3d7c 100644 --- a/test/blocks/sound_blocks_test.cpp +++ b/test/blocks/sound_blocks_test.cpp @@ -1,6 +1,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -334,3 +337,430 @@ TEST_F(SoundBlocksTest, Play_Stage) EXPECT_CALL(*player2, start).Times(0); builder.run(); } + +TEST_F(SoundBlocksTest, PlayUntilDone_NoSounds) +{ + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("sound_playuntildone"); + builder.addDropdownInput("SOUND_MENU", "pop"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(SoundBlocksTest, PlayUntilDone_InvalidSoundName) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("test", "b", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_playuntildone"); + builder.addDropdownInput("SOUND_MENU", "meow"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start).Times(0); + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(SoundBlocksTest, PlayUntilDone_SoundName) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("meow", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_playuntildone"); + builder.addDropdownInput("SOUND_MENU", "meow"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start()); + EXPECT_CALL(*player3, start).Times(0); + EXPECT_CALL(*player2, isPlaying()).WillRepeatedly(Return(true)); + thread.run(); + ASSERT_FALSE(thread.isFinished()); + + thread.run(); + ASSERT_FALSE(thread.isFinished()); + + EXPECT_CALL(*player2, isPlaying()).WillOnce(Return(false)); + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(SoundBlocksTest, PlayUntilDone_NumberIndex) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("3", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_playuntildone"); + builder.addValueInput("SOUND_MENU", 3); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start).Times(0); + EXPECT_CALL(*player3, start()); + EXPECT_CALL(*player3, isPlaying()).WillRepeatedly(Return(true)); + thread.run(); + ASSERT_FALSE(thread.isFinished()); + + EXPECT_CALL(*player3, isPlaying()).WillOnce(Return(false)); + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(SoundBlocksTest, PlayUntilDone_PositiveOutOfRangeNumberIndex) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("4", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_playuntildone"); + builder.addValueInput("SOUND_MENU", 4); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(*player1, start()); + EXPECT_CALL(*player2, start).Times(0); + EXPECT_CALL(*player3, start).Times(0); + EXPECT_CALL(*player1, isPlaying()).WillRepeatedly(Return(true)); + thread.run(); + ASSERT_FALSE(thread.isFinished()); + + EXPECT_CALL(*player1, isPlaying()).WillOnce(Return(false)); + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(SoundBlocksTest, PlayUntilDone_NegativeOutOfRangeNumberIndex) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("-4", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("meow", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_playuntildone"); + builder.addValueInput("SOUND_MENU", -4); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start()); + EXPECT_CALL(*player3, start).Times(0); + EXPECT_CALL(*player2, isPlaying()).WillRepeatedly(Return(true)); + thread.run(); + ASSERT_FALSE(thread.isFinished()); + + EXPECT_CALL(*player2, isPlaying()).WillOnce(Return(false)); + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(SoundBlocksTest, PlayUntilDone_InvalidNumberIndex) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("meow", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_playuntildone"); + builder.addValueInput("SOUND_MENU", std::numeric_limits::quiet_NaN()); + builder.addBlock("sound_playuntildone"); + builder.addValueInput("SOUND_MENU", std::numeric_limits::infinity()); + builder.addBlock("sound_playuntildone"); + builder.addValueInput("SOUND_MENU", -std::numeric_limits::infinity()); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start).Times(0); + EXPECT_CALL(*player3, start).Times(0); + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(SoundBlocksTest, PlayUntilDone_StringIndex) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("meow", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_playuntildone"); + builder.addValueInput("SOUND_MENU", "3"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start).Times(0); + EXPECT_CALL(*player3, start()); + EXPECT_CALL(*player3, isPlaying()).WillRepeatedly(Return(true)); + thread.run(); + ASSERT_FALSE(thread.isFinished()); + + EXPECT_CALL(*player3, isPlaying()).WillOnce(Return(false)); + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(SoundBlocksTest, PlayUntilDone_StringIndexExists) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("3", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_playuntildone"); + builder.addValueInput("SOUND_MENU", "3"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start()); + EXPECT_CALL(*player3, start).Times(0); + EXPECT_CALL(*player2, isPlaying()).WillRepeatedly(Return(true)); + thread.run(); + ASSERT_FALSE(thread.isFinished()); + + EXPECT_CALL(*player2, isPlaying()).WillOnce(Return(false)); + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(SoundBlocksTest, PlayUntilDone_OutOfRangeStringIndex) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("3", "b", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_playuntildone"); + builder.addValueInput("SOUND_MENU", "+7.0"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(*player1, start()); + EXPECT_CALL(*player2, start).Times(0); + EXPECT_CALL(*player3, start).Times(0); + EXPECT_CALL(*player1, isPlaying()).WillRepeatedly(Return(true)); + thread.run(); + ASSERT_FALSE(thread.isFinished()); + + EXPECT_CALL(*player1, isPlaying()).WillOnce(Return(false)); + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(SoundBlocksTest, PlayUntilDone_WhitespaceString) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("test", "a", "mp3")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + sprite->addSound(std::make_shared("pop", "b", "wav")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_playuntildone"); + builder.addValueInput("SOUND_MENU", ""); + builder.addBlock("sound_playuntildone"); + builder.addValueInput("SOUND_MENU", " "); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start).Times(0); + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(SoundBlocksTest, PlayUntilDone_Stage) +{ + auto stage = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + stage->addSound(std::make_shared("test", "a", "mp3")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + stage->addSound(std::make_shared("pop", "b", "wav")); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("sound_playuntildone"); + builder.addDropdownInput("SOUND_MENU", "test"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(stage.get(), &m_engineMock, &script); + + EXPECT_CALL(*player1, start()); + EXPECT_CALL(*player2, start).Times(0); + EXPECT_CALL(*player1, isPlaying()).WillRepeatedly(Return(true)); + thread.run(); + ASSERT_FALSE(thread.isFinished()); + + EXPECT_CALL(*player1, isPlaying()).WillOnce(Return(false)); + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} From 28a82f757775d9e622401330fa9bdb2156a45c1b Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 15 Apr 2025 21:21:49 +0200 Subject: [PATCH 03/11] Sound: Add owner property --- include/scratchcpp/sound.h | 5 ++++- src/scratch/sound.cpp | 9 ++++++++- src/scratch/sound_p.h | 2 ++ test/assets/sound_test.cpp | 25 +++++++++++++++++++++++++ test/mocks/soundmock.h | 2 +- 5 files changed, 40 insertions(+), 3 deletions(-) diff --git a/include/scratchcpp/sound.h b/include/scratchcpp/sound.h index 2c95d32d2..c6c170821 100644 --- a/include/scratchcpp/sound.h +++ b/include/scratchcpp/sound.h @@ -9,6 +9,7 @@ namespace libscratchcpp { +class Thread; class SoundPrivate; /*! \brief The Sound class represents a Scratch sound. */ @@ -34,13 +35,15 @@ class LIBSCRATCHCPP_EXPORT Sound : public Asset virtual void setVolume(double volume); virtual void setEffect(Effect effect, double value); - virtual void start(); + virtual void start(Thread *owner = nullptr); virtual void stop(); virtual bool isPlaying() const; std::shared_ptr clone() const; + Thread *owner() const; + protected: void processData(unsigned int size, void *data) override; virtual bool isClone() const override; diff --git a/src/scratch/sound.cpp b/src/scratch/sound.cpp index cd3db865e..8e9c733e9 100644 --- a/src/scratch/sound.cpp +++ b/src/scratch/sound.cpp @@ -66,10 +66,11 @@ void Sound::setEffect(Effect effect, double value) } /*! Starts the playback of the sound. */ -void Sound::start() +void Sound::start(Thread *owner) { // Stop sounds in clones (#538) stopCloneSounds(); + impl->owner = owner; impl->player->start(); } @@ -78,6 +79,7 @@ void Sound::stop() { // Stop sounds in clones (#538) stopCloneSounds(); + impl->owner = nullptr; impl->player->stop(); } @@ -107,6 +109,11 @@ std::shared_ptr Sound::clone() const return sound; } +Thread *Sound::owner() const +{ + return impl->owner; +} + void Sound::processData(unsigned int size, void *data) { if (impl->player->isLoaded()) diff --git a/src/scratch/sound_p.h b/src/scratch/sound_p.h index 9f2b460c7..83074bed2 100644 --- a/src/scratch/sound_p.h +++ b/src/scratch/sound_p.h @@ -11,6 +11,7 @@ namespace libscratchcpp { class Sound; +class Thread; struct SoundPrivate { @@ -22,6 +23,7 @@ struct SoundPrivate static IAudioOutput *audioOutput; std::shared_ptr player = nullptr; const Sound *cloneRoot = nullptr; + Thread *owner = nullptr; }; } // namespace libscratchcpp diff --git a/test/assets/sound_test.cpp b/test/assets/sound_test.cpp index b83d5a999..d87a65654 100644 --- a/test/assets/sound_test.cpp +++ b/test/assets/sound_test.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -128,6 +129,16 @@ TEST_F(SoundTest, Start) sound.start(); } +TEST_F(SoundTest, StartWithOwner) +{ + Sound sound("sound1", "a", "wav"); + Thread thread(nullptr, nullptr, nullptr); + + EXPECT_CALL(*m_player, start()); + sound.start(&thread); + ASSERT_EQ(sound.owner(), &thread); +} + TEST_F(SoundTest, Stop) { Sound sound("sound1", "a", "wav"); @@ -136,6 +147,20 @@ TEST_F(SoundTest, Stop) sound.stop(); } +TEST_F(SoundTest, StartAndStopWithOwner) +{ + Sound sound("sound1", "a", "wav"); + Thread thread(nullptr, nullptr, nullptr); + + EXPECT_CALL(*m_player, start()); + sound.start(&thread); + ASSERT_EQ(sound.owner(), &thread); + + EXPECT_CALL(*m_player, stop()); + sound.stop(); + ASSERT_EQ(sound.owner(), nullptr); +} + TEST_F(SoundTest, IsPlaying) { Sound sound("sound1", "a", "wav"); diff --git a/test/mocks/soundmock.h b/test/mocks/soundmock.h index 63c9526fc..985064474 100644 --- a/test/mocks/soundmock.h +++ b/test/mocks/soundmock.h @@ -16,7 +16,7 @@ class SoundMock : public Sound MOCK_METHOD(void, setVolume, (double), (override)); MOCK_METHOD(void, setEffect, (Sound::Effect, double), (override)); - MOCK_METHOD(void, start, (), (override)); + MOCK_METHOD(void, start, (Thread *), (override)); MOCK_METHOD(void, stop, (), (override)); MOCK_METHOD(bool, isPlaying, (), (const, override)); }; From f6e7df73c70e16664428197b310ba34952683c0f Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 15 Apr 2025 21:22:49 +0200 Subject: [PATCH 04/11] Stop waiting after waiting threads/sounds are stopped --- src/blocks/soundblocks.cpp | 40 +++++++++++---- src/blocks/soundblocks.h | 1 + test/blocks/sound_blocks_test.cpp | 85 +++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 9 deletions(-) diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index d64a13ab5..5d3a38bb6 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -2,7 +2,11 @@ #include #include +#include #include +#include +#include +#include #include #include "soundblocks.h" @@ -30,22 +34,38 @@ void SoundBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "sound_playuntildone", &compilePlayUntilDone); } +void SoundBlocks::onInit(IEngine *engine) +{ + engine->threadAboutToStop().connect([](Thread *thread) { + Target *target = thread->target(); + const auto &sounds = target->sounds(); + + for (auto sound : sounds) { + if (sound->owner() == thread) { + sound->stop(); + break; + } + } + }); +} + CompilerValue *SoundBlocks::compilePlay(Compiler *compiler) { auto sound = compiler->addInput("SOUND_MENU"); - compiler->addTargetFunctionCall("sound_play", Compiler::StaticType::Pointer, { Compiler::StaticType::Unknown }, { sound }); + auto storeOwner = compiler->addConstValue(false); + compiler->addFunctionCallWithCtx("sound_play", Compiler::StaticType::Pointer, { Compiler::StaticType::Unknown, Compiler::StaticType::Bool }, { sound, storeOwner }); return nullptr; } CompilerValue *SoundBlocks::compilePlayUntilDone(Compiler *compiler) { auto sound = compiler->addInput("SOUND_MENU"); - auto soundPtr = compiler->addTargetFunctionCall("sound_play", Compiler::StaticType::Pointer, { Compiler::StaticType::Unknown }, { sound }); + auto storeOwner = compiler->addConstValue(true); + auto soundPtr = compiler->addFunctionCallWithCtx("sound_play", Compiler::StaticType::Pointer, { Compiler::StaticType::Unknown, Compiler::StaticType::Bool }, { sound, storeOwner }); compiler->beginLoopCondition(); - auto numType = Compiler::StaticType::Number; - auto playing = compiler->addFunctionCall("sound_is_playing", Compiler::StaticType::Bool, { Compiler::StaticType::Pointer }, { soundPtr }); - compiler->beginWhileLoop(playing); + auto waiting = compiler->addFunctionCallWithCtx("sound_is_waiting", Compiler::StaticType::Bool, { Compiler::StaticType::Pointer }, { soundPtr }); + compiler->beginWhileLoop(waiting); compiler->endLoop(); return nullptr; @@ -93,23 +113,25 @@ int sound_get_index(Target *target, const ValueData *sound) return -1; } -extern "C" Sound *sound_play(Target *target, const ValueData *soundName) +extern "C" Sound *sound_play(ExecutionContext *ctx, const ValueData *soundName, bool storeOwner) { + Thread *thread = ctx->thread(); + Target *target = thread->target(); int index = sound_get_index(target, soundName); auto sound = target->soundAt(index); if (sound) { - sound->start(); + sound->start(storeOwner ? thread : nullptr); return sound.get(); } return nullptr; } -extern "C" bool sound_is_playing(Sound *sound) +extern "C" bool sound_is_waiting(ExecutionContext *ctx, Sound *sound) { if (!sound) return false; - return sound->isPlaying(); + return sound->owner() == ctx->thread() && sound->isPlaying(); } diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h index 2837ed51e..a83f20c01 100644 --- a/src/blocks/soundblocks.h +++ b/src/blocks/soundblocks.h @@ -17,6 +17,7 @@ class SoundBlocks : public IExtension Rgb color() const override; void registerBlocks(IEngine *engine) override; + void onInit(IEngine *engine) override; private: static CompilerValue *compilePlay(Compiler *compiler); diff --git a/test/blocks/sound_blocks_test.cpp b/test/blocks/sound_blocks_test.cpp index a706e3d7c..72b1816b8 100644 --- a/test/blocks/sound_blocks_test.cpp +++ b/test/blocks/sound_blocks_test.cpp @@ -26,6 +26,7 @@ class SoundBlocksTest : public testing::Test m_extension = std::make_unique(); m_engine = m_project.engine().get(); m_extension->registerBlocks(m_engine); + m_extension->onInit(m_engine); SoundPrivate::audioOutput = &m_outputMock; } @@ -428,6 +429,90 @@ TEST_F(SoundBlocksTest, PlayUntilDone_SoundName) ASSERT_TRUE(thread.isFinished()); } +TEST_F(SoundBlocksTest, PlayUntilDone_KillWaitingThread) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + auto sound2 = std::make_shared("meow", "b", "wav"); + sprite->addSound(sound2); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_playuntildone"); + builder.addDropdownInput("SOUND_MENU", "meow"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start()); + EXPECT_CALL(*player3, start).Times(0); + EXPECT_CALL(*player2, isPlaying()).WillRepeatedly(Return(true)); + thread.run(); + ASSERT_FALSE(thread.isFinished()); + ASSERT_EQ(sound2->owner(), &thread); + + EXPECT_CALL(*player2, stop()); + m_engine->threadAboutToStop()(&thread); + thread.kill(); + ASSERT_EQ(sound2->owner(), nullptr); +} + +TEST_F(SoundBlocksTest, PlayUntilDone_StopWaitingSound) +{ + auto sprite = std::make_shared(); + auto player1 = std::make_shared(); + auto player2 = std::make_shared(); + auto player3 = std::make_shared(); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player1)); + sprite->addSound(std::make_shared("pop", "a", "wav")); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player2)); + auto sound2 = std::make_shared("meow", "b", "wav"); + sprite->addSound(sound2); + + EXPECT_CALL(m_outputMock, createAudioPlayer()).WillOnce(Return(player3)); + sprite->addSound(std::make_shared("test", "c", "mp3")); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_playuntildone"); + builder.addDropdownInput("SOUND_MENU", "meow"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(*player1, start).Times(0); + EXPECT_CALL(*player2, start()); + EXPECT_CALL(*player3, start).Times(0); + EXPECT_CALL(*player2, isPlaying()).WillRepeatedly(Return(true)); + thread.run(); + ASSERT_FALSE(thread.isFinished()); + ASSERT_EQ(sound2->owner(), &thread); + + sound2->stop(); + EXPECT_CALL(*player2, isPlaying()).Times(0); + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + TEST_F(SoundBlocksTest, PlayUntilDone_NumberIndex) { auto sprite = std::make_shared(); From 0086d8b6761e458589d5c82ec90e6b92a229d0cc Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 17 Apr 2025 10:28:57 +0200 Subject: [PATCH 05/11] Implement sound_stopallsounds block --- src/blocks/soundblocks.cpp | 12 +++++++++++ src/blocks/soundblocks.h | 1 + test/blocks/sound_blocks_test.cpp | 36 +++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index 5d3a38bb6..868c2f180 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -32,6 +32,7 @@ void SoundBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "sound_play", &compilePlay); engine->addCompileFunction(this, "sound_playuntildone", &compilePlayUntilDone); + engine->addCompileFunction(this, "sound_stopallsounds", &compileStopAllSounds); } void SoundBlocks::onInit(IEngine *engine) @@ -71,6 +72,12 @@ CompilerValue *SoundBlocks::compilePlayUntilDone(Compiler *compiler) return nullptr; } +CompilerValue *SoundBlocks::compileStopAllSounds(Compiler *compiler) +{ + compiler->addFunctionCallWithCtx("sound_stopallsounds"); + return nullptr; +} + int sound_wrap_clamp_index(Target *target, int index) { const long soundCount = target->sounds().size(); @@ -135,3 +142,8 @@ extern "C" bool sound_is_waiting(ExecutionContext *ctx, Sound *sound) return sound->owner() == ctx->thread() && sound->isPlaying(); } + +extern "C" void sound_stopallsounds(ExecutionContext *ctx) +{ + ctx->engine()->stopSounds(); +} diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h index a83f20c01..17ae163b4 100644 --- a/src/blocks/soundblocks.h +++ b/src/blocks/soundblocks.h @@ -22,6 +22,7 @@ class SoundBlocks : public IExtension private: static CompilerValue *compilePlay(Compiler *compiler); static CompilerValue *compilePlayUntilDone(Compiler *compiler); + static CompilerValue *compileStopAllSounds(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/sound_blocks_test.cpp b/test/blocks/sound_blocks_test.cpp index 72b1816b8..15ca17bcf 100644 --- a/test/blocks/sound_blocks_test.cpp +++ b/test/blocks/sound_blocks_test.cpp @@ -849,3 +849,39 @@ TEST_F(SoundBlocksTest, PlayUntilDone_Stage) thread.run(); ASSERT_TRUE(thread.isFinished()); } + +TEST_F(SoundBlocksTest, StopAllSounds_Sprite) +{ + auto sprite = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_stopallsounds"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, stopSounds()); + thread.run(); +} + +TEST_F(SoundBlocksTest, StopAllSounds_Stage) +{ + auto stage = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("sound_stopallsounds"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(stage.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, stopSounds()); + thread.run(); +} From e6e14485980350d979df9441b5cf6ca65063bff5 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 17 Apr 2025 10:49:23 +0200 Subject: [PATCH 06/11] Implement sound_seteffectto block --- src/blocks/soundblocks.cpp | 32 ++++++++++++++++ src/blocks/soundblocks.h | 1 + test/blocks/sound_blocks_test.cpp | 61 +++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index 868c2f180..1ee838c17 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ void SoundBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "sound_play", &compilePlay); engine->addCompileFunction(this, "sound_playuntildone", &compilePlayUntilDone); engine->addCompileFunction(this, "sound_stopallsounds", &compileStopAllSounds); + engine->addCompileFunction(this, "sound_seteffectto", &compileSetEffectTo); } void SoundBlocks::onInit(IEngine *engine) @@ -78,6 +80,26 @@ CompilerValue *SoundBlocks::compileStopAllSounds(Compiler *compiler) return nullptr; } +CompilerValue *SoundBlocks::compileSetEffectTo(Compiler *compiler) +{ + Field *field = compiler->field("EFFECT"); + + if (!field) + return nullptr; + + const std::string &option = field->value().toString(); + + if (option == "PITCH") { + auto value = compiler->addInput("VALUE"); + compiler->addTargetFunctionCall("sound_set_pitch_effect", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { value }); + } else if (option == "PAN") { + auto value = compiler->addInput("VALUE"); + compiler->addTargetFunctionCall("sound_set_pan_effect", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { value }); + } + + return nullptr; +} + int sound_wrap_clamp_index(Target *target, int index) { const long soundCount = target->sounds().size(); @@ -147,3 +169,13 @@ extern "C" void sound_stopallsounds(ExecutionContext *ctx) { ctx->engine()->stopSounds(); } + +extern "C" void sound_set_pitch_effect(Target *target, double value) +{ + target->setSoundEffectValue(Sound::Effect::Pitch, value); +} + +extern "C" void sound_set_pan_effect(Target *target, double value) +{ + target->setSoundEffectValue(Sound::Effect::Pan, value); +} diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h index 17ae163b4..4b6841344 100644 --- a/src/blocks/soundblocks.h +++ b/src/blocks/soundblocks.h @@ -23,6 +23,7 @@ class SoundBlocks : public IExtension static CompilerValue *compilePlay(Compiler *compiler); static CompilerValue *compilePlayUntilDone(Compiler *compiler); static CompilerValue *compileStopAllSounds(Compiler *compiler); + static CompilerValue *compileSetEffectTo(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/sound_blocks_test.cpp b/test/blocks/sound_blocks_test.cpp index 15ca17bcf..89518c3c2 100644 --- a/test/blocks/sound_blocks_test.cpp +++ b/test/blocks/sound_blocks_test.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -885,3 +886,63 @@ TEST_F(SoundBlocksTest, StopAllSounds_Stage) EXPECT_CALL(m_engineMock, stopSounds()); thread.run(); } + +TEST_F(SoundBlocksTest, SetEffectTo_Invalid) +{ + auto target = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + builder.addBlock("sound_seteffectto"); + builder.addDropdownField("EFFECT", "abc"); + builder.addValueInput("VALUE", 0); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block); + Script script(target.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + + EXPECT_CALL(*target, setSoundEffectValue).Times(0); + thread.run(); +} + +TEST_F(SoundBlocksTest, SetEffectTo_Pitch) +{ + auto target = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + builder.addBlock("sound_seteffectto"); + builder.addDropdownField("EFFECT", "PITCH"); + builder.addValueInput("VALUE", 75.2); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block); + Script script(target.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + + EXPECT_CALL(*target, setSoundEffectValue(Sound::Effect::Pitch, 75.2)); + thread.run(); +} + +TEST_F(SoundBlocksTest, SetEffectTo_Pan) +{ + auto target = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + builder.addBlock("sound_seteffectto"); + builder.addDropdownField("EFFECT", "PAN"); + builder.addValueInput("VALUE", -23.8); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block); + Script script(target.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + + EXPECT_CALL(*target, setSoundEffectValue(Sound::Effect::Pan, -23.8)); + thread.run(); +} From 7af93e115fb4fbe07d27b6ec766afb0e23450cdc Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:20:39 +0200 Subject: [PATCH 07/11] Implement sound_changeeffectby block --- src/blocks/soundblocks.cpp | 31 +++++++++++++++ src/blocks/soundblocks.h | 1 + test/blocks/sound_blocks_test.cpp | 63 +++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index 1ee838c17..b644d9a69 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -35,6 +35,7 @@ void SoundBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "sound_playuntildone", &compilePlayUntilDone); engine->addCompileFunction(this, "sound_stopallsounds", &compileStopAllSounds); engine->addCompileFunction(this, "sound_seteffectto", &compileSetEffectTo); + engine->addCompileFunction(this, "sound_changeeffectby", &compileChangeEffectBy); } void SoundBlocks::onInit(IEngine *engine) @@ -100,6 +101,26 @@ CompilerValue *SoundBlocks::compileSetEffectTo(Compiler *compiler) return nullptr; } +CompilerValue *SoundBlocks::compileChangeEffectBy(Compiler *compiler) +{ + Field *field = compiler->field("EFFECT"); + + if (!field) + return nullptr; + + const std::string &option = field->value().toString(); + + if (option == "PITCH") { + auto value = compiler->addInput("VALUE"); + compiler->addTargetFunctionCall("sound_change_pitch_effect", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { value }); + } else if (option == "PAN") { + auto value = compiler->addInput("VALUE"); + compiler->addTargetFunctionCall("sound_change_pan_effect", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { value }); + } + + return nullptr; +} + int sound_wrap_clamp_index(Target *target, int index) { const long soundCount = target->sounds().size(); @@ -179,3 +200,13 @@ extern "C" void sound_set_pan_effect(Target *target, double value) { target->setSoundEffectValue(Sound::Effect::Pan, value); } + +extern "C" void sound_change_pitch_effect(Target *target, double value) +{ + target->setSoundEffectValue(Sound::Effect::Pitch, target->soundEffectValue(Sound::Effect::Pitch) + value); +} + +extern "C" void sound_change_pan_effect(Target *target, double value) +{ + target->setSoundEffectValue(Sound::Effect::Pan, target->soundEffectValue(Sound::Effect::Pan) + value); +} diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h index 4b6841344..2bce76d7c 100644 --- a/src/blocks/soundblocks.h +++ b/src/blocks/soundblocks.h @@ -24,6 +24,7 @@ class SoundBlocks : public IExtension static CompilerValue *compilePlayUntilDone(Compiler *compiler); static CompilerValue *compileStopAllSounds(Compiler *compiler); static CompilerValue *compileSetEffectTo(Compiler *compiler); + static CompilerValue *compileChangeEffectBy(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/sound_blocks_test.cpp b/test/blocks/sound_blocks_test.cpp index 89518c3c2..f9a38d22d 100644 --- a/test/blocks/sound_blocks_test.cpp +++ b/test/blocks/sound_blocks_test.cpp @@ -946,3 +946,66 @@ TEST_F(SoundBlocksTest, SetEffectTo_Pan) EXPECT_CALL(*target, setSoundEffectValue(Sound::Effect::Pan, -23.8)); thread.run(); } + +TEST_F(SoundBlocksTest, ChangeEffectBy_Invalid) +{ + auto target = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + builder.addBlock("sound_changeeffectby"); + builder.addDropdownField("EFFECT", "abc"); + builder.addValueInput("VALUE", 1); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block); + Script script(target.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + + EXPECT_CALL(*target, soundEffectValue).Times(0); + EXPECT_CALL(*target, setSoundEffectValue).Times(0); + thread.run(); +} + +TEST_F(SoundBlocksTest, ChangeEffectBy_Pitch) +{ + auto target = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + builder.addBlock("sound_changeeffectby"); + builder.addDropdownField("EFFECT", "PITCH"); + builder.addValueInput("VALUE", 75.2); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block); + Script script(target.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + + EXPECT_CALL(*target, soundEffectValue(Sound::Effect::Pitch)).WillOnce(Return(-28.6)); + EXPECT_CALL(*target, setSoundEffectValue(Sound::Effect::Pitch, 46.6)); + thread.run(); +} + +TEST_F(SoundBlocksTest, ChangeEffectBy_Pan) +{ + auto target = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + builder.addBlock("sound_changeeffectby"); + builder.addDropdownField("EFFECT", "PAN"); + builder.addValueInput("VALUE", -23.8); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block); + Script script(target.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + + EXPECT_CALL(*target, soundEffectValue(Sound::Effect::Pan)).WillOnce(Return(12.5)); + EXPECT_CALL(*target, setSoundEffectValue(Sound::Effect::Pan, -11.3)); + thread.run(); +} From b54ac00935556bb22a13b2bcff65ce4465a9a518 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:30:45 +0200 Subject: [PATCH 08/11] Implement sound_cleareffects block --- src/blocks/soundblocks.cpp | 12 ++++++++++++ src/blocks/soundblocks.h | 1 + test/blocks/sound_blocks_test.cpp | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index b644d9a69..d447cd167 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -36,6 +36,7 @@ void SoundBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "sound_stopallsounds", &compileStopAllSounds); engine->addCompileFunction(this, "sound_seteffectto", &compileSetEffectTo); engine->addCompileFunction(this, "sound_changeeffectby", &compileChangeEffectBy); + engine->addCompileFunction(this, "sound_cleareffects", &compileClearEffects); } void SoundBlocks::onInit(IEngine *engine) @@ -121,6 +122,12 @@ CompilerValue *SoundBlocks::compileChangeEffectBy(Compiler *compiler) return nullptr; } +CompilerValue *SoundBlocks::compileClearEffects(Compiler *compiler) +{ + compiler->addTargetFunctionCall("sound_cleareffects"); + return nullptr; +} + int sound_wrap_clamp_index(Target *target, int index) { const long soundCount = target->sounds().size(); @@ -210,3 +217,8 @@ extern "C" void sound_change_pan_effect(Target *target, double value) { target->setSoundEffectValue(Sound::Effect::Pan, target->soundEffectValue(Sound::Effect::Pan) + value); } + +extern "C" void sound_cleareffects(Target *target) +{ + target->clearSoundEffects(); +} diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h index 2bce76d7c..5a3ca1aab 100644 --- a/src/blocks/soundblocks.h +++ b/src/blocks/soundblocks.h @@ -25,6 +25,7 @@ class SoundBlocks : public IExtension static CompilerValue *compileStopAllSounds(Compiler *compiler); static CompilerValue *compileSetEffectTo(Compiler *compiler); static CompilerValue *compileChangeEffectBy(Compiler *compiler); + static CompilerValue *compileClearEffects(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/sound_blocks_test.cpp b/test/blocks/sound_blocks_test.cpp index f9a38d22d..df0ad435a 100644 --- a/test/blocks/sound_blocks_test.cpp +++ b/test/blocks/sound_blocks_test.cpp @@ -1009,3 +1009,22 @@ TEST_F(SoundBlocksTest, ChangeEffectBy_Pan) EXPECT_CALL(*target, setSoundEffectValue(Sound::Effect::Pan, -11.3)); thread.run(); } + +TEST_F(SoundBlocksTest, ClearEffects) +{ + auto target = std::make_shared(); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + builder.addBlock("sound_cleareffects"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block); + Script script(target.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + + EXPECT_CALL(*target, clearSoundEffects()); + EXPECT_CALL(*target, setSoundEffectValue).Times(0); + thread.run(); +} From 3b8d60013b58c7a489b42bf8eb3b01eb6bd85145 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:39:31 +0200 Subject: [PATCH 09/11] Implement sound_changevolumeby --- src/blocks/soundblocks.cpp | 13 +++++++++++++ src/blocks/soundblocks.h | 1 + test/blocks/sound_blocks_test.cpp | 30 ++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index d447cd167..7e291f1ad 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -37,6 +37,7 @@ void SoundBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "sound_seteffectto", &compileSetEffectTo); engine->addCompileFunction(this, "sound_changeeffectby", &compileChangeEffectBy); engine->addCompileFunction(this, "sound_cleareffects", &compileClearEffects); + engine->addCompileFunction(this, "sound_changevolumeby", &compileChangeVolumeBy); } void SoundBlocks::onInit(IEngine *engine) @@ -128,6 +129,13 @@ CompilerValue *SoundBlocks::compileClearEffects(Compiler *compiler) return nullptr; } +CompilerValue *SoundBlocks::compileChangeVolumeBy(Compiler *compiler) +{ + auto volume = compiler->addInput("VOLUME"); + compiler->addTargetFunctionCall("sound_changevolumeby", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { volume }); + return nullptr; +} + int sound_wrap_clamp_index(Target *target, int index) { const long soundCount = target->sounds().size(); @@ -222,3 +230,8 @@ extern "C" void sound_cleareffects(Target *target) { target->clearSoundEffects(); } + +extern "C" void sound_changevolumeby(Target *target, double volume) +{ + target->setVolume(target->volume() + volume); +} diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h index 5a3ca1aab..c989489c8 100644 --- a/src/blocks/soundblocks.h +++ b/src/blocks/soundblocks.h @@ -26,6 +26,7 @@ class SoundBlocks : public IExtension static CompilerValue *compileSetEffectTo(Compiler *compiler); static CompilerValue *compileChangeEffectBy(Compiler *compiler); static CompilerValue *compileClearEffects(Compiler *compiler); + static CompilerValue *compileChangeVolumeBy(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/sound_blocks_test.cpp b/test/blocks/sound_blocks_test.cpp index df0ad435a..b5aebf19d 100644 --- a/test/blocks/sound_blocks_test.cpp +++ b/test/blocks/sound_blocks_test.cpp @@ -1028,3 +1028,33 @@ TEST_F(SoundBlocksTest, ClearEffects) EXPECT_CALL(*target, setSoundEffectValue).Times(0); thread.run(); } + +TEST_F(SoundBlocksTest, ChangeVolumeBy_Sprite) +{ + auto sprite = std::make_shared(); + sprite->setVolume(42); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_changevolumeby"); + builder.addValueInput("VOLUME", 65.2); + builder.build(); + + sprite->setVolume(1.9); + builder.run(); + ASSERT_EQ(std::round(sprite->volume() * 100) / 100, 67.1); +} + +TEST_F(SoundBlocksTest, ChangeVolumeBy_Stage) +{ + auto stage = std::make_shared(); + stage->setVolume(18); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("sound_changevolumeby"); + builder.addValueInput("VOLUME", -38.4); + builder.build(); + + stage->setVolume(78.5); + builder.run(); + ASSERT_EQ(stage->volume(), 40.1); +} From fc7b85031e2e37ef8be98f4df1b1ebdf7d71768e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:44:55 +0200 Subject: [PATCH 10/11] Implement sound_setvolumeto block --- src/blocks/soundblocks.cpp | 13 +++++++++++++ src/blocks/soundblocks.h | 1 + test/blocks/sound_blocks_test.cpp | 30 ++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index 7e291f1ad..f1a620aab 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -38,6 +38,7 @@ void SoundBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "sound_changeeffectby", &compileChangeEffectBy); engine->addCompileFunction(this, "sound_cleareffects", &compileClearEffects); engine->addCompileFunction(this, "sound_changevolumeby", &compileChangeVolumeBy); + engine->addCompileFunction(this, "sound_setvolumeto", &compileSetVolumeTo); } void SoundBlocks::onInit(IEngine *engine) @@ -136,6 +137,13 @@ CompilerValue *SoundBlocks::compileChangeVolumeBy(Compiler *compiler) return nullptr; } +CompilerValue *SoundBlocks::compileSetVolumeTo(Compiler *compiler) +{ + auto volume = compiler->addInput("VOLUME"); + compiler->addTargetFunctionCall("sound_setvolumeto", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { volume }); + return nullptr; +} + int sound_wrap_clamp_index(Target *target, int index) { const long soundCount = target->sounds().size(); @@ -235,3 +243,8 @@ extern "C" void sound_changevolumeby(Target *target, double volume) { target->setVolume(target->volume() + volume); } + +extern "C" void sound_setvolumeto(Target *target, double volume) +{ + target->setVolume(volume); +} diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h index c989489c8..fe21f99f0 100644 --- a/src/blocks/soundblocks.h +++ b/src/blocks/soundblocks.h @@ -27,6 +27,7 @@ class SoundBlocks : public IExtension static CompilerValue *compileChangeEffectBy(Compiler *compiler); static CompilerValue *compileClearEffects(Compiler *compiler); static CompilerValue *compileChangeVolumeBy(Compiler *compiler); + static CompilerValue *compileSetVolumeTo(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/sound_blocks_test.cpp b/test/blocks/sound_blocks_test.cpp index b5aebf19d..29c97bab7 100644 --- a/test/blocks/sound_blocks_test.cpp +++ b/test/blocks/sound_blocks_test.cpp @@ -1058,3 +1058,33 @@ TEST_F(SoundBlocksTest, ChangeVolumeBy_Stage) builder.run(); ASSERT_EQ(stage->volume(), 40.1); } + +TEST_F(SoundBlocksTest, SetVolumeTo_Sprite) +{ + auto sprite = std::make_shared(); + sprite->setVolume(18); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_setvolumeto"); + builder.addValueInput("VOLUME", 65.2); + builder.build(); + + sprite->setVolume(1.9); + builder.run(); + ASSERT_EQ(sprite->volume(), 65.2); +} + +TEST_F(SoundBlocksTest, SetVolumeTo_Stage) +{ + auto stage = std::make_shared(); + stage->setVolume(42); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("sound_setvolumeto"); + builder.addValueInput("VOLUME", 38.4); + builder.build(); + + stage->setVolume(78.5); + builder.run(); + ASSERT_EQ(stage->volume(), 38.4); +} From 0d63b3bf2524c2ffdd69bf917bcccbb3b8e42877 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:54:11 +0200 Subject: [PATCH 11/11] Implement sound_volume block --- src/blocks/soundblocks.cpp | 11 +++++++++ src/blocks/soundblocks.h | 1 + test/blocks/sound_blocks_test.cpp | 39 +++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index f1a620aab..0dd35cdcd 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -39,6 +39,7 @@ void SoundBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "sound_cleareffects", &compileClearEffects); engine->addCompileFunction(this, "sound_changevolumeby", &compileChangeVolumeBy); engine->addCompileFunction(this, "sound_setvolumeto", &compileSetVolumeTo); + engine->addCompileFunction(this, "sound_volume", &compileVolume); } void SoundBlocks::onInit(IEngine *engine) @@ -144,6 +145,11 @@ CompilerValue *SoundBlocks::compileSetVolumeTo(Compiler *compiler) return nullptr; } +CompilerValue *SoundBlocks::compileVolume(Compiler *compiler) +{ + return compiler->addTargetFunctionCall("sound_volume", Compiler::StaticType::Number); +} + int sound_wrap_clamp_index(Target *target, int index) { const long soundCount = target->sounds().size(); @@ -248,3 +254,8 @@ extern "C" void sound_setvolumeto(Target *target, double volume) { target->setVolume(volume); } + +extern "C" double sound_volume(Target *target) +{ + return target->volume(); +} diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h index fe21f99f0..7f04d9c23 100644 --- a/src/blocks/soundblocks.h +++ b/src/blocks/soundblocks.h @@ -28,6 +28,7 @@ class SoundBlocks : public IExtension static CompilerValue *compileClearEffects(Compiler *compiler); static CompilerValue *compileChangeVolumeBy(Compiler *compiler); static CompilerValue *compileSetVolumeTo(Compiler *compiler); + static CompilerValue *compileVolume(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/sound_blocks_test.cpp b/test/blocks/sound_blocks_test.cpp index 29c97bab7..482368b6d 100644 --- a/test/blocks/sound_blocks_test.cpp +++ b/test/blocks/sound_blocks_test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -1088,3 +1089,41 @@ TEST_F(SoundBlocksTest, SetVolumeTo_Stage) builder.run(); ASSERT_EQ(stage->volume(), 38.4); } + +TEST_F(SoundBlocksTest, Volume_Sprite) +{ + auto sprite = std::make_shared(); + sprite->setVolume(50); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("sound_volume"); + builder.captureBlockReturnValue(); + builder.build(); + + sprite->setVolume(1.9); + builder.run(); + ASSERT_EQ(sprite->volume(), 1.9); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 1); + ASSERT_EQ(Value(list->data()[0]).toDouble(), 1.9); +} + +TEST_F(SoundBlocksTest, Volume_Stage) +{ + auto stage = std::make_shared(); + stage->setVolume(75); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("sound_volume"); + builder.captureBlockReturnValue(); + builder.build(); + + stage->setVolume(43.7); + builder.run(); + ASSERT_EQ(stage->volume(), 43.7); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 1); + ASSERT_EQ(Value(list->data()[0]).toDouble(), 43.7); +}