Skip to content

Commit 228bbd5

Browse files
committed
codal_port: Implement AudioTrack and AudioRecording.
Signed-off-by: Damien George <[email protected]>
1 parent 8c3b838 commit 228bbd5

8 files changed

+450
-40
lines changed

src/codal_port/Makefile

+3
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ SRC_C += \
6969
iters.c \
7070
main.c \
7171
microbit_accelerometer.c \
72+
microbit_audiorecording.c \
73+
microbit_audiotrack.c \
7274
microbit_button.c \
7375
microbit_compass.c \
7476
microbit_display.c \
@@ -100,6 +102,7 @@ SRC_C += \
100102
modspeech.c \
101103
modthis.c \
102104
mphalport.c \
105+
utils.c \
103106

104107
SRC_C += \
105108
shared/readline/readline.c \
+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2024 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/mphal.h"
28+
#include "drv_system.h"
29+
#include "modmicrobit.h"
30+
#include "modaudio.h"
31+
#include "utils.h"
32+
33+
mp_obj_t microbit_audio_recording_new(size_t num_bytes, uint32_t rate) {
34+
// Make sure size is non-zero.
35+
if (num_bytes == 0) {
36+
num_bytes = 1;
37+
}
38+
39+
// Create and return the AudioRecording object.
40+
uint8_t *data = m_new(uint8_t, num_bytes);
41+
memset(data, 128, num_bytes);
42+
return microbit_audio_track_new(MP_OBJ_NULL, num_bytes, data, rate);
43+
}
44+
45+
static mp_obj_t microbit_audio_recording_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
46+
(void)type_in;
47+
48+
enum { ARG_duration, ARG_rate };
49+
static const mp_arg_t allowed_args[] = {
50+
{ MP_QSTR_duration, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_OBJ_NULL} },
51+
{ MP_QSTR_rate, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(AUDIO_TRACK_DEFAULT_SAMPLE_RATE)} },
52+
};
53+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
54+
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
55+
56+
mp_int_t rate = mp_obj_get_int_allow_float(args[ARG_rate].u_obj);
57+
if (rate <= 0) {
58+
mp_raise_ValueError(MP_ERROR_TEXT("rate out of bounds"));
59+
}
60+
61+
mp_float_t duration_ms = mp_obj_get_float(args[ARG_duration].u_obj);
62+
if (duration_ms <= 0) {
63+
mp_raise_ValueError(MP_ERROR_TEXT("duration out of bounds"));
64+
}
65+
size_t num_bytes = duration_ms * rate / 1000;
66+
67+
return microbit_audio_recording_new(num_bytes, rate);
68+
}
69+
70+
static mp_obj_t microbit_audio_recording_copy(mp_obj_t self_in) {
71+
microbit_audio_track_obj_t *self = MP_OBJ_TO_PTR(self_in);
72+
uint8_t *data = m_new(uint8_t, self->size);
73+
memcpy(data, self->data, self->size);
74+
return microbit_audio_track_new(MP_OBJ_NULL, self->size, data, self->rate);
75+
}
76+
static MP_DEFINE_CONST_FUN_OBJ_1(microbit_audio_recording_copy_obj, microbit_audio_recording_copy);
77+
78+
static mp_obj_t microbit_audio_recording_track(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
79+
enum { ARG_start_ms, ARG_end_ms };
80+
static const mp_arg_t allowed_args[] = {
81+
{ MP_QSTR_start_ms, MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_INT(0)} },
82+
{ MP_QSTR_end_ms, MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_INT(-1)} },
83+
};
84+
// parse args
85+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
86+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
87+
88+
microbit_audio_track_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
89+
mp_int_t start_byte = mp_obj_get_float(args[ARG_start_ms].u_obj) * self->rate / 1000;
90+
mp_int_t end_byte;
91+
if (args[ARG_end_ms].u_obj == MP_OBJ_NEW_SMALL_INT(-1)) {
92+
end_byte = self->size;
93+
} else {
94+
end_byte = mp_obj_get_float(args[ARG_end_ms].u_obj) * self->rate / 1000;
95+
}
96+
97+
// Truncate start_byte to fit in valid range.
98+
start_byte = MAX(0, start_byte);
99+
start_byte = MIN(start_byte, self->size);
100+
101+
// Truncate end_byte to fit in valid range.
102+
end_byte = MAX(0, end_byte);
103+
end_byte = MIN(end_byte, self->size);
104+
105+
// Calculate length of track, truncating negative lengths to 0.
106+
size_t len = MAX(0, end_byte - start_byte);
107+
108+
// Create and return new track.
109+
return microbit_audio_track_new(pos_args[0], len, self->data + start_byte, self->rate);
110+
}
111+
static MP_DEFINE_CONST_FUN_OBJ_KW(microbit_audio_recording_track_obj, 1, microbit_audio_recording_track);
112+
113+
static const mp_rom_map_elem_t microbit_audio_recording_locals_dict_table[] = {
114+
{ MP_ROM_QSTR(MP_QSTR_get_rate), MP_ROM_PTR(&microbit_audio_track_get_rate_obj) },
115+
{ MP_ROM_QSTR(MP_QSTR_set_rate), MP_ROM_PTR(&microbit_audio_track_set_rate_obj) },
116+
{ MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&microbit_audio_recording_copy_obj) },
117+
{ MP_ROM_QSTR(MP_QSTR_track), MP_ROM_PTR(&microbit_audio_recording_track_obj) },
118+
};
119+
static MP_DEFINE_CONST_DICT(microbit_audio_recording_locals_dict, microbit_audio_recording_locals_dict_table);
120+
121+
MP_DEFINE_CONST_OBJ_TYPE(
122+
microbit_audio_recording_type,
123+
MP_QSTR_AudioRecording,
124+
MP_TYPE_FLAG_NONE,
125+
make_new, microbit_audio_recording_make_new,
126+
buffer, microbit_audio_track_get_buffer,
127+
locals_dict, &microbit_audio_recording_locals_dict
128+
);

src/codal_port/microbit_audiotrack.c

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2024 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/mphal.h"
28+
#include "drv_system.h"
29+
#include "modmicrobit.h"
30+
#include "modaudio.h"
31+
#include "utils.h"
32+
33+
mp_obj_t microbit_audio_track_new(mp_obj_t buffer_obj, size_t len, uint8_t *data, uint32_t rate) {
34+
microbit_audio_track_obj_t *self = m_new_obj(microbit_audio_track_obj_t);
35+
if (buffer_obj == MP_OBJ_NULL) {
36+
self->base.type = &microbit_audio_recording_type;
37+
} else {
38+
self->base.type = &microbit_audio_track_type;
39+
if (mp_obj_is_type(buffer_obj, &microbit_audio_track_type)) {
40+
buffer_obj = ((microbit_audio_track_obj_t *)MP_OBJ_TO_PTR(buffer_obj))->buffer_obj;
41+
}
42+
}
43+
self->buffer_obj = buffer_obj;
44+
self->size = len;
45+
self->rate = rate;
46+
self->data = data;
47+
return MP_OBJ_FROM_PTR(self);
48+
}
49+
50+
static mp_obj_t microbit_audio_track_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
51+
(void)type_in;
52+
53+
enum { ARG_buffer, ARG_rate };
54+
static const mp_arg_t allowed_args[] = {
55+
{ MP_QSTR_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_OBJ_NULL} },
56+
{ MP_QSTR_rate, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(AUDIO_TRACK_DEFAULT_SAMPLE_RATE)} },
57+
};
58+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
59+
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
60+
61+
mp_int_t rate = mp_obj_get_int_allow_float(args[ARG_rate].u_obj);
62+
if (rate <= 0) {
63+
mp_raise_ValueError(MP_ERROR_TEXT("rate out of bounds"));
64+
}
65+
66+
// Get buffer.
67+
mp_buffer_info_t bufinfo;
68+
mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_RW);
69+
70+
// Create and return the AudioTrack object.
71+
return microbit_audio_track_new(args[ARG_buffer].u_obj, bufinfo.len, bufinfo.buf, rate);
72+
}
73+
74+
static mp_obj_t microbit_audio_track_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
75+
microbit_audio_track_obj_t *self = MP_OBJ_TO_PTR(self_in);
76+
switch (op) {
77+
case MP_UNARY_OP_LEN:
78+
return MP_OBJ_NEW_SMALL_INT(self->size);
79+
default:
80+
return MP_OBJ_NULL; // op not supported
81+
}
82+
}
83+
84+
static mp_obj_t microbit_audio_track_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value_in) {
85+
microbit_audio_track_obj_t *self = MP_OBJ_TO_PTR(self_in);
86+
if (value_in == MP_OBJ_NULL) {
87+
// delete
88+
mp_raise_TypeError(MP_ERROR_TEXT("cannot delete elements of AudioTrack"));
89+
} else if (value_in == MP_OBJ_SENTINEL) {
90+
// load
91+
if (mp_obj_is_type(index, &mp_type_slice)) {
92+
mp_bound_slice_t slice;
93+
if (!mp_seq_get_fast_slice_indexes(self->size, index, &slice)) {
94+
mp_raise_NotImplementedError(MP_ERROR_TEXT("slices must have step=1"));
95+
}
96+
return microbit_audio_track_new(self->buffer_obj, slice.stop - slice.start, self->data + slice.start, self->rate);
97+
} else {
98+
size_t index_val = mp_get_index(self->base.type, self->size, index, false);
99+
return MP_OBJ_NEW_SMALL_INT(self->data[index_val]);
100+
}
101+
} else {
102+
// store
103+
size_t index_val = mp_get_index(self->base.type, self->size, index, false);
104+
mp_int_t value = mp_obj_get_int(value_in);
105+
if (value < 0 || value > 255) {
106+
mp_raise_ValueError(MP_ERROR_TEXT("value out of range"));
107+
}
108+
self->data[index_val] = value;
109+
return mp_const_none;
110+
}
111+
}
112+
113+
mp_int_t microbit_audio_track_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) {
114+
(void)flags;
115+
microbit_audio_track_obj_t *self = MP_OBJ_TO_PTR(self_in);
116+
bufinfo->buf = self->data;
117+
bufinfo->len = self->size;
118+
bufinfo->typecode = 'B';
119+
return 0;
120+
}
121+
122+
static mp_obj_t microbit_audio_track_get_rate(mp_obj_t self_in) {
123+
microbit_audio_track_obj_t *self = MP_OBJ_TO_PTR(self_in);
124+
return MP_OBJ_NEW_SMALL_INT(self->rate);
125+
}
126+
MP_DEFINE_CONST_FUN_OBJ_1(microbit_audio_track_get_rate_obj, microbit_audio_track_get_rate);
127+
128+
static mp_obj_t microbit_audio_track_set_rate(mp_obj_t self_in, mp_obj_t rate_in) {
129+
microbit_audio_track_obj_t *self = MP_OBJ_TO_PTR(self_in);
130+
mp_int_t rate = mp_obj_get_int_allow_float(rate_in);
131+
if (rate <= 0) {
132+
mp_raise_ValueError(MP_ERROR_TEXT("rate out of bounds"));
133+
}
134+
self->rate = rate;
135+
// TODO: only set if this frame is currently being played
136+
microbit_hal_audio_raw_set_rate(rate);
137+
return mp_const_none;
138+
}
139+
MP_DEFINE_CONST_FUN_OBJ_2(microbit_audio_track_set_rate_obj, microbit_audio_track_set_rate);
140+
141+
static mp_obj_t microbit_audio_track_copyfrom(mp_obj_t self_in, mp_obj_t other) {
142+
microbit_audio_track_obj_t *self = MP_OBJ_TO_PTR(self_in);
143+
mp_buffer_info_t bufinfo;
144+
mp_get_buffer_raise(other, &bufinfo, MP_BUFFER_READ);
145+
uint32_t len = MIN(bufinfo.len, self->size);
146+
memcpy(self->data, bufinfo.buf, len);
147+
return mp_const_none;
148+
}
149+
static MP_DEFINE_CONST_FUN_OBJ_2(microbit_audio_track_copyfrom_obj, microbit_audio_track_copyfrom);
150+
151+
static const mp_rom_map_elem_t microbit_audio_track_locals_dict_table[] = {
152+
{ MP_ROM_QSTR(MP_QSTR_get_rate), MP_ROM_PTR(&microbit_audio_track_get_rate_obj) },
153+
{ MP_ROM_QSTR(MP_QSTR_set_rate), MP_ROM_PTR(&microbit_audio_track_set_rate_obj) },
154+
{ MP_ROM_QSTR(MP_QSTR_copyfrom), MP_ROM_PTR(&microbit_audio_track_copyfrom_obj) },
155+
};
156+
static MP_DEFINE_CONST_DICT(microbit_audio_track_locals_dict, microbit_audio_track_locals_dict_table);
157+
158+
MP_DEFINE_CONST_OBJ_TYPE(
159+
microbit_audio_track_type,
160+
MP_QSTR_AudioTrack,
161+
MP_TYPE_FLAG_NONE,
162+
make_new, microbit_audio_track_make_new,
163+
unary_op, microbit_audio_track_unary_op,
164+
subscr, microbit_audio_track_subscr,
165+
buffer, microbit_audio_track_get_buffer,
166+
locals_dict, &microbit_audio_track_locals_dict
167+
);

0 commit comments

Comments
 (0)