Audio
This module allows you play sounds with the micro:bit.
By default sound output will be via the edge connector on pin 0 and the built-in speaker (V2). You can connect wired headphones or a speaker to pin 0 and GND on the edge connector to hear the sounds.
The audio
module can be imported as import audio
or accessed via
the microbit
module as microbit.audio
.
There are five different kinds of audio sources that can be played using the
audio.play()
function:
Built in sounds (V2), e.g.
audio.play(Sound.HAPPY)
Sound Effects (V2), a way to create custom sounds by configuring its parameters:
my_effect = audio.SoundEffect(freq_start=400, freq_end=2500, duration=500) audio.play(my_effect)
Audio Recordings, an object that can be used to record audio from the microphone:
recording = audio.AudioRecording(duration=4000) microphone.record_into(recording) audio.play(recording)
Audio Tracks, a way to point to a portion of the data in an
AudioRecording
or abytearray
and/or modify it:recording = audio.AudioRecording(duration=4000) microphone.record(recording) track = AudioTrack(recording)[1000:3000] audio.play(track)
Audio Frames, an instance or an iterable (like a list or generator) of Audio Frames, which are lists of samples with values from 0 to 255:
square_wave = audio.AudioFrame() for i in range(16): square_wave[i] = 0 square_wave[i + 16] = 255 audio.play([square_wave] * 64)
Functions
- audio.play(source, wait=True, pin=pin0, return_pin=None)
Play the audio source to completion.
- Parameters:
source –
There are three types of data that can be used as a source:
Sound
: Themicrobit
module contains a list of built-in sounds, e.g.audio.play(Sound.TWINKLE)
. A full list can be found in the Built in sounds section.SoundEffect
: A sound effect, or an iterable of sound effects, created via theaudio.SoundEffect()
classAudioRecording
: An instance ofAudioRecording
as described in the AudioRecording sectionAudioTrack
: An instance ofAudioTrack
as described in the AudioTrack sectionAudioFrame
: An instance or an iterable ofAudioFrame
instances as described in the AudioFrame Technical Details section
wait – If
wait
isTrue
, this function will block until the source is exhausted.pin – An optional argument to specify the output pin can be used to override the default of
pin0
. If we do not want any sound to play we can usepin=None
.return_pin – specifies a differential edge connector pin to connect to an external speaker instead of ground. This is ignored for the V2 revision.
- audio.is_playing()
- Returns:
True
if audio is playing, otherwise returnsFalse
.
- audio.stop()
Stops all audio playback.
- audio.sound_level()
Get the sound pressure level produced by audio currently being played.
- Returns:
A representation of the output sound pressure level in the range 0 to 255.
Built-in sounds V2
The built-in sounds can be called using audio.play(Sound.NAME)
.
Sound.GIGGLE
Sound.HAPPY
Sound.HELLO
Sound.MYSTERIOUS
Sound.SAD
Sound.SLIDE
Sound.SOARING
Sound.SPRING
Sound.TWINKLE
Sound.YAWN
Sounds Example
from microbit import *
while True:
if button_a.is_pressed() and button_b.is_pressed():
# When pressing both buttons only play via the edge connector
audio.play(Sound.HELLO, pin=pin0)
elif button_a.is_pressed():
# On button A play a sound and when it's done show an image
audio.play(Sound.HAPPY)
display.show(Image.HAPPY)
elif button_b.is_pressed():
# On button B play a sound and show an image at the same time
audio.play(Sound.TWINKLE, wait=False)
display.show(Image.BUTTERFLY)
sleep(500)
display.clear()
Sound Effects V2
- class audio.SoundEffect(freq_start=500, freq_end=2500, duration=500, vol_start=255, vol_end=0, waveform=WAVEFORM_SQUARE, fx=FX_NONE, shape=SHAPE_LOG)
An
SoundEffect
instance represents a sound effect, composed by a set of parameters configured via the constructor or attributes.All the parameters are optional, with default values as shown above, and they can all be modified via attributes of the same name. For example, we can first create an effect
my_effect = SoundEffect(duration=1000)
, and then change its attributesmy_effect.duration = 500
.- Parameters:
freq_start – Start frequency in Hertz (Hz), default:
500
freq_end – End frequency in Hertz (Hz), default:
2500
duration – Duration of the sound (ms), default:
500
vol_start – Start volume value, range 0-255, default:
255
vol_end – End volume value, range 0-255, default:
0
waveform – Type of waveform shape, one of these values:
WAVEFORM_SINE
,WAVEFORM_SAWTOOTH
,WAVEFORM_TRIANGLE
,WAVEFORM_SQUARE
,WAVEFORM_NOISE
(randomly generated noise). Default:WAVEFORM_SQUARE
fx – Effect to add on the sound, one of the following values:
FX_TREMOLO
,FX_VIBRATO
,FX_WARBLE
, orFX_NONE
. Default:FX_NONE
shape – The type of the interpolation curve between the start and end frequencies, different wave shapes have different rates of change in frequency. One of the following values:
SHAPE_LINEAR
,SHAPE_CURVE
,SHAPE_LOG
. Default:SHAPE_LOG
- copy()
- Returns:
A copy of the SoundEffect.
- freq_start
Start frequency in Hertz (Hz), a number between
0
and9999
.
- freq_end
End frequency in Hertz (Hz), a number between
0
and9999`
.
- duration
Duration of the sound in milliseconds, a number between
0
and9999
.
- vol_start
Start volume value, a number between
0
and255
.
- vol_end
End volume value, a number between
0
and255
.
- waveform
Type of waveform shape, one of these values:
WAVEFORM_SINE
,WAVEFORM_SAWTOOTH
,WAVEFORM_TRIANGLE
,WAVEFORM_SQUARE
,WAVEFORM_NOISE
(randomly generated noise).
- fx
Effect to add on the sound, one of the following values:
FX_TREMOLO
,FX_VIBRATO
,FX_WARBLE
, orNone
.
- shape
The type of interpolation curve between the start and end frequencies, different wave shapes have different rates of change in frequency. One of the following values:
SHAPE_LINEAR
,SHAPE_CURVE
,SHAPE_LOG
.
The arguments used to create any Sound Effect,
can be inspected by looking at each of the SoundEffect instance attributes,
or by converting the instance into a string (which can be done via str()
function, or by using a function that does the conversion automatically like
print()
).
For example, with the REPL you can inspect the default SoundEffects:
>>> print(audio.SoundEffect())
SoundEffect(freq_start=500, freq_end=2500, duration=500, vol_start=255, vol_end=0, waveform=WAVE_SQUARE, fx=FX_NONE, shape=SHAPE_LOG)
This format is “human readable”, which means it is easy for us to read,
and it looks very similar to the code needed to create that SoundEffect,
but it’s not quite right. The repr()
function can be used to create a
string of Python code that can be stored or transferred
(you could transmit sounds via micro:bit radio!) and be executed with the
eval()
function:
>>> from audio import SoundEffect
>>> sound_code = repr(SoundEffect())
>>> print(sound_code)
SoundEffect(500, 2500, 500, 255, 0, 3, 0, 18)
>>> eval("audio.play({})".format(sound_code))
Sound Effects Example
from microbit import *
# Play the default Sound Effect
audio.play(audio.SoundEffect())
# Create a new Sound Effect and immediately play it
audio.play(audio.SoundEffect(
freq_start=400,
freq_end=2000,
duration=500,
vol_start=100,
vol_end=255,
waveform=audio.SoundEffect.WAVEFORM_TRIANGLE,
fx=audio.SoundEffect.FX_VIBRATO,
shape=audio.SoundEffect.SHAPE_LOG
))
# Play a Sound Effect instance, modify an attribute, and play it again
my_effect = audio.SoundEffect(
freq_start=400,
freq_end=2000,
)
audio.play(my_effect)
my_effect.duration = 1000
audio.play(my_effect)
# You can also create a new effect based on an existing one, and modify
# any of its characteristics via arguments
my_modified_effect = my_effect.copy()
my_modified_effect.waveform = audio.SoundEffect.WAVEFORM_NOISE
audio.play(my_modified_effect)
# Use sensor data to modify and play an existing Sound Effect instance
my_effect.duration = 600
while True:
# int() might be temporarily needed: https://github.com/microbit-foundation/micropython-microbit-v2/issues/121
my_effect.freq_start = int(scale(accelerometer.get_x(), from_=(-2000, 2000), to=(0, 9999)))
my_effect.freq_end = int(scale(accelerometer.get_y(), from_=(-2000, 2000), to=(0, 9999)))
audio.play(my_effect)
if button_a.is_pressed():
# Button A silences the micro:bit
speaker.off()
display.show(Image("09090:00000:00900:09990:00900"))
sleep(500)
elif button_b.is_pressed():
# Button B re-enables the speaker & plays an effect while showing an image
speaker.on()
audio.play(audio.SoundEffect(), wait=False)
display.show(Image.MUSIC_QUAVER)
sleep(500)
sleep(150)
AudioRecording & AudioTrack V2
To record and play back audio, we need a way to store the audio data and the sampling rate that has been used to record it and play it back.
Two new classes are introduced in micro:bit V2 for this purpose:
The
AudioRecording
class holds its own audio data and sampling rate. It is initialised with a size defined in units of time, and it’s the object type that themicrophone.record()
function returns.The
AudioTrack
class contains its sampling rate, but does not hold its own data. It instead points to a buffer externally created, like anAudioRecording
, or a basic type like abytearray
. It’s similar to a memoryview and it can be used to easily modify the audio data or chop into portions of different sizes.
AudioRecording
- class audio.AudioRecording(duration, rate=7812)
The
AudioRecording
object contains audio data and the sampling rate associated to it.The size of the internal buffer will depend on the
rate
(number of samples per second) andduration
parameters. The larger these values are, the more memory that will be used.- Parameters:
duration – Indicates how many milliseconds of audio this instance can store.
rate – The sampling rate at which data will be stored via the microphone, or played via the
audio.play()
function.
- set_rate(sample_rate)
Configure the sampling rate associated with the data in the
AudioRecording
instance.- Parameters:
sample_rate – The sample rate to set.
- get_rate()
Return the configured sampling rate for this
AudioRecording
instance.- Returns:
The configured sample rate.
- copy()
- Returns:
a copy of the
AudioRecording
.
- track(start_ms=0, end_ms=-1)
Create an AudioTrack instance from a portion of the data in this
AudioRecording
instance.Out-of-range values will be truncated to the recording limits. If
end_ms
is lower thanstart_ms
, an empty track will be created.- Parameters:
start_ms – Where to start of the track in milliseconds.
end_ms – The end of the track in milliseconds. If the default value of
-1
is provided it will end the track at the end of the AudioRecording.
When an AudioRecording
is used to record data from the microphone,
a higher sampling rate produces better sound quality,
but it also uses more memory.
During playback, increasing the sampling rate speeds up the sound and decreasing the sample rate slows it down.
The data inside an AudioRecording
is not easy to modify, so the
AudioTrack
class is provided to help access the audio data like a list.
The method AudioRecording.track()
can be used to create an AudioTrack
,
and its arguments start_ms
and end_ms
can be used to slice portions
of the data.
AudioTrack
- class audio.AudioTrack(buffer, rate=None)
The
AudioTrack
object points to the data provided by the input buffer, which can be anAudioRecording
, anotherAudioTrack
, or a buffer-like object like abytearray
.When the input buffer has an associated rate (e.g. an
AudioRecording
orAudioTrack
), the rate is copied. If the buffer object does not have a rate, the default value of 7812 is used.Changes to an
AudioTrack
rate won’t affect the original source rate, so multiple instances pointing to the same buffer can have different rates and the original buffer rate would stay unmodified.- Parameters:
buffer – The buffer containing the audio data.
rate – The sampling rate at which data will be stored via the microphone, or played via the
audio.play()
function.
- set_rate(sample_rate)
Configure the sampling rate associated with the data in the
AudioTrack
instance.- Parameters:
sample_rate – The sample rate to set.
- get_rate()
Return the configured sampling rate for this
AudioTrack
instance.- Returns:
The configured sample rate.
- copyfrom(other)
Overwrite the data in this
AudioTrack
with the data from anotherAudioTrack
,AudioRecording
, or buffer-like object like abytearray
instance.If the input buffer is smaller than the available space in this instance, the rest of the data is left untouched. If it is larger, it will stop copying once this instance is filled.
- Parameters:
other – Buffer-like instance from which to copy the data.
An AudioTrack
can be created from an AudioRecording
, another
AudioTrack
, or a bytearray
and individual bytes can be accessed and
modified like elements in a list:
my_track = AudioTrack(bytearray(100))
# Create a square wave
half_length = len(my_track) // 2
for i in range(half_length):
my_track[i] = 255
for i in range(half_length, len(my_track)):
my_track[i] = 0
Or smaller AudioTracks can be created using slices, useful to send them via radio or serial:
recording = microphone.record(duration=2000)
track = AudioTrack(recording)
packet_size = 32
for i in range(0, len(track), packet_size):
radio.send_bytes(track[i:i+packet_size])
Example
from microbit import *
# An AudioRecording holds the audio data
recording = audio.AudioRecording(duration=4000)
# AudioTracks point to a portion of the data in the AudioRecording
# We can obtain the an AudioTrack from the AudioRecording.track() method
first_half = recording.track(end_ms=2000)
# Or we can create an AudioTrack from an AudioRecording and slice it
full_track = audio.AudioTrack(recording)
second_half = full_track[full_track.length() // 2:]
while True:
if button_a.is_pressed():
# We can record directly inside the AudioRecording
microphone.record(recording)
if button_b.is_pressed():
audio.play(recording, wait=False)
# The rate can be changed while playing
first_half.set_rate(
scale(accelerometer.get_x(), from_=(-1000, 1000), to=(3_000, 30_000))
)
if pin_logo.is_touched():
# We can also play the AudioTrack pointing to the AudioRecording
audio.play(first_half)
AudioFrame
- class audio.AudioFrame
An
AudioFrame
object is a list of 32 samples each of which is an unsigned byte (whole number between 0 and 255).It takes just over 4 ms to play a single frame.
- copyfrom(other)
Overwrite the data in this
AudioFrame
with the data from anotherAudioFrame
instance.- Parameters:
other –
AudioFrame
instance from which to copy the data.
Technical Details
Note
You don’t need to understand this section to use the audio
module.
It is just here in case you wanted to know how it works.
The audio
module can consumes an iterable (sequence, like list or tuple, or
generator) of AudioFrame
instances, each 32 samples at 7812.5 Hz,
which take just over 4 milliseconds to play each frame
(1/7812.5 * 32 = 0.004096 = 4096 microseconds).
The function play
fully copies all data from each AudioFrame
before it
calls next()
for the next frame, so a sound source can use the same
AudioFrame
repeatedly.
The audio
module has an internal 64 sample buffer from which it reads
samples. When reading reaches the start or the mid-point of the buffer, it
triggers a callback to fetch the next AudioFrame
which is then copied into
the buffer. This means that a sound source has under 4ms to compute the next
AudioFrame
, and for reliable operation needs to take less 2ms (which is
32000 cycles in micro:bit V1 or 128000 in V2, so should be plenty).
AudioFrame Example
Creating and populating AudioFrame
iterables and generators with
different sound waveforms:
from microbit import display, sleep, button_a
import audio
import math
def repeated_frame(frame, count):
for i in range(count):
yield frame
# Press button A to skip to next wave.
def show_wave(name, frame, duration=1500):
display.scroll(name + " wave", wait=False,delay=100)
audio.play(repeated_frame(frame, duration),wait=False)
for i in range(75):
sleep(100)
if button_a.is_pressed():
display.clear()
audio.stop()
break
frame = audio.AudioFrame()
for i in range(len(frame)):
frame[i] = int(math.sin(math.pi*i/16)*124+128.5)
show_wave("Sine", frame)
triangle = audio.AudioFrame()
QUARTER = len(triangle)//4
for i in range(QUARTER):
triangle[i] = i*15
triangle[i+QUARTER] = 248-i*15
triangle[i+QUARTER*2] = 128-i*15
triangle[i+QUARTER*3] = i*15+8
show_wave("Triangle", triangle)
square = audio.AudioFrame()
HALF = len(square)//2
for i in range(HALF):
square[i] = 8
square[i+HALF] = 248
show_wave("Square", square)
sleep(1000)
for i in range(len(frame)):
frame[i] = 252-i*8
show_wave("Sawtooth", frame)
del frame
#Generate a waveform that goes from triangle to square wave, reasonably smoothly.
frames = [ None ] * 32
for i in range(32):
frames[i] = frame = audio.AudioFrame()
for j in range(len(triangle)):
frame[j] = (triangle[j]*(32-i) + square[j]*i)>>5
def repeated_frames(frames, count):
for frame in frames:
for i in range(count):
yield frame
display.scroll("Ascending wave", wait=False)
audio.play(repeated_frames(frames, 60))