From 14e862f50ae0a990add70acf70b0f87c9efbca28 Mon Sep 17 00:00:00 2001 From: Assistant builder Date: Fri, 12 Jun 2026 12:03:14 +0000 Subject: [PATCH] Beep on detection: fire-and-forget OutputStream, input keeps flowing --- tests/02-wakeword-cs/Program.cs | 70 ++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/tests/02-wakeword-cs/Program.cs b/tests/02-wakeword-cs/Program.cs index c93f38f..a269080 100644 --- a/tests/02-wakeword-cs/Program.cs +++ b/tests/02-wakeword-cs/Program.cs @@ -11,6 +11,8 @@ const int Channels = 1; const uint BlockFrames = WakewordModel.FrameSamples; // 1280 const float Threshold = 0.5f; const double CooldownSeconds = 1.0; +const double BeepHz = 880.0; +const double BeepSeconds = 0.2; // .NET-side mirror for any readers that go through Environment.GetEnvironmentVariable, // AND libc setenv so PortAudio / onnxruntime (which use getenv()) see the values. @@ -79,6 +81,7 @@ try using var stream = new Stream( inParams, null, SampleRate, BlockFrames, StreamFlags.NoFlag, callback, IntPtr.Zero); stream.Start(); + short[] beepBuffer = MakeBeep(BeepHz, BeepSeconds, SampleRate); Console.WriteLine("Listening. Say 'alexa'. Ctrl-C to exit."); var sw = Stopwatch.StartNew(); @@ -95,7 +98,7 @@ try { Console.WriteLine($"DETECTED alexa score={score:0.000} t={now.TotalSeconds:0.0}s"); lastTrigger = now; - // Beep is added in Task 5. + FireAndForgetBeep(device, beepBuffer); } } } @@ -130,6 +133,71 @@ static int FindUsbDevice() return -1; // unreachable } +static short[] MakeBeep(double freqHz, double durationS, int sampleRate) +{ + int n = (int)(sampleRate * durationS); + var buf = new short[n]; + for (int i = 0; i < n; i++) + { + double t = i / (double)sampleRate; + double v = 0.3 * Math.Sin(2.0 * Math.PI * freqHz * t); + buf[i] = (short)(v * short.MaxValue); + } + return buf; +} + +static void FireAndForgetBeep(int device, short[] beepBuffer) +{ + int offset = 0; + var done = new ManualResetEventSlim(false); + + var outParams = new StreamParameters + { + device = device, + channelCount = 1, + sampleFormat = SampleFormat.Int16, + suggestedLatency = PortAudio.GetDeviceInfo(device).defaultLowOutputLatency, + hostApiSpecificStreamInfo = IntPtr.Zero, + }; + + Stream.Callback playCb = (IntPtr _, IntPtr output, uint frameCount, + ref StreamCallbackTimeInfo _2, StreamCallbackFlags _3, IntPtr _4) => + { + int remaining = beepBuffer.Length - offset; + int take = (int)Math.Min(frameCount, (uint)remaining); + int silence = (int)frameCount - take; + unsafe + { + short* dst = (short*)output.ToPointer(); + if (take > 0) + { + fixed (short* src = &beepBuffer[offset]) + Buffer.MemoryCopy(src, dst, take * sizeof(short), take * sizeof(short)); + offset += take; + } + for (int i = take; i < frameCount; i++) dst[i] = 0; + } + if (offset >= beepBuffer.Length) + { + done.Set(); + return StreamCallbackResult.Complete; + } + return StreamCallbackResult.Continue; + }; + + var stream = new Stream( + null, outParams, WakewordModel.SampleRate, 1024, StreamFlags.NoFlag, playCb, IntPtr.Zero); + Task.Run(() => + { + done.Wait(); + Thread.Sleep(200); + stream.Stop(); + stream.Dispose(); + done.Dispose(); + }); + stream.Start(); +} + static class Libc { [DllImport("libc", EntryPoint = "setenv")]