Skip to content

Commit c3c1cfd

Browse files
committed
Audio: Add basic arithemtic operations to audio frames; allows effects like reverb. Prohibit allocation in frame sources to prevent GC in interrupt.
1 parent ae5bb53 commit c3c1cfd

File tree

5 files changed

+173
-8
lines changed

5 files changed

+173
-8
lines changed

examples/play_file.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
#Plays a file on the specified pins.
22
import audio
33

4-
def audio_generator(file):
4+
def audio_generator(file, frame):
55
ln = -1
6-
frame = audio.AudioFrame()
76
while ln:
87
ln = file.readinto(frame)
98
yield frame
109

1110
def play_file(name, pin=None, return_pin=None):
11+
#Do allocation here, as we can't do it in an interrupt.
12+
frame = audio.AudioFrame()
1213
with open(name) as file:
13-
audio.play(audio_generator(file), pin=pin, return_pin=return_pin)
14+
audio.play(audio_generator(file, frame), pin=pin, return_pin=return_pin)

examples/reverb.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import audio
2+
3+
def from_file(file, frame):
4+
ln = -1
5+
while ln:
6+
ln = file.readinto(frame)
7+
yield frame
8+
9+
def reverb_gen(src, buckets, reflect, fadeout):
10+
bucket_count = len(buckets)
11+
bucket = 0
12+
for frame in src:
13+
echo = buckets[bucket]
14+
echo *= reflect
15+
echo += frame
16+
yield echo
17+
buckets[bucket] = echo
18+
bucket += 1
19+
if bucket == bucket_count:
20+
bucket = 0
21+
while fadeout:
22+
fadeout -= 1
23+
echo = buckets[bucket]
24+
echo *= reflect
25+
yield echo
26+
buckets[bucket] = echo
27+
bucket += 1
28+
if bucket == bucket_count:
29+
bucket = 0
30+
31+
def reverb(src, delay, reflect):
32+
#Do all allocation up front, so we don't need to do any in the generator.
33+
bucket_count = delay>>2
34+
buckets = [ None ] * bucket_count
35+
for i in range(bucket_count):
36+
buckets[i] = audio.AudioFrame()
37+
vol = 1.0
38+
fadeout = 0
39+
while vol > 0.05:
40+
fadeout += bucket_count
41+
vol *= reflect
42+
return reverb_gen(src, buckets, reflect, fadeout)
43+
44+
def play_file(name, delay=80, reflect=0.5):
45+
#Do allocation here, as we can't do it in an interrupt.
46+
frame = audio.AudioFrame()
47+
with open(name) as file:
48+
gen = from_file(file, frame)
49+
r = reverb(gen, delay, reflect)
50+
audio.play(r)

inc/genhdr/qstrdefs.generated.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,7 @@ QDEF(MP_QSTR_audio, (const byte*)"\x53\x05" "audio")
682682
QDEF(MP_QSTR_AudioFrame, (const byte*)"\xae\x0a" "AudioFrame")
683683
QDEF(MP_QSTR_return_pin, (const byte*)"\x27\x0a" "return_pin")
684684
QDEF(MP_QSTR_source, (const byte*)"\xb8\x06" "source")
685+
QDEF(MP_QSTR_copyfrom, (const byte*)"\x56\x08" "copyfrom")
685686
QDEF(MP_QSTR_os, (const byte*)"\x79\x02" "os")
686687
QDEF(MP_QSTR_uname, (const byte*)"\xb7\x05" "uname")
687688
QDEF(MP_QSTR_sysname, (const byte*)"\x9b\x07" "sysname")

inc/microbit/qstrdefsport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ Q(AudioFrame)
395395
Q(pin)
396396
Q(return_pin)
397397
Q(source)
398+
Q(copyfrom)
398399

399400
Q(name)
400401

source/microbit/modaudio.cpp

Lines changed: 117 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ extern "C" {
4141
#include "py/obj.h"
4242
#include "py/objstr.h"
4343
#include "py/mphal.h"
44+
#include "py/gc.h"
4445
#include "microbit/modaudio.h"
4546
#include "microbit/microbitobj.h"
4647
#include "microbit/microbitpin.h"
@@ -126,7 +127,7 @@ static volatile bool running = false;
126127
static bool sample = false;
127128
static volatile bool fetcher_ready = true;
128129
static bool double_pin = true;
129-
volatile int32_t audio_buffer_read_index;
130+
static volatile int32_t audio_buffer_read_index;
130131
static PinName pin0 = P0_3;
131132
static PinName pin1 = P0_2;
132133

@@ -181,14 +182,24 @@ static void audio_data_fetcher(void) {
181182
/* WARNING: We are executing in an interrupt handler.
182183
* If an exception is raised here then we must hand it to the VM. */
183184
mp_obj_t buffer_obj;
185+
gc_lock();
184186
nlr_buf_t nlr;
185187
if (nlr_push(&nlr) == 0) {
186188
buffer_obj = mp_iternext_allow_raise(audio_source_iter);
187189
nlr_pop();
190+
gc_unlock();
188191
} else {
192+
gc_unlock();
189193
if (!mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t*)nlr.ret_val)->type),
190194
MP_OBJ_FROM_PTR(&mp_type_StopIteration))) {
191195
// an exception other than StopIteration, so set it for the VM to raise later
196+
//If memory error, add appropriate message.
197+
mp_obj_exception_t *ex = (mp_obj_exception_t *)nlr.ret_val;
198+
if (mp_obj_get_type(nlr.ret_val) == &mp_type_MemoryError) {
199+
ex->args = (mp_obj_tuple_t *)mp_obj_new_tuple(1, NULL);
200+
ex->args->items[0] = mp_obj_new_str("Allocation in interrupt handler",
201+
strlen("Allocation in interrupt handler"), false);
202+
}
192203
MP_STATE_VM(mp_pending_exception) = MP_OBJ_FROM_PTR(nlr.ret_val);
193204
}
194205
buffer_obj = MP_OBJ_STOP_ITERATION;
@@ -455,7 +466,7 @@ STATIC mp_obj_t audio_frame_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t
455466
}
456467
}
457468

458-
STATIC mp_obj_t mp_obj_audio_frame_unary_op(mp_uint_t op, mp_obj_t self_in) {
469+
static mp_obj_t audio_frame_unary_op(mp_uint_t op, mp_obj_t self_in) {
459470
(void)self_in;
460471
switch (op) {
461472
case MP_UNARY_OP_LEN: return MP_OBJ_NEW_SMALL_INT(32);
@@ -472,22 +483,123 @@ static mp_int_t audio_frame_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufin
472483
return 0;
473484
}
474485

486+
static void add_into(microbit_audio_frame_obj_t *self, microbit_audio_frame_obj_t *other, bool add) {
487+
int mult = add ? 1 : -1;
488+
for (int i = 0; i < AUDIO_CHUNK_SIZE; i++) {
489+
unsigned val = (int)self->data[i] + mult*(other->data[i]-128);
490+
// Clamp to 0-255
491+
if (val > 255) {
492+
val = (1-(val>>31))*255;
493+
}
494+
self->data[i] = val;
495+
}
496+
}
497+
498+
static microbit_audio_frame_obj_t *copy(microbit_audio_frame_obj_t *self) {
499+
microbit_audio_frame_obj_t *result = new_microbit_audio_frame();
500+
for (int i = 0; i < AUDIO_CHUNK_SIZE; i++) {
501+
result->data[i] = self->data[i];
502+
}
503+
return result;
504+
}
505+
506+
mp_obj_t copyfrom(mp_obj_t self_in, mp_obj_t other) {
507+
microbit_audio_frame_obj_t *self = (microbit_audio_frame_obj_t *)self_in;
508+
if (mp_obj_get_type(other) != &microbit_audio_frame_type) {
509+
nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, "Must be an AudioBuffer"));
510+
}
511+
for (int i = 0; i < AUDIO_CHUNK_SIZE; i++) {
512+
self->data[i] = ((microbit_audio_frame_obj_t *)other)->data[i];
513+
}
514+
return mp_const_none;
515+
}
516+
MP_DEFINE_CONST_FUN_OBJ_2(copyfrom_obj, copyfrom);
517+
518+
union _i2f {
519+
int32_t bits;
520+
float value;
521+
};
522+
523+
/* Convert a small float to a fixed-point number */
524+
int32_t float_to_fixed(float f, uint32_t scale) {
525+
union _i2f x;
526+
x.value = f;
527+
int32_t sign = 1-((x.bits>>30)&2);
528+
/* Subtract 127 from exponent for IEEE-754 and 23 for mantissa scaling */
529+
int32_t exponent = ((x.bits>>23)&255)-150;
530+
/* Mantissa scaled by 2**23, including implicit 1 */
531+
int32_t mantissa = (1<<23) | ((x.bits)&((1<<23)-1));
532+
int32_t shift = scale+exponent;
533+
int32_t result;
534+
if (shift > 0) {
535+
result = sign*(mantissa<<shift);
536+
} else if (shift < -31) {
537+
result = 0;
538+
} else {
539+
result = sign*(mantissa>>(-shift));
540+
}
541+
// printf("Float %f: %d %d %x (scale %d) => %d\n", f, sign, exponent, mantissa, scale, result);
542+
return result;
543+
}
544+
545+
static void mult(microbit_audio_frame_obj_t *self, float f) {
546+
int scaled = float_to_fixed(f, 15);
547+
for (int i = 0; i < AUDIO_CHUNK_SIZE; i++) {
548+
unsigned val = ((((int)self->data[i]-128) * scaled) >> 15)+128;
549+
if (val > 255) {
550+
val = (1-(val>>31))*255;
551+
}
552+
self->data[i] = val;
553+
}
554+
}
555+
556+
STATIC mp_obj_t audio_frame_binary_op(mp_uint_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
557+
if (mp_obj_get_type(lhs_in) != &microbit_audio_frame_type) {
558+
return MP_OBJ_NULL; // op not supported
559+
}
560+
microbit_audio_frame_obj_t *lhs = (microbit_audio_frame_obj_t *)lhs_in;
561+
switch(op) {
562+
case MP_BINARY_OP_ADD:
563+
case MP_BINARY_OP_SUBTRACT:
564+
lhs = copy(lhs);
565+
case MP_BINARY_OP_INPLACE_ADD:
566+
case MP_BINARY_OP_INPLACE_SUBTRACT:
567+
if (mp_obj_get_type(rhs_in) != &microbit_audio_frame_type) {
568+
return MP_OBJ_NULL; // op not supported
569+
}
570+
add_into(lhs, (microbit_audio_frame_obj_t *)rhs_in, op==MP_BINARY_OP_ADD||op==MP_BINARY_OP_INPLACE_ADD);
571+
return lhs;
572+
case MP_BINARY_OP_MULTIPLY:
573+
lhs = copy(lhs);
574+
case MP_BINARY_OP_INPLACE_MULTIPLY:
575+
mult(lhs, mp_obj_get_float(rhs_in));
576+
return lhs;
577+
}
578+
return MP_OBJ_NULL; // op not supported
579+
}
580+
581+
STATIC const mp_map_elem_t microbit_audio_frame_locals_dict_table[] = {
582+
{ MP_OBJ_NEW_QSTR(MP_QSTR_copyfrom), (mp_obj_t)&copyfrom_obj },
583+
};
584+
STATIC MP_DEFINE_CONST_DICT(microbit_audio_frame_locals_dict, microbit_audio_frame_locals_dict_table);
585+
586+
475587
const mp_obj_type_t microbit_audio_frame_type = {
476588
{ &mp_type_type },
477589
.name = MP_QSTR_AudioFrame,
478590
.print = NULL,
479591
.make_new = microbit_audio_frame_new,
480592
.call = NULL,
481-
.unary_op = mp_obj_audio_frame_unary_op,
482-
.binary_op = NULL,
593+
.unary_op = audio_frame_unary_op,
594+
.binary_op = audio_frame_binary_op,
483595
.attr = NULL,
484596
.subscr = audio_frame_subscr,
485597
.getiter = NULL,
486598
.iternext = NULL,
487599
.buffer_p = { .get_buffer = audio_frame_get_buffer },
488600
.stream_p = NULL,
489601
.bases_tuple = NULL,
490-
.locals_dict = NULL,
602+
.locals_dict = (mp_obj_dict_t*)&microbit_audio_frame_locals_dict_table,
491603
};
492604

493605
microbit_audio_frame_obj_t *new_microbit_audio_frame(void) {

0 commit comments

Comments
 (0)