Skip to content

Giga: Bluetooth LE traffic starves audio playback #291

Closed

Description

@bdbarnett

On the Arduino GIGA R1 WiFi (DISPLAY_SHIELD variant), a Bluetooth LE session while midiEngine plays a MIDI file more than doubles the audio the DAC misses. Bluetooth itself keeps working: a BLE HID host session against a keyboard on another board received 99 of 99 events with the right text while the file played.

Measured with midiEngine playing a General MIDI file through the GeneralUser GS SoundFont at 48 kHz stereo, reading _audioif.dac_stats() before and after a 37-second stretch, and sampling the DAC's output register at 2 kHz in 100 ms windows:

Over 37 s Frames played Starved frames Silent 100 ms windows
No Bluetooth 1,787,102 128,603 (7.2%) 12 of 372
BLE HID host session (aioble) 1,811,024 311,404 (17.2%) 42 of 367

Both runs were at the REPL with the launcher's timers running, not under appkit.serve(), and the 2 kHz sampling timer costs some CPU of its own, so the absolute figures are higher than an app would see. The difference between the two rows is the Bluetooth stack's time on the same core as the audio render.

The render moving to the DAC interrupt (#290) should cover this along with garbage collections; this issue is the measurement that Bluetooth is a second source of the same starvation.

Activity

  1. bdbarnett commented on Nov 10, 2026

    @bdbarnett
    ContributorAuthor

    The Bluetooth stack on its own doesn't starve the audio. To separate it from the Python side, I played synthio at 48 kHz on the same board and read _audioif.dac_stats() around each run:

    Bluetooth load CPU it costs Starved frames
    Active scan about 0.3% none
    Connected, 100 notifications/s, 64 voices about 2% 0%
    Connected, 100 notifications/s, 152 voices (about 4% headroom left) about 2% 0%

    So even with the render close to saturating the core, the controller and host stack leave it alone. What differs in the midiEngine run above is aioble: its IRQ handler and the asyncio tasks around it run as Python in scheduler context, between render calls, and that's where the extra 10% of starved frames most likely comes from. That is the same mechanism as the garbage collections, so the render moving to the DAC interrupt (#290) is still the fix for both.

    Not rechecked: the midiEngine run itself, because its USB stick wasn't detected this time.

  2. bdbarnett commented on Nov 10, 2026

    @bdbarnett
    ContributorAuthor

    Re-run with #392 and audiodsp's interrupt hooks on a GIGA R1 with the Display Shield. A PC central wrote to the board about 50 times a second while the board notified every 10 ms, for 60 s, with Synth Station's electric piano playing a chord that changed every second:

    Firmware Starved frames Of played Writes received Notifies sent
    Interpreter render (before) 1,721 2,880,338 2,893 5,435
    Interrupt render 0 2,880,549 3,116 5,494

    The Bluetooth traffic got through the same in both runs; only the audio changed.

  3. bdbarnett commented on Nov 10, 2026

    @bdbarnett
    ContributorAuthor

    Fixed by #392: the GIGA renders from the DAC interrupt now, so aioble's Python callbacks can't hold the render off. In the BLE HID host case above, starved frames went from 1,721 to 0 (#290 has the full figures).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions