Skip to content

Commit 7b4e119

Browse files
committed
Audio module. Basic ability to play iterables of audio-frames.
1 parent d70f1ca commit 7b4e119

File tree

7 files changed

+613
-1
lines changed

7 files changed

+613
-1
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/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+0.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] = 120-i*15
32+
triangle[i+QUARTER*2] = -i*15
33+
triangle[i+QUARTER*3] = i*15-120
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] = -120
41+
square[i+HALF] = 120
42+
show_wave("Square", square)
43+
sleep(1000)
44+
45+
for i in range(len(frame)):
46+
frame[i] = 124-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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,9 @@ QDEF(MP_QSTR_randrange, (const byte*)"\xa3\x09" "randrange")
678678
QDEF(MP_QSTR_randint, (const byte*)"\xaf\x07" "randint")
679679
QDEF(MP_QSTR_choice, (const byte*)"\x2e\x06" "choice")
680680
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_source, (const byte*)"\xb8\x06" "source")
681684
QDEF(MP_QSTR_os, (const byte*)"\x79\x02" "os")
682685
QDEF(MP_QSTR_uname, (const byte*)"\xb7\x05" "uname")
683686
QDEF(MP_QSTR_sysname, (const byte*)"\x9b\x07" "sysname")

inc/microbit/modaudio.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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_start(void);
10+
void audio_stop(void);
11+
mp_obj_t audio_init(void);
12+
void audio_play_source(mp_obj_t src, bool wait);
13+
void audio_set_pins(mp_obj_t pin0_obj, mp_obj_t pin1_obj);
14+
15+
#define LOG_AUDIO_CHUNK_SIZE 5
16+
#define AUDIO_CHUNK_SIZE (1<<LOG_AUDIO_CHUNK_SIZE)
17+
#define AUDIO_BUFFER_SIZE (AUDIO_CHUNK_SIZE*2)
18+
#define AUDIO_CALLBACK_ID 0
19+
20+
typedef struct _microbit_audio_frame_obj_t {
21+
mp_obj_base_t base;
22+
int8_t data[AUDIO_CHUNK_SIZE];
23+
} microbit_audio_frame_obj_t;
24+
25+
extern const mp_obj_type_t microbit_audio_frame_type;
26+
27+
microbit_audio_frame_obj_t *new_microbit_audio_frame(void);
28+
29+
#endif // __MICROPY_INCLUDED_MICROBIT_AUDIO_H__

inc/microbit/mpconfigport.h

Lines changed: 4 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 }, \
@@ -120,6 +122,8 @@ extern const struct _mp_obj_module_t radio_module;
120122
void *async_music_data; \
121123
uint8_t *radio_buf; \
122124
void *pwm_next_event; \
125+
void *audio_buffer; \
126+
void *audio_source;
123127

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

inc/microbit/qstrdefsport.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,13 @@ Q(randint)
389389
Q(choice)
390390
Q(uniform)
391391

392+
Q(audio)
393+
Q(play)
394+
Q(AudioFrame)
395+
Q(pins)
396+
Q(source)
397+
392398
Q(name)
393-
Q(os)
394399

395400
Q(os)
396401
Q(uname)

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