11. Audio: AudioFrames
11.1. AudioFrames
frame = audio.AudioFrame()
to create the audioframe object.frame[i] = ...
to fill all 32 samples as i changes from 0 to 31.11.2. Sawtooth AudioFrame
from microbit import *
import audio
def get_sawtooth_frame():
frame = audio.AudioFrame()
# len = 32
for i in range(len(frame)):
frame[i] = int(252 - i * 8)
return frame
11.3. Generators to avoid memory limits
audio.play
plays an iterable (a list or generator) of AudioFrame instances.repeated_frame1
below, the list size is limited to about 8000 iterations (about 50 seconds) as it takes up memory.def repeated_frame1(frame, count):
# will hit a memory problem after about 8000 repeats
wave = []
for i in range(count):
wave.append(frame)
return wave
yield
in the for-loop to create a generator that releases each repeat of the frame
as it is needed in the calling code.repeated_frame
, uses a generator (via the yield keyword) to create an iterable object.def repeated_frame(frame, count):
# use a generator to reduce memory usage
for i in range(count):
yield frame
repeat_count = 40
to alter the length it is played.from microbit import * # Importing all the modules from microbit library
import audio # Importing the audio module
def play_rep_frame(name, frame, count):
"""
This function plays a repeated audio frame.
Parameters:
name (str): The name of the audio frame.
frame (AudioFrame): The audio frame to be repeated.
count (int): The number of times the frame is to be repeated.
"""
# Generate the repeated audio frame
wave = repeated_frame(frame, count)
# If an audio is already playing
while audio.is_playing():
sleep(4)
audio.stop()
display.scroll(name, wait=False, delay=60)
# Display the name of the audio frame
audio.play(wave, wait=False)
def repeated_frame(frame, count):
"""
This function generates a repeated audio frame using a generator to reduce memory usage.
Parameters:
frame (AudioFrame): The audio frame to be repeated.
count (int): The number of times the frame is to be repeated.
Returns:
generator: A generator that yields the audio frame 'count' number of times.
"""
for i in range(count): # Repeat for 'count' number of times
yield frame # Yield the audio frame
def get_sawtooth_frame():
"""
This function generates a sawtooth audio frame.
Returns:
AudioFrame: A sawtooth audio frame.
"""
frame = audio.AudioFrame() # Create a new audio frame
for i in range(len(frame)): # For each sample in the audio frame
frame[i] = int(252 - i * 8) # Generate a sawtooth wave
return frame # Return the sawtooth audio frame
repeat_count = 40 # The number of times the audio frame is to be repeated
sawtooth_frame = get_sawtooth_frame() # Get the sawtooth audio frame
while True: # Main loop
if button_a.is_pressed(): # If the A-button is pressed
play_rep_frame("saw", sawtooth_frame, repeat_count) # Play the sawtooth audio frame
sleep(100) # Wait for 100 milliseconds
11.4. Common AudioFrame structures
from microbit import * # Importing all the modules from microbit library
import audio # Importing the audio module
def play_rep_frame(name, frame, count):
"""
This function plays a repeated audio frame.
Parameters:
name (str): The name of the audio frame.
frame (AudioFrame): The audio frame to be repeated.
count (int): The number of times the frame is to be repeated.
"""
wave = repeated_frame(frame, count) # Generate the repeated audio frame
while audio.is_playing(): # If an audio is already playing
sleep(4) # Wait for 4 milliseconds
audio.stop() # Stop the currently playing audio
display.scroll(name, wait=False, delay=60) # Display the name of the audio frame
audio.play(wave, wait=False) # Play the new audio frame
def repeated_frame(frame, count):
"""
This function generates a repeated audio frame using a generator to reduce memory usage.
Parameters:
frame (AudioFrame): The audio frame to be repeated.
count (int): The number of times the frame is to be repeated.
Returns:
generator: A generator that yields the audio frame 'count' number of times.
"""
for i in range(count): # Repeat for 'count' number of times
yield frame # Yield the audio frame
def get_sawtooth_frame():
"""
This function generates a sawtooth audio frame.
Returns:
AudioFrame: A sawtooth audio frame.
"""
frame = audio.AudioFrame() # Create a new audio frame
for i in range(len(frame)): # For each sample in the audio frame
frame[i] = int(252 - i * 8) # Generate a sawtooth wave
return frame # Return the sawtooth audio frame
def get_sawtooth2_frame():
"""
This function generates a modified sawtooth audio frame.
Returns:
AudioFrame: A modified sawtooth audio frame.
"""
frame = audio.AudioFrame() # Create a new audio frame
for i in range(len(frame)): # For each sample in the audio frame
if i < len(frame) // 2:
frame[i] = int(252 - i * 16)
else:
frame[i] = int(252 - (i - 16) * 16)
return frame # Return the modified sawtooth audio frame
def get_square_frame():
"""
This function generates a square wave audio frame.
Returns:
AudioFrame: A square wave audio frame.
"""
frame = audio.AudioFrame() # Create a new audio frame
for i in range(len(frame)): # For each sample in the audio frame
if i < len(frame) // 2:
frame[i] = 252
else:
frame[i] = 0
return frame # Return the square wave audio frame
def get_square2_frame():
"""
This function generates a modified square wave audio frame.
Returns:
AudioFrame: A modified square wave audio frame.
"""
frame = audio.AudioFrame() # Create a new audio frame
for i in range(len(frame)): # For each sample in the audio frame
if i < len(frame) // 4:
frame[i] = 252
elif i < len(frame) * 2 // 4:
frame[i] = 0
elif i < len(frame) * 3 // 4:
frame[i] = 252
else:
frame[i] = 0
return frame # Return the modified square wave audio frame
def get_triangle_frame():
"""
This function generates a triangle wave audio frame.
Returns:
AudioFrame: A triangle wave audio frame.
"""
frame = audio.AudioFrame() # Create a new audio frame
for i in range(len(frame)): # For each sample in the audio frame
if i < len(frame) // 2:
frame[i] = i * 8
else:
frame[i] = 252 - (i - 16) * 8
return frame # Return the triangle wave audio frame
def get_triangle2_frame():
"""
This function generates a modified triangle wave audio frame.
Returns:
AudioFrame: A modified triangle wave audio frame.
"""
frame = audio.AudioFrame() # Create a new audio frame
for i in range(len(frame)): # For each sample in the audio frame
if i < len(frame) // 4:
frame[i] = i * 16
elif i < len(frame) * 2 // 4:
frame[i] = 252 - (i - 8) * 16
elif i < len(frame) * 3 // 4:
frame[i] = (i - 16) * 16
else:
frame[i] = 252 - (i - 24) * 16
return frame # Return the modified triangle wave audio frame
repeat_count = 40
sawtooth_frame = get_sawtooth_frame()
sawtooth2_frame = get_sawtooth2_frame()
square_frame = get_square_frame()
square2_frame = get_square2_frame()
triangle_frame = get_triangle_frame()
triangle2_frame = get_triangle2_frame()
while True:
if pin_logo.is_touched():
play_rep_frame("saw", sawtooth_frame, repeat_count)
sleep(repeat_count * 5)
play_rep_frame("saw2", sawtooth2_frame, repeat_count)
elif button_a.is_pressed():
play_rep_frame("sqr", square_frame, repeat_count)
sleep(repeat_count * 5)
play_rep_frame("sqr2", square2_frame, repeat_count)
elif button_b.is_pressed():
play_rep_frame("tri", triangle_frame, repeat_count)
sleep(repeat_count * 5)
play_rep_frame("tri2", triangle2_frame, repeat_count)
sleep(100)
11.5. Yield from and yield in Generators
def gen1(list_of_words):
for word in list_of_words:
for char in word:
yield char
def gen2():
greetings = [
["Hello", "friend"],
["Greetings", "human"],
["G'day", "mate"],
]
for greeting in greetings:
print("")
yield from gen1(greeting)
# Use a loop to print the values generated by gen2
for value in gen2():
print(value, end=",")
gen1(list_of_words): This is a generator function that takes a list of words as input. For each word in the list, it iterates over the characters in the word and yields each character one by one. The yield keyword here is used to produce a sequence of values over time, rather than computing them at once and returning them in a list for example. This can be more memory-efficient and flexible, especially for large sequences.
gen2(): This is another generator function. It defines a list of greetings, where each greeting is a list of words. It then iterates over these greetings, and for each greeting, it uses yield from to yield all characters from gen1(greeting).
The yield from statement is a convenient way to yield all values from another generator or iterable. In this case, it yields all characters from each greeting produced by gen1. This allows you to flatten the nested structure of the greetings list into a sequence of characters.
for value in gen2(): print(value, end=”,”): This is a loop that iterates over the values yielded by gen2(), and prints each value followed by a comma. Because gen2() yields characters from the greetings, this will print all the characters in the greetings, each separated by a comma.
11.6. More complex AudioFrames
Yield
and yield from
are used to make it memory efficient.from microbit import *
import audio
def get_square_wave_frame(frequency):
"""
This function generates a square wave frame for a given frequency.
Args:
frequency (float): The frequency of the note in Hz.
Returns:
frame (audio.AudioFrame): A frame of the square wave at the given frequency.
"""
frame = audio.AudioFrame() # Initialize an empty audio frame
period = int(7812.5 / frequency) # The period of the waveform in samples
# Generate the square wave
for i in range(len(frame)):
if (i // (period // 2)) % 2 == 0:
frame[i] = 255 # High part of the wave
else:
frame[i] = 0 # Low part of the wave
return frame
def play_chord(frequencies):
"""
This function generates the audio frames for a chord and plays it.
Args:
frequencies (list): A list of frequencies in the chord.
Yields:
frame (audio.AudioFrame): An audio frame of the chord to be played.
"""
for frequency in frequencies:
frame = get_square_wave_frame(frequency) # Get the square wave frame for the frequency
for _ in range(20): # Play the note for a certain duration
yield frame
def play_sequence():
"""
This function plays a sequence of chords.
Yields:
frame (audio.AudioFrame): An audio frame of the sequence to be played.
"""
# Frequencies of the notes in the chords
chords = [
[392.00, 493.88, 587.33], # G major (G4, B4, D5)
[523.25, 659.25, 784.00], # C major (C5, E5, G5)
[587.33, 739.99, 880.00] # D major (D5, F#5, A5)
]
# Play each chord in the sequence
for chord in chords:
sleep(100) # Wait for a short duration between chords
yield from play_chord(chord) # Play the chord
# Main loop
while True:
# Check if the logo pin is touched
if pin_logo.is_touched():
# If the logo pin is touched, play the sequence of chords
audio.play(play_sequence())
sleep(10)
11.7. Advanced Technical Details
The audio
module can consume an iterable (sequence, like list or tuple, or generator) of AudioFrame
instances, each 32 samples at 7812.5 Hz, and uses linear interpolation to output a PWM signal at 32.5 kHz, which gives tolerable sound quality.
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.
11.8. Advanced Example
- Defining functions for generating and playing waves:
repeated_frame(frame, count): This function takes a frame (a single cycle of a waveform) and a count, and yields the same frame for the given count. This is used to repeat a waveform.
show_wave(name, frame, duration=1000): This function takes a name, a frame, and a duration. It scrolls the name of the wave on the micro:bit’s display, plays the audio of the wave for the given duration, and stops if button A is pressed.
repeated_frames(frames, count): Similar to repeated_frame, but this function takes multiple frames and yields each frame for the given count. This is used to repeat a sequence of waveforms.
show_waves(name, frames, duration=60): Similar to show_wave, but this function takes multiple frames. It scrolls the name of the wave on the micro:bit’s display, plays the audio of the sequence of waves for the given duration, and stops if button A is pressed.
generate_frames(wave_1, wave_2): This function takes two waves and generates a sequence of frames that transition smoothly from the first wave to the second.
- Defining functions for different waveforms:
sin_wave(): This function generates a sine wave.
tri_wave(): This function generates a triangle wave.
sq_wave(): This function generates a square wave.
saw_wave(): This function generates a sawtooth wave.
Main loop: The main loop of the script continuously generates each type of wave and plays it using the show_wave function. It also generates a sequence of frames that transition from a triangle wave to a square wave and plays it using the show_waves function.
from microbit import *
import audio
import math
def repeated_frame(frame, count):
for _ in range(count):
yield frame
def show_wave(name, frame, duration=1000):
display.scroll(name + " wave", wait=False, delay=80)
audio.play(repeated_frame(frame, duration), wait=False)
for _ in range(75):
sleep(100)
# Press button-A to skip to next wave.
if button_a.was_pressed():
display.clear()
audio.stop()
break
####
def repeated_frames(frames, count):
for frame in frames:
for _ in range(count):
yield frame
def show_waves(name, frames, duration=60):
display.scroll(name + " wave", wait=False, delay=80)
audio.play(repeated_frames(frames, duration), wait=False)
for _ in range(75):
sleep(1000)
# Press button-A to skip to next wave.
if button_a.was_pressed():
display.clear()
audio.stop()
break
#Generate a waveform that goes from one wave to another wave, reasonably smoothly.
def generate_frames(wave_1, wave_2, frame_count=10):
frames = []
for i in range(frame_count):
frame = audio.AudioFrame()
for j in range(len(wave_1)):
frame[j] = (wave_1[j]*(frame_count-i) + wave_2[j]*i) //frame_count
frames.append(frame)
return frames
#####
def sin_wave():
frame = audio.AudioFrame()
for i in range(len(frame)):
frame[i] = int(math.sin(math.pi*i/16)*124+128.5)
return frame
def tri_wave():
frame = audio.AudioFrame()
# QUARTER = 8; len(frame) = 32
QUARTER = len(frame)//4
for i in range(QUARTER):
frame[i] = i*15
frame[i+QUARTER] = 248-i*15
frame[i+QUARTER*2] = 128-i*15
frame[i+QUARTER*3] = i*15+8
return frame
def sq_wave():
frame = audio.AudioFrame()
# HALF = 16; len(frame) = 32
HALF = len(frame)//2
for i in range(HALF):
frame[i] = 8
frame[i+HALF] = 248
return frame
def saw_wave():
frame = audio.AudioFrame()
for i in range(len(frame)):
frame[i] = 252-i*8
return frame
while True:
sin = sin_wave()
show_wave("Sine", sin)
##
saw = saw_wave()
show_wave("Sawtooth", saw)
##
tri = tri_wave()
show_wave("Triangle", tri)
##
square = sq_wave()
show_wave("Square", square)
##
tri_squares = generate_frames(tri, square)
show_waves("Tri_Squares", tri_squares)
The code below uses a sin wave generator. Short sounds are made by looping through increasing frequency values.
from microbit import *
import audio
import math
def repeated_frame(frame, count):
for _ in range(count):
yield frame
def show_wave(name, frame, duration=1000):
display.scroll(name, wait=False, delay=50)
audio.play(repeated_frame(frame, duration), wait=False)
for _ in range(20):
sleep(50)
# Press button-A to skip to next wave.
if button_a.was_pressed():
display.clear()
audio.stop()
break
def sin_wave(frequency):
frame = audio.AudioFrame()
length = len(frame)
for i in range(length):
frame[i] = int(math.sin(2 * math.pi * frequency / 128 * i / length) * 124 + 128.5)
return frame
while True:
# frequency = accelerometer.get_x()
for frequency in range(20, 1024, 50):
sin = sin_wave(frequency)
show_wave(frequency, sin, duration=50)