-
Couldn't load subscription status.
- Fork 287
docs: Add Sound Effects documentation. #753
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e7ff826
b89aa5d
ce19f55
c0a2909
63fe6a0
eac0497
8cfea23
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,29 +6,57 @@ 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 | ||
| :doc:`built-in speaker <speaker>` **V2**. You can connect wired headphones or | ||
| :doc:`built-in speaker <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 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**), | ||
| e.g. ``audio.play(Sound.HAPPY)`` | ||
| 2. `Sound Effects <#sound-effects-v2>`_ (**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) | ||
|
|
||
| 3. `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:: | ||
|
|
||
| square_wave = audio.AudioFrame() | ||
| for i in range(16): | ||
| square_wave[i] = 0 | ||
| square_wave[i + 16] = 255 | ||
| audio.play([square_wave] * 64) | ||
|
|
||
|
|
||
| Functions | ||
| ========= | ||
|
|
||
| .. py:function:: play(source, wait=True, pin=pin0, return_pin=None) | ||
|
|
||
| Play the source to completion. | ||
| Play the audio source to completion. | ||
|
|
||
| :param source: ``Sound``: The ``microbit`` module contains a list of | ||
| built-in sounds that your can pass to ``audio.play()``. | ||
| :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 | ||
| - ``AudioFrame``: An iterable of ``AudioFrame`` instances as described | ||
| in the `AudioFrame Technical Details <#id2>`_ section | ||
|
|
||
| ``AudioFrame``: The source agrument can also be an iterable | ||
| of ``AudioFrame`` elements as described below. | ||
| :param wait: If ``wait`` is ``True``, this function will block until the | ||
| source is exhausted. | ||
|
|
||
| :param 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 use ``pin=None``. | ||
|
|
||
| :param return_pin: specifies a differential edge connector pin to connect | ||
| to an external speaker instead of ground. This is ignored for the **V2** | ||
| revision. | ||
|
|
@@ -41,34 +69,9 @@ Functions | |
|
|
||
| Stops all audio playback. | ||
|
|
||
| Classes | ||
| ======= | ||
|
|
||
| .. py:class:: | ||
| 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. | ||
|
|
||
| .. py:function:: copyfrom(other) | ||
|
|
||
| Overwrite the data in this ``AudioFrame`` with the data from another | ||
| ``AudioFrame`` instance. | ||
|
|
||
| :param other: ``AudioFrame`` instance from which to copy the data. | ||
|
|
||
|
|
||
| Using audio | ||
| =========== | ||
|
|
||
| You will need a sound source, as input to the ``play`` function. You can use | ||
| the built-in sounds **V2** from the ``microbit`` module, ``microbit.Sound``, or | ||
| generate your own, like in ``examples/waveforms.py``. | ||
|
|
||
| Built-in sounds **V2** | ||
| ---------------------- | ||
| ====================== | ||
|
|
||
| The built-in sounds can be called using ``audio.play(Sound.NAME)``. | ||
|
|
||
|
|
@@ -83,8 +86,154 @@ The built-in sounds can be called using ``audio.play(Sound.NAME)``. | |
| * ``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** | ||
| ==================== | ||
|
|
||
| .. py:class:: | ||
| SoundEffect(freq_start=500, freq_end=2500, duration=500, vol_start=255, vol_end=0, wave=WAVE_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 attributes ``my_effect.duration = 500``. | ||
|
|
||
| :param freq_start: Start frequency in Hertz (Hz), default: ``500`` | ||
| :param freq_end: End frequency in Hertz (Hz), default: ``2500`` | ||
| :param duration: Duration of the sound (ms), default: ``500`` | ||
| :param vol_start: Start volume value, range 0-255, default: ``255`` | ||
| :param vol_end: End volume value, range 0-255, default: ``0`` | ||
| :param wave: Type of wave shape, one of these values: ``WAVE_SINE``, | ||
| ``WAVE_SAWTOOTH``, ``WAVE_TRIANGLE``, ``WAVE_SQUARE``, | ||
| ``WAVE_NOISE`` (randomly generated noise). Default: ``WAVE_SQUARE`` | ||
| :param fx: Effect to add on the sound, one of the following values: | ||
| ``FX_TREMOLO``, ``FX_VIBRATO``, ``FX_WARBLE``, or ``FX_NONE``. | ||
| Default: ``FX_NONE`` | ||
| :param 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`` | ||
|
|
||
| .. py:function:: copy() | ||
|
|
||
| :returns: A copy of the SoundEffect. | ||
|
|
||
| .. py:attribute:: freq_start | ||
|
|
||
| Start frequency in Hertz (Hz), a number between ``0`` and ``9999``. | ||
|
|
||
| .. py:attribute:: freq_end | ||
|
|
||
| End frequency in Hertz (Hz), a number between ``0`` and ``9999```. | ||
|
|
||
| .. py:attribute:: duration | ||
|
|
||
| Duration of the sound in milliseconds, a number between ``0`` and | ||
| ``9999``. | ||
|
|
||
| .. py:attribute:: vol_start | ||
|
|
||
| Start volume value, a number between ``0`` and ``255``. | ||
|
|
||
| .. py:attribute:: vol_end | ||
|
|
||
| End volume value, a number between ``0`` and ``255``. | ||
|
|
||
| .. py:attribute:: wave | ||
|
|
||
| Type of wave shape, one of these values: ``WAVE_SINE``, | ||
| ``WAVE_SAWTOOTH``, ``WAVE_TRIANGLE``, ``WAVE_SQUARE``, | ||
| ``WAVE_NOISE`` (randomly generated noise). | ||
|
|
||
| .. py:attribute:: fx | ||
|
|
||
| Effect to add on the sound, one of the following values: | ||
| ``FX_TREMOLO``, ``FX_VIBRATO``, ``FX_WARBLE``, or ``None``. | ||
|
|
||
| .. py:attribute:: 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 :doc:`REPL </devguide/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, wave=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)) | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was really happy with including these, but I think their relationship to the built-in sounds is curious to explain. I don't know that this means we shouldn't have them, but I'm struggling to work out what to call them.
This is one downside to "SoundEffect" over pure "Effect" - it ties the built-in sounds more closely to the SoundEffects - though @microbit-carlos points out that it does make sense that a 'Sound' is made out of SoundEffects... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The existing built-in sounds could be rewritten as a (constant) tuple of (constant) Eg: >>> audio.Sound.SPRING
(SoundEffect(...), SoundEffect(...))Then users could really see how There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is something that was quite desirable, our only concern is all the extra flash usage, specially if the strings are already in CODAL and probably cannot be excluded as they are. |
||
| Sound Effects Example | ||
| --------------------- | ||
|
|
||
| .. include:: ../examples/soundeffects.py | ||
| :code: python | ||
|
|
||
| AudioFrame | ||
| ========== | ||
|
|
||
| .. py:class:: | ||
| 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. | ||
|
|
||
| .. py:function:: copyfrom(other) | ||
|
|
||
| Overwrite the data in this ``AudioFrame`` with the data from another | ||
| ``AudioFrame`` instance. | ||
|
|
||
| :param 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. | ||
|
|
@@ -104,11 +253,11 @@ 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, so should be plenty). | ||
| 32000 cycles in micro:bit V1 or 128000 in V2, so should be plenty). | ||
|
|
||
|
|
||
| Example | ||
| ======= | ||
| AudioFrame Example | ||
| ------------------ | ||
|
|
||
| .. include:: ../examples/waveforms.py | ||
| :code: python | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| 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, | ||
| wave=audio.SoundEffect.WAVE_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.wave = audio.SoundEffect.WAVE_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 neededhttps://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(): | ||
| # On button B re-enable speaker & play an effect while showing an image | ||
| speaker.on() | ||
| audio.play(audio.SoundEffect(), wait=False) | ||
| display.show(Image.MUSIC_QUAVER) | ||
| sleep(500) | ||
|
|
||
| sleep(150) |
Uh oh!
There was an error while loading. Please reload this page.