Skip to content

DVI audio can play 44.1 kHz and 32 kHz sound too - #11607

Open
mikeysklar wants to merge 2 commits into
adafruit:mainfrom
mikeysklar:dvi-audio-rates
Open

mikeysklar wants to merge 2 commits into
adafruit:mainfrom
mikeysklar:dvi-audio-rates

Conversation

@mikeysklar

Copy link
Copy Markdown
Collaborator

What

DVI audio now plays 32, 44.1 and 48 kHz sound.

Why

Most music files are 44.1 kHz, so they no longer need resampling first.

This matches the rates the Arduino DVI-HSTX library supports.

Builds on #11574, which should merge first. Only the last commit is new here.

Tested

Feather RP2350 (8 MB PSRAM), DVI monitor with speakers, 11.0.0-alpha.1 plus #11574 and this PR.

Test Result
audioout_rates.py, 1 kHz tone at each rate, twice All six play, heard on the monitor
audioout_bad_values.py, 22.05 and 96 kHz ValueError: Invalid sample_rate
board_audio.py Passes
supervisor.runtime.audio at 44.1, 32, then 48 kHz All play
Text to speech demo at 48 kHz Still speaks

Not tested: Fruit Jam, Metro RP2350, other monitors.

Detail
44.1 kHz Usual N of 6272, CTS rounded
RAM About 780 bytes more internal RAM while audio is on
Changing rate The monitor may mute briefly
Idle after 44.1 kHz Silence keeps being written, since frames never repeat exactly
Not from this PR audioout_bad_values.py's 720-wide block hits MemoryError on a Feather with DVI forced on; same on #11574

Written with Claude Code. I reviewed the diff, built it, and ran every test above on the board.

🤖 Generated with Claude Code

@mikeysklar
mikeysklar marked this pull request as ready for review October 14, 2026 20:43

@tannewt tannewt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this needs rebasing/merging now that the dvi audio memory optimizations are merged.

play() takes the rate from the sample. The ACR and audio InfoFrame
packets are built once per rate when audio starts, and the refill picks
the set for the current rate, so DMA never reads a packet being
rewritten. Channel status carries the matching IEC 60958 rate code.
At 44.1 kHz the search for an exact CTS finds nothing near 20 ACR
packets a frame, so it uses the usual N of 6272 and rounds CTS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikeysklar

mikeysklar commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator Author

Rebased onto main now that #11574 is merged, so this is down to the one rates commit.

Sorry for the force push.

Retested on a Feather RP2350: 32, 44.1 and 48 kHz. All passed.

  • bad values
  • supervisor.runtime.audio
  • TTS demo

@mikeysklar
mikeysklar requested a review from tannewt October 15, 2026 19:52
@tannewt

tannewt commented Oct 16, 2026

Copy link
Copy Markdown
Member

Sorry for the force push.

NP. Main thing is to avoid it when there are existing inline comments.

@tannewt tannewt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One naming Q. Thanks!

Comment on lines +224 to +225
// IEC 60958 byte 0 = 0x04 (copy permitted), byte 3 = channel_status
// (the sample rate).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does channel_status store more than sample_rate? If not, it'd be clearer to call it that.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It only holds the sample rate code.

hstx_packet_set_audio_samples() now takes sample_rate in Hz, like hstx_packet_set_audio_infoframe(), and looks up the code itself.

Retested on a Feather RP2350 with a DVI monitor. A 1 kHz tone at 48, 44.1 and 32 kHz, twice round, all heard at the same pitch.

rates_show.py
# Plays a 1 kHz tone at each rate and shows the rate on screen.
import array
import math
import time

import audiocore
import board
import displayio
import framebufferio
import picodvi
import terminalio
from adafruit_display_text import label

displayio.release_displays()
fb = picodvi.Framebuffer(
    320, 240,
    clk_dp=board.CKP, clk_dn=board.CKN,
    red_dp=board.D0P, red_dn=board.D0N,
    green_dp=board.D1P, green_dn=board.D1N,
    blue_dp=board.D2P, blue_dn=board.D2N,
    color_depth=8,
)
audio = picodvi.AudioOut(fb)
display = framebufferio.FramebufferDisplay(fb)
text = label.Label(terminalio.FONT, text="wait", color=0xFFFF00, scale=6,
                   anchor_point=(0.5, 0.5), anchored_position=(160, 120))
group = displayio.Group()
group.append(text)
display.root_group = group
time.sleep(3)

for rate in (48000, 44100, 32000, 48000, 44100, 32000):
    length = rate // 100  # Ten periods of 1 kHz.
    tone = audiocore.RawSample(
        array.array("h", [int(math.sin(math.pi * 20 * i / length) * 8000) for i in range(length)]),
        sample_rate=rate,
    )
    text.text = str(rate)
    audio.play(tone, loop=True)
    time.sleep(4)
    print(rate, "playing", audio.playing)
    audio.stop()
    text.text = "-"
    time.sleep(1)
text.text = "done"
print("done")
48000 playing True
44100 playing True
32000 playing True
48000 playing True
44100 playing True
32000 playing True
done

It takes the rate in Hz, like hstx_packet_set_audio_infoframe, and
looks up the IEC 60958 sample frequency code itself, as upstream
pico_hdmi does. The packets sent are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikeysklar
mikeysklar requested a review from tannewt October 16, 2026 21:22
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