Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion modules.lock
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
# sibling checkout is linked there instead) and compiles the port for its
# interpreter. Move the commit here to move every LVGL build, MicroPython's and
# CircuitPython's.
audiodsp https://github.com/PyDevices/audiodsp.git 6d08b0784939fa122ccb0dabeda5816bd8f70597
audiodsp https://github.com/PyDevices/audiodsp.git 8d5c4a0ca69f12425c39c811ae190682655e5805
omnipython-lvgl https://github.com/PyDevices/omnipython-lvgl.git c34fbeeb0440e71acb62c92ddee1c3c65d089622
pygraphics https://github.com/PyDevices/pygraphics.git f97fd2e20e73f23c2138ab52a630cf04b0a7aea1
ulab https://github.com/v923z/micropython-ulab.git 1d3ddd8f5228e2c7335dac71f4846e6abb0ec9f9
Expand Down
10 changes: 10 additions & 0 deletions modules/audioif/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

## Unreleased

- **The GIGA's audio no longer stops for the interpreter.** The pump renders
from an interrupt of its own, on its own stack, so a garbage collection, a
write to `/flash` or a Python callback that holds the interpreter longer
than the DAC's ring no longer plays silence. Synth Station played ten
minutes with a `gc.collect()` every 3 s and a 32 KB write every 30 s without
a starved frame, where the same run on the interpreter starved 3.5%.
`_audioif.render_mode()` switches between `"irq"` and `"interpreter"`, and
`_audioif.render_stats()` reports the longest render and its stack use. It
needs an audiodsp with the interrupt hooks; with an older one the pump
stays on the interpreter.
- **A microphone and a speaker on two clocks.** `_audioif.Input(...,
ring=ring)` on Windows reads the microphone on its own thread into an
`audiopump.Ring`, so an `audiopump.DriftResampler` can play it at the
Expand Down
29 changes: 19 additions & 10 deletions modules/audioif/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,24 @@ and `while out.playing: pass` waits for either. `quiescent_value` takes
only CircuitPython's default, 0x8000, which is where the DAC rests, and
`sample_rate=` (ours) is the rate it opens at before the first `play()`.

The port has no threads, so the pump runs in the engine's service mode, on the
interpreter: a soft timer looks at the DMA every 4 ms and schedules the pump
whenever a block's worth of its ring has played. It runs between
bytecodes and while the interpreter sleeps. A C call that holds the interpreter
for longer than the ring underruns, and the DAC plays
silence until the pump catches up; `starved()` counts it. The ring holds about
93 ms; `buffer_ms=` (ours) asks for more or less, up to 12,288 frames (278 ms at
44.1 kHz). More rides out longer stalls and puts the sound further behind, so a
music player can ask for 250 and an instrument should leave it alone.
The port has no threads, so the pump runs in the engine's service mode: a soft
timer looks at the DMA every 4 ms and, whenever a block's worth of its ring has
played, raises an interrupt of the driver's own that runs the pump on its own
stack. A garbage collection, a write to `/flash` or a long Python callback
doesn't hold the audio up: Synth Station played ten minutes with a
`gc.collect()` every 3 s and 32 KB written to `/flash` every 30 s without a
starved frame. Run on the interpreter, the same test starved 3.5% of them.

`_audioif.render_mode("interpreter")` runs the pump on the interpreter instead,
between bytecodes and while it sleeps, and `render_mode("irq")` goes back;
`_audioif.render_stats()` says how long the longest render took and how much of
its stack it used. With an audiodsp too old for the interrupt, the interpreter
is the only mode. There, a C call that holds the interpreter for longer than
the ring underruns, and the DAC plays silence until the pump catches up;
`starved()` counts it either way. The ring holds about 93 ms; `buffer_ms=`
(ours) asks for more or less, up to 12,288 frames (278 ms at 44.1 kHz). More
rides out longer stalls and puts the sound further behind, so a music player
can ask for 250 and an instrument should leave it alone.

## `audioi2sin.I2SIn` — recording from a microphone

Expand Down Expand Up @@ -312,7 +321,7 @@ it and it has not been done.
| **ESP32 family** | a FreeRTOS task pinned to the core the interpreter is not on, optionally with its stack in PSRAM | `xSemaphoreCreateRecursiveMutex`, which is priority-inheriting | `esp_timer_get_time` | the I2S channel, written from the pull loop | `Input`, single-port or two-port |
| **unix** | `pthread` | recursive `pthread_mutex` | `clock_gettime` | a file the loop `write(2)`s each block to | — |
| **Windows** | `_beginthreadex` | `CRITICAL_SECTION` | `QueryPerformanceCounter` | a file descriptor, or a WASAPI speaker written from the pull loop | `Input`, a WASAPI microphone read on the pump's thread |
| **STM32H7** (Arduino GIGA R1) | none: the engine's service mode, run by a soft timer that schedules the pump on the interpreter whenever the DAC has played a block | — | `mp_hal_ticks_us` | the DAC's two channels, through a circular DMA paced by TIM6 | a PDM microphone through DFSDM1, or an ADC1 pin paced by TIM15, into one ring by DMA; not an `Input` |
| **STM32H7** (Arduino GIGA R1) | none: the engine's service mode, run from an interrupt of the driver's own, on its own stack, whenever the DAC has played a block (or on the interpreter, by `render_mode()`) | the render interrupt, held off by the interpreter's side | `mp_hal_ticks_us` | the DAC's two channels, through a circular DMA paced by TIM6 | a PDM microphone through DFSDM1, or an ADC1 pin paced by TIM15, into one ring by DMA; not an `Input` |
| **WebAssembly** | none — **and none is needed** | — | — | — | — |

WebAssembly is not a gap. That build has no threads (no `-pthread`, no
Expand Down
Loading
Loading