CircuitPython Audio Out

CircuitPython 3.0 and higher comes with an updated audioio, which provides built-in audio output support. You can play generated tones. You can also play, pause and resume wave files. You can have 3V-peak-to-peak analog output or I2S digital output. In this page we will show using analog output.

This is great for all kinds of projects that require sound, like a tone piano or anything where you'd like to add audio effects!

ESP8266, Trinket M0 and Gemma M0 do not support audioio! You must use an M0 Express or M4 Express board for this.

The first example will show you how to generate a tone and play it using a button. The second example will show you how to play, pause, and resume a wave file using a button to resume. Both will play the audio through an audio jack. The default volume on both of these examples is painfully high through headphones. So, we've added a potentiometer and included some code in the tone generation example to control volume.

In our code, we'll use pin A0 for our audio output, as this is the only DAC pin available on every Express board. The M0 Express boards have audio output on A0. The M4 Express boards have two audio output pins, A0 and A1, however we'll be using only A0 in this guide.

Play a Tone

Copy and paste the following code into code.py using your favorite editor, and save the file.

import time
import array
import math
import audioio
import board
import digitalio

button = digitalio.DigitalInOut(board.A1)
button.switch_to_input(pull=digitalio.Pull.UP)

tone_volume = 0.1  # Increase this to increase the volume of the tone.
frequency = 440  # Set this to the Hz of the tone you want to generate.
length = 8000 // frequency
sine_wave = array.array("H", [0] * length)
for i in range(length):
    sine_wave[i] = int((1 + math.sin(math.pi * 2 * i / length)) * tone_volume * (2 ** 15 - 1))

audio = audioio.AudioOut(board.A0)
sine_wave_sample = audioio.RawSample(sine_wave)

while True:
    if not button.value:
        audio.play(sine_wave_sample, loop=True)
        time.sleep(1)
        audio.stop()

First we create the button object, assign it to pin A1, and set it as an input with a pull-up. Even though the button switch involves digitalio, we're using an A-pin so that the same setup code will work across all the boards.

Since the default volume was incredibly high, we included a tone_volume variable in the sine wave code. You can use the code to control the volume by increasing or decreasing this number to increase or decrease the volume. You can also control volume with the potentiometer by rotating the knob.

To set the frequency of the generated tone, change the number assigned to the frequency variable to the Hz of the tone you'd like to generate.

Then, we generate one period of a sine wave with the math.sin function, and assign it to sine_wave.

Next, we create the audio object, and assign it to pin A0.

We create a sample of the sine wave by using RawSample and providing the sine_wave we created.

Inside our loop, we check to see if the button is pressed. The button has two states True and False. The button.value defaults to the True state when not pressed. So, to check if it has been pressed, we're looking for the False state. So, we check to see if not button.value which is the equivalent of not True, or False.

Once the button is pressed, we play the sample we created and we loop it. The time.sleep(1) tells it to loop (play) for 1 second. Then we stop it after 1 second is up. You can increase or decrease the length of time it plays by increasing or decreasing the number of seconds provided to time.sleep(). Try changing it from 1 to 0.5. Now try changing it to 2. You can change it to whatever works for you!

That's it!

Play a Wave File

You can use any supported wave file you like. CircuitPython supports mono or stereo, at 22 KHz sample rate (or less) and 16-bit WAV format. The M0 boards support ONLY MONO. The reason for mono is that there's only one analog output on those boards! The M4 boards support stereo as they have two outputs. The 22 KHz or less because the circuitpython can't handle more data than that (and also it will not sound much better) and the DAC output is 10-bit so anything over 16-bit will just take up room without better quality.

Since the WAV file must fit on the CircuitPython file system, it must be under 2 MB.

CircuitPython does not support OGG or MP3. Just WAV!

We have a detailed guide on how to generate WAV files here.

We've included the one we used here. Download it and copy it to your board.

We're going to play the wave file for 6 seconds, pause it, wait for a button to be pressed, and then resume the file to play through to the end. Then it loops back to the beginning and starts again! Let's take a look.

Copy and paste the following code into code.py using your favorite editor, and save the file.

import time
import audioio
import board
import digitalio

button = digitalio.DigitalInOut(board.A1)
button.switch_to_input(pull=digitalio.Pull.UP)

wave_file = open("StreetChicken.wav", "rb")
wave = audioio.WaveFile(wave_file)
audio = audioio.AudioOut(board.A0)

while True:
    audio.play(wave)

    # This allows you to do other things while the audio plays!
    t = time.monotonic()
    while time.monotonic() - t < 6:
        pass

    audio.pause()
    print("Waiting for button press to continue!")
    while button.value:
        pass
    audio.resume()
    while audio.playing:
        pass
    print("Done!")

First we create the button object, assign it to pin A1, and set it as an input with a pull-up.

Next we then open the file, "StreetChicken.wav" as a readable binary and store the file object in wave_file which is what we use to actually read audio from: wave_file = open("StreetChicken.wav", "rb").

Now we will ask the audio playback system to load the wave data from the file wave = audioio.WaveFile(wave_file) and finally request that the audio is played through the A0 analog output pin audio = audioio.AudioOut(board.A0).

The audio file is now ready to go, and can be played at any time with audio.play(wave)!

Inside our loop, we start by playing the file.

Next we have the block that tells the code to wait 6 seconds before pausing the file. We chose to go with using time.monotonic() because it's non-blocking which means you can do other things while the file is playing, like control servos or NeoPixels! At any given point in time, time.monotonic() is equal to the number seconds since your board was last power-cycled. (The soft-reboot that occurs with the auto-reload when you save changes to your CircuitPython code, or enter and exit the REPL, does not start it over.) When it is called, it returns a number with a decimal. When you assign time.monotonic() to a variable, that variable is equal to the number of seconds that time.monotonic() was equal to at the moment the variable was assigned. You can then call it again and subtract the variable from time.monotonic() to get the amount of time that has passed. For more details, check out this example.

So, we assign t = time.monotonic() to get a starting point. Then we say pass, or "do nothing" until the difference between t and time.monotonic() is greater than 6seconds. In other words, continue playing until 6 seconds passes. Remember, you can add in other code here to do other things while you're playing audio for 6 seconds.

Then we pause the audio and print to the serial console, "Waiting for button press to continue!"

Now we're going to wait for a button press in the same way we did for playing the generated tone. We're saying while button.value, or while the button is returning True, pass. Once the button is pressed, it returns False, and this tells the code to continue.

Once the button is pressed, we resume playing the file. We tell it to finish playing saying while audio.playing: pass.

Finally, we print to the serial console, "Done!"

You can do this with any supported wave file, and you can include all kinds of things in your project while the file is playing. Give it a try!

Wire It Up

Along with your microcontroller board, we're going to be using:

Breadboard-Friendly 3.5mm Stereo Headphone Jack

PRODUCT ID: 1699
Pipe audio in or out of your project with this very handy breadboard-friendly audio jack. It's a stereo jack with disconnect-switches on Left and Right channels as well as a center...
$0.95
IN STOCK

Tactile Switch Buttons (12mm square, 6mm tall) x 10 pack

PRODUCT ID: 1119
Medium-sized clicky momentary switches are standard input "buttons" on electronic projects. These work best in a PCB but
$2.50
IN STOCK

Panel Mount 10K potentiometer (Breadboard Friendly)

PRODUCT ID: 562
This potentiometer is a two-in-one, good in a breadboard or with a panel. It's a fairly standard linear taper 10K ohm potentiometer, with a grippy shaft. It's smooth and easy...
$0.95
IN STOCK

100uF 16V Electrolytic Capacitors - Pack of 10

PRODUCT ID: 2193
We like capacitors so much we made a kids show about them. ...
$1.95
IN STOCK

Full sized breadboard

PRODUCT ID: 239
This is a 'full-size' breadboard, 830 tie points. Good for small and medium projects. It's 2.2" x 7" (5.5 cm x 17 cm) with a standard double-strip in the middle...
OUT OF STOCK

Premium Male/Male Jumper Wires - 20 x 6" (150mm)

PRODUCT ID: 1957
These Male/Male Jumper Wires are handy for making wire harnesses or jumpering between headers on PCB's. These premium jumper wires are 6" (150mm) long and come in a 'strip' of 20...
$1.95
IN STOCK

And to make it easier to wire up the Circuit Playground Express:

Small Alligator Clip to Male Jumper Wire Bundle - 6 Pieces

PRODUCT ID: 3448
When working with unusual non-header-friendly surfaces, these handy cables will be your best friends! No longer will you have long, cumbersome strands of alligator clips. These...
$3.95
IN STOCK

Button switches with four pins are really two pairs of pins. When wiring up a button switch with four pins, the easiest way to verify that you're wiring up the correct pins is to wire up opposite corners of the button switch. Then there's no chance that you'll accidentally wire up the same pin twice.

Here are the steps you're going to follow to wire up these components:

  • Connect the ground pin on your board to a ground rail on the breadboard because you'll be connecting all three components to ground. 
  • Connect one pin on the button switch to pin A1 on your board, and the opposite pin on the button switch to the ground rail on the breadboard.
  • Connect the left and right pin on the audio jack to each other.
  • Connect the center pin on the audio jack to the ground rail on the breadboard.
  • Connect the left pin to the negative side of a 100mF capacitor.
  • Connect the positive side of the capacitor to the center pin on the potentiometer.
  • Connect the right pin on the potentiometer to pin A0 on your board.
  • Connect the left pin of the potentiometer to the ground rail on the breadboard.

The list below shows wiring diagrams to help with finding the correct pins and wiring up the different components. The ground wires are black. The wire for the button switch is yellow. The wires involved with audio are blue.

Wiring is the same for the M4 versions of the boards as it is for the M0 versions. Follow the same image for both.

 

Use a breadboard to make your wiring neat and tidy!

Circuit Playground Express is wired electrically the same as the ItsyBitsy/Feather/Metro above but we use alligator clip to jumper wires instead of plain jumpers

This guide was first published on Aug 02, 2019. It was last updated on Aug 02, 2019. This page (CircuitPython Audio Out) was last updated on Sep 17, 2019.