Findings: C# record-play probe outcome

This commit is contained in:
2026-06-12 10:11:19 +00:00
parent 87a85db38d
commit 7c4621b3b7
+48
View File
@@ -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
- `<AllowUnsafeBlocks>true</AllowUnsafeBlocks>` 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`