Skip to content

Commit

Permalink
docs: Update recording & playback based on review.
Browse files Browse the repository at this point in the history
  • Loading branch information
microbit-carlos committed Feb 26, 2024
1 parent f9ac2a2 commit eef81c0
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 103 deletions.
113 changes: 34 additions & 79 deletions docs/audio.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ 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 four different kinds of audio sources that can be played using the
There are three different kinds of audio sources that can be played using the
:py:meth:`audio.play` function:

1. `Built in sounds <#built-in-sounds-v2>`_ (**V2**),
Expand All @@ -24,14 +24,9 @@ There are four different kinds of audio sources that can be played using the
my_effect = audio.SoundEffect(freq_start=400, freq_end=2500, duration=500)
audio.play(my_effect)

3. `AudioBuffer <#audiobuffer>`_ (**V2**), a generic buffer for audio that can
be used to record sound from the micro:bit V2 built-in microphone::

my_audio_buffer = microphone.record()
audio.play(my_audio_buffer)

4. `Audio Frames <#audioframe>`_, an iterable (like a list or a generator)
of Audio Frames, which are lists of 32 samples with values from 0 to 255::
3. `Audio Frames <#audioframe>`_, 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):
Expand All @@ -47,18 +42,16 @@ Functions
Play the audio source to completion.

:param source: There are four types of data that can be used as a source:
:param source: There are three types of data that can be used as a source:

- ``Sound``: The ``microbit`` 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 <#built-in-sounds-v2>`_ section.
- ``SoundEffect``: A sound effect, or an iterable of sound effects,
created via the :py:meth:`audio.SoundEffect` class
- ``AudioBuffer``: An audio buffer, or an iterable of audio buffers,
created via the :py:meth:`audio.AudioBuffer` class or
:doc:`microphone.record() <microphone>` function
- ``AudioFrame``: An iterable of ``AudioFrame`` instances as described
in the `AudioFrame Technical Details <#id2>`_ section
- ``AudioFrame``: An instance or an iterable of ``AudioFrame``
instances as described in the
`AudioFrame Technical Details <#technical-details>`_ section

:param wait: If ``wait`` is ``True``, this function will block until the
source is exhausted.
Expand All @@ -79,6 +72,13 @@ Functions
Stops all audio playback.

.. py:function:: set_rate(sample_rate)
Changes the sampling rate of ``AudioFrame`` playback.
The default playback rate is 7812 samples per second.
Decreasing the playback sampling rate results in slowed down sound, and
increasing it speeds it up.


Built-in sounds **V2**
======================
Expand Down Expand Up @@ -226,70 +226,16 @@ Sound Effects Example
:code: python


Audio Buffer **V2**
===================

.. py:class::
AudioBuffer(duration=3000, rate=11000)

Create a buffer to contain audio data and its sampling rate.

The sampling rate is configured via constructor or instance attribute,
and is used by the ``microphone.record_into()`` and
``audio.play()`` functions to configure the recording and playback rates.

For audio recording, reducing the number of samples recorded per second
will reduce the size of the data buffer, but also reduce the sound quality.
And increasing the sampling rate increases the buffer size and sound
quality.

For audio playback, reducing the sampling rate compared with the recording
rate, will slow down the audio. And increasing the playback rate
will speed it up.

The size of the buffer will be determined by the samples per second
and the ``duration`` configured when creating a new instance.

:param duration: Indicates in milliseconds, how much sound data the buffer
can contained at the configured ``data_rate``.
:param rate: Sampling rate of for the data in the buffer. This value is
used for recording and playback, and can be edited as an attribute.

.. py:function:: copy()
:returns: A copy of the Audio Buffer.

.. py:attribute:: rate
The sampling rate for the data inside the buffer.
TODO: Indicate range of valid values here.

Audio Buffer Example
--------------------

::

my_buffer = audio.AudioBuffer(duration=5000)
microphone.record_into(my_buffer)
audio.play(my_buffer)

# A smaller buffer can be generated with the same duration by using
# a lower sampling rate
smaller_buffer = audio.AudioBuffer(duration=5000, rate=5500)
microphone.record_into(my_buffer)
audio.play(my_buffer)


AudioFrame
==========

.. py:class::
AudioFrame
AudioFrame(size=32)

An ``AudioFrame`` object is a list of 32 samples each of which is an unsigned byte
(whole number between 0 and 255).
An ``AudioFrame`` object is a list of 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.
:param size: How many samples to contain in this instance.

.. py:function:: copyfrom(other)
Expand All @@ -305,13 +251,19 @@ Technical Details
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, and uses
linear interpolation to output a PWM signal at 32.5 kHz, which gives tolerable
sound quality.
The ``audio.play()`` function can consume an instance or iterable
(sequence, like list or tuple, or generator) of ``AudioFrame`` instances.
Its default playback rate is 7812 Hz, and uses linear interpolation to output
a PWM signal at 32.5 kHz.

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
Each ``AudioFrame`` instance is 32 samples by default, but it can be
configured to a different size via constructor.

So, for example, playing 32 samples at 7812 Hz takes just over 4 milliseconds
(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
Expand All @@ -325,5 +277,8 @@ the buffer. This means that a sound source has under 4ms to compute the next
AudioFrame Example
------------------

Creating and populating ``AudioFrame`` iterables and generators with
different sound waveforms:

.. include:: ../examples/waveforms.py
:code: python
83 changes: 59 additions & 24 deletions docs/microphone.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,51 @@ accessible via variables in ``microbit.SoundEvent``:
Recording
=========

TODO:
* Describe the feature.
* Indicate how the sampling rate relates to recording quality.
* Indicate how changing the sampling rate on the fly affects playback speed.
* What happens if the user changes the sampling rate while recording?
The microphone can record audio into an :doc:`AudioFrame <audio>`, which can
then be played with the ``audio.play()`` function.

::
Audio sampling is the process of converting sound into a digital format.
To do this, the microphone takes samples of the sound waves at regular
intervals. How many samples are recorded per second is known as the
"sampling rate", so recording at a higher sampling rate increases the sound
quality, but as more samples are saved, it also takes more memory.

The microphone sampling rate can be configured during sound recording via
the ``rate`` argument in the ``record()`` and ``record_into()`` functions.

At the other side, the audio playback sampling rate indicates how many samples
are played per second. So if audio is played back with a higher sampling rate
than the rate used during recording, then the audio will sound speeded up.
If the playback sampling rate is twice the recording rate, the sound will take
half the time to be played to completion. Similarly, if the playback rate
is halved, it will play half as many samples per second, and so it will
take twice as long to play the same amount of samples.

How do you think a voice recording will sound if the playback rate is
increased or decreased? Let's try it out!::

from microbit import *

RECORDING_SAMPLING_RATE = 11000

while True:
if pin_logo.is_touched():
# Record and play back at the same rate
my_recording = microphone.record(duration=3000, rate=RECORDING_SAMPLING_RATE)
audio.play(my_recording)

if button_a.is_pressed():
my_recording = microphone.record(duration=5000)
# Play back at half the sampling rate
my_recording = microphone.record(duration=3000, rate=RECORDING_SAMPLING_RATE)
audio.set_rate(RECORDING_SAMPLING_RATE / 2)
audio.play(my_recording)

if button_b.is_pressed():
# Play back at twice the sampling rate
my_recording = microphone.record(duration=3000, rate=RECORDING_SAMPLING_RATE)
audio.set_rate(RECORDING_SAMPLING_RATE * 2)
audio.play(my_recording)

sleep(200)

Functions
Expand Down Expand Up @@ -79,7 +110,6 @@ Functions
* **event**: a sound event, such as ``SoundEvent.LOUD`` or
``SoundEvent.QUIET``.

* **value**: The threshold level in the range 0-255. For example,
``set_threshold(SoundEvent.LOUD, 250)`` will only trigger if the sound is
very loud (>= 250).
Expand All @@ -89,7 +119,7 @@ Functions
* **return**: a representation of the sound pressure level in the range 0 to
255.

.. py:function:: record(duration=3000, rate=11000, wait=True)
.. py:function:: record(duration=3000, rate=7812, wait=True)
Record sound for the amount of time indicated by ``duration`` at the
sampling rate indicated by ``rate``.
Expand All @@ -98,22 +128,23 @@ Functions
recording and the sampling rate. The higher these values, the more memory
it will use.

A lower sampling rate will reduce memory consumption and sound quality.
A lower sampling rate will reduce both memory consumption and sound
quality.

If there isn't enough memory available a ``MemoryError`` will be raised.

:param duration: How much time to record in milliseconds.
:param rate: Number of samples to capture per second.
:param wait: When set to ``True`` it blocks until the recording is
done, if it is set to ``False`` it will run in the background.
:returns: An ``AudioBuffer``, configured at the provided ``duration``
and ``rate``, with the sound data.
:returns: An ``AudioFrame`` with the sound samples.

.. py:function:: record_into(buffer, rate=11000, wait=True)
.. py:function:: record_into(buffer, rate=7812, wait=True)
Record sound into an existing ``AudioBuffer``.
Record sound into an existing ``AudioFrame`` until it is filled,
or the ``stop_recording()`` function is called.

:param buffer: An ``AudioBuffer`` to record the microphone sound.
:param buffer: An ``AudioFrame`` to record sound.
:param rate: Number of samples to capture per second.
:param wait: When set to ``True`` it blocks until the recording is
done, if it is set to ``False`` it will run in the background.
Expand All @@ -134,7 +165,7 @@ Functions
``microphone.SENSITIVITY_HIGH``.

These constants correspond to a number, and any values between these
constants are valid arguments
constants are valid arguments.

:param gain: Microphone gain.

Expand Down Expand Up @@ -196,35 +227,39 @@ An example of recording and playback with a display animation::

from microbit import *

talk_open = Image(
mouth_open = Image(
"09090:"
"00000:"
"09990:"
"90009:"
"09990"
)
talk_closed = Image(
mouth_closed = Image(
"09090:"
"00000:"
"00000:"
"99999:"
"00000"
)

my_recording = audio.AudioBuffer(duration=5000, rate=5500)
RECORDING_RATE = 5500
RECORDING_SECONDS = 5
RECORDING_SIZE = RECORDING_RATE * RECORDING_SECONDS

my_recording = audio.AudioBuffer(size=RECORDING_SIZE)

while True:
if button_a.is_pressed():
microphone.record_into(my_recording, rate=5500, wait=False)
display.show([talk_open, talk_closed], loop=True, wait=False, delay=150)
while button_a.is_pressed():
microphone.record_into(my_recording, rate=RECORDING_RATE, wait=False)
display.show([mouth_open, mouth_closed], loop=True, wait=False, delay=150)
while button_a.is_pressed() and microphone.is_recording():
sleep(50)
display.show(mouth_open, loop=False) # workaround issue #150
microphone.stop_recording()
display.clear()
if button_b.is_pressed():
audio.play(my_recording, wait=False)
while audio.is_playing():
x = accelerometer.get_x()
my_recording.rate = scale(x, (-1000, 1000), (2250, 11000))
audio.set_rate(scale(x, (-1000, 1000), (2250, 11000)))
sleep(50)
sleep(100)

0 comments on commit eef81c0

Please sign in to comment.