fix(engines): restore MEMLCelium's feedback tap (L7)

feedback_/fbzm1_/fb_smooth_alpha_ were read every process() call but nothing
ever wrote feedback_, so the path was silently inert. Git history shows this
was a *muted* feature, not dead code: `feedback = v0 * feedbackGain` with
feedbackGain 0.1f (MEMLCeliumAudioApp.hpp @ d095688), then "0; //0.1f" —
zeroed with the value kept in the comment — then the write was dropped
entirely when the engine was ported to nisps/engines/ at the 2026-04-29
rewrite (8d0d47b). Operator decision: port it back, live at 0.1f.

The tap is voice 0's own enveloped output, post-envelope and pre voice-1/mix/
shape — NOT the final output. This differs from PAFSynth's feedback shape;
where the sibling and the reference disagreed, the reference won.

Not schema-exposed: the reference hardcoded it, and MEMLCelium has no voice
spaces to vary it by. If feedback depth becomes a research axis it wants to be
a runtime parameter — noted, not built.

Verification, and a gap worth recording: parity-check.sh PASSES (max delta
2.38e-7) but that is NOT evidence for this change — its harness only exercises
PAFSynth and ChannelStrip, and the impulse baseline runs all-params-0.5, under
which voice 0's sequencer never triggers, so v0_env stays 0 and the new write
is 0 either way. That baseline is blind to this path, not a check on it. So it
was cross-checked directly instead: sequencer params forced to trigger early,
4800 samples native vs WASM through the nisps_engine_* C ABI —

  native vs WASM max abs diff  1.19e-7   (tolerance 1e-5)
  all 4800 samples nonzero, finite, max amplitude ~0.99 (tanh-bounded)
  same run with gain forced to 0 diverges by up to 1.49 — not a no-op

No fixture regenerated. nisps.wasm ships here because it is a tracked artifact
under the Phase 0 freshness gate.

Follow-up: the parity harness covers 2 of 8 engines. "Firmware and WASM share
the same engines" is asserted repo-wide but tested narrowly.
This commit is contained in:
monkey-w1n5t0n 2026-07-21 17:04:35 +02:00
parent c19d84619e
commit 1f38f1a7fb
2 changed files with 14 additions and 0 deletions

Binary file not shown.

View file

@ -185,6 +185,10 @@ class MEMLCeliumEngine {
const float p2 = v0_paf2_.play(freq2, freq2 + (v0_paf2_cf_ * freq2), const float p2 = v0_paf2_.play(freq2, freq2 + (v0_paf2_cf_ * freq2),
v0_paf2_bw_, v0_paf_vib_, v0_paf_vfr_, v0_paf2_shift_, true); v0_paf2_bw_, v0_paf_vib_, v0_paf_vfr_, v0_paf2_shift_, true);
float v0 = (p0 + p1 + p2) * v0_env; float v0 = (p0 + p1 + p2) * v0_env;
// Feedback tap: voice 0's own enveloped output, one block behind (see
// fbsmooth above) — matches firmware's `feedback = v0 * feedbackGain;`
// taken at this same point (post-envelope, pre voice-1/mix/shape).
feedback_ = v0 * feedback_gain_;
// ----- Voice 1 ----- // ----- Voice 1 -----
const float v1_env = v1_amp_env_.play(); const float v1_env = v1_amp_env_.play();
@ -293,6 +297,16 @@ class MEMLCeliumEngine {
float feedback_ = 0.f; float feedback_ = 0.f;
float fbzm1_ = 0.f; float fbzm1_ = 0.f;
float fb_smooth_alpha_ = 0.5f; float fb_smooth_alpha_ = 0.5f;
// Restored 2026-07-21 (finding L7): firmware history shows this went
// 0.1f (live, `modes/AudioApps/MEMLCeliumAudioApp.hpp` @ d095688) -> "0;
// //0.1f" (explicitly muted, value preserved in comment) -> the write
// was dropped entirely when the engine was ported to nisps/engines/ at
// the 2026-04-29 rewrite (8d0d47b) — feedback_/fbzm1_/fb_smooth_alpha_
// kept being read in process() but nothing wrote feedback_, so the path
// was silently inert. Operator decision: port it back live at 0.1f, not
// muted. Not schema-exposed — a single fixed value (MEMLCelium has no
// voice spaces to vary it by, unlike PAFSynth's feedback_gain_).
float feedback_gain_ = 0.1f;
}; };
static_assert(AudioEngine<MEMLCeliumEngine>, "MEMLCeliumEngine must satisfy AudioEngine"); static_assert(AudioEngine<MEMLCeliumEngine>, "MEMLCeliumEngine must satisfy AudioEngine");