Skip to content

Two audio playback bugs (UPDATE: one) #8432

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

Open
PaintYourDragon opened this issue Sep 25, 2023 · 2 comments
Open

Two audio playback bugs (UPDATE: one) #8432

PaintYourDragon opened this issue Sep 25, 2023 · 2 comments
Labels
audio bug rp2040 Raspberry Pi RP2040
Milestone

Comments

@PaintYourDragon
Copy link

CircuitPython version

Adafruit CircuitPython 8.2.6 on 2023-09-12; Raspberry Pi Pico with rp2040

Code/REPL

""" Demonstrates a couple of issues in CircuitPython audio: 8-bit WAV
    playing fails with RuntimeError, and RawSample playback does not
    clear .playing flag at end.
    Test system is RP2040 Pico. Occurs with both audiopwmio and
    audiobusio.I2S; example shows just audiopwmio for simplicity.
"""

import array
import time
import board
import audiocore
import audiopwmio

audio = audiopwmio.PWMAudioOut(board.GP22)

def play(filename, usebuf, message):
    """ Play requested WAV file, can use standard buffer or allocate a
        larger (1K) working buffer. 16-bit WAV plays OK. 8-bit WAV fails
        (regardless of buffer selection) at audio.play() with:
        "RuntimeError: Internal audio buffer too small"
    """
    print(message, end="")
    file = open(filename, 'rb')
    if usebuf:
        print(" w/custom buffer")
        data = audiocore.WaveFile(file, bytearray(1024))
    else:
        print(" w/standard buffer")
        data = audiocore.WaveFile(file)
    audio.play(data)
    while audio.playing:
        pass


play("hello16.wav", False, "16-bit WAV")
play("hello16.wav", True, "16-bit WAV")
play("hello8.wav", False, "8-bit WAV")  # FAILS
play("hello8.wav", True, "8-bit WAV")  # FAILS

print("8-bit WAV 'manually' via RawSample")
file = open('hello8.wav', 'rb')
file.seek(40, 0)
lw = file.read(4)
length = lw[0] + (lw[1] << 8) + (lw[2] << 16) + (lw[3] << 24)
raw = array.array("h", range(length))
for i in range(length):
    raw[i] = file.read(1)[0] * 257 - 32768
data = audiocore.RawSample(raw, sample_rate=8000)
# This DOES play through once...
audio.play(data)
while audio.playing:
    pass
# ...but this line is never reached:
print("HELLO!")
# (Have tried explicitly setting loop=False, makes no difference)

Behavior

For 8-bit WAVs:
RuntimeError: Internal audio buffer too small

For RawSample workaround:
Audio plays but .playing flag never clears, code can’t block correctly.

Description

16-bit WAVs play as expected, only 8-bit WAVs encounter the RuntimeError. This occurs whether using the standard audio buffer or when allocating a larger bytearray.

Trying to work around the bug via RawSample presents a different issue. Audio plays through once correctly, but the .playing flag is never cleared; code can’t block correctly.

Additional information

WAVs used by example code can be retrieved here:
https://www.dropbox.com/scl/fo/22rduhyy1rdzcd3m9d638/h?rlkey=a7fpsbcsw8747ki9u6yr58jz8&dl=0
(Have tested with different 8-bit WAVs, same issue)

Aside from the above, super happy to see the progress happening in CircuitPython audio, e.g. MP3 looping works great where it didn’t used to. Thank you!

@tannewt tannewt added audio rp2040 Raspberry Pi RP2040 labels Sep 26, 2023
@tannewt tannewt added this to the 9.0.0 milestone Sep 26, 2023
@PaintYourDragon
Copy link
Author

PR #8436 fixes the first bug (8-bit WAV playback on RP2040).

The second bug is a bit beyond me but I can see where it’s happening in ports/raspberrypi/audio_dma.c, in audio_dma_load_next_block(). RawSample audio playback is single-buffered, it just plays straight from the array in one pass. Double-buffered audio makes multiple calls through audiosample_get_buffer() and audio_dma_convert_samples() to determine when there’s no more data to read and to play back; a combination of GET_BUFFER_DONE and 0, respectively. Single-buffering returns GET_BUFFER_DONE and the source buffer size on a single call, but there is no subsequent invocation of this function that gets a 0 for the latter value.

ANYWAY, I need to move on to other things but am leaving notes here. With WAV playback fixed, the workaround (and its associated bug) wouldn’t have occurred. But, in principle, it’s still a bug. The typical use case for RawSample playback is looped playback of a waveform and this would not be encountered, but there can still be situations where single-pass RawSamples might be used.

The aforementioned PR is for RP2040. I have not dug out other hardware nor gone through other ports to see if the same bug(s) lurk there or if this is specific to the RP2040 DMA code, but probably will test/check eventually as it’s relevant to a guide in progress.

Additionally, I have not gone through other ports to see if the 8- to 16-bit expansion is performed correctly everywhere…but it’s a super common misunderstanding, e.g. seen before in the analogio.AnalogIn() range that I think was addressed from CP 8.0 forward. Myself or someone might make a pass through all audio-related code for N to >N bit conversion and make sure it’s not simply a left-shift, but a duplication of most-significant bits into the empty least-significant bits to avoid unwanted DC offset in the output waveform.

@PaintYourDragon PaintYourDragon changed the title Two audio playback bugs Two audio playback bugs (UPDATE: one) Sep 30, 2023
@jepler
Copy link

jepler commented Dec 1, 2023

note that introducing an AudioMixer and playing the RawSample on it may be an effective workaround.

@dhalbert dhalbert modified the milestones: 9.0.0, Long term Dec 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
audio bug rp2040 Raspberry Pi RP2040
Projects
None yet
Development

No branches or pull requests

4 participants