Todbot's Audio Tricks

In CircuitPython, there are three different techniques to output audio:

  • DAC using audioio
  • PWM using audiopwmio - requires an external RC filter (at least)
  • I2S using audiobusio - requires external I2S decoder hardware

CircuitPython support on particular microcontroller may include support for several of these methods (e.g. SAMD51 supports DAC & I2S, just one (e.g. ESP32-S2 supports only I2S) or even none (e.g. Teensy).

Audio out using PWM

This uses the audiopwmio library, only available for Raspberry Pi Pico (or other RP2040-based boards) and nRF52840-based boards like Adafruit Feather nRF52840 Express. On RP2040-based boards, any pin can be PWM Audio pin. See the audiopwomio Support Matrix for details.

import time,board
from audiocore import WaveFile
from audiopwmio import PWMAudioOut as AudioOut
wave_file = open("laser2.wav", "rb")
wave = WaveFile(wave_file)
audio = AudioOut(board.TX) # must be PWM-capable pin
while True:
    print("audio is playing:",audio.playing)
    if not audio.playing:
      audio.play(wave)
      wave.sample_rate = int(wave.sample_rate * 0.90) # play 10% slower each time
    time.sleep(0.1)

Note: Sometimes the audiopwmio driver gets confused, particularly if there's other USB access, so you may have to reset the board to get PWM audio to work again.

Note: WAV file whould be "16-bit Unsigned PCM" format. Sample rate can be up to 44.1 kHz, and is parsed by audiocore.WaveFile.

Note: PWM output must be filtered and converted to line-level to be usable. Use an RC circuit to accomplish this, see this twitter thread for details.

Audio out using a DAC

Some CircuitPython boards have one or more built-in Digital to Audio Converters (DACs). These are on specific pins. The code is the the same as above, with just the import line changing.

import time,random,board
from audiocore import WaveFile
from audioio import AudioOut as AudioOut # only DAC
wave_file = open("laser20.wav", "rb")
wave = WaveFile(wave_file)
audio = AudioOut(board.A0)  # must be DAC-capable pin, A0 on QTPy Haxpress
while True:
  print("audio is playing:",audio.playing)
  if not audio.playing:
    audio.play(wave)
    wave.sample_rate = int(wave.sample_rate * 0.90) # play 10% slower each time
  time.sleep(0.1)

Preparing WAV files for CircuitPython

Convert files to appropriate WAV format (mono, 22050 Hz, 16-bit signed seem best). There are many software sound editing programs. Here are two solutions.

1) See the Adafruit guide Microcontroller Compatible Audio File Conversion

2) Use the command line program sox.

sox loop.mp3 -b 16 -c 1 -r 22050 loop.wav

To get sox on various platforms:

This guide was first published on Apr 02, 2022. It was last updated on Jun 25, 2021.

This page (Audio) was last updated on Mar 23, 2022.

Text editor powered by tinymce.