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)
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