Skip to content

Commit 2155ab3

Browse files
committed
Merge branch 'audio-cleaner-resets' of https://github.com/markshannon/micropython into markshannon-audio-cleaner-resets
2 parents a463521 + 23d783b commit 2155ab3

File tree

14 files changed

+1118
-5
lines changed

14 files changed

+1118
-5
lines changed

docs/audio.rst

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
Audio
2+
*******
3+
4+
.. py:module:: audio
5+
6+
This module allows you play sounds from a speaker attached to the Microbit.
7+
In order to use the audio module you will need to provide a sound source.
8+
9+
A sound source is an iterable (sequence, like list or tuple, or a generator) of
10+
frames, each of 32 samples.
11+
The ``audio`` modules plays samples at the rate of 7812.5 samples per second,
12+
which means that it can reproduce frequencies up to 3.9kHz.
13+
14+
Functions
15+
=========
16+
17+
.. py:function:: play(source, wait=True, pins=(pin0, pin1))
18+
19+
Play the source to completion.
20+
21+
``source`` is an iterable, each element of which must be an ``AudioFrame``.
22+
23+
If ``wait`` is ``True``, this function will block until the source is exhausted.
24+
25+
``pins`` specifies which pins the speaker is connected to.
26+
27+
Classes
28+
=======
29+
30+
.. py:class::
31+
AudioFrame
32+
33+
An ``AudioFrame`` object is a list of 32 samples each of which is a signed byte
34+
(whole number between -128 and 127).
35+
36+
It takes just over 4 ms to play a single frame.
37+
38+
Using audio
39+
===========
40+
41+
You will need a sound source, as input to the ``play`` function. You can generate your own, like in
42+
``examples/waveforms.py`` or you can use the sound sources provided by modules like ``synth``.
43+
44+
45+
Technical Details
46+
=================
47+
48+
.. note::
49+
You don't need to understand this section to use the ``audio`` module.
50+
It is just here in case you wanted to know how it works.
51+
52+
The ``audio`` module consumes samples at 7812.5 kHz, and uses linear interpolation to
53+
output a PWM signal at 32.5 kHz, which gives tolerable sound quality.
54+
55+
The function ``play`` fully copies all data from each ``AudioFrame`` before it
56+
calls ``next()`` for the next frame, so a sound source can use the same ``AudioFrame``
57+
repeatedly.
58+
59+
The ``audio`` module has an internal 64 sample buffer from which it reads samples.
60+
When reading reaches the start or the mid-point of the buffer, it triggers a callback to
61+
fetch the next ``AudioFrame`` which is then copied into the buffer.
62+
This means that a sound source has under 4ms to compute the next ``AudioFrame``,
63+
and for reliable operation needs to take less 2ms (which is 32000 cycles, so should be plenty).
64+
65+
66+
Example
67+
=======
68+
69+
.. include:: ../examples/waveforms.py
70+
:code: python
71+

examples/play_file.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#Plays a file on the specified pins.
2+
import audio
3+
4+
def audio_generator(file, frame):
5+
ln = -1
6+
while ln:
7+
ln = file.readinto(frame)
8+
yield frame
9+
10+
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()
13+
with open(name) as file:
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)

examples/waveforms.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from microbit import display, sleep, button_a
2+
import audio
3+
import math
4+
5+
def repeated_frame(frame, count):
6+
for i in range(count):
7+
yield frame
8+
9+
# Press button A to skip to next wave.
10+
def show_wave(name, frame, duration=1500):
11+
display.scroll(name + " wave", wait=False,delay=100)
12+
audio.play(repeated_frame(frame, duration),wait=False)
13+
for i in range(75):
14+
sleep(100)
15+
if button_a.is_pressed():
16+
display.clear()
17+
audio.stop()
18+
break
19+
20+
frame = audio.AudioFrame()
21+
22+
for i in range(len(frame)):
23+
frame[i] = int(math.sin(math.pi*i/16)*124+128.5)
24+
show_wave("Sine", frame)
25+
26+
triangle = audio.AudioFrame()
27+
28+
QUARTER = len(triangle)//4
29+
for i in range(QUARTER):
30+
triangle[i] = i*15
31+
triangle[i+QUARTER] = 248-i*15
32+
triangle[i+QUARTER*2] = 128-i*15
33+
triangle[i+QUARTER*3] = i*15+8
34+
show_wave("Triangle", triangle)
35+
36+
square = audio.AudioFrame()
37+
38+
HALF = len(square)//2
39+
for i in range(HALF):
40+
square[i] = 8
41+
square[i+HALF] = 248
42+
show_wave("Square", square)
43+
sleep(1000)
44+
45+
for i in range(len(frame)):
46+
frame[i] = 252-i*8
47+
show_wave("Sawtooth", frame)
48+
49+
del frame
50+
51+
#Generate a waveform that goes from triangle to square wave, reasonably smoothly.
52+
frames = [ None ] * 32
53+
for i in range(32):
54+
frames[i] = frame = audio.AudioFrame()
55+
for j in range(len(triangle)):
56+
frame[j] = (triangle[j]*(32-i) + square[j]*i)>>5
57+
58+
def repeated_frames(frames, count):
59+
for frame in frames:
60+
for i in range(count):
61+
yield frame
62+
63+
64+
display.scroll("Ascending wave", wait=False)
65+
audio.play(repeated_frames(frames, 60))

inc/genhdr/qstrdefs.generated.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ QDEF(MP_QSTR_read_analog, (const byte*)"\x62\x0b" "read_analog")
351351
QDEF(MP_QSTR_write_analog, (const byte*)"\x2d\x0c" "write_analog")
352352
QDEF(MP_QSTR_set_analog_period, (const byte*)"\x08\x11" "set_analog_period")
353353
QDEF(MP_QSTR_set_analog_period_microseconds, (const byte*)"\xee\x1e" "set_analog_period_microseconds")
354+
QDEF(MP_QSTR_get_analog_period_microseconds, (const byte*)"\x7a\x1e" "get_analog_period_microseconds")
354355
QDEF(MP_QSTR_is_touched, (const byte*)"\x04\x0a" "is_touched")
355356
QDEF(MP_QSTR_MicroBitIO, (const byte*)"\xe6\x0a" "MicroBitIO")
356357
QDEF(MP_QSTR_pin0, (const byte*)"\x02\x04" "pin0")
@@ -677,6 +678,11 @@ QDEF(MP_QSTR_randrange, (const byte*)"\xa3\x09" "randrange")
677678
QDEF(MP_QSTR_randint, (const byte*)"\xaf\x07" "randint")
678679
QDEF(MP_QSTR_choice, (const byte*)"\x2e\x06" "choice")
679680
QDEF(MP_QSTR_uniform, (const byte*)"\x01\x07" "uniform")
681+
QDEF(MP_QSTR_audio, (const byte*)"\x53\x05" "audio")
682+
QDEF(MP_QSTR_AudioFrame, (const byte*)"\xae\x0a" "AudioFrame")
683+
QDEF(MP_QSTR_return_pin, (const byte*)"\x27\x0a" "return_pin")
684+
QDEF(MP_QSTR_source, (const byte*)"\xb8\x06" "source")
685+
QDEF(MP_QSTR_copyfrom, (const byte*)"\x56\x08" "copyfrom")
680686
QDEF(MP_QSTR_os, (const byte*)"\x79\x02" "os")
681687
QDEF(MP_QSTR_uname, (const byte*)"\xb7\x05" "uname")
682688
QDEF(MP_QSTR_sysname, (const byte*)"\x9b\x07" "sysname")
@@ -688,6 +694,7 @@ QDEF(MP_QSTR_writable, (const byte*)"\xf7\x08" "writable")
688694
QDEF(MP_QSTR_listdir, (const byte*)"\x98\x07" "listdir")
689695
QDEF(MP_QSTR_machine, (const byte*)"\x60\x07" "machine")
690696
QDEF(MP_QSTR_size, (const byte*)"\x20\x04" "size")
697+
QDEF(MP_QSTR_is_playing, (const byte*)"\x04\x0a" "is_playing")
691698
QDEF(MP_QSTR_radio, (const byte*)"\xd4\x05" "radio")
692699
QDEF(MP_QSTR_config, (const byte*)"\x4f\x06" "config")
693700
QDEF(MP_QSTR_send_bytes, (const byte*)"\xbf\x0a" "send_bytes")

inc/lib/pwm.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#ifndef __MICROPY_INCLUDED_LIB_PWM_H__
2+
#define __MICROPY_INCLUDED_LIB_PWM_H__
3+
4+
void pwm_start(void);
5+
void pwm_stop(void);
6+
7+
int32_t pwm_set_period_us(int32_t us);
8+
int32_t pwm_get_period_us(void);
9+
int pwm_set_duty_cycle(int32_t pin, int32_t value);
10+
11+
#endif // __MICROPY_INCLUDED_LIB_PWM_H__

inc/microbit/microbitpin.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
#define MICROBIT_PIN_P14 (P0_22)
3535
#define MICROBIT_PIN_P15 (P0_21)
3636

37+
mp_obj_t microbit_pin_write_digital(mp_obj_t self_in, mp_obj_t value_in);
38+
mp_obj_t microbit_pin_read_digital(mp_obj_t self_in);
39+
3740
#endif
3841

3942
#endif // __MICROPY_INCLUDED_MICROBIT_MICROBITPIN_H__

inc/microbit/modaudio.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
#ifndef __MICROPY_INCLUDED_MICROBIT_AUDIO_H__
3+
#define __MICROPY_INCLUDED_MICROBIT_AUDIO_H__
4+
5+
#include "nrf.h"
6+
#include "py/obj.h"
7+
#include "py/runtime.h"
8+
9+
void audio_play_source(mp_obj_t src, mp_obj_t pin1, mp_obj_t pin2, bool wait);
10+
11+
#define LOG_AUDIO_CHUNK_SIZE 5
12+
#define AUDIO_CHUNK_SIZE (1<<LOG_AUDIO_CHUNK_SIZE)
13+
#define AUDIO_BUFFER_SIZE (AUDIO_CHUNK_SIZE*2)
14+
#define AUDIO_CALLBACK_ID 0
15+
16+
typedef struct _microbit_audio_frame_obj_t {
17+
mp_obj_base_t base;
18+
uint8_t data[AUDIO_CHUNK_SIZE];
19+
} microbit_audio_frame_obj_t;
20+
21+
extern const mp_obj_type_t microbit_audio_frame_type;
22+
23+
microbit_audio_frame_obj_t *new_microbit_audio_frame(void);
24+
25+
#endif // __MICROPY_INCLUDED_MICROBIT_AUDIO_H__

inc/microbit/mpconfigport.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ extern const struct _mp_obj_module_t neopixel_module;
9595
extern const struct _mp_obj_module_t random_module;
9696
extern const struct _mp_obj_module_t os_module;
9797
extern const struct _mp_obj_module_t radio_module;
98+
extern const struct _mp_obj_module_t audio_module;
9899

99100
#define MICROPY_PORT_BUILTIN_MODULES \
100101
{ MP_OBJ_NEW_QSTR(MP_QSTR_microbit), (mp_obj_t)&microbit_module }, \
@@ -106,6 +107,7 @@ extern const struct _mp_obj_module_t radio_module;
106107
{ MP_OBJ_NEW_QSTR(MP_QSTR_random), (mp_obj_t)&random_module }, \
107108
{ MP_OBJ_NEW_QSTR(MP_QSTR_os), (mp_obj_t)&os_module }, \
108109
{ MP_OBJ_NEW_QSTR(MP_QSTR_radio), (mp_obj_t)&radio_module }, \
110+
{ MP_OBJ_NEW_QSTR(MP_QSTR_audio), (mp_obj_t)&audio_module }, \
109111
\
110112
/* the following provide aliases for existing modules */ \
111113
{ MP_OBJ_NEW_QSTR(MP_QSTR_collections), (mp_obj_t)&mp_module_collections }, \
@@ -119,6 +121,9 @@ extern const struct _mp_obj_module_t radio_module;
119121
void *async_data[2]; \
120122
void *async_music_data; \
121123
uint8_t *radio_buf; \
124+
void *pwm_next_event; \
125+
void *audio_buffer; \
126+
void *audio_source;
122127

123128
// We need to provide a declaration/definition of alloca()
124129
#include <alloca.h>

inc/microbit/qstrdefsport.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Q(read_analog)
2828
Q(write_analog)
2929
Q(set_analog_period)
3030
Q(set_analog_period_microseconds)
31+
Q(get_analog_period_microseconds)
3132
Q(is_touched)
3233

3334
Q(MicroBitIO)
@@ -388,8 +389,15 @@ Q(randint)
388389
Q(choice)
389390
Q(uniform)
390391

392+
Q(audio)
393+
Q(play)
394+
Q(AudioFrame)
395+
Q(pin)
396+
Q(return_pin)
397+
Q(source)
398+
Q(copyfrom)
399+
391400
Q(name)
392-
Q(os)
393401

394402
Q(os)
395403
Q(uname)
@@ -409,6 +417,7 @@ Q(listdir)
409417
Q(machine)
410418
Q(size)
411419

420+
Q(is_playing)
412421
Q(radio)
413422
Q(reset)
414423
Q(config)

0 commit comments

Comments
 (0)