From cbea62b2a9a21bcbfcdb1f6451d3b7fe05b47908 Mon Sep 17 00:00:00 2001 From: Wong Ding Feng Date: Sat, 30 May 2026 00:42:36 +0800 Subject: [PATCH] fix: add portaudio to LD_LIBRARY_PATH and add flake lockfile Move LD_LIBRARY_PATH out of env block and include portaudio so audio devices are discoverable at runtime. Add flake.lock and a quick microphone test script. Co-Authored-By: Claude Opus 4.6 --- flake.lock | 27 ++++++++++++++++ flake.nix | 9 +++--- test_mic.py | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 flake.lock create mode 100644 test_mic.py diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..ea58a4b --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1779786838, + "narHash": "sha256-0geHoGiR5f8qiXg+gO4rSF6Up6Var+kKqiOv9AO/uUc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "f44f7788c891fbe5542177df78374f8cdab10e8f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix index 2b05d43..6172504 100644 --- a/flake.nix +++ b/flake.nix @@ -21,11 +21,10 @@ cudaPackages.cudatoolkit ]; - env = { - LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ - pkgs.cudaPackages.cudatoolkit - ]; - }; + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ + pkgs.portaudio + pkgs.cudaPackages.cudatoolkit + ]; }; }; } diff --git a/test_mic.py b/test_mic.py new file mode 100644 index 0000000..87f7c95 --- /dev/null +++ b/test_mic.py @@ -0,0 +1,88 @@ +"""Quick microphone tests. Run: uv run python test_mic.py""" + +import numpy as np +import sounddevice as sd +import sys +import time + +SAMPLE_RATE = 16000 + + +def test_device_info(): + """Show which device will be used for recording.""" + default_input = sd.default.device[0] + info = sd.query_devices(default_input) + print(f"Default input device [{default_input}]: {info['name']}") + print(f" Max input channels: {info['max_input_channels']}") + print(f" Default sample rate: {info['default_samplerate']}") + assert info["max_input_channels"] > 0, "Default device has no input channels!" + print(" PASS\n") + + +def test_record_1s(): + """Record 1 second and check we got non-silent audio.""" + print("Recording 1 second... (speak or make noise!)") + audio = sd.rec(SAMPLE_RATE, samplerate=SAMPLE_RATE, channels=1, dtype="float32") + sd.wait() + audio = audio.flatten() + + peak = np.max(np.abs(audio)) + rms = np.sqrt(np.mean(audio ** 2)) + print(f" Samples: {len(audio)}") + print(f" Peak amplitude: {peak:.4f}") + print(f" RMS: {rms:.6f}") + + assert len(audio) == SAMPLE_RATE, f"Expected {SAMPLE_RATE} samples, got {len(audio)}" + assert peak > 0, "All zeros — mic not capturing anything" + if peak < 0.001: + print(" WARNING: Very low signal — mic might be muted or too far away") + else: + print(" Signal level looks good") + print(" PASS\n") + + +def test_record_levels(): + """Record 3 seconds in 1-second chunks, show live levels.""" + print("Recording 3 seconds — speak during seconds 2-3 for comparison...") + for i in range(3): + audio = sd.rec(SAMPLE_RATE, samplerate=SAMPLE_RATE, channels=1, dtype="float32") + sd.wait() + audio = audio.flatten() + rms = np.sqrt(np.mean(audio ** 2)) + peak = np.max(np.abs(audio)) + bar = "#" * int(min(peak * 200, 50)) + print(f" Second {i+1}: peak={peak:.4f} rms={rms:.6f} |{bar}") + print(" PASS\n") + + +def test_stream_callback(): + """Test that InputStream callback fires correctly.""" + frames_received = [] + + def callback(indata, frames, time_info, status): + if status: + print(f" Status: {status}") + frames_received.append(len(indata)) + + print("Testing InputStream callback for 1 second...") + with sd.InputStream(samplerate=SAMPLE_RATE, channels=1, dtype="float32", + callback=callback, blocksize=800): + time.sleep(1) + + total_frames = sum(frames_received) + expected = SAMPLE_RATE + print(f" Callbacks fired: {len(frames_received)}") + print(f" Total frames: {total_frames} (expected ~{expected})") + print(f" Blocksize per callback: {frames_received[0] if frames_received else 'N/A'}") + assert len(frames_received) > 0, "No callbacks received!" + assert abs(total_frames - expected) < expected * 0.2, f"Frame count off by >20%" + print(" PASS\n") + + +if __name__ == "__main__": + print("=== Microphone Tests ===\n") + test_device_info() + test_record_1s() + test_record_levels() + test_stream_callback() + print("All tests passed!")