Conversation
The feature unit declares MUTE and VOLUME as read/write and the control requests for them are answered, but the values were only stored and read back. Because the device claims those controls, the host sends the stream at full scale and leaves the attenuation to it, so the slider moved and nothing happened. Each feature unit now keeps its own mute and volume and a Q15 gain worked out from them, which the speaker task applies to what the host sent and the microphone task to what the board is about to send. At the default of 0 dB the gain is unity and the samples pass through unchanged. The range offered to the host was -90 dB to +90 dB; there is nothing above unity to give, so it is now -60 dB to 0 dB, and a volume from the host is clamped into it. Mute and volume were single shared arrays, so a headset's two feature units could not hold different settings. sample_rate was validated against a minimum of 1, but the microphone sizes its buffer as sample_rate / 1000, so anything below 1 kHz gave it a zero-length one. Fixes adafruit#11302.
Author
|
Testing and diagnostic script. |
Member
|
We hadn't settled on this behavior in #11302. @jbirchall-svg and @FoamyGuy what do you think? |
Collaborator
|
I agree with Scott from the linked issue:
I think this volume should not be applied automatically. It would be nice to have it exposed to the CircuitPython side so that it can be checked and responded to from there, but making it adjust automatically seems to me like it will increase chances for people to think it's not working when the volume is set on the host side and they don't realize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Code written by Claude Code, guided and corrected by @peterbay.
Fixes #11302.
The problem
usb_audiotells the host it has a volume and a mute control — the feature unit in both the microphone and the speaker descriptor declaresMUTEandVOLUMEas read/write, and the control requests for them are answered — but nothing is ever done with the values. They are stored inusb_audio_mute[]andusb_audio_volume[]and read back to the host, and that is all. The samples pass through untouched.Because the device claims those controls, Windows hands the whole job to it: it sends the stream at full scale and expects the device to attenuate. So the slider moves, the device dutifully reports back whatever it was told, and the audio does not change. Muting does nothing either.
Two smaller things fall out of the same area:
The changes
The host's mute and volume are applied to the samples. Each feature unit keeps its own mute and volume, and a Q15 gain is worked out from them — master and per-channel volume in series, mute folded in as zero — so the sample paths only multiply.
usb_audio_speaker_taskscales what the host sent before it reaches the sample, andusb_audio_microphone_taskscales what the board is about to send. At the default of 0 dB the gain is exactly unity and the samples are passed through unchanged, not multiplied by 1.The volume range is now −60 dB to 0 dB, in 1 dB steps. Nothing above unity, so the host's slider cannot ask the device to clip.
Mute and volume are per feature unit. The single-direction functions have one; the headset has one for its speaker and one for its microphone, and they are now independent.
A volume from the host is clamped into the range it was given. Hosts stay inside it, but what gets applied is kept inside it whatever arrives.
sample_ratenow has to be at least 1000. The microphone hands the host one millisecond of audio at a time, computed assample_rate / 1000, so anything below 1 kHz sized that buffer at zero. It was validated against a minimum of 1.A docstring paragraph for the new behaviour, the per-mode endpoint and interface counts (the old text gave the microphone's numbers for all three modes), and one stale comment in
tusb_config.hthat still said the speaker descriptor had not landed yet.Testing
Seeed XIAO nRF52840 Sense with an Adafruit Audio BFF, on two builds differing only by this patch.
boot.pyenables a speaker at 16 kHz mono;code.pybridgesusb_speaker.read()intoaudiobusio.I2SOutand prints the peak amplitude it received each second.The host plays a steady 440 Hz tone whose own amplitude is 12000, so the peak the board reports is the gain that was applied, in absolute numbers.
Before the patch the peak does not move at all — not for the slider, not for mute. After it, it tracks both, mute is digital silence, and at maximum it is the same 12001 as before, which is the tone arriving unscaled.
Audible on the BFF speaker throughout, and the volume slider audibly works after the patch.
A headset (
microphone=True, speaker=True) was also checked on this board: it enumerates on both builds and Windows offers both a playback and a recording endpoint, with no change from this patch. The microphone side of the gain is the same code as the speaker side but is not in the table — this board's PDM microphone was not wired up for it.No new translatable strings.