v0.2: streaming playback, model params, text input
- Stream audio directly to device via BufferedWaveProvider (no temp WAVs) - Add Noise/Speed/NoiseW sliders with live value labels - Sliders reinit engine on MouseUp (not Scroll) to avoid locking - Add text input field with Speak button for direct synthesis - Test button uses text input if non-empty - Fix sentence terminator auto-append (espeak-ng final-word drop) - Fix UTF-8 text marshaling (piper_synthesize_start keeps text ref) - Fix voice catalogue language column (JSON snake_case mapping) - Use piper_default_synthesize_options for model-specific defaults - Fix nullable warnings in designer files - Fix unused _playing field in AudioOutput
This commit is contained in:
@@ -8,7 +8,6 @@ internal sealed class AudioOutput : IDisposable
|
|||||||
private BufferedWaveProvider? _bufferProvider;
|
private BufferedWaveProvider? _bufferProvider;
|
||||||
private int _sampleRate;
|
private int _sampleRate;
|
||||||
private string _deviceName = string.Empty;
|
private string _deviceName = string.Empty;
|
||||||
private bool _playing;
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public Action<string>? Log { get; set; }
|
public Action<string>? Log { get; set; }
|
||||||
@@ -35,7 +34,6 @@ internal sealed class AudioOutput : IDisposable
|
|||||||
|
|
||||||
_waveOut.Init(_bufferProvider);
|
_waveOut.Init(_bufferProvider);
|
||||||
_waveOut.PlaybackStopped += OnPlaybackStopped;
|
_waveOut.PlaybackStopped += OnPlaybackStopped;
|
||||||
_playing = true;
|
|
||||||
_waveOut.Play();
|
_waveOut.Play();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +58,6 @@ internal sealed class AudioOutput : IDisposable
|
|||||||
|
|
||||||
private void OnPlaybackStopped(object? sender, StoppedEventArgs e)
|
private void OnPlaybackStopped(object? sender, StoppedEventArgs e)
|
||||||
{
|
{
|
||||||
_playing = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
@@ -74,7 +71,6 @@ internal sealed class AudioOutput : IDisposable
|
|||||||
}
|
}
|
||||||
_bufferProvider?.ClearBuffer();
|
_bufferProvider?.ClearBuffer();
|
||||||
_bufferProvider = null;
|
_bufferProvider = null;
|
||||||
_playing = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int FindDevice(string? deviceName)
|
private static int FindDevice(string? deviceName)
|
||||||
|
|||||||
Generated
+99
-8
@@ -1,3 +1,4 @@
|
|||||||
|
#nullable enable
|
||||||
namespace Robovoice.App;
|
namespace Robovoice.App;
|
||||||
|
|
||||||
partial class MainForm
|
partial class MainForm
|
||||||
@@ -22,6 +23,15 @@ partial class MainForm
|
|||||||
private Label lblTextInput = null!;
|
private Label lblTextInput = null!;
|
||||||
private TextBox txtTextInput = null!;
|
private TextBox txtTextInput = null!;
|
||||||
private Button btnSpeakInput = null!;
|
private Button btnSpeakInput = null!;
|
||||||
|
private Label lblNoise = null!;
|
||||||
|
private TrackBar trkNoise = null!;
|
||||||
|
private Label lblNoiseVal = null!;
|
||||||
|
private Label lblSpeed = null!;
|
||||||
|
private TrackBar trkSpeed = null!;
|
||||||
|
private Label lblSpeedVal = null!;
|
||||||
|
private Label lblNoiseW = null!;
|
||||||
|
private TrackBar trkNoiseW = null!;
|
||||||
|
private Label lblNoiseWVal = null!;
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
@@ -52,6 +62,15 @@ partial class MainForm
|
|||||||
lblTextInput = new Label();
|
lblTextInput = new Label();
|
||||||
txtTextInput = new TextBox();
|
txtTextInput = new TextBox();
|
||||||
btnSpeakInput = new Button();
|
btnSpeakInput = new Button();
|
||||||
|
lblNoise = new Label();
|
||||||
|
trkNoise = new TrackBar();
|
||||||
|
lblNoiseVal = new Label();
|
||||||
|
lblSpeed = new Label();
|
||||||
|
trkSpeed = new TrackBar();
|
||||||
|
lblSpeedVal = new Label();
|
||||||
|
lblNoiseW = new Label();
|
||||||
|
trkNoiseW = new TrackBar();
|
||||||
|
lblNoiseWVal = new Label();
|
||||||
|
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
|
|
||||||
@@ -126,23 +145,86 @@ partial class MainForm
|
|||||||
lblLineStatus.TextAlign = ContentAlignment.MiddleRight;
|
lblLineStatus.TextAlign = ContentAlignment.MiddleRight;
|
||||||
lblLineStatus.ForeColor = Color.DarkBlue;
|
lblLineStatus.ForeColor = Color.DarkBlue;
|
||||||
|
|
||||||
|
// lblNoise
|
||||||
|
lblNoise.Text = "Noise:";
|
||||||
|
lblNoise.Location = new Point(12, 82);
|
||||||
|
lblNoise.Size = new Size(40, 23);
|
||||||
|
lblNoise.TextAlign = ContentAlignment.MiddleLeft;
|
||||||
|
|
||||||
|
// trkNoise (0-1000 → 0.0-1.0, default 667)
|
||||||
|
trkNoise.Location = new Point(52, 78);
|
||||||
|
trkNoise.Size = new Size(120, 45);
|
||||||
|
trkNoise.Minimum = 0;
|
||||||
|
trkNoise.Maximum = 1000;
|
||||||
|
trkNoise.Value = 667;
|
||||||
|
trkNoise.TickFrequency = 200;
|
||||||
|
trkNoise.Orientation = Orientation.Horizontal;
|
||||||
|
|
||||||
|
// lblNoiseVal
|
||||||
|
lblNoiseVal.Text = "0.667";
|
||||||
|
lblNoiseVal.Location = new Point(175, 82);
|
||||||
|
lblNoiseVal.Size = new Size(35, 23);
|
||||||
|
lblNoiseVal.TextAlign = ContentAlignment.MiddleLeft;
|
||||||
|
|
||||||
|
// lblSpeed
|
||||||
|
lblSpeed.Text = "Speed:";
|
||||||
|
lblSpeed.Location = new Point(220, 82);
|
||||||
|
lblSpeed.Size = new Size(40, 23);
|
||||||
|
lblSpeed.TextAlign = ContentAlignment.MiddleLeft;
|
||||||
|
|
||||||
|
// trkSpeed (50-300 → 0.5-3.0, default 100)
|
||||||
|
trkSpeed.Location = new Point(260, 78);
|
||||||
|
trkSpeed.Size = new Size(120, 45);
|
||||||
|
trkSpeed.Minimum = 50;
|
||||||
|
trkSpeed.Maximum = 300;
|
||||||
|
trkSpeed.Value = 100;
|
||||||
|
trkSpeed.TickFrequency = 50;
|
||||||
|
trkSpeed.Orientation = Orientation.Horizontal;
|
||||||
|
|
||||||
|
// lblSpeedVal
|
||||||
|
lblSpeedVal.Text = "1.00";
|
||||||
|
lblSpeedVal.Location = new Point(383, 82);
|
||||||
|
lblSpeedVal.Size = new Size(35, 23);
|
||||||
|
lblSpeedVal.TextAlign = ContentAlignment.MiddleLeft;
|
||||||
|
|
||||||
|
// lblNoiseW
|
||||||
|
lblNoiseW.Text = "NoiseW:";
|
||||||
|
lblNoiseW.Location = new Point(428, 82);
|
||||||
|
lblNoiseW.Size = new Size(45, 23);
|
||||||
|
lblNoiseW.TextAlign = ContentAlignment.MiddleLeft;
|
||||||
|
|
||||||
|
// trkNoiseW (0-1000 → 0.0-1.0, default 800)
|
||||||
|
trkNoiseW.Location = new Point(475, 78);
|
||||||
|
trkNoiseW.Size = new Size(120, 45);
|
||||||
|
trkNoiseW.Minimum = 0;
|
||||||
|
trkNoiseW.Maximum = 1000;
|
||||||
|
trkNoiseW.Value = 800;
|
||||||
|
trkNoiseW.TickFrequency = 200;
|
||||||
|
trkNoiseW.Orientation = Orientation.Horizontal;
|
||||||
|
|
||||||
|
// lblNoiseWVal
|
||||||
|
lblNoiseWVal.Text = "0.800";
|
||||||
|
lblNoiseWVal.Location = new Point(598, 82);
|
||||||
|
lblNoiseWVal.Size = new Size(45, 23);
|
||||||
|
lblNoiseWVal.TextAlign = ContentAlignment.MiddleLeft;
|
||||||
|
|
||||||
// txtLog
|
// txtLog
|
||||||
txtLog.Location = new Point(12, 112);
|
txtLog.Location = new Point(12, 149);
|
||||||
txtLog.Size = new Size(800, 310);
|
txtLog.Size = new Size(800, 310);
|
||||||
|
|
||||||
// lblTextInput
|
// lblTextInput
|
||||||
lblTextInput.Text = "Text:";
|
lblTextInput.Text = "Text:";
|
||||||
lblTextInput.Location = new Point(12, 84);
|
lblTextInput.Location = new Point(12, 121);
|
||||||
lblTextInput.Size = new Size(35, 23);
|
lblTextInput.Size = new Size(35, 23);
|
||||||
lblTextInput.TextAlign = ContentAlignment.MiddleLeft;
|
lblTextInput.TextAlign = ContentAlignment.MiddleLeft;
|
||||||
|
|
||||||
// txtTextInput
|
// txtTextInput
|
||||||
txtTextInput.Location = new Point(50, 81);
|
txtTextInput.Location = new Point(50, 118);
|
||||||
txtTextInput.Size = new Size(680, 23);
|
txtTextInput.Size = new Size(680, 23);
|
||||||
|
|
||||||
// btnSpeakInput
|
// btnSpeakInput
|
||||||
btnSpeakInput.Text = "Speak";
|
btnSpeakInput.Text = "Speak";
|
||||||
btnSpeakInput.Location = new Point(737, 80);
|
btnSpeakInput.Location = new Point(737, 117);
|
||||||
btnSpeakInput.Size = new Size(75, 25);
|
btnSpeakInput.Size = new Size(75, 25);
|
||||||
btnSpeakInput.UseVisualStyleBackColor = true;
|
btnSpeakInput.UseVisualStyleBackColor = true;
|
||||||
txtLog.ReadOnly = true;
|
txtLog.ReadOnly = true;
|
||||||
@@ -152,20 +234,20 @@ partial class MainForm
|
|||||||
|
|
||||||
// chkMinimizeToTray
|
// chkMinimizeToTray
|
||||||
chkMinimizeToTray.Text = "Minimize to tray on close";
|
chkMinimizeToTray.Text = "Minimize to tray on close";
|
||||||
chkMinimizeToTray.Location = new Point(12, 440);
|
chkMinimizeToTray.Location = new Point(12, 477);
|
||||||
chkMinimizeToTray.Size = new Size(180, 24);
|
chkMinimizeToTray.Size = new Size(180, 24);
|
||||||
chkMinimizeToTray.UseVisualStyleBackColor = true;
|
chkMinimizeToTray.UseVisualStyleBackColor = true;
|
||||||
|
|
||||||
// btnClearLog
|
// btnClearLog
|
||||||
btnClearLog.Text = "Clear Log";
|
btnClearLog.Text = "Clear Log";
|
||||||
btnClearLog.Location = new Point(737, 438);
|
btnClearLog.Location = new Point(737, 475);
|
||||||
btnClearLog.Size = new Size(75, 25);
|
btnClearLog.Size = new Size(75, 25);
|
||||||
btnClearLog.UseVisualStyleBackColor = true;
|
btnClearLog.UseVisualStyleBackColor = true;
|
||||||
|
|
||||||
// MainForm
|
// MainForm
|
||||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
ClientSize = new Size(824, 472);
|
ClientSize = new Size(824, 509);
|
||||||
Controls.Add(lblPttKey);
|
Controls.Add(lblPttKey);
|
||||||
Controls.Add(cmbPttKey);
|
Controls.Add(cmbPttKey);
|
||||||
Controls.Add(lblVoice);
|
Controls.Add(lblVoice);
|
||||||
@@ -178,13 +260,22 @@ partial class MainForm
|
|||||||
Controls.Add(btnBrowseFile);
|
Controls.Add(btnBrowseFile);
|
||||||
Controls.Add(lblFileName);
|
Controls.Add(lblFileName);
|
||||||
Controls.Add(lblLineStatus);
|
Controls.Add(lblLineStatus);
|
||||||
|
Controls.Add(lblNoise);
|
||||||
|
Controls.Add(trkNoise);
|
||||||
|
Controls.Add(lblNoiseVal);
|
||||||
|
Controls.Add(lblSpeed);
|
||||||
|
Controls.Add(trkSpeed);
|
||||||
|
Controls.Add(lblSpeedVal);
|
||||||
|
Controls.Add(lblNoiseW);
|
||||||
|
Controls.Add(trkNoiseW);
|
||||||
|
Controls.Add(lblNoiseWVal);
|
||||||
Controls.Add(lblTextInput);
|
Controls.Add(lblTextInput);
|
||||||
Controls.Add(txtTextInput);
|
Controls.Add(txtTextInput);
|
||||||
Controls.Add(btnSpeakInput);
|
Controls.Add(btnSpeakInput);
|
||||||
Controls.Add(txtLog);
|
Controls.Add(txtLog);
|
||||||
Controls.Add(chkMinimizeToTray);
|
Controls.Add(chkMinimizeToTray);
|
||||||
Controls.Add(btnClearLog);
|
Controls.Add(btnClearLog);
|
||||||
MinimumSize = new Size(840, 510);
|
MinimumSize = new Size(840, 547);
|
||||||
Text = "Robovoice";
|
Text = "Robovoice";
|
||||||
FormBorderStyle = FormBorderStyle.FixedSingle;
|
FormBorderStyle = FormBorderStyle.FixedSingle;
|
||||||
MaximizeBox = false;
|
MaximizeBox = false;
|
||||||
|
|||||||
@@ -49,6 +49,13 @@ internal sealed partial class MainForm : Form
|
|||||||
cmbOutput.SelectedIndexChanged += OnOutputChanged;
|
cmbOutput.SelectedIndexChanged += OnOutputChanged;
|
||||||
cmbVoice.SelectedIndexChanged += OnVoiceChanged;
|
cmbVoice.SelectedIndexChanged += OnVoiceChanged;
|
||||||
|
|
||||||
|
trkNoise.Scroll += OnSliderScroll;
|
||||||
|
trkSpeed.Scroll += OnSliderScroll;
|
||||||
|
trkNoiseW.Scroll += OnSliderScroll;
|
||||||
|
trkNoise.MouseUp += OnSliderReleased;
|
||||||
|
trkSpeed.MouseUp += OnSliderReleased;
|
||||||
|
trkNoiseW.MouseUp += OnSliderReleased;
|
||||||
|
|
||||||
SetupTray();
|
SetupTray();
|
||||||
|
|
||||||
await InitializeEngineAsync();
|
await InitializeEngineAsync();
|
||||||
@@ -124,7 +131,12 @@ internal sealed partial class MainForm : Form
|
|||||||
_audioOutput?.Dispose();
|
_audioOutput?.Dispose();
|
||||||
_tts?.DisposeAsync().AsTask().Wait();
|
_tts?.DisposeAsync().AsTask().Wait();
|
||||||
|
|
||||||
_tts = new LibPiperTtsEngine(modelPath, _espeakDataPath);
|
_tts = new LibPiperTtsEngine(
|
||||||
|
modelPath,
|
||||||
|
_espeakDataPath,
|
||||||
|
noiseScale: trkNoise.Value / 1000.0f,
|
||||||
|
lengthScale: trkSpeed.Value / 100.0f,
|
||||||
|
noiseWScale: trkNoiseW.Value / 1000.0f);
|
||||||
_audioOutput = new AudioOutput();
|
_audioOutput = new AudioOutput();
|
||||||
_sttSource = new FileSttSource();
|
_sttSource = new FileSttSource();
|
||||||
_orchestrator?.DisposeAsync().AsTask().Wait();
|
_orchestrator?.DisposeAsync().AsTask().Wait();
|
||||||
@@ -272,6 +284,21 @@ internal sealed partial class MainForm : Form
|
|||||||
await InitializeEngineAsync();
|
await InitializeEngineAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnSliderScroll(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
lblNoiseVal.Text = $"{trkNoise.Value / 1000.0:F3}";
|
||||||
|
lblSpeedVal.Text = $"{trkSpeed.Value / 100.0:F2}";
|
||||||
|
lblNoiseWVal.Text = $"{trkNoiseW.Value / 1000.0:F3}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSliderReleased(object? sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (cmbVoice.SelectedItem is string name && VoiceCatalogue.IsVoiceInstalled(_voicesDir, name))
|
||||||
|
{
|
||||||
|
_ = InitializeEngineAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnPttKeyChanged(object? sender, EventArgs e)
|
private void OnPttKeyChanged(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
SetupHotkey();
|
SetupHotkey();
|
||||||
|
|||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
#nullable enable
|
||||||
namespace Robovoice.App;
|
namespace Robovoice.App;
|
||||||
|
|
||||||
partial class VoiceManagerForm
|
partial class VoiceManagerForm
|
||||||
|
|||||||
Reference in New Issue
Block a user