v0.1: Robovoice — PTT re-voicing app
WinForms tray app that captures text (from file or direct input) and synthesizes speech via libpiper (Piper TTS), outputting to any audio device (VB-CABLE for virtual mic routing). Features: - Global PTT hotkey (configurable F1-F12) via WH_KEYBOARD_LL - Text file sequential reader (line-by-line on each PTT cycle) - Direct text input with Speak button - Voice manager: browse 147-voice Piper catalogue, download, remove - libpiper P/Invoke wrapper with UTF-8 marshaling, streaming chunks - BufferedWaveProvider streaming playback with trailing silence flush - Sentence terminator auto-append (fixes espeak-ng final-word drop) - Tray icon with minimize-to-tray Architecture: - Robovoice.Core: ITtsEngine, ISttSource interfaces, voice catalogue - Robovoice.Tts.LibPiper: P/Invoke wrapper for libpiper.dll - Robovoice.Stt.File: text file STT source (testing without mic server) - Robovoice.Stt.Udp: UDP client stub (for future Linux mic server) - Robovoice.App: WinForms UI, orchestrator, PTT hotkey, audio output
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Robovoice.App;
|
||||
|
||||
internal sealed class PttHotkey : IDisposable
|
||||
{
|
||||
private const int WH_KEYBOARD_LL = 13;
|
||||
private const int WM_KEYDOWN = 0x0100;
|
||||
private const int WM_KEYUP = 0x0101;
|
||||
private const int WM_SYSKEYDOWN = 0x0104;
|
||||
private const int WM_SYSKEYUP = 0x0105;
|
||||
|
||||
private readonly LowLevelKeyboardProc _proc;
|
||||
private IntPtr _hook = IntPtr.Zero;
|
||||
private bool _isDown;
|
||||
private bool _disposed;
|
||||
|
||||
public Keys Key { get; set; } = Keys.F8;
|
||||
|
||||
public event EventHandler? Pressed;
|
||||
public event EventHandler? Released;
|
||||
|
||||
public PttHotkey()
|
||||
{
|
||||
_proc = HookCallback;
|
||||
}
|
||||
|
||||
public void Install()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (_hook != IntPtr.Zero) return;
|
||||
|
||||
IntPtr hModule = GetModuleHandle(null);
|
||||
_hook = SetWindowsHookEx(WH_KEYBOARD_LL, _proc, hModule, 0);
|
||||
}
|
||||
|
||||
public void Uninstall()
|
||||
{
|
||||
if (_hook != IntPtr.Zero)
|
||||
{
|
||||
UnhookWindowsHookEx(_hook);
|
||||
_hook = IntPtr.Zero;
|
||||
_isDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
if (nCode >= 0)
|
||||
{
|
||||
int vkCode = Marshal.ReadInt32(lParam);
|
||||
Keys key = (Keys)vkCode;
|
||||
|
||||
bool isDown = wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN;
|
||||
bool isUp = wParam == WM_KEYUP || wParam == WM_SYSKEYUP;
|
||||
|
||||
if (key == Key && isDown && !_isDown)
|
||||
{
|
||||
_isDown = true;
|
||||
Pressed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else if (key == Key && isUp && _isDown)
|
||||
{
|
||||
_isDown = false;
|
||||
Released?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
return CallNextHookEx(_hook, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
Uninstall();
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr GetModuleHandle(string? lpModuleName);
|
||||
}
|
||||
Reference in New Issue
Block a user