From 3a05683418efb2bea1df28fa6ce6367eeffb7530 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Sat, 31 Oct 2026 03:29:09 -0500 Subject: [PATCH] audiograph: round a sample with two adds instead of a branchy helper Every sample every node writes goes through sat16, and since #314 its rounding was round_even: a dozen instructions and two branches, there so RP2350 CircuitPython links without lrintf. Adding and subtracting 1.5 * 2^23 rounds a clamped sample to nearest, ties to even, exactly as round_even does: all 2^32 float bit patterns give the same int16 either way, and a ties-away version planted in the same check differs on 32,770 of them. On the P4 DEV-KIT (128-frame blocks, Poly106 with six notes held), graph.run goes from 24.65 to 23.12 ms per 1024 frames; a two-input mix from 23.7 to 19.2 us a block, noise from 19.6 to 14.4, the VCA from 17.3 to 13.2. The board's renders of Poly106, Poly6, Mono101, Poly8 and PolyA6 hash the same before and after, and every graph probe prints the same bytes on CPython. A target that evaluates float wider than float (x87) keeps round_even. --- src/shared/audiodsp_graph.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/shared/audiodsp_graph.c b/src/shared/audiodsp_graph.c index 495d093..9a80b22 100644 --- a/src/shared/audiodsp_graph.c +++ b/src/shared/audiodsp_graph.c @@ -9,6 +9,7 @@ #include "shared/audiodsp_graph_overdrive.h" +#include #include #include @@ -807,6 +808,20 @@ static void derive(audiodsp_graph_t *graph, audiodsp_graph_node_t *n) { } } +// Every sample every node writes goes through sat16, so its rounding is the +// runner's hottest arithmetic. Adding 1.5 * 2^23 puts a clamped sample in +// [2^23, 2^24), where a float holds whole numbers only: the add rounds it to +// an integer the way the FPU rounds, nearest with ties to even, and the +// subtract takes it back exactly. That is round_even's answer for every float +// (all 2^32 of them checked against it), in two adds instead of a dozen +// instructions and two branches; on the P4 it is a fifth of a mixer's time. +// It needs float arithmetic done in float (FLT_EVAL_METHOD 0); a target that +// carries floats wider, as x87 does, keeps round_even. +#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD == 0 +#define GRAPH_SAT16_MAGIC 1 +#define GRAPH_ROUND_MAGIC 12582912.0f +#endif + static inline int16_t sat16(float v) { if (v > 32767.0f) { return 32767; @@ -814,7 +829,14 @@ static inline int16_t sat16(float v) { if (v < -32768.0f) { return -32768; } +#ifdef GRAPH_SAT16_MAGIC + if (!(v == v)) { + return 0; + } + return (int16_t)(int32_t)((v + GRAPH_ROUND_MAGIC) - GRAPH_ROUND_MAGIC); +#else return (int16_t)round_even(v); +#endif } static void run_env(audiodsp_graph_node_t *n, const int16_t *gate, int16_t *out,