Skip to content

Commit 8eae08b

Browse files
authored
Merge pull request #640 from scratchcpp/llvm_sound_blocks
Implement sound blocks
2 parents 40c1163 + 0d63b3b commit 8eae08b

File tree

8 files changed

+1406
-4
lines changed

8 files changed

+1406
-4
lines changed

include/scratchcpp/sound.h

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

12+
class Thread;
1213
class SoundPrivate;
1314

1415
/*! \brief The Sound class represents a Scratch sound. */
@@ -34,13 +35,15 @@ class LIBSCRATCHCPP_EXPORT Sound : public Asset
3435
virtual void setVolume(double volume);
3536
virtual void setEffect(Effect effect, double value);
3637

37-
virtual void start();
38+
virtual void start(Thread *owner = nullptr);
3839
virtual void stop();
3940

4041
virtual bool isPlaying() const;
4142

4243
std::shared_ptr<Sound> clone() const;
4344

45+
Thread *owner() const;
46+
4447
protected:
4548
void processData(unsigned int size, void *data) override;
4649
virtual bool isClone() const override;

src/blocks/soundblocks.cpp

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

3+
#include <scratchcpp/iengine.h>
4+
#include <scratchcpp/compiler.h>
5+
#include <scratchcpp/compilerconstant.h>
6+
#include <scratchcpp/field.h>
7+
#include <scratchcpp/target.h>
8+
#include <scratchcpp/thread.h>
9+
#include <scratchcpp/value.h>
10+
#include <scratchcpp/executioncontext.h>
11+
#include <algorithm>
12+
313
#include "soundblocks.h"
414

515
using namespace libscratchcpp;
@@ -21,4 +31,231 @@ Rgb SoundBlocks::color() const
2131

2232
void SoundBlocks::registerBlocks(IEngine *engine)
2333
{
34+
engine->addCompileFunction(this, "sound_play", &compilePlay);
35+
engine->addCompileFunction(this, "sound_playuntildone", &compilePlayUntilDone);
36+
engine->addCompileFunction(this, "sound_stopallsounds", &compileStopAllSounds);
37+
engine->addCompileFunction(this, "sound_seteffectto", &compileSetEffectTo);
38+
engine->addCompileFunction(this, "sound_changeeffectby", &compileChangeEffectBy);
39+
engine->addCompileFunction(this, "sound_cleareffects", &compileClearEffects);
40+
engine->addCompileFunction(this, "sound_changevolumeby", &compileChangeVolumeBy);
41+
engine->addCompileFunction(this, "sound_setvolumeto", &compileSetVolumeTo);
42+
engine->addCompileFunction(this, "sound_volume", &compileVolume);
43+
}
44+
45+
void SoundBlocks::onInit(IEngine *engine)
46+
{
47+
engine->threadAboutToStop().connect([](Thread *thread) {
48+
Target *target = thread->target();
49+
const auto &sounds = target->sounds();
50+
51+
for (auto sound : sounds) {
52+
if (sound->owner() == thread) {
53+
sound->stop();
54+
break;
55+
}
56+
}
57+
});
58+
}
59+
60+
CompilerValue *SoundBlocks::compilePlay(Compiler *compiler)
61+
{
62+
auto sound = compiler->addInput("SOUND_MENU");
63+
auto storeOwner = compiler->addConstValue(false);
64+
compiler->addFunctionCallWithCtx("sound_play", Compiler::StaticType::Pointer, { Compiler::StaticType::Unknown, Compiler::StaticType::Bool }, { sound, storeOwner });
65+
return nullptr;
66+
}
67+
68+
CompilerValue *SoundBlocks::compilePlayUntilDone(Compiler *compiler)
69+
{
70+
auto sound = compiler->addInput("SOUND_MENU");
71+
auto storeOwner = compiler->addConstValue(true);
72+
auto soundPtr = compiler->addFunctionCallWithCtx("sound_play", Compiler::StaticType::Pointer, { Compiler::StaticType::Unknown, Compiler::StaticType::Bool }, { sound, storeOwner });
73+
74+
compiler->beginLoopCondition();
75+
auto waiting = compiler->addFunctionCallWithCtx("sound_is_waiting", Compiler::StaticType::Bool, { Compiler::StaticType::Pointer }, { soundPtr });
76+
compiler->beginWhileLoop(waiting);
77+
compiler->endLoop();
78+
79+
return nullptr;
80+
}
81+
82+
CompilerValue *SoundBlocks::compileStopAllSounds(Compiler *compiler)
83+
{
84+
compiler->addFunctionCallWithCtx("sound_stopallsounds");
85+
return nullptr;
86+
}
87+
88+
CompilerValue *SoundBlocks::compileSetEffectTo(Compiler *compiler)
89+
{
90+
Field *field = compiler->field("EFFECT");
91+
92+
if (!field)
93+
return nullptr;
94+
95+
const std::string &option = field->value().toString();
96+
97+
if (option == "PITCH") {
98+
auto value = compiler->addInput("VALUE");
99+
compiler->addTargetFunctionCall("sound_set_pitch_effect", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { value });
100+
} else if (option == "PAN") {
101+
auto value = compiler->addInput("VALUE");
102+
compiler->addTargetFunctionCall("sound_set_pan_effect", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { value });
103+
}
104+
105+
return nullptr;
106+
}
107+
108+
CompilerValue *SoundBlocks::compileChangeEffectBy(Compiler *compiler)
109+
{
110+
Field *field = compiler->field("EFFECT");
111+
112+
if (!field)
113+
return nullptr;
114+
115+
const std::string &option = field->value().toString();
116+
117+
if (option == "PITCH") {
118+
auto value = compiler->addInput("VALUE");
119+
compiler->addTargetFunctionCall("sound_change_pitch_effect", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { value });
120+
} else if (option == "PAN") {
121+
auto value = compiler->addInput("VALUE");
122+
compiler->addTargetFunctionCall("sound_change_pan_effect", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { value });
123+
}
124+
125+
return nullptr;
126+
}
127+
128+
CompilerValue *SoundBlocks::compileClearEffects(Compiler *compiler)
129+
{
130+
compiler->addTargetFunctionCall("sound_cleareffects");
131+
return nullptr;
132+
}
133+
134+
CompilerValue *SoundBlocks::compileChangeVolumeBy(Compiler *compiler)
135+
{
136+
auto volume = compiler->addInput("VOLUME");
137+
compiler->addTargetFunctionCall("sound_changevolumeby", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { volume });
138+
return nullptr;
139+
}
140+
141+
CompilerValue *SoundBlocks::compileSetVolumeTo(Compiler *compiler)
142+
{
143+
auto volume = compiler->addInput("VOLUME");
144+
compiler->addTargetFunctionCall("sound_setvolumeto", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { volume });
145+
return nullptr;
146+
}
147+
148+
CompilerValue *SoundBlocks::compileVolume(Compiler *compiler)
149+
{
150+
return compiler->addTargetFunctionCall("sound_volume", Compiler::StaticType::Number);
151+
}
152+
153+
int sound_wrap_clamp_index(Target *target, int index)
154+
{
155+
const long soundCount = target->sounds().size();
156+
157+
if (index < 0)
158+
return (soundCount + index % (-soundCount)) % soundCount;
159+
else if (index >= soundCount)
160+
return index % soundCount;
161+
else
162+
return index;
163+
}
164+
165+
int sound_get_index(Target *target, const ValueData *sound)
166+
{
167+
if (!value_isString(sound)) {
168+
// Numbers should be treated as sound indices
169+
if (value_isNaN(sound) || value_isInfinity(sound) || value_isNegativeInfinity(sound))
170+
return -1;
171+
else
172+
return sound_wrap_clamp_index(target, value_toLong(sound) - 1);
173+
} else {
174+
// Strings should be treated as sound names, where possible
175+
// TODO: Use UTF-16 in Target
176+
// StringPtr *nameStr = value_toStringPtr(sound);
177+
std::string nameStr;
178+
value_toString(sound, &nameStr);
179+
const int soundIndex = target->findSound(nameStr);
180+
181+
auto it = std::find_if(nameStr.begin(), nameStr.end(), [](char c) { return !std::isspace(c); });
182+
bool isWhiteSpace = (it == nameStr.end());
183+
184+
if (soundIndex != -1) {
185+
return soundIndex;
186+
// Try to cast the string to a number (and treat it as a costume index)
187+
// Pure whitespace should not be treated as a number
188+
} else if (value_isValidNumber(sound) && !isWhiteSpace)
189+
return sound_wrap_clamp_index(target, value_toLong(sound) - 1);
190+
}
191+
192+
return -1;
193+
}
194+
195+
extern "C" Sound *sound_play(ExecutionContext *ctx, const ValueData *soundName, bool storeOwner)
196+
{
197+
Thread *thread = ctx->thread();
198+
Target *target = thread->target();
199+
int index = sound_get_index(target, soundName);
200+
auto sound = target->soundAt(index);
201+
202+
if (sound) {
203+
sound->start(storeOwner ? thread : nullptr);
204+
return sound.get();
205+
}
206+
207+
return nullptr;
208+
}
209+
210+
extern "C" bool sound_is_waiting(ExecutionContext *ctx, Sound *sound)
211+
{
212+
if (!sound)
213+
return false;
214+
215+
return sound->owner() == ctx->thread() && sound->isPlaying();
216+
}
217+
218+
extern "C" void sound_stopallsounds(ExecutionContext *ctx)
219+
{
220+
ctx->engine()->stopSounds();
221+
}
222+
223+
extern "C" void sound_set_pitch_effect(Target *target, double value)
224+
{
225+
target->setSoundEffectValue(Sound::Effect::Pitch, value);
226+
}
227+
228+
extern "C" void sound_set_pan_effect(Target *target, double value)
229+
{
230+
target->setSoundEffectValue(Sound::Effect::Pan, value);
231+
}
232+
233+
extern "C" void sound_change_pitch_effect(Target *target, double value)
234+
{
235+
target->setSoundEffectValue(Sound::Effect::Pitch, target->soundEffectValue(Sound::Effect::Pitch) + value);
236+
}
237+
238+
extern "C" void sound_change_pan_effect(Target *target, double value)
239+
{
240+
target->setSoundEffectValue(Sound::Effect::Pan, target->soundEffectValue(Sound::Effect::Pan) + value);
241+
}
242+
243+
extern "C" void sound_cleareffects(Target *target)
244+
{
245+
target->clearSoundEffects();
246+
}
247+
248+
extern "C" void sound_changevolumeby(Target *target, double volume)
249+
{
250+
target->setVolume(target->volume() + volume);
251+
}
252+
253+
extern "C" void sound_setvolumeto(Target *target, double volume)
254+
{
255+
target->setVolume(volume);
256+
}
257+
258+
extern "C" double sound_volume(Target *target)
259+
{
260+
return target->volume();
24261
}

src/blocks/soundblocks.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
namespace libscratchcpp
88
{
99

10+
class IAudioOutput;
11+
1012
class SoundBlocks : public IExtension
1113
{
1214
public:
@@ -15,6 +17,18 @@ class SoundBlocks : public IExtension
1517
Rgb color() const override;
1618

1719
void registerBlocks(IEngine *engine) override;
20+
void onInit(IEngine *engine) override;
21+
22+
private:
23+
static CompilerValue *compilePlay(Compiler *compiler);
24+
static CompilerValue *compilePlayUntilDone(Compiler *compiler);
25+
static CompilerValue *compileStopAllSounds(Compiler *compiler);
26+
static CompilerValue *compileSetEffectTo(Compiler *compiler);
27+
static CompilerValue *compileChangeEffectBy(Compiler *compiler);
28+
static CompilerValue *compileClearEffects(Compiler *compiler);
29+
static CompilerValue *compileChangeVolumeBy(Compiler *compiler);
30+
static CompilerValue *compileSetVolumeTo(Compiler *compiler);
31+
static CompilerValue *compileVolume(Compiler *compiler);
1832
};
1933

2034
} // namespace libscratchcpp

src/scratch/sound.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,11 @@ void Sound::setEffect(Effect effect, double value)
6666
}
6767

6868
/*! Starts the playback of the sound. */
69-
void Sound::start()
69+
void Sound::start(Thread *owner)
7070
{
7171
// Stop sounds in clones (#538)
7272
stopCloneSounds();
73+
impl->owner = owner;
7374
impl->player->start();
7475
}
7576

@@ -78,6 +79,7 @@ void Sound::stop()
7879
{
7980
// Stop sounds in clones (#538)
8081
stopCloneSounds();
82+
impl->owner = nullptr;
8183
impl->player->stop();
8284
}
8385

@@ -107,6 +109,11 @@ std::shared_ptr<Sound> Sound::clone() const
107109
return sound;
108110
}
109111

112+
Thread *Sound::owner() const
113+
{
114+
return impl->owner;
115+
}
116+
110117
void Sound::processData(unsigned int size, void *data)
111118
{
112119
if (impl->player->isLoaded())

src/scratch/sound_p.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace libscratchcpp
1111
{
1212

1313
class Sound;
14+
class Thread;
1415

1516
struct SoundPrivate
1617
{
@@ -22,6 +23,7 @@ struct SoundPrivate
2223
static IAudioOutput *audioOutput;
2324
std::shared_ptr<IAudioPlayer> player = nullptr;
2425
const Sound *cloneRoot = nullptr;
26+
Thread *owner = nullptr;
2527
};
2628

2729
} // namespace libscratchcpp

test/assets/sound_test.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <scratchcpp/sound.h>
22
#include <scratchcpp/sprite.h>
3+
#include <scratchcpp/thread.h>
34
#include <scratch/sound_p.h>
45
#include <audiooutputmock.h>
56
#include <audioplayermock.h>
@@ -128,6 +129,16 @@ TEST_F(SoundTest, Start)
128129
sound.start();
129130
}
130131

132+
TEST_F(SoundTest, StartWithOwner)
133+
{
134+
Sound sound("sound1", "a", "wav");
135+
Thread thread(nullptr, nullptr, nullptr);
136+
137+
EXPECT_CALL(*m_player, start());
138+
sound.start(&thread);
139+
ASSERT_EQ(sound.owner(), &thread);
140+
}
141+
131142
TEST_F(SoundTest, Stop)
132143
{
133144
Sound sound("sound1", "a", "wav");
@@ -136,6 +147,20 @@ TEST_F(SoundTest, Stop)
136147
sound.stop();
137148
}
138149

150+
TEST_F(SoundTest, StartAndStopWithOwner)
151+
{
152+
Sound sound("sound1", "a", "wav");
153+
Thread thread(nullptr, nullptr, nullptr);
154+
155+
EXPECT_CALL(*m_player, start());
156+
sound.start(&thread);
157+
ASSERT_EQ(sound.owner(), &thread);
158+
159+
EXPECT_CALL(*m_player, stop());
160+
sound.stop();
161+
ASSERT_EQ(sound.owner(), nullptr);
162+
}
163+
139164
TEST_F(SoundTest, IsPlaying)
140165
{
141166
Sound sound("sound1", "a", "wav");

0 commit comments

Comments
 (0)