2
0
mirror of https://github.com/9ParsonsB/Pulsar.git synced 2025-07-03 01:03:41 -04:00

Herald v2 (#74)

* Add speech rate setting

* Add volume slider

* New speech manager skeleton

* User API key from resx

* Implement voice list retrieve via new api

* Rewrite to use ObAPI, remove all dependancies

* Use volume setting

* Clean up using statements

* Volume and timing adjustments

* Lookup rate value

* Use numeric rates for tighter spread

* Manage plugin data folder via core interface

* Add check that nullable settings are not null.

* Get file size before it's deleted.

* Improve old settings migration.

* Ignore cache sizes below 1MB

* Re-index orphaned files in cache, purge legacy wav files.

* Call top level error logging for native voice exception.

* Async title and detail requests to remove pause

* Remove NetCoreAudio use of temp files.

* Remove orphan using.
This commit is contained in:
Jonathan Miller
2022-04-04 11:58:30 -02:30
committed by GitHub
parent 3cc8cc3abe
commit 1950d477fd
19 changed files with 1127 additions and 288 deletions

View File

@ -0,0 +1,33 @@
using System;
using System.IO;
using System.Threading.Tasks;
using NetCoreAudio.Interfaces;
namespace NetCoreAudio.Players
{
internal class LinuxPlayer : UnixPlayerBase, IPlayer
{
protected override string GetBashCommand(string fileName)
{
if (Path.GetExtension(fileName).ToLower().Equals(".mp3"))
{
return "mpg123 -q";
}
else
{
return "aplay -q";
}
}
public override Task SetVolume(byte percent)
{
if (percent > 100)
throw new ArgumentOutOfRangeException(nameof(percent), "Percent can't exceed 100");
var tempProcess = StartBashProcess($"amixer -M set 'Master' {percent}%");
tempProcess.WaitForExit();
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Threading.Tasks;
using NetCoreAudio.Interfaces;
namespace NetCoreAudio.Players
{
internal class MacPlayer : UnixPlayerBase, IPlayer
{
protected override string GetBashCommand(string fileName)
{
return "afplay";
}
public override Task SetVolume(byte percent)
{
if (percent > 100)
throw new ArgumentOutOfRangeException(nameof(percent), "Percent can't exceed 100");
var tempProcess = StartBashProcess($"osascript -e \"set volume output volume {percent}\"");
tempProcess.WaitForExit();
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,110 @@
using NetCoreAudio.Interfaces;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace NetCoreAudio.Players
{
internal abstract class UnixPlayerBase : IPlayer
{
private Process _process = null;
internal const string PauseProcessCommand = "kill -STOP {0}";
internal const string ResumeProcessCommand = "kill -CONT {0}";
public event EventHandler PlaybackFinished;
public bool Playing { get; private set; }
public bool Paused { get; private set; }
protected abstract string GetBashCommand(string fileName);
public async Task Play(string fileName)
{
await Stop();
var BashToolName = GetBashCommand(fileName);
_process = StartBashProcess($"{BashToolName} '{fileName}'");
_process.EnableRaisingEvents = true;
_process.Exited += HandlePlaybackFinished;
_process.ErrorDataReceived += HandlePlaybackFinished;
_process.Disposed += HandlePlaybackFinished;
Playing = true;
}
public Task Pause()
{
if (Playing && !Paused && _process != null)
{
var tempProcess = StartBashProcess(string.Format(PauseProcessCommand, _process.Id));
tempProcess.WaitForExit();
Paused = true;
}
return Task.CompletedTask;
}
public Task Resume()
{
if (Playing && Paused && _process != null)
{
var tempProcess = StartBashProcess(string.Format(ResumeProcessCommand, _process.Id));
tempProcess.WaitForExit();
Paused = false;
}
return Task.CompletedTask;
}
public Task Stop()
{
if (_process != null)
{
_process.Kill();
_process.Dispose();
_process = null;
}
Playing = false;
Paused = false;
return Task.CompletedTask;
}
protected Process StartBashProcess(string command)
{
var escapedArgs = command.Replace("\"", "\\\"");
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = $"-c \"{escapedArgs}\"",
RedirectStandardOutput = true,
RedirectStandardInput = true,
UseShellExecute = false,
CreateNoWindow = true,
}
};
process.Start();
return process;
}
internal void HandlePlaybackFinished(object sender, EventArgs e)
{
if (Playing)
{
Playing = false;
PlaybackFinished?.Invoke(this, e);
}
}
public abstract Task SetVolume(byte percent);
public void Dispose()
{
Stop().Wait();
}
}
}

View File

@ -0,0 +1,141 @@
using NetCoreAudio.Interfaces;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
namespace NetCoreAudio.Players
{
internal class WindowsPlayer : IPlayer
{
[DllImport("winmm.dll")]
private static extern int mciSendString(string command, StringBuilder stringReturn, int returnLength, IntPtr hwndCallback);
[DllImport("winmm.dll")]
private static extern int mciGetErrorString(int errorCode, StringBuilder errorText, int errorTextSize);
[DllImport("winmm.dll")]
public static extern int waveOutSetVolume(IntPtr hwo, uint dwVolume);
private Timer _playbackTimer;
private Stopwatch _playStopwatch;
private string _fileName;
public event EventHandler PlaybackFinished;
public bool Playing { get; private set; }
public bool Paused { get; private set; }
public Task Play(string fileName)
{
_fileName = $"\"{fileName}\"";
_playbackTimer = new Timer
{
AutoReset = false
};
_playStopwatch = new Stopwatch();
ExecuteMciCommand($"Status {_fileName} Length");
ExecuteMciCommand($"Play {_fileName}");
Paused = false;
Playing = true;
_playbackTimer.Elapsed += HandlePlaybackFinished;
_playbackTimer.Start();
_playStopwatch.Start();
return Task.CompletedTask;
}
public Task Pause()
{
if (Playing && !Paused)
{
ExecuteMciCommand($"Pause {_fileName}");
Paused = true;
_playbackTimer.Stop();
_playStopwatch.Stop();
_playbackTimer.Interval -= _playStopwatch.ElapsedMilliseconds;
}
return Task.CompletedTask;
}
public Task Resume()
{
if (Playing && Paused)
{
ExecuteMciCommand($"Resume {_fileName}");
Paused = false;
_playbackTimer.Start();
_playStopwatch.Reset();
_playStopwatch.Start();
}
return Task.CompletedTask;
}
public Task Stop()
{
if (Playing)
{
ExecuteMciCommand($"Stop {_fileName}");
Playing = false;
Paused = false;
_playbackTimer.Stop();
_playStopwatch.Stop();
}
return Task.CompletedTask;
}
private void HandlePlaybackFinished(object sender, ElapsedEventArgs e)
{
Playing = false;
PlaybackFinished?.Invoke(this, e);
_playbackTimer?.Dispose();
_playbackTimer = null;
}
private Task ExecuteMciCommand(string commandString)
{
var sb = new StringBuilder();
var result = mciSendString(commandString, sb, 1024 * 1024, IntPtr.Zero);
if (result != 0)
{
var errorSb = new StringBuilder($"Error executing MCI command '{commandString}'. Error code: {result}.");
var sb2 = new StringBuilder(128);
mciGetErrorString(result, sb2, 128);
errorSb.Append($" Message: {sb2}");
throw new Exception(errorSb.ToString());
}
if (commandString.ToLower().StartsWith("status") && int.TryParse(sb.ToString(), out var length))
_playbackTimer.Interval = length + 75;
return Task.CompletedTask;
}
public Task SetVolume(byte percent)
{
// Calculate the volume that's being set
int NewVolume = ushort.MaxValue / 100 * percent;
// Set the same volume for both the left and the right channels
uint NewVolumeAllChannels = ((uint)NewVolume & 0x0000ffff) | ((uint)NewVolume << 16);
// Set the volume
waveOutSetVolume(IntPtr.Zero, NewVolumeAllChannels);
return Task.CompletedTask;
}
public void Dispose()
{
Stop().Wait();
ExecuteMciCommand("Close All");
}
}
}