Skip to content

Instantly share code, notes, and snippets.

@todbot
Last active June 12, 2024 23:11
Show Gist options
  • Save todbot/3ed3bdb91ca5365a956fed0874e69952 to your computer and use it in GitHub Desktop.
Save todbot/3ed3bdb91ca5365a956fed0874e69952 to your computer and use it in GitHub Desktop.
fade between different drum loops on QTPy RP2040 or Xiao RP2040 in CircuitPython
"""
beatfader_demo.py -- fade between different drum loops
10 Jun 2024 - @todbot / Tod Kurt
originally from
https://github.com/todbot/circuitpython-tricks/blob/main/larger-tricks/beatfader.py
Demonstrate how to play WAVs via PWM audio
Functions, designed for QTPy RP2040 or Xiao RP2040:
- knob on A0 -- master "fader" between different playing WAV loops
- PWM audio on MOSI
To use:
- Copy the "beatfader_wavs" directory at this URL to the CIRCUITPY drive
https://github.com/todbot/circuitpython-tricks/tree/main/larger-tricks/beatfader_wavs
- Or use your own loops, prefer 22050 kHz sample rate and up to around 10 or so
"""
import os, time
import board, analogio
import audiocore, audiomixer, audiopwmio
wav_dir = "/beatfader_wavs"
def load_wavs(wav_dir):
""" find all the WAVs in a given dir, use the first WAV found as model
for what the other WAVs should be, and returns filenames, sample_rate, and
channel_count"""
some_wav_fnames =[]
for fname in sorted(os.listdir(wav_dir)): # sort so we have known order
if fname.lower().endswith('.wav') and not fname.startswith('.'):
some_wav_fnames.append(wav_dir+"/"+fname)
print("some_wav_fnames:", some_wav_fnames)
# determine their sample rate and channel count of first sample,
# use it to verify all other samples match
with audiocore.WaveFile(some_wav_fnames[0]) as wav:
sample_rate = wav.sample_rate
channel_count = wav.channel_count
print("sample_rate", sample_rate, "chan_cnt", channel_count)
good_wav_fnames = [] # these are the good ones
for i, fname in enumerate(some_wav_fnames):
with audiocore.WaveFile(some_wav_fnames[i]) as wav:
if wav.sample_rate != sample_rate or wav.channel_count != channel_count:
print("bad WAV file:", fname)
else:
good_wav_fnames.append(fname)
return good_wav_fnames, sample_rate, channel_count
wav_fnames, sample_rate, channel_count = load_wavs(wav_dir)
print("found wavs:", wav_fnames, "with sample=rate",
sample_rate, "channel_count", channel_count)
audio = audiopwmio.PWMAudioOut(board.MOSI)
mixer = audiomixer.Mixer(sample_rate = sample_rate,
voice_count = len(wav_fnames),
channel_count = channel_count,
bits_per_sample=16, samples_signed=True,
buffer_size=2048)
audio.play(mixer)
knob = analogio.AnalogIn(board.A0)
# start all WAVs playing, in sync, but quiet
for i, fname in enumerate(wav_fnames):
wave = audiocore.WaveFile(fname)
mixer.voice[i].level = 0.0 # start quiet
mixer.voice[i].play(wave, loop=True) # start playing
num_wavs = len(wav_fnames)
last_time = 0
fader_n = num_wavs * 0.8 # amount of overlap
fader_m = 1/(num_wavs-1) # size of slice
while True:
knob_pct = knob.value / 65535
for i,voice in enumerate(mixer.voice):
voice.level = min(max( 1 - (fader_n * (knob_pct - fader_m*i))**2, 0), 1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment