From 7c4621b3b7bc042a21a61dcc6b90b3ad3f8740d3 Mon Sep 17 00:00:00 2001 From: Assistant builder Date: Fri, 12 Jun 2026 10:11:19 +0000 Subject: [PATCH] Findings: C# record-play probe outcome --- findings.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/findings.md b/findings.md index 8d49aae..9535e84 100644 --- a/findings.md +++ b/findings.md @@ -130,6 +130,54 @@ Not in scope for prototypes. The main assistant needs a user systemd service so 5. Local-only fallback when the network is down — required behaviour or out of scope? 6. Multi-language? Or English only for v1? +## C# probe outcome (2026-06-12 — Test 1 ported to C#) + +The Python Test 1 (record 5 s → write WAV → play back) was re-implemented in C# / .NET 9 to validate that the Pi client can be written in the same language as the backend. The full probe lives at `tests/01-record-play-cs/` with the deploy script at `bin/probe-cs`. All three pass criteria from the original spec held on hardware. + +### What we proved + +- **PortAudioSharp2 1.0.6** is a usable .NET binding for PortAudio. Conventional API surface (`PortAudio.Initialize`, `Stream` ctor taking input/output `StreamParameters`, callback delegate with `ref StreamCallbackTimeInfo`) — matched our assumptions, no adjustments needed. +- The NuGet package **bundles `libportaudio.so` for linux-arm64** in its runtime folder, so a self-contained publish doesn't need to rely on the Pi's system `libportaudio2`. (Both are present; the bundled one wins.) +- A single `dotnet publish -c Release -r linux-arm64 --self-contained` produces a ~70 MB directory that runs as-is on the Pi. **No .NET install on the Pi.** Deploy is `dotnet publish → scp -r → ssh ./Probe`, wrapped in `bin/probe-cs`. +- The `PA_ALSA_PLUGHW=1` trick from the original findings is still required in C# — but with a real twist (see next section). +- Recording, RMS metering, WAV writing, and playback all behave identically to the Python prototype. Pass criteria 1 (recognisable playback), 2 (meter responsive), 3 (no over/underflow) all met. + +### Key gotcha — .NET env vars do NOT propagate to libc `getenv()` on Linux + +`Environment.SetEnvironmentVariable("PA_ALSA_PLUGHW", "1")` updates .NET's process-table mirror but does **not** call libc `setenv`. libportaudio reads its config via `getenv("PA_ALSA_PLUGHW")` and sees nothing. ALSA then opens the USB Speaker Phone as `hw:3,0` (native 48 kHz only), and the 16 kHz stream fails with `paInvalidSampleRate`. + +Python's `os.environ[x] = y` calls `setenv` synchronously, which is why the Python probe never tripped on this. + +**Fix:** P/Invoke libc `setenv` directly, alongside the managed call: + +```csharp +[DllImport("libc", EntryPoint = "setenv")] +static extern int setenv(string name, string value, int overwrite); + +Environment.SetEnvironmentVariable("PA_ALSA_PLUGHW", "1"); // for .NET-side readers +setenv("PA_ALSA_PLUGHW", "1", 1); // for the C runtime view PortAudio reads +``` + +**Implication for future C# probes and the eventual Pi client:** any env knob a C library reads (PortAudio, onnxruntime, any P/Invoked native) must be set via libc `setenv`, not just `Environment.SetEnvironmentVariable`. Bake a small `Libc.setenv` helper into the eventual client. + +### Small .NET ergonomics + +- `true` is required in the csproj for the callback's `Buffer.MemoryCopy` over `short*`. +- `ImplicitUsings` pulls in `System.IO.Stream`, which collides with `PortAudioSharp.Stream`. Disambiguate with `using Stream = PortAudioSharp.Stream;`. +- Top-level statements don't auto-pick up the project's own namespace. Add `using RecordPlayProbe;` (or whatever the project's namespace is) so the entry-point file can reach internal helpers. + +### Open questions deferred to Test 2 + +- **No equivalent to openwakeword exists in .NET.** The choices are (a) port the openwakeword inference pipeline to C# using `Microsoft.ML.OnnxRuntime` (mel extractor → Google speech embedder → keyword classifier, three ONNX files), (b) use Picovoice Porcupine's .NET SDK (commercial, key-gated), (c) Vosk + keyword spotting (heavier). Decision deferred to the Test 2 brainstorm. +- The `Libc.setenv` workaround will likely be needed again for any onnxruntime env knob on the Pi. + +### Reference paths (C# probe) + +- Spec: `docs/superpowers/specs/2026-06-12-record-play-csharp-probe-design.md` +- Plan: `docs/superpowers/plans/2026-06-12-record-play-csharp-probe.md` +- Code: `tests/01-record-play-cs/` +- Deploy: `bin/probe-cs` + ## Reference paths - Spec: `docs/superpowers/specs/2026-06-11-voice-assistant-prototypes-design.md`