Skip to content

Commit 19dc670

Browse files
committed
codal_port: Add SoundEffect class.
Signed-off-by: Damien George <[email protected]>
1 parent 569e361 commit 19dc670

File tree

4 files changed

+310
-1
lines changed

4 files changed

+310
-1
lines changed

src/codal_port/Makefile

Lines changed: 1 addition & 0 deletions
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

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
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+
30+
#define SOUND_EXPR_TOTAL_LENGTH (72)
31+
32+
#define SOUND_EXPR_WAVE_OFFSET (0)
33+
#define SOUND_EXPR_WAVE_LENGTH (1)
34+
#define SOUND_EXPR_VOLUME_START_OFFSET (1)
35+
#define SOUND_EXPR_VOLUME_START_LENGTH (4)
36+
#define SOUND_EXPR_FREQUENCY_START_OFFSET (5)
37+
#define SOUND_EXPR_FREQUENCY_START_LENGTH (4)
38+
#define SOUND_EXPR_DURATION_OFFSET (9)
39+
#define SOUND_EXPR_DURATION_LENGTH (4)
40+
#define SOUND_EXPR_SHAPE_OFFSET (13)
41+
#define SOUND_EXPR_SHAPE_LENGTH (2)
42+
#define SOUND_EXPR_FREQUENCY_END_OFFSET (18)
43+
#define SOUND_EXPR_FREQUENCY_END_LENGTH (4)
44+
#define SOUND_EXPR_VOLUME_END_OFFSET (26)
45+
#define SOUND_EXPR_VOLUME_END_LENGTH (4)
46+
#define SOUND_EXPR_STEPS_OFFSET (30)
47+
#define SOUND_EXPR_STEPS_LENGTH (4)
48+
#define SOUND_EXPR_FX_CHOICE_OFFSET (34)
49+
#define SOUND_EXPR_FX_CHOICE_LENGTH (2)
50+
#define SOUND_EXPR_FX_PARAM_OFFSET (36)
51+
#define SOUND_EXPR_FX_PARAM_LENGTH (4)
52+
#define SOUND_EXPR_FX_STEPS_OFFSET (40)
53+
#define SOUND_EXPR_FX_STEPS_LENGTH (4)
54+
55+
#define SOUND_EXPR_ENCODE_VOLUME(v) ((v) * 1023 / 255)
56+
#define SOUND_EXPR_DECODE_VOLUME(v) ((v) * 255 / 1023)
57+
58+
#define SOUND_EXPR_DEFAULT "3""1023""0500""0500""18""000""2500""0000""0000""0128""00""0001""0024""0000000000000000000000000000"
59+
60+
#define SOUND_EFFECT_WAVE_SINE (0)
61+
#define SOUND_EFFECT_WAVE_SAWTOOTH (1)
62+
#define SOUND_EFFECT_WAVE_TRIANGLE (2)
63+
#define SOUND_EFFECT_WAVE_SQUARE (3)
64+
#define SOUND_EFFECT_WAVE_NOISE (4)
65+
66+
#define SOUND_EFFECT_INTER_LINEAR (1)
67+
#define SOUND_EFFECT_INTER_CURVE (2)
68+
#define SOUND_EFFECT_INTER_LOG (18)
69+
70+
#define SOUND_EFFECT_FX_NONE (0) // Python "None" object
71+
#define SOUND_EFFECT_FX_TREMOLO (2)
72+
#define SOUND_EFFECT_FX_VIBRATO (1)
73+
#define SOUND_EFFECT_FX_WARBLE (3)
74+
75+
typedef struct _microbit_soundeffect_obj_t {
76+
mp_obj_base_t base;
77+
bool is_mutable;
78+
char sound_expr[SOUND_EXPR_TOTAL_LENGTH];
79+
} microbit_soundeffect_obj_t;
80+
81+
STATIC const uint16_t wave_to_qstr_table[5] = {
82+
[SOUND_EFFECT_WAVE_SINE] = MP_QSTR_WAVE_SINE,
83+
[SOUND_EFFECT_WAVE_SAWTOOTH] = MP_QSTR_WAVE_SAWTOOTH,
84+
[SOUND_EFFECT_WAVE_TRIANGLE] = MP_QSTR_WAVE_TRIANGLE,
85+
[SOUND_EFFECT_WAVE_SQUARE] = MP_QSTR_WAVE_SQUARE,
86+
[SOUND_EFFECT_WAVE_NOISE] = MP_QSTR_WAVE_NOISE,
87+
};
88+
89+
STATIC const uint16_t fx_to_qstr_table[4] = {
90+
[0] = MP_QSTR_None,
91+
[SOUND_EFFECT_FX_TREMOLO] = MP_QSTR_FX_TREMOLO,
92+
[SOUND_EFFECT_FX_VIBRATO] = MP_QSTR_FX_VIBRATO,
93+
[SOUND_EFFECT_FX_WARBLE] = MP_QSTR_FX_WARBLE,
94+
};
95+
96+
const char *microbit_soundeffect_get_sound_expr_data(mp_obj_t self_in) {
97+
const microbit_soundeffect_obj_t *self = MP_OBJ_TO_PTR(self_in);
98+
return &self->sound_expr[0];
99+
}
100+
101+
STATIC void sound_expr_encode(microbit_soundeffect_obj_t *self, size_t offset, size_t length, unsigned int value) {
102+
if (offset == SOUND_EXPR_VOLUME_START_OFFSET || offset == SOUND_EXPR_VOLUME_END_OFFSET) {
103+
value = SOUND_EXPR_ENCODE_VOLUME(value);
104+
}
105+
for (size_t i = length; i > 0; --i) {
106+
self->sound_expr[offset + i - 1] = '0' + value % 10;
107+
value /= 10;
108+
}
109+
}
110+
111+
STATIC unsigned int sound_expr_decode(const microbit_soundeffect_obj_t *self, size_t offset, size_t length) {
112+
unsigned int value = 0;
113+
for (size_t i = 0; i < length; ++i) {
114+
value = value * 10 + self->sound_expr[offset + i] - '0';
115+
}
116+
if (offset == SOUND_EXPR_VOLUME_START_OFFSET || offset == SOUND_EXPR_VOLUME_END_OFFSET) {
117+
value = SOUND_EXPR_DECODE_VOLUME(value);
118+
}
119+
return value;
120+
}
121+
122+
STATIC void microbit_soundeffect_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
123+
const microbit_soundeffect_obj_t *self = MP_OBJ_TO_PTR(self_in);
124+
125+
unsigned int freq_start = sound_expr_decode(self, SOUND_EXPR_FREQUENCY_START_OFFSET, SOUND_EXPR_FREQUENCY_START_LENGTH);
126+
unsigned int freq_end = sound_expr_decode(self, SOUND_EXPR_FREQUENCY_END_OFFSET, SOUND_EXPR_FREQUENCY_END_LENGTH);
127+
unsigned int duration = sound_expr_decode(self, SOUND_EXPR_DURATION_OFFSET, SOUND_EXPR_DURATION_LENGTH);
128+
unsigned int vol_start = sound_expr_decode(self, SOUND_EXPR_VOLUME_START_OFFSET, SOUND_EXPR_VOLUME_START_LENGTH);
129+
unsigned int vol_end = sound_expr_decode(self, SOUND_EXPR_VOLUME_END_OFFSET, SOUND_EXPR_VOLUME_END_LENGTH);
130+
unsigned int wave = sound_expr_decode(self, SOUND_EXPR_WAVE_OFFSET, SOUND_EXPR_WAVE_LENGTH);
131+
unsigned int fx = sound_expr_decode(self, SOUND_EXPR_FX_CHOICE_OFFSET, SOUND_EXPR_FX_CHOICE_LENGTH);
132+
unsigned int shape = sound_expr_decode(self, SOUND_EXPR_SHAPE_OFFSET, SOUND_EXPR_SHAPE_LENGTH);
133+
134+
mp_printf(print, "SoundEffect("
135+
"freq_start=%d, "
136+
"freq_end=%d, "
137+
"duration=%d, "
138+
"vol_start=%d, "
139+
"vol_end=%d, "
140+
"wave=%q, "
141+
"fx=%q, ",
142+
freq_start,
143+
freq_end,
144+
duration,
145+
vol_start,
146+
vol_end,
147+
wave_to_qstr_table[wave],
148+
fx_to_qstr_table[fx]
149+
);
150+
151+
// Support shape values that don't have a corresponding constant assigned.
152+
switch (shape) {
153+
case SOUND_EFFECT_INTER_LINEAR:
154+
mp_printf(print, "interpolation=INTER_LINEAR)");
155+
break;
156+
case SOUND_EFFECT_INTER_CURVE:
157+
mp_printf(print, "interpolation=INTER_CURVE)");
158+
break;
159+
case SOUND_EFFECT_INTER_LOG:
160+
mp_printf(print, "interpolation=INTER_LOG)");
161+
break;
162+
default:
163+
mp_printf(print, "interpolation=%d)", shape);
164+
break;
165+
}
166+
}
167+
168+
// Constructor:
169+
// SoundEffect(preset=None, *, freq_start, freq_end, duration, vol_start, vol_end, wave, fx, interpolation)
170+
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) {
171+
enum { ARG_preset, ARG_freq_start, ARG_freq_end, ARG_duration, ARG_vol_start, ARG_vol_end, ARG_wave, ARG_fx, ARG_interpolation };
172+
static const mp_arg_t allowed_args[] = {
173+
{ MP_QSTR_preset, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} },
174+
{ MP_QSTR_freq_start, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
175+
{ MP_QSTR_freq_end, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
176+
{ MP_QSTR_duration, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
177+
{ MP_QSTR_vol_start, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
178+
{ MP_QSTR_vol_end, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
179+
{ MP_QSTR_wave, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
180+
{ MP_QSTR_fx, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
181+
{ MP_QSTR_interpolation, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
182+
};
183+
184+
// Parse arguments.
185+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
186+
mp_arg_parse_all_kw_array(n_args, n_kw, args_in, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
187+
188+
// Create sound effect object.
189+
microbit_soundeffect_obj_t *self = m_new_obj(microbit_soundeffect_obj_t);
190+
self->base.type = type;
191+
self->is_mutable = true;
192+
193+
// Initialise the sound expression data with the preset values.
194+
const char *sound_expr_preset = SOUND_EXPR_DEFAULT;
195+
if (args[ARG_preset].u_obj != mp_const_none) {
196+
if (mp_obj_is_type(args[ARG_preset].u_obj, &microbit_soundeffect_type)) {
197+
sound_expr_preset = microbit_soundeffect_get_sound_expr_data(args[ARG_preset].u_obj);
198+
} else {
199+
sound_expr_preset = mp_obj_str_get_str(args[ARG_preset].u_obj);
200+
}
201+
}
202+
memcpy(&self->sound_expr[0], sound_expr_preset, SOUND_EXPR_TOTAL_LENGTH);
203+
204+
// Modify any given parameters.
205+
if (args[ARG_freq_start].u_obj != MP_OBJ_NULL) {
206+
sound_expr_encode(self, SOUND_EXPR_FREQUENCY_START_OFFSET, SOUND_EXPR_FREQUENCY_START_LENGTH, mp_obj_get_int(args[ARG_freq_start].u_obj));
207+
}
208+
if (args[ARG_freq_end].u_obj != MP_OBJ_NULL) {
209+
sound_expr_encode(self, SOUND_EXPR_FREQUENCY_END_OFFSET, SOUND_EXPR_FREQUENCY_END_LENGTH, mp_obj_get_int(args[ARG_freq_end].u_obj));
210+
}
211+
if (args[ARG_duration].u_obj != MP_OBJ_NULL) {
212+
sound_expr_encode(self, SOUND_EXPR_DURATION_OFFSET, SOUND_EXPR_DURATION_LENGTH, mp_obj_get_int(args[ARG_duration].u_obj));
213+
}
214+
if (args[ARG_vol_start].u_obj != MP_OBJ_NULL) {
215+
sound_expr_encode(self, SOUND_EXPR_VOLUME_START_OFFSET, SOUND_EXPR_VOLUME_START_LENGTH, mp_obj_get_int(args[ARG_vol_start].u_obj));
216+
}
217+
if (args[ARG_vol_end].u_obj != MP_OBJ_NULL) {
218+
sound_expr_encode(self, SOUND_EXPR_VOLUME_END_OFFSET, SOUND_EXPR_VOLUME_END_LENGTH, mp_obj_get_int(args[ARG_vol_end].u_obj));
219+
}
220+
if (args[ARG_wave].u_obj != MP_OBJ_NULL) {
221+
sound_expr_encode(self, SOUND_EXPR_WAVE_OFFSET, SOUND_EXPR_WAVE_LENGTH, mp_obj_get_int(args[ARG_wave].u_obj));
222+
}
223+
if (args[ARG_fx].u_obj != MP_OBJ_NULL) {
224+
sound_expr_encode(self, SOUND_EXPR_FX_CHOICE_OFFSET, SOUND_EXPR_FX_CHOICE_LENGTH, args[ARG_fx].u_obj == mp_const_none ? 0 : mp_obj_get_int(args[ARG_fx].u_obj));
225+
}
226+
if (args[ARG_interpolation].u_obj != MP_OBJ_NULL) {
227+
sound_expr_encode(self, SOUND_EXPR_SHAPE_OFFSET, SOUND_EXPR_SHAPE_LENGTH, mp_obj_get_int(args[ARG_interpolation].u_obj));
228+
}
229+
230+
// Return new sound effect object
231+
return MP_OBJ_FROM_PTR(self);
232+
}
233+
234+
typedef struct _soundeffect_attr_t {
235+
uint16_t qst;
236+
uint8_t offset;
237+
uint8_t length;
238+
} soundeffect_attr_t;
239+
240+
STATIC const soundeffect_attr_t soundeffect_attr_table[] = {
241+
{ MP_QSTR_freq_start, SOUND_EXPR_FREQUENCY_START_OFFSET, SOUND_EXPR_FREQUENCY_START_LENGTH },
242+
{ MP_QSTR_freq_end, SOUND_EXPR_FREQUENCY_END_OFFSET, SOUND_EXPR_FREQUENCY_END_LENGTH },
243+
{ MP_QSTR_duration, SOUND_EXPR_DURATION_OFFSET, SOUND_EXPR_DURATION_LENGTH },
244+
{ MP_QSTR_vol_start, SOUND_EXPR_VOLUME_START_OFFSET, SOUND_EXPR_VOLUME_START_LENGTH },
245+
{ MP_QSTR_vol_end, SOUND_EXPR_VOLUME_END_OFFSET, SOUND_EXPR_VOLUME_END_LENGTH },
246+
{ MP_QSTR_wave, SOUND_EXPR_WAVE_OFFSET, SOUND_EXPR_WAVE_LENGTH },
247+
{ MP_QSTR_fx, SOUND_EXPR_FX_CHOICE_OFFSET, SOUND_EXPR_FX_CHOICE_LENGTH },
248+
{ MP_QSTR_interpolation, SOUND_EXPR_SHAPE_OFFSET, SOUND_EXPR_SHAPE_LENGTH },
249+
};
250+
251+
STATIC void microbit_soundeffect_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
252+
microbit_soundeffect_obj_t *self = MP_OBJ_TO_PTR(self_in);
253+
const soundeffect_attr_t *soundeffect_attr = NULL;
254+
for (size_t i = 0; i < MP_ARRAY_SIZE(soundeffect_attr_table); ++i) {
255+
if (soundeffect_attr_table[i].qst == attr) {
256+
soundeffect_attr = &soundeffect_attr_table[i];
257+
break;
258+
}
259+
}
260+
if (soundeffect_attr == NULL) {
261+
// Invalid attribute.
262+
return;
263+
}
264+
if (dest[0] == MP_OBJ_NULL) {
265+
// Load attribute.
266+
unsigned int value = sound_expr_decode(self, soundeffect_attr->offset, soundeffect_attr->length);
267+
if (attr == MP_QSTR_fx && value == 0) {
268+
dest[0] = mp_const_none;
269+
} else {
270+
dest[0] = MP_OBJ_NEW_SMALL_INT(value);
271+
}
272+
} else if (dest[1] != MP_OBJ_NULL) {
273+
// Store attribute.
274+
if (self->is_mutable) {
275+
unsigned int value = 0;
276+
if (dest[1] != mp_const_none) {
277+
value = mp_obj_get_int(dest[1]);
278+
}
279+
sound_expr_encode(self, soundeffect_attr->offset, soundeffect_attr->length, value);
280+
dest[0] = MP_OBJ_NULL; // Indicate store succeeded.
281+
}
282+
}
283+
}
284+
285+
STATIC const mp_rom_map_elem_t microbit_soundeffect_locals_dict_table[] = {
286+
};
287+
STATIC MP_DEFINE_CONST_DICT(microbit_soundeffect_locals_dict, microbit_soundeffect_locals_dict_table);
288+
289+
const mp_obj_type_t microbit_soundeffect_type = {
290+
{ &mp_type_type },
291+
.name = MP_QSTR_MicroBitSoundEffect,
292+
.print = microbit_soundeffect_print,
293+
.make_new = microbit_soundeffect_make_new,
294+
.attr = microbit_soundeffect_attr,
295+
.locals_dict = (mp_obj_dict_t *)&microbit_soundeffect_locals_dict,
296+
};

src/codal_port/modaudio.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,17 @@ void microbit_audio_play_source(mp_obj_t src, mp_obj_t pin_select, bool wait, ui
137137
audio_init(sample_rate);
138138
microbit_pin_audio_select(pin_select);
139139

140+
const char *sound_expr_data = NULL;
140141
if (mp_obj_is_type(src, &microbit_sound_type)) {
141142
const microbit_sound_obj_t *sound = (const microbit_sound_obj_t *)MP_OBJ_TO_PTR(src);
142-
microbit_hal_audio_play_expression_by_name(sound->name);
143+
sound_expr_data = sound->name;
144+
} else if (mp_obj_is_type(src, &microbit_soundeffect_type)) {
145+
// TODO make sure we keep a reference to this so GC doesn't reclaim if playing in the background?
146+
sound_expr_data = microbit_soundeffect_get_sound_expr_data(src);
147+
}
148+
149+
if (sound_expr_data != NULL) {
150+
microbit_hal_audio_play_expression_by_name(sound_expr_data);
143151
if (wait) {
144152
nlr_buf_t nlr;
145153
if (nlr_push(&nlr) == 0) {
@@ -215,6 +223,7 @@ STATIC const mp_rom_map_elem_t audio_globals_table[] = {
215223
{ MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&microbit_audio_play_obj) },
216224
{ MP_ROM_QSTR(MP_QSTR_is_playing), MP_ROM_PTR(&microbit_audio_is_playing_obj) },
217225
{ MP_ROM_QSTR(MP_QSTR_AudioFrame), MP_ROM_PTR(&microbit_audio_frame_type) },
226+
{ MP_ROM_QSTR(MP_QSTR_SoundEffect), MP_ROM_PTR(&microbit_soundeffect_type) },
218227
};
219228
STATIC MP_DEFINE_CONST_DICT(audio_module_globals, audio_globals_table);
220229

src/codal_port/modmicrobit.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ extern const mp_obj_type_t microbit_dig_pin_type;
8585
extern const mp_obj_type_t microbit_touch_pin_type;
8686
extern const mp_obj_type_t microbit_touch_only_pin_type;
8787
extern const mp_obj_type_t microbit_sound_type;
88+
extern const mp_obj_type_t microbit_soundeffect_type;
8889
extern const mp_obj_type_t microbit_soundevent_type;
8990

9091
extern const struct _microbit_pin_obj_t microbit_p0_obj;
@@ -214,6 +215,8 @@ void microbit_pin_audio_speaker_enable(bool enable);
214215
void microbit_pin_audio_select(mp_const_obj_t select);
215216
void microbit_pin_audio_free(void);
216217

218+
const char *microbit_soundeffect_get_sound_expr_data(mp_obj_t self_in);
219+
217220
MP_DECLARE_CONST_FUN_OBJ_0(microbit_reset_obj);
218221

219222
#endif // MICROPY_INCLUDED_MICROBIT_MODMICROBIT_H

0 commit comments

Comments
 (0)