v0.4: persistent config in AppData, voices in LOCALAPPDATA, remove Speak button
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace Robovoice.App;
|
||||||
|
|
||||||
|
public sealed class AppConfig
|
||||||
|
{
|
||||||
|
public string Voice { get; set; } = string.Empty;
|
||||||
|
public int PttKey { get; set; } = (int)Keys.F8;
|
||||||
|
public string OutputDevice { get; set; } = string.Empty;
|
||||||
|
public int NoiseScale { get; set; } = 667;
|
||||||
|
public int LengthScale { get; set; } = 100;
|
||||||
|
public int NoiseWScale { get; set; } = 800;
|
||||||
|
public bool MinimizeToTray { get; set; } = true;
|
||||||
|
|
||||||
|
public static string AppDataDir => Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
|
"Hackmaster", "Robovoice");
|
||||||
|
|
||||||
|
public static string ConfigPath => Path.Combine(AppDataDir, "config.json");
|
||||||
|
public static string VoicesDir => Path.Combine(AppDataDir, "Piper-voices");
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static AppConfig Load()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(ConfigPath))
|
||||||
|
{
|
||||||
|
using var stream = File.OpenRead(ConfigPath);
|
||||||
|
return JsonSerializer.Deserialize<AppConfig>(stream) ?? new AppConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return new AppConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(AppDataDir);
|
||||||
|
using var stream = File.Create(ConfigPath);
|
||||||
|
JsonSerializer.Serialize(stream, this, JsonOptions);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+2
-9
@@ -22,7 +22,6 @@ partial class MainForm
|
|||||||
private Button btnClearLog = null!;
|
private Button btnClearLog = null!;
|
||||||
private Label lblTextInput = null!;
|
private Label lblTextInput = null!;
|
||||||
private TextBox txtTextInput = null!;
|
private TextBox txtTextInput = null!;
|
||||||
private Button btnSpeakInput = null!;
|
|
||||||
private Label lblNoise = null!;
|
private Label lblNoise = null!;
|
||||||
private TrackBar trkNoise = null!;
|
private TrackBar trkNoise = null!;
|
||||||
private Label lblNoiseVal = null!;
|
private Label lblNoiseVal = null!;
|
||||||
@@ -61,7 +60,6 @@ partial class MainForm
|
|||||||
btnClearLog = new Button();
|
btnClearLog = new Button();
|
||||||
lblTextInput = new Label();
|
lblTextInput = new Label();
|
||||||
txtTextInput = new TextBox();
|
txtTextInput = new TextBox();
|
||||||
btnSpeakInput = new Button();
|
|
||||||
lblNoise = new Label();
|
lblNoise = new Label();
|
||||||
trkNoise = new TrackBar();
|
trkNoise = new TrackBar();
|
||||||
lblNoiseVal = new Label();
|
lblNoiseVal = new Label();
|
||||||
@@ -85,6 +83,7 @@ partial class MainForm
|
|||||||
txtPttKey.Size = new Size(80, 23);
|
txtPttKey.Size = new Size(80, 23);
|
||||||
txtPttKey.ReadOnly = true;
|
txtPttKey.ReadOnly = true;
|
||||||
txtPttKey.TextAlign = HorizontalAlignment.Center;
|
txtPttKey.TextAlign = HorizontalAlignment.Center;
|
||||||
|
txtPttKey.TabStop = false;
|
||||||
|
|
||||||
// lblVoice
|
// lblVoice
|
||||||
lblVoice.Text = "Voice:";
|
lblVoice.Text = "Voice:";
|
||||||
@@ -221,13 +220,8 @@ partial class MainForm
|
|||||||
|
|
||||||
// txtTextInput
|
// txtTextInput
|
||||||
txtTextInput.Location = new Point(50, 118);
|
txtTextInput.Location = new Point(50, 118);
|
||||||
txtTextInput.Size = new Size(680, 23);
|
txtTextInput.Size = new Size(762, 23);
|
||||||
|
|
||||||
// btnSpeakInput
|
|
||||||
btnSpeakInput.Text = "Speak";
|
|
||||||
btnSpeakInput.Location = new Point(737, 117);
|
|
||||||
btnSpeakInput.Size = new Size(75, 25);
|
|
||||||
btnSpeakInput.UseVisualStyleBackColor = true;
|
|
||||||
txtLog.ReadOnly = true;
|
txtLog.ReadOnly = true;
|
||||||
txtLog.Font = new Font("Consolas", 9F);
|
txtLog.Font = new Font("Consolas", 9F);
|
||||||
txtLog.BackColor = Color.FromArgb(30, 30, 30);
|
txtLog.BackColor = Color.FromArgb(30, 30, 30);
|
||||||
@@ -272,7 +266,6 @@ partial class MainForm
|
|||||||
Controls.Add(lblNoiseWVal);
|
Controls.Add(lblNoiseWVal);
|
||||||
Controls.Add(lblTextInput);
|
Controls.Add(lblTextInput);
|
||||||
Controls.Add(txtTextInput);
|
Controls.Add(txtTextInput);
|
||||||
Controls.Add(btnSpeakInput);
|
|
||||||
Controls.Add(txtLog);
|
Controls.Add(txtLog);
|
||||||
Controls.Add(chkMinimizeToTray);
|
Controls.Add(chkMinimizeToTray);
|
||||||
Controls.Add(btnClearLog);
|
Controls.Add(btnClearLog);
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ namespace Robovoice.App;
|
|||||||
|
|
||||||
internal sealed partial class MainForm : Form
|
internal sealed partial class MainForm : Form
|
||||||
{
|
{
|
||||||
private readonly string _voicesDir = @"C:\data\opencode\robovoice-voices";
|
private readonly string _voicesDir = AppConfig.VoicesDir;
|
||||||
private readonly string _espeakDataPath;
|
private readonly string _espeakDataPath;
|
||||||
|
private readonly AppConfig _config;
|
||||||
|
|
||||||
private LibPiperTtsEngine? _tts;
|
private LibPiperTtsEngine? _tts;
|
||||||
private AudioOutput? _audioOutput;
|
private AudioOutput? _audioOutput;
|
||||||
@@ -25,22 +26,41 @@ internal sealed partial class MainForm : Form
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_espeakDataPath = Path.Combine(AppContext.BaseDirectory, "espeak-ng-data");
|
_espeakDataPath = Path.Combine(AppContext.BaseDirectory, "espeak-ng-data");
|
||||||
|
_config = AppConfig.Load();
|
||||||
|
Directory.CreateDirectory(AppConfig.AppDataDir);
|
||||||
|
Directory.CreateDirectory(_voicesDir);
|
||||||
Load += OnLoad;
|
Load += OnLoad;
|
||||||
FormClosing += OnFormClosing;
|
FormClosing += OnFormClosing;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnLoad(object? sender, EventArgs e)
|
private async void OnLoad(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
ActiveControl = txtLog;
|
||||||
PopulateVoices();
|
PopulateVoices();
|
||||||
PopulateOutputDevices();
|
PopulateOutputDevices();
|
||||||
|
|
||||||
|
_pttKey = (Keys)_config.PttKey;
|
||||||
|
if (_pttKey == Keys.None)
|
||||||
_pttKey = Keys.F8;
|
_pttKey = Keys.F8;
|
||||||
txtPttKey.Text = KeyToDisplayString(_pttKey);
|
txtPttKey.Text = KeyToDisplayString(_pttKey);
|
||||||
if (cmbVoice.Items.Count > 0)
|
|
||||||
|
if (!string.IsNullOrEmpty(_config.Voice) && cmbVoice.Items.Contains(_config.Voice))
|
||||||
|
cmbVoice.SelectedItem = _config.Voice;
|
||||||
|
else if (cmbVoice.Items.Count > 0)
|
||||||
cmbVoice.SelectedIndex = 0;
|
cmbVoice.SelectedIndex = 0;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_config.OutputDevice) && cmbOutput.Items.Contains(_config.OutputDevice))
|
||||||
|
cmbOutput.SelectedItem = _config.OutputDevice;
|
||||||
|
else
|
||||||
AutoSelectCableOutput();
|
AutoSelectCableOutput();
|
||||||
|
|
||||||
|
trkNoise.Value = _config.NoiseScale;
|
||||||
|
trkSpeed.Value = _config.LengthScale;
|
||||||
|
trkNoiseW.Value = _config.NoiseWScale;
|
||||||
|
OnSliderScroll(null, EventArgs.Empty);
|
||||||
|
|
||||||
|
chkMinimizeToTray.Checked = _config.MinimizeToTray;
|
||||||
|
|
||||||
btnBrowseFile.Click += OnBrowseFile;
|
btnBrowseFile.Click += OnBrowseFile;
|
||||||
btnTestVoice.Click += OnTestVoice;
|
btnTestVoice.Click += OnTestVoice;
|
||||||
btnManageVoices.Click += OnManageVoices;
|
btnManageVoices.Click += OnManageVoices;
|
||||||
@@ -57,6 +77,10 @@ internal sealed partial class MainForm : Form
|
|||||||
trkSpeed.MouseUp += OnSliderReleased;
|
trkSpeed.MouseUp += OnSliderReleased;
|
||||||
trkNoiseW.MouseUp += OnSliderReleased;
|
trkNoiseW.MouseUp += OnSliderReleased;
|
||||||
|
|
||||||
|
chkMinimizeToTray.CheckedChanged += (_, _) => SaveConfig();
|
||||||
|
|
||||||
|
Resize += OnResize;
|
||||||
|
|
||||||
SetupTray();
|
SetupTray();
|
||||||
|
|
||||||
await InitializeEngineAsync();
|
await InitializeEngineAsync();
|
||||||
@@ -98,6 +122,7 @@ internal sealed partial class MainForm : Form
|
|||||||
_captureHook?.Dispose();
|
_captureHook?.Dispose();
|
||||||
_captureHook = null;
|
_captureHook = null;
|
||||||
SetupHotkey();
|
SetupHotkey();
|
||||||
|
SaveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CancelCapture()
|
private void CancelCapture()
|
||||||
@@ -334,6 +359,7 @@ internal sealed partial class MainForm : Form
|
|||||||
cmbVoice.SelectedIndex = 0;
|
cmbVoice.SelectedIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SaveConfig();
|
||||||
_ = InitializeEngineAsync();
|
_ = InitializeEngineAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,6 +370,7 @@ internal sealed partial class MainForm : Form
|
|||||||
|
|
||||||
if (cmbVoice.SelectedItem is string name)
|
if (cmbVoice.SelectedItem is string name)
|
||||||
{
|
{
|
||||||
|
SaveConfig();
|
||||||
_ = ReinitializeEngineAsync(name);
|
_ = ReinitializeEngineAsync(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,6 +395,7 @@ internal sealed partial class MainForm : Form
|
|||||||
|
|
||||||
private void OnSliderReleased(object? sender, MouseEventArgs e)
|
private void OnSliderReleased(object? sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
|
SaveConfig();
|
||||||
if (cmbVoice.SelectedItem is string name && VoiceCatalogue.IsVoiceInstalled(_voicesDir, name))
|
if (cmbVoice.SelectedItem is string name && VoiceCatalogue.IsVoiceInstalled(_voicesDir, name))
|
||||||
{
|
{
|
||||||
_ = InitializeEngineAsync();
|
_ = InitializeEngineAsync();
|
||||||
@@ -379,6 +407,7 @@ internal sealed partial class MainForm : Form
|
|||||||
if (_orchestrator is not null)
|
if (_orchestrator is not null)
|
||||||
_orchestrator.OutputDeviceName = cmbOutput.SelectedItem as string ?? string.Empty;
|
_orchestrator.OutputDeviceName = cmbOutput.SelectedItem as string ?? string.Empty;
|
||||||
Log($"Output device: {_orchestrator?.OutputDeviceName}");
|
Log($"Output device: {_orchestrator?.OutputDeviceName}");
|
||||||
|
SaveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateLineStatus()
|
private void UpdateLineStatus()
|
||||||
@@ -415,6 +444,15 @@ internal sealed partial class MainForm : Form
|
|||||||
_trayIcon.DoubleClick += (_, _) => ShowWindow();
|
_trayIcon.DoubleClick += (_, _) => ShowWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnResize(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (WindowState == FormWindowState.Minimized && chkMinimizeToTray.Checked)
|
||||||
|
{
|
||||||
|
Hide();
|
||||||
|
WindowState = FormWindowState.Normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ShowWindow()
|
private void ShowWindow()
|
||||||
{
|
{
|
||||||
Show();
|
Show();
|
||||||
@@ -422,6 +460,18 @@ internal sealed partial class MainForm : Form
|
|||||||
Activate();
|
Activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SaveConfig()
|
||||||
|
{
|
||||||
|
_config.Voice = cmbVoice.SelectedItem as string ?? string.Empty;
|
||||||
|
_config.PttKey = (int)_pttKey;
|
||||||
|
_config.OutputDevice = cmbOutput.SelectedItem as string ?? string.Empty;
|
||||||
|
_config.NoiseScale = trkNoise.Value;
|
||||||
|
_config.LengthScale = trkSpeed.Value;
|
||||||
|
_config.NoiseWScale = trkNoiseW.Value;
|
||||||
|
_config.MinimizeToTray = chkMinimizeToTray.Checked;
|
||||||
|
_config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
private void Log(string message)
|
private void Log(string message)
|
||||||
{
|
{
|
||||||
if (IsDisposed) return;
|
if (IsDisposed) return;
|
||||||
@@ -438,15 +488,9 @@ internal sealed partial class MainForm : Form
|
|||||||
|
|
||||||
private void OnFormClosing(object? sender, FormClosingEventArgs e)
|
private void OnFormClosing(object? sender, FormClosingEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.CloseReason == CloseReason.UserClosing && chkMinimizeToTray.Checked && _trayIcon?.Visible == true)
|
|
||||||
{
|
|
||||||
e.Cancel = true;
|
|
||||||
Hide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_pttHotkey?.Dispose();
|
_pttHotkey?.Dispose();
|
||||||
_trayIcon!.Visible = false;
|
_trayIcon!.Visible = false;
|
||||||
_orchestrator?.DisposeAsync().AsTask().Wait();
|
_orchestrator?.DisposeAsync().AsTask().Wait();
|
||||||
|
SaveConfig();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user