Repository navigation
Speaker: keep the DMA fed at the hold level while waiting for new data - #391
Merged
Merged
Conversation
After the last samples of a sound, spk_task slept dma_buf_count times, for a time estimated from dma_buf_len, before writing the fade-out ramp. With the ESP-IDF 5 I2S driver (Arduino core 3.x) the ramp was observed to arrive after the DMA had reached the auto-cleared region: the DAC output dropped to the zero level for about half a DMA buffer and then jumped back to the bias level at the end of every tone, heard as noise on the ESP32 internal DAC (e.g. M5Stack Basic, m5stack#371). The legacy driver has the same buffer layout and auto-clear, so the timed wait was fragile there as well, although the drop was not observed with Arduino core 2.x. Write one buffer of the hold level per step instead of sleeping. The blocking write paces the loop by the DMA itself, so the hold data and the ramp follow the last samples without a gap, and no timing estimate is needed. The hold level is the current DAC bias for use_dac and 0 otherwise. The buzzer output keeps the timed wait, to leave its behavior unchanged in this fix. A new sound requested during this hold now starts after the queued hold data, about one DMA ring later (dma_buf_count * dma_buf_len samples), instead of being packed right after the previous sound. Measured on M5Stack Basic (Arduino core 3.3.x, sample_rate 96 kHz, dma_buf_len 256, dma_buf_count 8, GPIO25 with a logic analyzer, 12 tones of 500 ms with 50 ms gaps): drops at the tone end 12 -> 0. The interval from completion of the final audio write to submission of the fade-out ramp is about 21 ms (previously about 15 ms).
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.
Fixes #371.
Problem
On the ESP32 internal DAC (e.g. M5Stack Basic), every tone ends with a short drop of the output to the DAC zero level followed by a jump back to the bias level, before the fade-out. It is heard as crackling at the end of each tone. Observed with Arduino core 3.x (ESP-IDF 5 I2S driver).
Cause
After the last samples of a sound,
spk_tasksleptdma_buf_counttimes, for a time estimated fromdma_buf_len, before writing the fade-out ramp. The DMA reached the auto-cleared part of the ring before the ramp was queued, so about half a DMA buffer of zeros was output, then the ramp started from the bias level.Change
While waiting for new data, write one buffer of the hold level per step instead of sleeping. The blocking write paces the loop by the DMA itself, so the hold data and the ramp follow the last samples without a gap. The hold level is the current DAC bias for
use_dacand 0 otherwise. The buzzer output keeps the timed wait (its behavior is unchanged).Side effect: a new sound requested during this hold starts after the queued hold data (about one DMA ring later) instead of being packed right after the previous sound.
Test
end()called right after a tone (during the hold writes), 42 times on Basic and Core2: all returned (max 3 ms / 10 ms) andbegin()succeeded again.