v0.5: TCP STT client with PTT on/off, server endpoint in config + UI
This commit is contained in:
@@ -12,6 +12,7 @@ public sealed class AppConfig
|
||||
public int LengthScale { get; set; } = 100;
|
||||
public int NoiseWScale { get; set; } = 800;
|
||||
public bool MinimizeToTray { get; set; } = true;
|
||||
public string ServerEndpoint { get; set; } = "127.0.0.1:5210";
|
||||
|
||||
public static string AppDataDir => Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
|
||||
Generated
+27
-2
@@ -16,6 +16,9 @@ partial class MainForm
|
||||
private Label lblFile = null!;
|
||||
private Button btnBrowseFile = null!;
|
||||
private Label lblFileName = null!;
|
||||
private Label lblServer = null!;
|
||||
private TextBox txtServer = null!;
|
||||
private Button btnConnect = null!;
|
||||
private Label lblLineStatus = null!;
|
||||
private RichTextBox txtLog = null!;
|
||||
private CheckBox chkMinimizeToTray = null!;
|
||||
@@ -54,6 +57,9 @@ partial class MainForm
|
||||
lblFile = new Label();
|
||||
btnBrowseFile = new Button();
|
||||
lblFileName = new Label();
|
||||
lblServer = new Label();
|
||||
txtServer = new TextBox();
|
||||
btnConnect = new Button();
|
||||
lblLineStatus = new Label();
|
||||
txtLog = new RichTextBox();
|
||||
chkMinimizeToTray = new CheckBox();
|
||||
@@ -134,13 +140,29 @@ partial class MainForm
|
||||
// lblFileName
|
||||
lblFileName.Text = "(none)";
|
||||
lblFileName.Location = new Point(155, 48);
|
||||
lblFileName.Size = new Size(490, 23);
|
||||
lblFileName.Size = new Size(280, 23);
|
||||
lblFileName.TextAlign = ContentAlignment.MiddleLeft;
|
||||
lblFileName.ForeColor = Color.Gray;
|
||||
|
||||
// lblServer
|
||||
lblServer.Text = "Server:";
|
||||
lblServer.Location = new Point(440, 48);
|
||||
lblServer.Size = new Size(45, 23);
|
||||
lblServer.TextAlign = ContentAlignment.MiddleLeft;
|
||||
|
||||
// txtServer
|
||||
txtServer.Location = new Point(488, 45);
|
||||
txtServer.Size = new Size(110, 23);
|
||||
|
||||
// btnConnect
|
||||
btnConnect.Text = "Connect";
|
||||
btnConnect.Location = new Point(603, 44);
|
||||
btnConnect.Size = new Size(60, 25);
|
||||
btnConnect.UseVisualStyleBackColor = true;
|
||||
|
||||
// lblLineStatus
|
||||
lblLineStatus.Text = "";
|
||||
lblLineStatus.Location = new Point(650, 48);
|
||||
lblLineStatus.Location = new Point(645, 48);
|
||||
lblLineStatus.Size = new Size(160, 23);
|
||||
lblLineStatus.TextAlign = ContentAlignment.MiddleRight;
|
||||
lblLineStatus.ForeColor = Color.DarkBlue;
|
||||
@@ -254,6 +276,9 @@ partial class MainForm
|
||||
Controls.Add(lblFile);
|
||||
Controls.Add(btnBrowseFile);
|
||||
Controls.Add(lblFileName);
|
||||
Controls.Add(lblServer);
|
||||
Controls.Add(txtServer);
|
||||
Controls.Add(btnConnect);
|
||||
Controls.Add(lblLineStatus);
|
||||
Controls.Add(lblNoise);
|
||||
Controls.Add(trkNoise);
|
||||
|
||||
+62
-23
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Robovoice.Core;
|
||||
using Robovoice.Stt.File;
|
||||
using Robovoice.Tts.LibPiper;
|
||||
|
||||
namespace Robovoice.App;
|
||||
@@ -8,7 +7,7 @@ internal sealed class Orchestrator : IAsyncDisposable
|
||||
{
|
||||
private readonly LibPiperTtsEngine _tts;
|
||||
private readonly AudioOutput _audioOutput;
|
||||
private readonly FileSttSource _sttSource;
|
||||
private readonly ISttSource _sttSource;
|
||||
private readonly Action<string> _log;
|
||||
private CancellationTokenSource? _currentCts;
|
||||
private bool _disposed;
|
||||
@@ -18,7 +17,7 @@ internal sealed class Orchestrator : IAsyncDisposable
|
||||
public Orchestrator(
|
||||
LibPiperTtsEngine tts,
|
||||
AudioOutput audioOutput,
|
||||
FileSttSource sttSource,
|
||||
ISttSource sttSource,
|
||||
Action<string> log)
|
||||
{
|
||||
_tts = tts;
|
||||
@@ -97,15 +96,6 @@ internal sealed class Orchestrator : IAsyncDisposable
|
||||
sw.Stop();
|
||||
}
|
||||
|
||||
public void TriggerNextLine()
|
||||
{
|
||||
_sttSource.EmitNext();
|
||||
}
|
||||
|
||||
public string? GetPendingLine() => _sttSource.GetPendingLine();
|
||||
public int GetCurrentLineIndex() => _sttSource.CurrentIndex;
|
||||
public int GetLineCount() => _sttSource.LineCount;
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Robovoice.Core\Robovoice.Core.csproj" />
|
||||
<ProjectReference Include="..\Robovoice.Tts.LibPiper\Robovoice.Tts.LibPiper.csproj" />
|
||||
<ProjectReference Include="..\Robovoice.Stt.File\Robovoice.Stt.File.csproj" />
|
||||
<ProjectReference Include="..\Robovoice.Stt.Udp\Robovoice.Stt.Udp.csproj" />
|
||||
<ProjectReference Include="..\Robovoice.Stt.Tcp\Robovoice.Stt.Tcp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user