Skip to content

Commit 9a5958b

Browse files
committed
codal_port/microbit_soundeffect: Add audio.SoundEffect class.
As per issue #103 and PR #106. Current limitations: - can't play an iterable of SoundEffect objects - if a sound effect is playing with wait=False and another one is started then the first one will be stopped immediately Signed-off-by: Damien George <[email protected]>
1 parent 2b57ddd commit 9a5958b

File tree

7 files changed

+365
-4
lines changed

7 files changed

+365
-4
lines changed

src/codal_app/microbithal.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ void microbit_hal_audio_select_pin(int pin);
160160
void microbit_hal_audio_select_speaker(bool enable);
161161
void microbit_hal_audio_set_volume(int value);
162162
bool microbit_hal_audio_is_expression_active(void);
163-
void microbit_hal_audio_play_expression_by_name(const char *name);
163+
void microbit_hal_audio_play_expression(const char *expr);
164164
void microbit_hal_audio_stop_expression(void);
165165

166166
void microbit_hal_audio_init(uint32_t sample_rate);

src/codal_app/microbithal_audio.cpp

+7-2
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,15 @@ bool microbit_hal_audio_is_expression_active(void) {
9393
return sound_synth_active_count > 0;
9494
}
9595

96-
void microbit_hal_audio_play_expression_by_name(const char *name) {
96+
void microbit_hal_audio_play_expression(const char *expr) {
9797
++sound_synth_active_count;
9898
uBit.audio.soundExpressions.stop();
99-
uBit.audio.soundExpressions.playAsync(name);
99+
100+
// `expr` can be a built-in expression name, or expression data.
101+
// If it's expression data this method parses the data and stores
102+
// it in another buffer ready to play. So `expr` does not need
103+
// to live for the duration of the playing.
104+
uBit.audio.soundExpressions.playAsync(expr);
100105
}
101106

102107
void microbit_hal_audio_stop_expression(void) {

src/codal_port/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ SRC_C += \
7272
microbit_pinaudio.c \
7373
microbit_pinmode.c \
7474
microbit_sound.c \
75+
microbit_soundeffect.c \
7576
microbit_soundevent.c \
7677
microbit_speaker.c \
7778
microbit_spi.c \

src/codal_port/microbit_soundeffect.c

+344
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2022 Damien P. George
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#include "py/runtime.h"
28+
#include "modmicrobit.h"
29+
#include "modaudio.h"
30+
31+
#define SOUND_EXPR_TOTAL_LENGTH (72)
32+
33+
#define SOUND_EXPR_WAVE_OFFSET (0)
34+
#define SOUND_EXPR_WAVE_LENGTH (1)
35+
#define SOUND_EXPR_VOLUME_START_OFFSET (1)
36+
#define SOUND_EXPR_VOLUME_START_LENGTH (4)
37+
#define SOUND_EXPR_FREQUENCY_START_OFFSET (5)
38+
#define SOUND_EXPR_FREQUENCY_START_LENGTH (4)
39+
#define SOUND_EXPR_DURATION_OFFSET (9)
40+
#define SOUND_EXPR_DURATION_LENGTH (4)
41+
#define SOUND_EXPR_SHAPE_OFFSET (13)
42+
#define SOUND_EXPR_SHAPE_LENGTH (2)
43+
#define SOUND_EXPR_FREQUENCY_END_OFFSET (18)
44+
#define SOUND_EXPR_FREQUENCY_END_LENGTH (4)
45+
#define SOUND_EXPR_VOLUME_END_OFFSET (26)
46+
#define SOUND_EXPR_VOLUME_END_LENGTH (4)
47+
#define SOUND_EXPR_STEPS_OFFSET (30)
48+
#define SOUND_EXPR_STEPS_LENGTH (4)
49+
#define SOUND_EXPR_FX_CHOICE_OFFSET (34)
50+
#define SOUND_EXPR_FX_CHOICE_LENGTH (2)
51+
#define SOUND_EXPR_FX_PARAM_OFFSET (36)
52+
#define SOUND_EXPR_FX_PARAM_LENGTH (4)
53+
#define SOUND_EXPR_FX_STEPS_OFFSET (40)
54+
#define SOUND_EXPR_FX_STEPS_LENGTH (4)
55+
56+
#define SOUND_EXPR_ENCODE_VOLUME(v) (((v) * 1023 + 127) / 255)
57+
#define SOUND_EXPR_DECODE_VOLUME(v) (((v) * 255 + 511) / 1023)
58+
59+
#define SOUND_EFFECT_WAVE_SINE (0)
60+
#define SOUND_EFFECT_WAVE_SAWTOOTH (1)
61+
#define SOUND_EFFECT_WAVE_TRIANGLE (2)
62+
#define SOUND_EFFECT_WAVE_SQUARE (3)
63+
#define SOUND_EFFECT_WAVE_NOISE (4)
64+
65+
#define SOUND_EFFECT_SHAPE_LINEAR (1)
66+
#define SOUND_EFFECT_SHAPE_CURVE (2)
67+
#define SOUND_EFFECT_SHAPE_LOG (18)
68+
69+
#define SOUND_EFFECT_FX_NONE (0)
70+
#define SOUND_EFFECT_FX_TREMOLO (2)
71+
#define SOUND_EFFECT_FX_VIBRATO (1)
72+
#define SOUND_EFFECT_FX_WARBLE (3)
73+
74+
#define SOUND_EFFECT_DEFAULT_FREQ_START (500)
75+
#define SOUND_EFFECT_DEFAULT_FREQ_END (2500)
76+
#define SOUND_EFFECT_DEFAULT_DURATION (500)
77+
#define SOUND_EFFECT_DEFAULT_VOL_START (255)
78+
#define SOUND_EFFECT_DEFAULT_VOL_END (0)
79+
#define SOUND_EFFECT_DEFAULT_WAVE (SOUND_EFFECT_WAVE_SQUARE)
80+
#define SOUND_EFFECT_DEFAULT_FX (SOUND_EFFECT_FX_NONE)
81+
#define SOUND_EFFECT_DEFAULT_SHAPE (SOUND_EFFECT_SHAPE_LOG)
82+
83+
typedef struct _microbit_soundeffect_obj_t {
84+
mp_obj_base_t base;
85+
bool is_mutable;
86+
char sound_expr[SOUND_EXPR_TOTAL_LENGTH];
87+
} microbit_soundeffect_obj_t;
88+
89+
typedef struct _soundeffect_attr_t {
90+
uint16_t qst;
91+
uint8_t offset;
92+
uint8_t length;
93+
} soundeffect_attr_t;
94+
95+
STATIC const uint16_t wave_to_qstr_table[5] = {
96+
[SOUND_EFFECT_WAVE_SINE] = MP_QSTR_WAVE_SINE,
97+
[SOUND_EFFECT_WAVE_SAWTOOTH] = MP_QSTR_WAVE_SAWTOOTH,
98+
[SOUND_EFFECT_WAVE_TRIANGLE] = MP_QSTR_WAVE_TRIANGLE,
99+
[SOUND_EFFECT_WAVE_SQUARE] = MP_QSTR_WAVE_SQUARE,
100+
[SOUND_EFFECT_WAVE_NOISE] = MP_QSTR_WAVE_NOISE,
101+
};
102+
103+
STATIC const uint16_t fx_to_qstr_table[4] = {
104+
[SOUND_EFFECT_FX_NONE] = MP_QSTR_FX_NONE,
105+
[SOUND_EFFECT_FX_TREMOLO] = MP_QSTR_FX_TREMOLO,
106+
[SOUND_EFFECT_FX_VIBRATO] = MP_QSTR_FX_VIBRATO,
107+
[SOUND_EFFECT_FX_WARBLE] = MP_QSTR_FX_WARBLE,
108+
};
109+
110+
STATIC const soundeffect_attr_t soundeffect_attr_table[] = {
111+
{ MP_QSTR_freq_start, SOUND_EXPR_FREQUENCY_START_OFFSET, SOUND_EXPR_FREQUENCY_START_LENGTH },
112+
{ MP_QSTR_freq_end, SOUND_EXPR_FREQUENCY_END_OFFSET, SOUND_EXPR_FREQUENCY_END_LENGTH },
113+
{ MP_QSTR_duration, SOUND_EXPR_DURATION_OFFSET, SOUND_EXPR_DURATION_LENGTH },
114+
{ MP_QSTR_vol_start, SOUND_EXPR_VOLUME_START_OFFSET, SOUND_EXPR_VOLUME_START_LENGTH },
115+
{ MP_QSTR_vol_end, SOUND_EXPR_VOLUME_END_OFFSET, SOUND_EXPR_VOLUME_END_LENGTH },
116+
{ MP_QSTR_wave, SOUND_EXPR_WAVE_OFFSET, SOUND_EXPR_WAVE_LENGTH },
117+
{ MP_QSTR_fx, SOUND_EXPR_FX_CHOICE_OFFSET, SOUND_EXPR_FX_CHOICE_LENGTH },
118+
{ MP_QSTR_shape, SOUND_EXPR_SHAPE_OFFSET, SOUND_EXPR_SHAPE_LENGTH },
119+
};
120+
121+
const char *microbit_soundeffect_get_sound_expr_data(mp_obj_t self_in) {
122+
const microbit_soundeffect_obj_t *self = MP_OBJ_TO_PTR(self_in);
123+
return &self->sound_expr[0];
124+
}
125+
126+
STATIC void sound_expr_encode(microbit_soundeffect_obj_t *self, size_t offset, size_t length, unsigned int value) {
127+
if (offset == SOUND_EXPR_VOLUME_START_OFFSET || offset == SOUND_EXPR_VOLUME_END_OFFSET) {
128+
value = SOUND_EXPR_ENCODE_VOLUME(value);
129+
}
130+
for (size_t i = length; i > 0; --i) {
131+
self->sound_expr[offset + i - 1] = '0' + value % 10;
132+
value /= 10;
133+
}
134+
}
135+
136+
STATIC unsigned int sound_expr_decode(const microbit_soundeffect_obj_t *self, size_t offset, size_t length) {
137+
unsigned int value = 0;
138+
for (size_t i = 0; i < length; ++i) {
139+
value = value * 10 + self->sound_expr[offset + i] - '0';
140+
}
141+
if (offset == SOUND_EXPR_VOLUME_START_OFFSET || offset == SOUND_EXPR_VOLUME_END_OFFSET) {
142+
value = SOUND_EXPR_DECODE_VOLUME(value);
143+
}
144+
return value;
145+
}
146+
147+
STATIC void microbit_soundeffect_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
148+
const microbit_soundeffect_obj_t *self = MP_OBJ_TO_PTR(self_in);
149+
150+
unsigned int freq_start = sound_expr_decode(self, SOUND_EXPR_FREQUENCY_START_OFFSET, SOUND_EXPR_FREQUENCY_START_LENGTH);
151+
unsigned int freq_end = sound_expr_decode(self, SOUND_EXPR_FREQUENCY_END_OFFSET, SOUND_EXPR_FREQUENCY_END_LENGTH);
152+
unsigned int duration = sound_expr_decode(self, SOUND_EXPR_DURATION_OFFSET, SOUND_EXPR_DURATION_LENGTH);
153+
unsigned int vol_start = sound_expr_decode(self, SOUND_EXPR_VOLUME_START_OFFSET, SOUND_EXPR_VOLUME_START_LENGTH);
154+
unsigned int vol_end = sound_expr_decode(self, SOUND_EXPR_VOLUME_END_OFFSET, SOUND_EXPR_VOLUME_END_LENGTH);
155+
unsigned int wave = sound_expr_decode(self, SOUND_EXPR_WAVE_OFFSET, SOUND_EXPR_WAVE_LENGTH);
156+
unsigned int fx = sound_expr_decode(self, SOUND_EXPR_FX_CHOICE_OFFSET, SOUND_EXPR_FX_CHOICE_LENGTH);
157+
unsigned int shape = sound_expr_decode(self, SOUND_EXPR_SHAPE_OFFSET, SOUND_EXPR_SHAPE_LENGTH);
158+
159+
if (kind == PRINT_STR) {
160+
mp_printf(print, "SoundEffect("
161+
"freq_start=%d, "
162+
"freq_end=%d, "
163+
"duration=%d, "
164+
"vol_start=%d, "
165+
"vol_end=%d, "
166+
"wave=%q, "
167+
"fx=%q, ",
168+
freq_start,
169+
freq_end,
170+
duration,
171+
vol_start,
172+
vol_end,
173+
wave_to_qstr_table[wave],
174+
fx_to_qstr_table[fx]
175+
);
176+
177+
// Support shape values that don't have a corresponding constant assigned.
178+
switch (shape) {
179+
case SOUND_EFFECT_SHAPE_LINEAR:
180+
mp_printf(print, "shape=SHAPE_LINEAR)");
181+
break;
182+
case SOUND_EFFECT_SHAPE_CURVE:
183+
mp_printf(print, "shape=SHAPE_CURVE)");
184+
break;
185+
case SOUND_EFFECT_SHAPE_LOG:
186+
mp_printf(print, "shape=SHAPE_LOG)");
187+
break;
188+
default:
189+
mp_printf(print, "shape=%d)", shape);
190+
break;
191+
}
192+
} else {
193+
// PRINT_REPR
194+
mp_printf(print, "SoundEffect(%d, %d, %d, %d, %d, %d, %d, %d)",
195+
freq_start, freq_end, duration, vol_start, vol_end, wave, fx, shape);
196+
}
197+
}
198+
199+
// Constructor:
200+
// SoundEffect(freq_start, freq_end, duration, vol_start, vol_end, wave, fx, shape)
201+
STATIC mp_obj_t microbit_soundeffect_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args_in) {
202+
enum { ARG_freq_start, ARG_freq_end, ARG_duration, ARG_vol_start, ARG_vol_end, ARG_wave, ARG_fx, ARG_shape };
203+
static const mp_arg_t allowed_args[] = {
204+
{ MP_QSTR_freq_start, MP_ARG_INT, {.u_int = SOUND_EFFECT_DEFAULT_FREQ_START} },
205+
{ MP_QSTR_freq_end, MP_ARG_INT, {.u_int = SOUND_EFFECT_DEFAULT_FREQ_END} },
206+
{ MP_QSTR_duration, MP_ARG_INT, {.u_int = SOUND_EFFECT_DEFAULT_DURATION} },
207+
{ MP_QSTR_vol_start, MP_ARG_INT, {.u_int = SOUND_EFFECT_DEFAULT_VOL_START} },
208+
{ MP_QSTR_vol_end, MP_ARG_INT, {.u_int = SOUND_EFFECT_DEFAULT_VOL_END} },
209+
{ MP_QSTR_wave, MP_ARG_INT, {.u_int = SOUND_EFFECT_DEFAULT_WAVE} },
210+
{ MP_QSTR_fx, MP_ARG_INT, {.u_int = SOUND_EFFECT_DEFAULT_FX} },
211+
{ MP_QSTR_shape, MP_ARG_INT, {.u_int = SOUND_EFFECT_DEFAULT_SHAPE} },
212+
};
213+
214+
// Parse arguments.
215+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
216+
mp_arg_parse_all_kw_array(n_args, n_kw, args_in, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
217+
218+
// Create sound effect object.
219+
microbit_soundeffect_obj_t *self = m_new_obj(microbit_soundeffect_obj_t);
220+
self->base.type = type;
221+
self->is_mutable = true;
222+
223+
// Initialise base parameters of the sound expression data.
224+
memset(&self->sound_expr[0], '0', SOUND_EXPR_TOTAL_LENGTH);
225+
sound_expr_encode(self, SOUND_EXPR_STEPS_OFFSET, SOUND_EXPR_STEPS_LENGTH, 128);
226+
sound_expr_encode(self, SOUND_EXPR_FX_PARAM_OFFSET, SOUND_EXPR_FX_PARAM_LENGTH, 1);
227+
sound_expr_encode(self, SOUND_EXPR_FX_STEPS_OFFSET, SOUND_EXPR_FX_STEPS_LENGTH, 24);
228+
229+
// Modify any given parameters.
230+
sound_expr_encode(self, SOUND_EXPR_FREQUENCY_START_OFFSET, SOUND_EXPR_FREQUENCY_START_LENGTH, args[ARG_freq_start].u_int);
231+
sound_expr_encode(self, SOUND_EXPR_FREQUENCY_END_OFFSET, SOUND_EXPR_FREQUENCY_END_LENGTH, args[ARG_freq_end].u_int);
232+
sound_expr_encode(self, SOUND_EXPR_DURATION_OFFSET, SOUND_EXPR_DURATION_LENGTH, args[ARG_duration].u_int);
233+
sound_expr_encode(self, SOUND_EXPR_VOLUME_START_OFFSET, SOUND_EXPR_VOLUME_START_LENGTH, args[ARG_vol_start].u_int);
234+
sound_expr_encode(self, SOUND_EXPR_VOLUME_END_OFFSET, SOUND_EXPR_VOLUME_END_LENGTH, args[ARG_vol_end].u_int);
235+
sound_expr_encode(self, SOUND_EXPR_WAVE_OFFSET, SOUND_EXPR_WAVE_LENGTH, args[ARG_wave].u_int);
236+
sound_expr_encode(self, SOUND_EXPR_FX_CHOICE_OFFSET, SOUND_EXPR_FX_CHOICE_LENGTH, args[ARG_fx].u_int);
237+
sound_expr_encode(self, SOUND_EXPR_SHAPE_OFFSET, SOUND_EXPR_SHAPE_LENGTH, args[ARG_shape].u_int);
238+
239+
// Return new sound effect object
240+
return MP_OBJ_FROM_PTR(self);
241+
}
242+
243+
STATIC void microbit_soundeffect_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
244+
microbit_soundeffect_obj_t *self = MP_OBJ_TO_PTR(self_in);
245+
const soundeffect_attr_t *soundeffect_attr = NULL;
246+
for (size_t i = 0; i < MP_ARRAY_SIZE(soundeffect_attr_table); ++i) {
247+
if (soundeffect_attr_table[i].qst == attr) {
248+
soundeffect_attr = &soundeffect_attr_table[i];
249+
break;
250+
}
251+
}
252+
if (soundeffect_attr == NULL) {
253+
// Invalid attribute, set MP_OBJ_SENTINEL to continue lookup in locals dict.
254+
dest[1] = MP_OBJ_SENTINEL;
255+
return;
256+
}
257+
if (dest[0] == MP_OBJ_NULL) {
258+
// Load attribute.
259+
unsigned int value = sound_expr_decode(self, soundeffect_attr->offset, soundeffect_attr->length);
260+
if (attr == MP_QSTR_fx && value == 0) {
261+
dest[0] = mp_const_none;
262+
} else {
263+
dest[0] = MP_OBJ_NEW_SMALL_INT(value);
264+
}
265+
} else if (dest[1] != MP_OBJ_NULL) {
266+
// Store attribute.
267+
if (self->is_mutable) {
268+
unsigned int value = 0;
269+
if (dest[1] != mp_const_none) {
270+
value = mp_obj_get_int(dest[1]);
271+
}
272+
sound_expr_encode(self, soundeffect_attr->offset, soundeffect_attr->length, value);
273+
dest[0] = MP_OBJ_NULL; // Indicate store succeeded.
274+
}
275+
}
276+
}
277+
278+
STATIC mp_obj_t microbit_soundeffect_from_string(mp_obj_t str_in) {
279+
microbit_soundeffect_obj_t *self = m_new_obj(microbit_soundeffect_obj_t);
280+
self->base.type = &microbit_soundeffect_type;
281+
self->is_mutable = true;
282+
283+
// Initialise the sound expression data with the preset values.
284+
memset(&self->sound_expr[0], '0', SOUND_EXPR_TOTAL_LENGTH);
285+
size_t len;
286+
const char *str = mp_obj_str_get_data(str_in, &len);
287+
if (len > SOUND_EXPR_TOTAL_LENGTH) {
288+
len = SOUND_EXPR_TOTAL_LENGTH;
289+
}
290+
memcpy(&self->sound_expr[0], str, len);
291+
292+
return MP_OBJ_FROM_PTR(self);
293+
}
294+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(microbit_soundeffect_from_string_obj, microbit_soundeffect_from_string);
295+
STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(microbit_soundeffect_from_string_staticmethod_obj, MP_ROM_PTR(&microbit_soundeffect_from_string_obj));
296+
297+
STATIC mp_obj_t microbit_soundeffect_copy(mp_obj_t self_in) {
298+
microbit_soundeffect_obj_t *self = MP_OBJ_TO_PTR(self_in);
299+
microbit_soundeffect_obj_t *copy = m_new_obj(microbit_soundeffect_obj_t);
300+
copy->base.type = self->base.type;
301+
copy->is_mutable = true;
302+
memcpy(&copy->sound_expr[0], &self->sound_expr[0], SOUND_EXPR_TOTAL_LENGTH);
303+
304+
return MP_OBJ_FROM_PTR(copy);
305+
}
306+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(microbit_soundeffect_copy_obj, microbit_soundeffect_copy);
307+
308+
STATIC const mp_rom_map_elem_t microbit_soundeffect_locals_dict_table[] = {
309+
// Static methods.
310+
{ MP_ROM_QSTR(MP_QSTR__from_string), MP_ROM_PTR(&microbit_soundeffect_from_string_staticmethod_obj) },
311+
312+
// Instance methods.
313+
{ MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&microbit_soundeffect_copy_obj) },
314+
315+
// Class constants.
316+
#define C(NAME) { MP_ROM_QSTR(MP_QSTR_ ## NAME), MP_ROM_INT(SOUND_EFFECT_ ## NAME) }
317+
318+
C(WAVE_SINE),
319+
C(WAVE_SAWTOOTH),
320+
C(WAVE_TRIANGLE),
321+
C(WAVE_SQUARE),
322+
C(WAVE_NOISE),
323+
324+
C(SHAPE_LINEAR),
325+
C(SHAPE_CURVE),
326+
C(SHAPE_LOG),
327+
328+
C(FX_NONE),
329+
C(FX_TREMOLO),
330+
C(FX_VIBRATO),
331+
C(FX_WARBLE),
332+
333+
#undef C
334+
};
335+
STATIC MP_DEFINE_CONST_DICT(microbit_soundeffect_locals_dict, microbit_soundeffect_locals_dict_table);
336+
337+
const mp_obj_type_t microbit_soundeffect_type = {
338+
{ &mp_type_type },
339+
.name = MP_QSTR_MicroBitSoundEffect,
340+
.print = microbit_soundeffect_print,
341+
.make_new = microbit_soundeffect_make_new,
342+
.attr = microbit_soundeffect_attr,
343+
.locals_dict = (mp_obj_dict_t *)&microbit_soundeffect_locals_dict,
344+
};

0 commit comments

Comments
 (0)