Skip to content

Add new audiospeed.Resampler module#11057

Merged
dhalbert merged 6 commits into
adafruit:mainfrom
relic-se:resampler
Jun 19, 2026
Merged

Add new audiospeed.Resampler module#11057
dhalbert merged 6 commits into
adafruit:mainfrom
relic-se:resampler

Conversation

@relic-se

@relic-se relic-se commented Jun 17, 2026

Copy link
Copy Markdown

Automatically resamples source sample to match destination sample rate. Shares fixed point speed functionality with audiospeed.SpeedChanger.

Demonstration using pico_test_synth with raspberry_pi_pico:

import array
import audiobusio
import audiocore
import audiomixer
import audiospeed
import board
import math
import time

IN_SAMPLE_RATE = 32000
OUT_SAMPLE_RATES = (8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000)

# Set up audio output
audio = audiobusio.I2SOut(bit_clock=board.GP20, word_select=board.GP21, data=board.GP22)

# Generate one period of sine wave
length = IN_SAMPLE_RATE // 440
sine_wave = array.array("H", [0] * length)
for i in range(length):
    sine_wave[i] = min(max(int(math.sin(math.pi * 2 * i / length) * (2 ** 15) + 2 ** 15), -65536), 65535)
sine_wave = audiocore.RawSample(sine_wave, sample_rate=IN_SAMPLE_RATE)

# Process sample through resampler
resampler = audiospeed.Resampler(sine_wave)

# Test each sample rate
for x in OUT_SAMPLE_RATES:
    mixer = audiomixer.Mixer(sample_rate=x, samples_signed=False)
    audio.play(mixer)
    mixer.play(resampler, loop=True)
    
    print(x, resampler.rate)
    time.sleep(1)
    
    mixer.stop_voice()
    audio.stop()
    mixer.deinit()
    del mixer

Notes:

  • Some resampling rates slightly alter pitch. I'm chocking this up to the lack of linear interpolation.
  • The property audiospeed.Resampler.rate exists as a getter primarily for testing purposes. This can be removed if desired.
  • audiospeed.SpeedChanger is assigned a sample within the constructor. I decided to use the play/stop/playing paradigm instead to allow swapping samples. source could be added to the constructor of audiospeed.Resampler as an optional argument if desired. I decided to match the implementation of SpeedChanger by assigning the source only in the constructor.

For interest: @todbot @gamblor21 @kevinjwalters

@relic-se relic-se marked this pull request as draft June 17, 2026 19:12
@relic-se relic-se marked this pull request as ready for review June 17, 2026 19:29
@dhalbert

Copy link
Copy Markdown
Collaborator

@relic-se the build failure is due to a version change in some supporting software. If you rebase your PR to main latest or merge main, and then push that, the build should succeed.

@relic-se

Copy link
Copy Markdown
Author

@dhalbert Thanks for the heads up. I was trying my hand at building the zephyr port locally to figure out the issue, but that's a much easier fix.

@dhalbert

Copy link
Copy Markdown
Collaborator

Build reports these errors, so it is something missing from the compile or link lists for zephyr:

usr/bin/ld: /home/runner/work/circuitpython/circuitpython/shared-module/audiocore/__init__.c:208:(.text.audiosample_must_match+0x96): undefined reference to `audiospeed_resampler_type'
/usr/bin/ld: /home/runner/work/circuitpython/circuitpython/shared-module/audiocore/__init__.c:223:(.text.audiosample_must_match+0xeb): undefined reference to `audiospeed_resampler_type'
/usr/bin/ld: /home/runner/work/circuitpython/circuitpython/shared-module/audiocore/__init__.c:223:(.text.audiosample_must_match+0x16c): undefined reference to `audiospeed_resampler_type'
/usr/bin/ld: /home/runner/work/circuitpython/circuitpython/shared-module/audiocore/__init__.c:225:(.text.audiosample_must_match+0x180): undefined reference to `audiospeed_resampler_set_sample_rate'

@relic-se

Copy link
Copy Markdown
Author

@dhalbert I don't yet understand why this is happening. The references to shared-bindings/audiospeed/Resampler.h within shared-module/audiocore/__init__.c should be controlled using compile-time directives relying on CIRCUITPY_AUDIOSPEED which is defined on the following line: https://github.com/adafruit/circuitpython/pull/11057/changes#diff-be80dccee1b8ea2a741647a281b24e1e6c48b7e175584c4162cb81edda81af52R154

This should only be enabled within ports/raspberrypi/mpconfigport.mk and isn't available on any other ports.

I don't understand the Zephyr port enough to understand why it is ignoring these directives. I'll start by figuring out how to build it on my end. 😆

Comment thread shared-module/audiocore/__init__.c Outdated
Comment thread shared-module/audiocore/__init__.c Outdated

@dhalbert dhalbert left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thank you!

@dhalbert dhalbert merged commit 64a1be7 into adafruit:main Jun 19, 2026
672 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants