Voice-activated push-to-talk for Windows. Your microphone level crosses a threshold → PushToOpen virtually holds a key you've bound. You go quiet → it releases. Threshold, key, timing, input device, monitor, noise suppression, window-restriction, theme — all configurable from a clean WinUI 3 desktop app.
- Real-time RMS + peak metering with a smoothed gradient VU bar + moving threshold line
- Threshold state machine with attack, release, and debounce windows
- Reliable Windows
SendInput-based key simulation (scan codes + extended-key handling); keyboard or mouse buttons 1–5 - Settings auto-persist (debounced JSON write to
%LocalAppData%\PushToOpen\settings.json) - Compact floating overlay window with lock-position toggle, smooth pointer-capture drag, live mute icon, dB readout
- System tray with Open / threshold presets / Exit (Command-bound, namescope-safe)
- Optional launch on Windows startup (per-user
HKCU\…\Run) - Device hot-swap: disconnect/reconnect the mic and PushToOpen rebinds automatically
- Fail-safe: any unexpected exit, device loss, or close releases the virtual key
- Hear yourself — pipe your mic to your default output for monitoring (with its own gain). Use headphones.
- Voice noise suppression — pure C# FFT-based spectral subtraction. Hand-rolled Cooley-Tukey, periodic-Hann analysis at 50% hop (COLA), per-bin noise floor learned during low-energy frames, oversubtraction with smoothed Wiener gain. Cleans steady-state hiss + fan noise from the threshold detection and monitor path. (Other apps still see your raw mic — see honest limits below.)
- Input gain trim + noise gate floor
- Push-to-talk binding (keyboard or mouse, default
V) - Mute toggle global hotkey — separate key flips Mute on/off from any focused window
- Both bind via in-app capture (Esc cancels)
- "Window" tab — pick an open app. PushToOpen only fires while that process owns the foreground window. Tab to anything else → gate stays closed, no key. Auto-resumes when you focus it again.
- Six built-in palettes — Indigo Night, Phosphor, Crimson, Aurora, Sunset, Mono
- Custom PNG/JPG background image with adjustable opacity (translucent watermark — unpackaged WinUI 3 can't do real backdrop blur)
- Layout modes: full Sidebar or Compact (icon-only nav rail)
PushToOpen is a key simulator, not a virtual audio device. It reads your microphone to decide whether to press a key — it doesn't replace what Discord, Teams, Zoom, etc. capture. That means:
- The noise-suppression toggle improves threshold accuracy (no false triggers from fan noise) + the Hear-yourself monitor playback. It does not clean up the mic stream other apps record.
- For system-wide neural noise suppression (Krisp-grade), use Discord's built-in Krisp toggle (Voice Settings → Noise Suppression → Krisp), NVIDIA Broadcast, or a virtual cable + VoiceMeeter.
- Windows 10 build 19041+ or Windows 11, x64
- Self-contained published binaries need no .NET install
- Building from source: .NET 8 SDK
The dist/ folder in the repo holds a ready-to-run self-contained build:
dist/
├── Start PushToOpen.lnk ← double-click this
├── Install Desktop Shortcut.bat ← optional: adds a Desktop shortcut
├── README.txt
└── PushToOpen/ ← program + DLLs (don't touch)
Zip the whole dist/ folder to share. Recipient unzips anywhere, double-clicks Start PushToOpen.lnk. No installer, no admin, no runtime download.
From the repo root:
dotnet restore
dotnet msbuild .\src\PushToOpen\PushToOpen.csproj -p:Configuration=Release -p:Platform=x64Or open PushToOpen.sln in Visual Studio 2022 (17.8+) and press F5. Platform must be x64 — WindowsAppSDK self-contained mode rejects AnyCPU.
Single-folder self-contained publish:
dotnet publish .\src\PushToOpen\PushToOpen.csproj `
-c Release -p:Platform=x64 `
-p:WindowsAppSDKSelfContained=true `
-p:SelfContained=true `
--runtime win-x64 `
--output .\dist\PushToOpenBoth <SelfContained>true</SelfContained> and <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained> are required — the second bundles the WinAppSDK native DLLs alongside the .NET runtime.
- Open the app.
- Audio tab → pick your microphone.
- Hotkey tab → bind the key PushToOpen should hold while you talk (default
V). - Live tab → speak. Drag Threshold so the gate opens when you talk and closes when you're silent.
- (Optional) Hear yourself under Audio to verify what the noise suppressor is doing. Headphones.
- (Optional) Window tab → pin PushToOpen to one app so it doesn't fire while you type in other windows.
- (Optional) Themes tab → pick a palette + background image + layout.
src/PushToOpen/
├── App.xaml(.cs) – DI bootstrap, theme apply, lifecycle, overlay mgmt, global hotkey wiring
├── Program.cs – STA entry point
├── MainWindow.xaml(.cs) – Shell, tray icon (Command-bound), status bar
├── Views/
│ ├── MainShellView.xaml(.cs) – Sidebar / content / status, layout-mode switching, theme bg image
│ ├── OverlayView.xaml(.cs) – Compact pod with lock toggle + mute icon
│ └── OverlayWindow.xaml(.cs) – Borderless window + pointer-capture drag
├── Controls/VuMeter.cs – Custom level + threshold + active-glow control
├── Styles/ – Color brushes + control templates (live-mutated by ThemeService)
├── Models/
│ ├── AppSettings – Persistent state for everything below
│ ├── AudioDeviceInfo, KeyBindingInfo, WindowInfo
│ └── ThemeDefinition – Palette + accent gradient stops
└── Services/
├── SettingsService – Debounced atomic JSON persistence (tmp → rename)
├── AudioCaptureService – WASAPI capture, RMS / peak, device hot-swap, monitor, NS pipeline
├── NoiseSuppressor – Hand-rolled FFT + spectral subtraction
├── ThresholdEngine – Attack / release / debounce / mute / app-gate state machine
├── InputSimulator – SendInput key down/up (down stays true on failure → retries)
├── HotkeyCaptureService – Low-level kbd + mouse hook for binding capture
├── GlobalHotkeyListener – Always-on hook listening for the mute-toggle binding
├── ForegroundWatcher – Polls GetForegroundWindow every 250ms
├── WindowEnumerator – EnumWindows + DWM-cloak / tool-window filtering
├── ThemeService – Six palettes, live brush.Color mutation
├── StartupService – HKCU Run key registration
└── PushToTalkCoordinator – Wires audio → engine → input + foreground → app-gate + settings → everything
WASAPI capture ─► AudioCaptureService ─► [optional NS] ─► ThresholdEngine ─► InputSimulator
│ │
▼ ▼
ViewModels Tray / Overlay UI
GetForegroundWindow (250ms poll) ─► ForegroundWatcher ─► Coordinator.ApplyAppGate
│
▼
ThresholdEngine.SetAppGate
Low-level hook (mute-toggle key) ─► GlobalHotkeyListener ─► Task.Run ─► Settings.Mutate(Muted = !Muted)
- Never leak a held key.
PushToTalkCoordinatorsubscribes toAppDomain.ProcessExitandUnhandledExceptionand releases the simulator before exit.InputSimulator.Dispose()also releases.OnTrayExitreleases + force-terminates. - Audio thread isolation. WASAPI
OnDataAvailablereads shared doubles under_gatelock; UI updates marshal viaDispatcherHelper. Noise suppressor processes in-place on the callback thread (cheap STFT, ~few % of one core). - Device hot-swap.
AudioCaptureServiceregistersIMMNotificationClient; device add/remove/default-change triggersTask.Run(RefreshDevicesAsync)(off the COM callback thread). - Hook-thread safety. Low-level keyboard hook callbacks (mute-toggle handler) bounce off the hook thread via
Task.Runbefore doing settings mutation — keeps callbacks well underLowLevelHooksTimeout.
%LocalAppData%\PushToOpen\settings.json — atomic write (tmp → rename) with ~400 ms debounce. Delete to reset.
MIT.