Skip to content

Commit d7b86ef

Browse files
authored
Merge pull request #408 from scratchcpp/sound_playback_blocks
Implement sound playback blocks
2 parents 7d0608f + 293aed7 commit d7b86ef

File tree

12 files changed

+809
-7
lines changed

12 files changed

+809
-7
lines changed

include/scratchcpp/iengine.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ class LIBSCRATCHCPP_EXPORT IEngine
8181
/*! Automatically called from clones that are being deleted. */
8282
virtual void deinitClone(std::shared_ptr<Sprite> clone) = 0;
8383

84+
/*! Stops all currently playing sounds. */
85+
virtual void stopSounds() = 0;
86+
8487
/*! Steps all currently running threads. Use this to implement a custom event loop. */
8588
virtual void step() = 0;
8689

src/audio/audioplayer.cpp

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,23 +51,36 @@ bool AudioPlayer::load(unsigned int size, const void *data, unsigned long sample
5151
}
5252

5353
m_loaded = true;
54+
ma_sound_set_volume(m_sound, m_volume);
5455
return true;
5556
}
5657

5758
void AudioPlayer::setVolume(float volume)
5859
{
59-
if (!AudioEngine::initialized())
60+
m_volume = volume;
61+
62+
if (!m_loaded)
6063
return;
6164

6265
ma_sound_set_volume(m_sound, volume);
6366
}
6467

6568
void AudioPlayer::start()
6669
{
67-
if (!AudioEngine::initialized())
70+
if (!m_loaded)
6871
return;
6972

70-
ma_result result = ma_sound_start(m_sound);
73+
if (isPlaying())
74+
stop();
75+
76+
ma_result result = ma_sound_seek_to_pcm_frame(m_sound, 0);
77+
78+
if (result != MA_SUCCESS) {
79+
std::cerr << "Failed to seek to PCM frame 0." << std::endl;
80+
m_started = false;
81+
}
82+
83+
result = ma_sound_start(m_sound);
7184

7285
if (result != MA_SUCCESS) {
7386
std::cerr << "Failed to start sound." << std::endl;
@@ -78,7 +91,7 @@ void AudioPlayer::start()
7891

7992
void AudioPlayer::stop()
8093
{
81-
if (!AudioEngine::initialized())
94+
if (!m_loaded)
8295
return;
8396

8497
ma_result result = ma_sound_stop(m_sound);
@@ -90,7 +103,7 @@ void AudioPlayer::stop()
90103

91104
bool AudioPlayer::isPlaying() const
92105
{
93-
if (!AudioEngine::initialized())
106+
if (!m_loaded)
94107
return false;
95108

96109
return m_started && !m_sound->atEnd;

src/audio/audioplayer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class AudioPlayer : public IAudioPlayer
2929
ma_sound *m_sound;
3030
bool m_loaded = false;
3131
bool m_started = false;
32+
float m_volume = 1;
3233
};
3334

3435
} // namespace libscratchcpp

src/blocks/looksblocks.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -685,8 +685,8 @@ unsigned int LooksBlocks::size(VirtualMachine *vm)
685685

686686
void LooksBlocks::setCostumeByIndex(Target *target, long index)
687687
{
688-
// TODO: Remove this (#248)
689-
std::size_t costumeCount = target->costumes().size();
688+
long costumeCount = target->costumes().size();
689+
690690
if (index < 0 || index >= costumeCount) {
691691
if (index < 0)
692692
index = std::fmod(costumeCount + std::fmod(index, -costumeCount), costumeCount);

src/blocks/soundblocks.cpp

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#include <scratchcpp/iengine.h>
44
#include <scratchcpp/compiler.h>
55
#include <scratchcpp/target.h>
6+
#include <scratchcpp/input.h>
7+
#include <scratchcpp/sound.h>
68

79
#include "soundblocks.h"
810

@@ -16,14 +18,86 @@ std::string SoundBlocks::name() const
1618
void SoundBlocks::registerBlocks(IEngine *engine)
1719
{
1820
// Blocks
21+
engine->addCompileFunction(this, "sound_play", &compilePlay);
22+
engine->addCompileFunction(this, "sound_playuntildone", &compilePlayUntilDone);
23+
engine->addCompileFunction(this, "sound_stopallsounds", &compileStopAllSounds);
1924
engine->addCompileFunction(this, "sound_changevolumeby", &compileChangeVolumeBy);
2025
engine->addCompileFunction(this, "sound_setvolumeto", &compileSetVolumeTo);
2126
engine->addCompileFunction(this, "sound_volume", &compileVolume);
2227

2328
// Inputs
29+
engine->addInput(this, "SOUND_MENU", SOUND_MENU);
2430
engine->addInput(this, "VOLUME", VOLUME);
2531
}
2632

33+
bool SoundBlocks::compilePlayCommon(Compiler *compiler, bool untilDone, bool *byIndex)
34+
{
35+
Target *target = compiler->target();
36+
assert(target);
37+
38+
if (!target)
39+
return false;
40+
41+
Input *input = compiler->input(SOUND_MENU);
42+
43+
if (input->type() != Input::Type::ObscuredShadow) {
44+
assert(input->pointsToDropdownMenu());
45+
std::string value = input->selectedMenuItem();
46+
47+
int index = target->findSound(value);
48+
49+
if (index == -1) {
50+
Value v(value);
51+
52+
if (v.type() == Value::Type::Integer) {
53+
compiler->addConstValue(v.toLong() - 1);
54+
compiler->addFunctionCall(untilDone ? &playByIndexUntilDone : &playByIndex);
55+
56+
if (byIndex)
57+
*byIndex = true;
58+
59+
return true;
60+
}
61+
} else {
62+
compiler->addConstValue(index);
63+
compiler->addFunctionCall(untilDone ? &playByIndexUntilDone : &playByIndex);
64+
65+
if (byIndex)
66+
*byIndex = true;
67+
68+
return true;
69+
}
70+
} else {
71+
compiler->addInput(input);
72+
compiler->addFunctionCall(untilDone ? &playUntilDone : &play);
73+
74+
if (byIndex)
75+
*byIndex = false;
76+
77+
return true;
78+
}
79+
80+
return false;
81+
}
82+
83+
void SoundBlocks::compilePlay(Compiler *compiler)
84+
{
85+
compilePlayCommon(compiler, false);
86+
}
87+
88+
void SoundBlocks::compilePlayUntilDone(Compiler *compiler)
89+
{
90+
bool byIndex = false;
91+
92+
if (compilePlayCommon(compiler, true, &byIndex))
93+
compiler->addFunctionCall(byIndex ? &checkSoundByIndex : &checkSound);
94+
}
95+
96+
void SoundBlocks::compileStopAllSounds(Compiler *compiler)
97+
{
98+
compiler->addFunctionCall(&stopAllSounds);
99+
}
100+
27101
void SoundBlocks::compileChangeVolumeBy(Compiler *compiler)
28102
{
29103
compiler->addInput(VOLUME);
@@ -41,6 +115,161 @@ void SoundBlocks::compileVolume(Compiler *compiler)
41115
compiler->addFunctionCall(&volume);
42116
}
43117

118+
Sound *SoundBlocks::getSoundByIndex(Target *target, long index)
119+
{
120+
long soundCount = target->sounds().size();
121+
122+
if (index < 0 || index >= soundCount) {
123+
if (index < 0)
124+
index = std::fmod(soundCount + std::fmod(index, -soundCount), soundCount);
125+
else
126+
index = std::fmod(index, soundCount);
127+
}
128+
129+
return target->soundAt(index).get();
130+
}
131+
132+
Sound *SoundBlocks::playCommon(VirtualMachine *vm)
133+
{
134+
Target *target = vm->target();
135+
assert(target);
136+
const Value *name = vm->getInput(0, 1);
137+
138+
if (target) {
139+
Sound *sound = target->soundAt(target->findSound(name->toString())).get();
140+
141+
if (sound) {
142+
sound->start();
143+
return sound;
144+
}
145+
146+
else if (name->type() == Value::Type::Integer) {
147+
sound = getSoundByIndex(target, name->toLong() - 1);
148+
149+
if (sound) {
150+
sound->start();
151+
return sound;
152+
}
153+
}
154+
}
155+
156+
return nullptr;
157+
}
158+
159+
Sound *SoundBlocks::playByIndexCommon(VirtualMachine *vm)
160+
{
161+
Target *target = vm->target();
162+
assert(target);
163+
164+
if (target) {
165+
Sound *sound = getSoundByIndex(target, vm->getInput(0, 1)->toInt());
166+
167+
if (sound) {
168+
sound->start();
169+
return sound;
170+
}
171+
}
172+
173+
return nullptr;
174+
}
175+
176+
unsigned int SoundBlocks::play(VirtualMachine *vm)
177+
{
178+
Sound *sound = playCommon(vm);
179+
180+
if (sound)
181+
m_waitingSounds.erase(sound);
182+
183+
return 1;
184+
}
185+
186+
unsigned int SoundBlocks::playByIndex(VirtualMachine *vm)
187+
{
188+
Sound *sound = playByIndexCommon(vm);
189+
190+
if (sound)
191+
m_waitingSounds.erase(sound);
192+
193+
return 1;
194+
}
195+
196+
unsigned int SoundBlocks::playUntilDone(VirtualMachine *vm)
197+
{
198+
Sound *sound = playCommon(vm);
199+
200+
if (sound)
201+
m_waitingSounds[sound] = vm;
202+
203+
return 0; // leave the register for checkSound()
204+
}
205+
206+
unsigned int SoundBlocks::playByIndexUntilDone(VirtualMachine *vm)
207+
{
208+
Sound *sound = playByIndexCommon(vm);
209+
210+
if (sound)
211+
m_waitingSounds[sound] = vm;
212+
213+
return 0; // leave the register for checkSoundByIndex()
214+
}
215+
216+
unsigned int SoundBlocks::checkSound(VirtualMachine *vm)
217+
{
218+
Target *target = vm->target();
219+
assert(target);
220+
const Value *name = vm->getInput(0, 1);
221+
222+
if (target) {
223+
Sound *sound = target->soundAt(target->findSound(name->toString())).get();
224+
225+
if (!sound && name->type() == Value::Type::Integer)
226+
sound = getSoundByIndex(target, name->toLong() - 1);
227+
228+
if (sound) {
229+
auto it = m_waitingSounds.find(sound);
230+
231+
if (it != m_waitingSounds.cend() && it->second == vm) {
232+
if (sound->isPlaying())
233+
vm->stop(true, true, true);
234+
else
235+
m_waitingSounds.erase(sound);
236+
}
237+
}
238+
}
239+
240+
return 1;
241+
}
242+
243+
unsigned int SoundBlocks::checkSoundByIndex(VirtualMachine *vm)
244+
{
245+
Target *target = vm->target();
246+
assert(target);
247+
248+
if (target) {
249+
auto sound = getSoundByIndex(target, vm->getInput(0, 1)->toInt());
250+
251+
if (sound) {
252+
auto it = m_waitingSounds.find(sound);
253+
254+
if (it != m_waitingSounds.cend() && it->second == vm) {
255+
if (sound->isPlaying())
256+
vm->stop(true, true, true);
257+
else
258+
m_waitingSounds.erase(sound);
259+
}
260+
}
261+
}
262+
263+
return 1;
264+
}
265+
266+
unsigned int SoundBlocks::stopAllSounds(VirtualMachine *vm)
267+
{
268+
vm->engine()->stopSounds();
269+
m_waitingSounds.clear();
270+
return 0;
271+
}
272+
44273
unsigned int SoundBlocks::changeVolumeBy(VirtualMachine *vm)
45274
{
46275
if (Target *target = vm->target())

0 commit comments

Comments
 (0)