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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy