mirror of
https://github.com/9ParsonsB/Pulsar.git
synced 2025-04-05 17:39:39 -04:00
* Add file association for .eop, prompt for install dir * Handle .eop or .aip file passed as arg. * VS2022 version bump * Filter neutron stars and black holes from fast spinning criteria. * Adjustments for new "high value" check * Refactor herald cache * Fix element order and namespaces for voice moods. * Add explicit .Stop() between audio player calls. * Use nullsafe member access instead of skipping * Don't queue up a title that's already queued. * Improve body ordinal handling for explorer speech titles. * Escape strings being inserted into xml * Handle flip-flopping JSON type * Converter for flip-flopping property type * Use the converter * Escape characters *before* we wrap it in xml. * Give Eahlstan his clear button. :D * Exclude all stars from fast rotation check. * Close outstanding popup notifications on exit. * TO DONE * [Herald] Suppress duplicate notification titles for spoken notifications If you have notifications from multiple plugins producing notifications with the same title in quick succession (ie. "Body A 1 e" from both Explorer and BioInsights), the title on successive notifications will not be spoken again to save the breath of our friendly Azure speakers. * Doc update * Remove unintended member hiding * Fix export errors when exporting BioInsights data, cleanup Discovered a couple issues with exporting BioInsights data resulting from using two different types of objects in the data grid; improved error handling as well. Also cleaned up some old-style read all code. * Add read-all on launch setting * Updated framework xml * Improve high-value body description text Co-authored-by: Fred Kuipers <mr.fredk@gmail.com>
148 lines
5.4 KiB
C#
148 lines
5.4 KiB
C#
using Observatory.Framework;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using System.Linq;
|
|
using NetCoreAudio;
|
|
using System.Threading;
|
|
using System;
|
|
using System.Diagnostics;
|
|
|
|
namespace Observatory.Herald
|
|
{
|
|
class HeraldQueue
|
|
{
|
|
private Queue<NotificationArgs> notifications;
|
|
private bool processing;
|
|
private string voice;
|
|
private string style;
|
|
private string rate;
|
|
private byte volume;
|
|
private SpeechRequestManager speechManager;
|
|
private Player audioPlayer;
|
|
private Action<Exception, String> ErrorLogger;
|
|
|
|
public HeraldQueue(SpeechRequestManager speechManager, Action<Exception, String> errorLogger)
|
|
{
|
|
this.speechManager = speechManager;
|
|
processing = false;
|
|
notifications = new();
|
|
audioPlayer = new();
|
|
ErrorLogger = errorLogger;
|
|
}
|
|
|
|
|
|
internal void Enqueue(NotificationArgs notification, string selectedVoice, string selectedStyle = "", string selectedRate = "", int volume = 75)
|
|
{
|
|
voice = selectedVoice;
|
|
style = selectedStyle;
|
|
rate = selectedRate;
|
|
// Ignore invalid values; assume default.
|
|
volume = volume >= 0 && volume <= 100 ? volume : 75;
|
|
|
|
// Volume is perceived logarithmically, convert to exponential curve
|
|
// to make perceived volume more in line with value set.
|
|
this.volume = ((byte)System.Math.Floor(System.Math.Pow(volume / 100.0, 2.0) * 100));
|
|
|
|
Debug.WriteLine("Attempting to de-dupe notification titles against '{0}': '{1}'",
|
|
notification.Title.Trim().ToLower(),
|
|
String.Join(',', notifications.Select(n => n.Title.Trim().ToLower())));
|
|
|
|
if (notifications.Where(n => n.Title.Trim().ToLower() == notification.Title.Trim().ToLower()).Any())
|
|
{
|
|
// Suppress title.
|
|
notification.Suppression |= NotificationSuppression.Title;
|
|
}
|
|
notifications.Enqueue(notification);
|
|
|
|
if (!processing)
|
|
{
|
|
processing = true;
|
|
ProcessQueueAsync();
|
|
}
|
|
}
|
|
|
|
private async void ProcessQueueAsync()
|
|
{
|
|
await Task.Factory.StartNew(ProcessQueue);
|
|
}
|
|
|
|
private void ProcessQueue()
|
|
{
|
|
Thread.Sleep(200); // Allow time for other notifications to arrive.
|
|
NotificationArgs notification = null;
|
|
try
|
|
{
|
|
while (notifications.Any())
|
|
{
|
|
audioPlayer.SetVolume(volume).Wait();
|
|
notification = notifications.Dequeue();
|
|
Debug.WriteLine("Processing notification: {0} - {1}", notification.Title, notification.Detail);
|
|
|
|
List<Task<string>> audioRequestTasks = new();
|
|
|
|
if (!notification.Suppression.HasFlag(NotificationSuppression.Title))
|
|
{
|
|
audioRequestTasks.Add(string.IsNullOrWhiteSpace(notification.TitleSsml)
|
|
? RetrieveAudioToFile(notification.Title)
|
|
: RetrieveAudioSsmlToFile(notification.TitleSsml));
|
|
}
|
|
|
|
if (!notification.Suppression.HasFlag(NotificationSuppression.Detail))
|
|
{
|
|
audioRequestTasks.Add(string.IsNullOrWhiteSpace(notification.DetailSsml)
|
|
? RetrieveAudioToFile(notification.Detail)
|
|
: RetrieveAudioSsmlToFile(notification.DetailSsml));
|
|
}
|
|
|
|
PlayAudioRequestsSequentially(audioRequestTasks);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Failed to fetch/play notification: {notification?.Title} - {notification?.Detail}");
|
|
ErrorLogger(ex, "while retrieving and playing audio for a notification");
|
|
}
|
|
finally
|
|
{
|
|
processing = false;
|
|
}
|
|
}
|
|
|
|
private async Task<string> RetrieveAudioToFile(string text)
|
|
{
|
|
return await RetrieveAudioSsmlToFile($"<speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xml:lang=\"en-US\"><voice name=\"\">{System.Security.SecurityElement.Escape(text)}</voice></speak>");
|
|
}
|
|
|
|
private async Task<string> RetrieveAudioSsmlToFile(string ssml)
|
|
{
|
|
return await speechManager.GetAudioFileFromSsml(ssml, voice, style, rate);
|
|
}
|
|
|
|
private void PlayAudioRequestsSequentially(List<Task<string>> requestTasks)
|
|
{
|
|
foreach (var request in requestTasks)
|
|
{
|
|
string file = request.Result;
|
|
try
|
|
{
|
|
Debug.WriteLine($"Playing audio file: {file}");
|
|
audioPlayer.Play(file).Wait();
|
|
} catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Failed to play {file}: {ex.Message}");
|
|
ErrorLogger(ex, $"while playing: {file}");
|
|
}
|
|
|
|
while (audioPlayer.Playing)
|
|
Thread.Sleep(50);
|
|
|
|
// Explicit stop to ensure device is ready for next file.
|
|
// ...hopefully.
|
|
audioPlayer.Stop(true).Wait();
|
|
|
|
}
|
|
speechManager.CommitCache();
|
|
}
|
|
}
|
|
}
|