Skip to content

Commit 2f7619f

Browse files
authored
Merge pull request #322 from scratchcpp/volume_blocks
Implement volume blocks
2 parents bb7a80a + a77513c commit 2f7619f

File tree

8 files changed

+350
-9
lines changed

8 files changed

+350
-9
lines changed

include/scratchcpp/target.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ class LIBSCRATCHCPP_EXPORT Target
6868
int layerOrder() const;
6969
void setLayerOrder(int newLayerOrder);
7070

71-
int volume() const;
72-
void setVolume(int newVolume);
71+
double volume() const;
72+
void setVolume(double newVolume);
7373

7474
IEngine *engine() const;
7575
void setEngine(IEngine *engine);

src/blocks/soundblocks.cpp

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

3+
#include <scratchcpp/iengine.h>
4+
#include <scratchcpp/compiler.h>
5+
#include <scratchcpp/target.h>
6+
37
#include "soundblocks.h"
48

59
using namespace libscratchcpp;
@@ -11,4 +15,54 @@ std::string SoundBlocks::name() const
1115

1216
void SoundBlocks::registerBlocks(IEngine *engine)
1317
{
18+
// Blocks
19+
engine->addCompileFunction(this, "sound_changevolumeby", &compileChangeVolumeBy);
20+
engine->addCompileFunction(this, "sound_setvolumeto", &compileSetVolumeTo);
21+
engine->addCompileFunction(this, "sound_volume", &compileVolume);
22+
23+
// Inputs
24+
engine->addInput(this, "VOLUME", VOLUME);
25+
}
26+
27+
void SoundBlocks::compileChangeVolumeBy(Compiler *compiler)
28+
{
29+
compiler->addInput(VOLUME);
30+
compiler->addFunctionCall(&changeVolumeBy);
31+
}
32+
33+
void SoundBlocks::compileSetVolumeTo(Compiler *compiler)
34+
{
35+
compiler->addInput(VOLUME);
36+
compiler->addFunctionCall(&setVolumeTo);
37+
}
38+
39+
void SoundBlocks::compileVolume(Compiler *compiler)
40+
{
41+
compiler->addFunctionCall(&volume);
42+
}
43+
44+
unsigned int SoundBlocks::changeVolumeBy(VirtualMachine *vm)
45+
{
46+
if (Target *target = vm->target())
47+
target->setVolume(target->volume() + vm->getInput(0, 1)->toDouble());
48+
49+
return 1;
50+
}
51+
52+
unsigned int SoundBlocks::setVolumeTo(VirtualMachine *vm)
53+
{
54+
if (Target *target = vm->target())
55+
target->setVolume(vm->getInput(0, 1)->toDouble());
56+
57+
return 1;
58+
}
59+
60+
unsigned int SoundBlocks::volume(VirtualMachine *vm)
61+
{
62+
if (Target *target = vm->target())
63+
vm->addReturnValue(target->volume());
64+
else
65+
vm->addReturnValue(0);
66+
67+
return 0;
1468
}

src/blocks/soundblocks.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,32 @@ namespace libscratchcpp
1111
class SoundBlocks : public IBlockSection
1212
{
1313
public:
14+
enum Inputs
15+
{
16+
VOLUME
17+
};
18+
19+
enum Fields
20+
{
21+
22+
};
23+
24+
enum FieldValues
25+
{
26+
27+
};
28+
1429
std::string name() const override;
1530

1631
void registerBlocks(IEngine *engine) override;
32+
33+
static void compileChangeVolumeBy(Compiler *compiler);
34+
static void compileSetVolumeTo(Compiler *compiler);
35+
static void compileVolume(Compiler *compiler);
36+
37+
static unsigned int changeVolumeBy(VirtualMachine *vm);
38+
static unsigned int setVolumeTo(VirtualMachine *vm);
39+
static unsigned int volume(VirtualMachine *vm);
1740
};
1841

1942
} // namespace libscratchcpp

src/scratch/target.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -338,15 +338,20 @@ void Target::setLayerOrder(int newLayerOrder)
338338
}
339339

340340
/*! Returns the volume. */
341-
int Target::volume() const
341+
double Target::volume() const
342342
{
343343
return impl->volume;
344344
}
345345

346346
/*! Sets the volume. */
347-
void Target::setVolume(int newVolume)
348-
{
349-
impl->volume = newVolume;
347+
void Target::setVolume(double newVolume)
348+
{
349+
if (newVolume >= 100)
350+
impl->volume = 100;
351+
else if (newVolume <= 0)
352+
impl->volume = 0;
353+
else
354+
impl->volume = newVolume;
350355
}
351356

352357
/*! Returns the engine. */

src/scratch/target_p.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ struct TargetPrivate
3030
std::vector<std::shared_ptr<Costume>> costumes;
3131
std::vector<std::shared_ptr<Sound>> sounds;
3232
int layerOrder = 0;
33-
int volume = 100;
33+
double volume = 100;
3434
};
3535

3636
} // namespace libscratchcpp

test/blocks/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,19 @@ target_link_libraries(
141141
)
142142

143143
gtest_discover_tests(looks_blocks_test)
144+
145+
# sound_blocks_test
146+
add_executable(
147+
sound_blocks_test
148+
sound_blocks_test.cpp
149+
)
150+
151+
target_link_libraries(
152+
sound_blocks_test
153+
GTest::gtest_main
154+
GTest::gmock_main
155+
scratchcpp
156+
scratchcpp_mocks
157+
)
158+
159+
gtest_discover_tests(sound_blocks_test)

test/blocks/sound_blocks_test.cpp

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
#include <scratchcpp/compiler.h>
2+
#include <scratchcpp/block.h>
3+
#include <scratchcpp/input.h>
4+
#include <scratchcpp/field.h>
5+
#include <scratchcpp/target.h>
6+
#include <enginemock.h>
7+
8+
#include "../common.h"
9+
#include "blocks/soundblocks.h"
10+
#include "engine/internal/engine.h"
11+
12+
using namespace libscratchcpp;
13+
14+
using ::testing::Return;
15+
16+
class SoundBlocksTest : public testing::Test
17+
{
18+
public:
19+
void SetUp() override
20+
{
21+
m_section = std::make_unique<SoundBlocks>();
22+
m_section->registerBlocks(&m_engine);
23+
}
24+
25+
std::shared_ptr<Block> createNullBlock(const std::string &id)
26+
{
27+
std::shared_ptr<Block> block = std::make_shared<Block>(id, "");
28+
BlockComp func = [](Compiler *compiler) { compiler->addInstruction(vm::OP_NULL); };
29+
block->setCompileFunction(func);
30+
31+
return block;
32+
}
33+
34+
void addValueInput(std::shared_ptr<Block> block, const std::string &name, SoundBlocks::Inputs id, const Value &value) const
35+
{
36+
auto input = std::make_shared<Input>(name, Input::Type::Shadow);
37+
input->setPrimaryValue(value);
38+
input->setInputId(id);
39+
block->addInput(input);
40+
}
41+
42+
void addObscuredInput(std::shared_ptr<Block> block, const std::string &name, SoundBlocks::Inputs id, std::shared_ptr<Block> valueBlock) const
43+
{
44+
auto input = std::make_shared<Input>(name, Input::Type::ObscuredShadow);
45+
input->setValueBlock(valueBlock);
46+
input->setInputId(id);
47+
block->addInput(input);
48+
}
49+
50+
std::shared_ptr<Input> addNullInput(std::shared_ptr<Block> block, const std::string &name, SoundBlocks::Inputs id) const
51+
{
52+
auto input = std::make_shared<Input>(name, Input::Type::Shadow);
53+
input->setInputId(id);
54+
block->addInput(input);
55+
56+
return input;
57+
}
58+
59+
void addDropdownInput(std::shared_ptr<Block> block, const std::string &name, SoundBlocks::Inputs id, const std::string &selectedValue, std::shared_ptr<Block> valueBlock = nullptr) const
60+
{
61+
if (valueBlock)
62+
addObscuredInput(block, name, id, valueBlock);
63+
else {
64+
auto input = addNullInput(block, name, id);
65+
auto menu = std::make_shared<Block>(block->id() + "_menu", block->opcode() + "_menu");
66+
input->setValueBlock(menu);
67+
addDropdownField(menu, name, static_cast<SoundBlocks::Fields>(-1), selectedValue, static_cast<SoundBlocks::FieldValues>(-1));
68+
}
69+
}
70+
71+
void addDropdownField(std::shared_ptr<Block> block, const std::string &name, SoundBlocks::Fields id, const std::string &value, SoundBlocks::FieldValues valueId) const
72+
{
73+
auto field = std::make_shared<Field>(name, value);
74+
field->setFieldId(id);
75+
field->setSpecialValueId(valueId);
76+
block->addField(field);
77+
}
78+
79+
std::unique_ptr<IBlockSection> m_section;
80+
Engine m_engine;
81+
EngineMock m_engineMock;
82+
};
83+
84+
TEST_F(SoundBlocksTest, Name)
85+
{
86+
ASSERT_EQ(m_section->name(), "Sound");
87+
}
88+
89+
TEST_F(SoundBlocksTest, CategoryVisible)
90+
{
91+
ASSERT_TRUE(m_section->categoryVisible());
92+
}
93+
94+
TEST_F(SoundBlocksTest, RegisterBlocks)
95+
{
96+
// Blocks
97+
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sound_changevolumeby", &SoundBlocks::compileChangeVolumeBy));
98+
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sound_setvolumeto", &SoundBlocks::compileSetVolumeTo));
99+
EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sound_volume", &SoundBlocks::compileVolume));
100+
101+
// Inputs
102+
EXPECT_CALL(m_engineMock, addInput(m_section.get(), "VOLUME", SoundBlocks::VOLUME));
103+
104+
m_section->registerBlocks(&m_engineMock);
105+
}
106+
107+
TEST_F(SoundBlocksTest, ChangeVolumeBy)
108+
{
109+
Compiler compiler(&m_engineMock);
110+
111+
// change volume by (53.05)
112+
auto block1 = std::make_shared<Block>("a", "sound_changevolumeby");
113+
addValueInput(block1, "VOLUME", SoundBlocks::VOLUME, 53.05);
114+
115+
// change volume by (-2.13)
116+
auto block2 = std::make_shared<Block>("b", "sound_changevolumeby");
117+
addValueInput(block2, "VOLUME", SoundBlocks::VOLUME, -2.13);
118+
119+
EXPECT_CALL(m_engineMock, functionIndex(&SoundBlocks::changeVolumeBy)).Times(2).WillRepeatedly(Return(0));
120+
121+
compiler.init();
122+
123+
compiler.setBlock(block1);
124+
SoundBlocks::compileChangeVolumeBy(&compiler);
125+
126+
compiler.setBlock(block2);
127+
SoundBlocks::compileChangeVolumeBy(&compiler);
128+
129+
compiler.end();
130+
131+
ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_HALT }));
132+
ASSERT_EQ(compiler.constValues(), std::vector<Value>({ 53.05, -2.13 }));
133+
}
134+
135+
TEST_F(SoundBlocksTest, ChangeVolumeByImpl)
136+
{
137+
static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
138+
static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_HALT };
139+
static BlockFunc functions[] = { &SoundBlocks::changeVolumeBy };
140+
static Value constValues[] = { 53.05, -2.13 };
141+
142+
Target target;
143+
target.setVolume(42.4);
144+
145+
VirtualMachine vm(&target, nullptr, nullptr);
146+
147+
vm.setBytecode(bytecode1);
148+
vm.setFunctions(functions);
149+
vm.setConstValues(constValues);
150+
vm.run();
151+
152+
ASSERT_EQ(vm.registerCount(), 0);
153+
ASSERT_EQ(std::round(target.volume() * 100) / 100, 95.45);
154+
155+
vm.reset();
156+
vm.setBytecode(bytecode2);
157+
vm.run();
158+
159+
ASSERT_EQ(vm.registerCount(), 0);
160+
ASSERT_EQ(std::round(target.volume() * 100) / 100, 93.32);
161+
}
162+
163+
TEST_F(SoundBlocksTest, SetVolumeTo)
164+
{
165+
Compiler compiler(&m_engineMock);
166+
167+
// set volume to (43.409) %
168+
auto block = std::make_shared<Block>("a", "sound_setvolumeto");
169+
addValueInput(block, "VOLUME", SoundBlocks::VOLUME, 43.409);
170+
171+
EXPECT_CALL(m_engineMock, functionIndex(&SoundBlocks::setVolumeTo)).WillOnce(Return(0));
172+
173+
compiler.init();
174+
compiler.setBlock(block);
175+
SoundBlocks::compileSetVolumeTo(&compiler);
176+
compiler.end();
177+
178+
ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }));
179+
ASSERT_EQ(compiler.constValues().size(), 1);
180+
ASSERT_EQ(compiler.constValues()[0].toDouble(), 43.409);
181+
}
182+
183+
TEST_F(SoundBlocksTest, SetVolumeToImpl)
184+
{
185+
static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
186+
static BlockFunc functions[] = { &SoundBlocks::setVolumeTo };
187+
static Value constValues[] = { 43.409 };
188+
189+
Target target;
190+
target.setVolume(42.4);
191+
192+
VirtualMachine vm(&target, nullptr, nullptr);
193+
194+
vm.setBytecode(bytecode);
195+
vm.setFunctions(functions);
196+
vm.setConstValues(constValues);
197+
vm.run();
198+
199+
ASSERT_EQ(vm.registerCount(), 0);
200+
ASSERT_EQ(target.volume(), 43.409);
201+
}
202+
203+
TEST_F(SoundBlocksTest, Volume)
204+
{
205+
Compiler compiler(&m_engineMock);
206+
207+
auto block = std::make_shared<Block>("a", "sound_volume");
208+
209+
EXPECT_CALL(m_engineMock, functionIndex(&SoundBlocks::volume)).WillOnce(Return(0));
210+
211+
compiler.init();
212+
compiler.setBlock(block);
213+
SoundBlocks::compileVolume(&compiler);
214+
compiler.end();
215+
216+
ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT }));
217+
ASSERT_TRUE(compiler.constValues().empty());
218+
}
219+
220+
TEST_F(SoundBlocksTest, VolumeImpl)
221+
{
222+
static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
223+
static BlockFunc functions[] = { &SoundBlocks::volume };
224+
225+
Target target;
226+
target.setVolume(42.4);
227+
228+
VirtualMachine vm(&target, nullptr, nullptr);
229+
230+
vm.setBytecode(bytecode);
231+
vm.setFunctions(functions);
232+
vm.run();
233+
234+
ASSERT_EQ(vm.registerCount(), 1);
235+
ASSERT_EQ(vm.getInput(0, 1)->toDouble(), 42.4);
236+
}

0 commit comments

Comments
 (0)