v0.5: TCP STT client with PTT on/off, server endpoint in config + UI

This commit is contained in:
2026-08-11 09:03:50 +00:00
parent 08d6eeb46e
commit 4a58b94f35
11 changed files with 696 additions and 53 deletions
+62 -23
View File
@@ -2,7 +2,7 @@ using NAudio.Wave;
using Robovoice.App;
using Robovoice.Core;
using Robovoice.Core.Voices;
using Robovoice.Stt.File;
using Robovoice.Stt.Tcp;
using Robovoice.Tts.LibPiper;
using System.Diagnostics;
@@ -16,7 +16,7 @@ internal sealed partial class MainForm : Form
private LibPiperTtsEngine? _tts;
private AudioOutput? _audioOutput;
private FileSttSource? _sttSource;
private TcpSttSource? _sttSource;
private Orchestrator? _orchestrator;
private PttHotkey? _pttHotkey;
private NotifyIcon? _trayIcon;
@@ -61,6 +61,7 @@ internal sealed partial class MainForm : Form
OnSliderScroll(null, EventArgs.Empty);
chkMinimizeToTray.Checked = _config.MinimizeToTray;
txtServer.Text = _config.ServerEndpoint;
btnBrowseFile.Click += OnBrowseFile;
btnTestVoice.Click += OnTestVoice;
@@ -70,6 +71,8 @@ internal sealed partial class MainForm : Form
txtPttKey.KeyDown += OnPttKeyDown;
cmbOutput.SelectedIndexChanged += OnOutputChanged;
cmbVoice.SelectedIndexChanged += OnVoiceChanged;
txtServer.Leave += OnServerChanged;
btnConnect.Click += OnConnect;
trkNoise.Scroll += OnSliderScroll;
trkSpeed.Scroll += OnSliderScroll;
@@ -239,7 +242,13 @@ internal sealed partial class MainForm : Form
lengthScale: trkSpeed.Value / 100.0f,
noiseWScale: trkNoiseW.Value / 1000.0f);
_audioOutput = new AudioOutput();
_sttSource = new FileSttSource();
if (_sttSource is null)
{
_sttSource = new TcpSttSource { ServerEndpoint = txtServer.Text, Log = Log };
Log($"STT endpoint: {_sttSource.ServerEndpoint} (press Connect)");
}
_orchestrator?.DisposeAsync().AsTask().Wait();
_orchestrator = new Orchestrator(_tts, _audioOutput, _sttSource, Log)
{
@@ -249,7 +258,7 @@ internal sealed partial class MainForm : Form
try
{
await _orchestrator.InitializeTtsAsync();
Log("Engine ready. Select a text file and press PTT key.");
Log("Engine ready. Press PTT to send to STT server.");
SetupHotkey();
}
catch (Exception ex)
@@ -271,22 +280,27 @@ internal sealed partial class MainForm : Form
private void OnPttPressed(object? sender, EventArgs e)
{
Log("PTT pressed");
try
{
_sttSource?.SendOn();
}
catch (Exception ex)
{
Log($"SendOn failed: {ex.Message}");
}
}
private void OnPttReleased(object? sender, EventArgs e)
{
Log("PTT released");
if (_orchestrator is null || _sttSource is null) return;
if (_sttSource.LineCount == 0)
try
{
Log("No text file loaded.");
return;
_sttSource?.SendOff();
}
catch (Exception ex)
{
Log($"SendOff failed: {ex.Message}");
}
UpdateLineStatus();
_orchestrator.TriggerNextLine();
UpdateLineStatus();
}
private void OnBrowseFile(object? sender, EventArgs e)
@@ -294,16 +308,16 @@ internal sealed partial class MainForm : Form
using var dlg = new OpenFileDialog
{
Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*",
Title = "Select a text file to read sequentially",
Title = "Select a text file to speak",
};
if (dlg.ShowDialog() == DialogResult.OK)
{
_sttSource?.LoadFile(dlg.FileName);
string text = File.ReadAllText(dlg.FileName);
lblFileName.Text = Path.GetFileName(dlg.FileName);
lblFileName.ForeColor = Color.Black;
UpdateLineStatus();
Log($"Loaded: {dlg.FileName} ({_sttSource?.LineCount} lines)");
Log($"Loaded: {dlg.FileName} ({text.Length} chars)");
_ = _orchestrator?.SynthesizeAsync(text);
}
}
@@ -411,15 +425,38 @@ internal sealed partial class MainForm : Form
SaveConfig();
}
private void UpdateLineStatus()
private void OnServerChanged(object? sender, EventArgs e)
{
if (_sttSource is null || _sttSource.LineCount == 0)
if (_sttSource is not null)
{
lblLineStatus.Text = "";
_sttSource.ServerEndpoint = txtServer.Text;
Log($"Server endpoint: {txtServer.Text}");
}
SaveConfig();
}
private async void OnConnect(object? sender, EventArgs e)
{
if (_sttSource is null)
{
Log("STT source not initialized.");
return;
}
int displayIdx = (_sttSource.CurrentIndex % _sttSource.LineCount) + 1;
lblLineStatus.Text = $"Line {displayIdx}/{_sttSource.LineCount}";
_sttSource.ServerEndpoint = txtServer.Text;
SaveConfig();
btnConnect.Enabled = false;
try
{
if (_sttSource.IsRunning)
await _sttSource.ReconnectAsync();
else
await _sttSource.StartAsync();
}
finally
{
btnConnect.Enabled = true;
}
}
private void SetupTray()
@@ -487,6 +524,7 @@ internal sealed partial class MainForm : Form
_config.LengthScale = trkSpeed.Value;
_config.NoiseWScale = trkNoiseW.Value;
_config.MinimizeToTray = chkMinimizeToTray.Checked;
_config.ServerEndpoint = txtServer.Text;
_config.Save();
}
@@ -508,7 +546,8 @@ internal sealed partial class MainForm : Form
{
_pttHotkey?.Dispose();
_trayIcon!.Visible = false;
_orchestrator?.DisposeAsync().AsTask().Wait();
_orchestrator?.DisposeAsync().AsTask().Wait(2000);
_sttSource?.DisposeAsync().AsTask().Wait(2000);
SaveConfig();
}
}