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
515using namespace libscratchcpp ;
@@ -21,4 +31,231 @@ Rgb SoundBlocks::color() const
2131
2232void 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}
0 commit comments