From ed0570922a161ad2a85e85f93e5ebca8d2e91da5 Mon Sep 17 00:00:00 2001 From: VoidX Date: Tue, 21 Jan 2025 00:14:39 +0100 Subject: [PATCH] In-CavernPipe layout change and 4.1.3 support --- Cavern.WPF/Consts/Language.cs | 13 ++ Cavern.WPF/Consts/LanguageExtensions.cs | 47 +++++++ .../ChannelSelectorStrings.hu-HU.xaml | 8 ++ .../Resources/ChannelSelectorStrings.xaml | 7 ++ Cavern.WPF/Resources/CommonStrings.hu-HU.xaml | 1 + Cavern.WPF/Resources/CommonStrings.xaml | 1 + Cavern/Channels/ChannelPrototype.Consts.cs | 25 ++++ Cavern/Channels/ChannelPrototype.cs | 15 ++- Cavern/Internals/Configuration.cs | 82 ++++++++++++ Cavern/Internals/_Exceptions.cs | 15 +++ Cavern/Utilities/VectorExtensions.cs | 6 + .../CavernPipeServer/CavernPipeRenderer.cs | 8 +- .../CavernPipeServer/CavernPipeServer.csproj | 1 + .../CavernPipeServer/ChannelMeters.cs | 5 +- .../CavernPipeServer/Consts/Language.cs | 6 - .../MainWindow.ContextMenu.cs | 117 ++++++++++++++++++ .../CavernPipeServer/MainWindow.xaml.cs | 67 +++++----- CavernSamples/CavernPipeServer/PipeHandler.cs | 18 ++- .../Resources/MainWindowStrings.hu-HU.xaml | 11 +- .../Resources/MainWindowStrings.xaml | 11 +- .../CavernizeGUI/CavernizeGUI.csproj | 1 + .../CavernizeGUI/Elements/RenderTarget.cs | 28 +---- .../CavernizeGUI/MainWindow.Popups.cs | 25 +--- CavernSamples/CavernizeGUI/MainWindow.xaml.cs | 4 +- .../Resources/MainWindowStrings.hu-HU.xaml | 5 - .../Resources/MainWindowStrings.xaml | 6 - 26 files changed, 421 insertions(+), 112 deletions(-) create mode 100644 Cavern.WPF/Consts/LanguageExtensions.cs create mode 100644 Cavern/Internals/Configuration.cs create mode 100644 Cavern/Internals/_Exceptions.cs create mode 100644 CavernSamples/CavernPipeServer/MainWindow.ContextMenu.cs diff --git a/Cavern.WPF/Consts/Language.cs b/Cavern.WPF/Consts/Language.cs index d8ba6117..978c2bde 100644 --- a/Cavern.WPF/Consts/Language.cs +++ b/Cavern.WPF/Consts/Language.cs @@ -45,6 +45,18 @@ public static class Language { public static void Error(string message) => MessageBox.Show(message, (string)GetCommonStrings()["TErro"], MessageBoxButton.OK, MessageBoxImage.Error); + /// + /// Show a warning with the title in the user's language. + /// + public static MessageBoxResult Warning(string message) => + MessageBox.Show(message, (string)GetCommonStrings()["TWarn"], MessageBoxButton.OK, MessageBoxImage.Warning); + + /// + /// Show a warning with the title in the user's language. + /// + public static MessageBoxResult Warning(string message, MessageBoxButton buttons) => + MessageBox.Show(message, (string)GetCommonStrings()["TWarn"], buttons, MessageBoxImage.Warning); + /// /// Return a channel's name in the user's language or fall back to its short name. /// @@ -73,6 +85,7 @@ public static string Translate(this ReferenceChannel channel) { ReferenceChannel.TopRearLeft => (string)dictionary["SpTRL"], ReferenceChannel.TopRearCenter => (string)dictionary["SpTRC"], ReferenceChannel.TopRearRight => (string)dictionary["SpTRR"], + ReferenceChannel.Unknown => (string)dictionary["SpUnk"], _ => channel.GetShortName() }; } diff --git a/Cavern.WPF/Consts/LanguageExtensions.cs b/Cavern.WPF/Consts/LanguageExtensions.cs new file mode 100644 index 00000000..f71c0c55 --- /dev/null +++ b/Cavern.WPF/Consts/LanguageExtensions.cs @@ -0,0 +1,47 @@ +using System.Runtime.CompilerServices; +using System.Text; +using System.Windows; + +using Cavern.Channels; + +namespace Cavern.WPF.Consts { + /// + /// Extension functions for calculating translated text. + /// + public static class LanguageExtensions { + /// + /// Display how a set of spatial shall be wired for regular audio interfaces. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void DisplayWiring(this ReferenceChannel[] channels) => channels.DisplayWiring(null); + + /// + /// Display how a set of spatial shall be wired for regular audio interfaces when some channels are + /// into 8 channels to be extracted when the speaker is wired to two positive terminals. These matrixed channels + /// are not part of the base . + /// + public static void DisplayWiring(this ReferenceChannel[] channels, + (ReferenceChannel source, ReferenceChannel posPhase, ReferenceChannel negPhase)[] matrixed) { + ResourceDictionary language = Language.GetChannelSelectorStrings(); + ChannelPrototype[] prototypes = ChannelPrototype.Get(channels); + if (channels.Length > 8) { + MessageBox.Show(string.Format((string)language["Over8"], string.Join(string.Empty, prototypes.Select(x => "\n- " + x.Name)), + (string)language["WrGui"])); + return; + } + + StringBuilder output = new StringBuilder(); + ReferenceChannel[] standard = ChannelPrototype.GetStandardMatrix(prototypes.Length); + for (int i = 0; i < prototypes.Length; i++) { + output.AppendLine(string.Format((string)language["ChCon"], channels[i].Translate(), standard[i].Translate())); + } + if (matrixed != null) { + for (int i = 0; i < matrixed.Length; i++) { + output.AppendLine(string.Format((string)language["ChCMx"], + matrixed[i].source.Translate(), matrixed[i].posPhase.Translate(), matrixed[i].negPhase.Translate())); + } + } + MessageBox.Show(output.ToString(), (string)language["WrGui"]); + } + } +} \ No newline at end of file diff --git a/Cavern.WPF/Resources/ChannelSelectorStrings.hu-HU.xaml b/Cavern.WPF/Resources/ChannelSelectorStrings.hu-HU.xaml index 59802852..3ac89eab 100644 --- a/Cavern.WPF/Resources/ChannelSelectorStrings.hu-HU.xaml +++ b/Cavern.WPF/Resources/ChannelSelectorStrings.hu-HU.xaml @@ -26,4 +26,12 @@ Felső hátsó bal Felső hátsó közép Felső hátsó jobb + Ismeretlen + + Bekötési útmutató + Kösd a (z) {0} hangszórót a rendszered {1} kimenetére. + Kösd a (z) {0} hangszóró + csatlakozóját a(z) {1} kimenet +-ára, valamint a hangszóró - csatlakozóját + a(z) {2} kimenet +-ára. + Nincs szabványos bekötés 8 csatorna felett. Masszívan többcsatornás interfészeknél a csatornák + rendje pontosan az, ami a fájlban is:{0} \ No newline at end of file diff --git a/Cavern.WPF/Resources/ChannelSelectorStrings.xaml b/Cavern.WPF/Resources/ChannelSelectorStrings.xaml index 0d79e584..69568b5c 100644 --- a/Cavern.WPF/Resources/ChannelSelectorStrings.xaml +++ b/Cavern.WPF/Resources/ChannelSelectorStrings.xaml @@ -26,4 +26,11 @@ Top rear left Top rear center Top rear right + Unknown + + Wiring guide + Connect the {0} speaker to your system's {1} output. + Connect the {0} speaker's + terminal to the {1} output's + and the speaker's - terminal to the {2} output's +. + There is no standard wiring over 8 channels. For massively multichannel interfaces, + the channel order is exactly as it's in the file:{0} \ No newline at end of file diff --git a/Cavern.WPF/Resources/CommonStrings.hu-HU.xaml b/Cavern.WPF/Resources/CommonStrings.hu-HU.xaml index ee29fd5f..48c13782 100644 --- a/Cavern.WPF/Resources/CommonStrings.hu-HU.xaml +++ b/Cavern.WPF/Resources/CommonStrings.hu-HU.xaml @@ -4,5 +4,6 @@ OK Mégse Hiba + Figyelmeztetés Minden támogatott formátum|{0} \ No newline at end of file diff --git a/Cavern.WPF/Resources/CommonStrings.xaml b/Cavern.WPF/Resources/CommonStrings.xaml index 2bc55ab5..62092b0b 100644 --- a/Cavern.WPF/Resources/CommonStrings.xaml +++ b/Cavern.WPF/Resources/CommonStrings.xaml @@ -4,5 +4,6 @@ OK Cancel Error + Warning All supported formats|{0} \ No newline at end of file diff --git a/Cavern/Channels/ChannelPrototype.Consts.cs b/Cavern/Channels/ChannelPrototype.Consts.cs index 8809e101..07520363 100644 --- a/Cavern/Channels/ChannelPrototype.Consts.cs +++ b/Cavern/Channels/ChannelPrototype.Consts.cs @@ -99,11 +99,31 @@ public static readonly ChannelPrototype /// public static readonly ReferenceChannel[] ref300 = { ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter }; + /// + /// Standard 3.1.2 setup (L, R, C, LFE, TFL, TFR). + /// + public static readonly ReferenceChannel[] ref312 = { ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE, ReferenceChannel.TopFrontLeft, ReferenceChannel.TopFrontRight }; + /// /// Standard 4.0.0 setup (L, R, SL, SR). /// public static readonly ReferenceChannel[] ref400 = { ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.SideLeft, ReferenceChannel.SideRight }; + /// + /// Standard 4.0.4 setup (L, R, RL, RR, TFL, TFR, TRL, TRR). + /// + public static readonly ReferenceChannel[] ref404 = { ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.RearLeft, ReferenceChannel.RearRight, ReferenceChannel.TopFrontLeft, ReferenceChannel.TopFrontRight, ReferenceChannel.TopRearLeft, ReferenceChannel.TopRearRight }; + + /// + /// Standard 4.1.1 setup (L, R, C, LFE, RC, TFC). + /// + public static readonly ReferenceChannel[] ref411 = { ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE, ReferenceChannel.RearCenter, ReferenceChannel.TopFrontCenter }; + + /// + /// Standard 4.1.3 setup (L, R, C, LFE, RC, TFC, TRL, TRR). + /// + public static readonly ReferenceChannel[] ref413 = { ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE, ReferenceChannel.RearCenter, ReferenceChannel.TopFrontCenter, ReferenceChannel.TopRearLeft, ReferenceChannel.TopRearRight }; + /// /// Standard 5.0.0 setup (L, R, C, SL, SR). /// @@ -194,6 +214,11 @@ public static readonly ChannelPrototype /// public static readonly ReferenceChannel[] wav906 = { ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.RearLeft, ReferenceChannel.RearRight, ReferenceChannel.SideLeft, ReferenceChannel.SideRight, ReferenceChannel.WideLeft, ReferenceChannel.WideRight, ReferenceChannel.TopFrontLeft, ReferenceChannel.TopFrontCenter, ReferenceChannel.TopFrontRight, ReferenceChannel.TopRearLeft, ReferenceChannel.TopRearCenter, ReferenceChannel.TopRearRight }; + /// + /// Standard 9.1.0 setup (L, R, C, LFE, RL, RR, SL, SR, WL, WR). + /// + public static readonly ReferenceChannel[] ref910 = { ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE, ReferenceChannel.RearLeft, ReferenceChannel.RearRight, ReferenceChannel.SideLeft, ReferenceChannel.SideRight, ReferenceChannel.WideLeft, ReferenceChannel.WideRight }; + /// /// Standard 9.1.4 setup (L, R, C, LFE, RL, RR, SL, SR, WL, WR, TFL, TFR, TRL, TRR). /// diff --git a/Cavern/Channels/ChannelPrototype.cs b/Cavern/Channels/ChannelPrototype.cs index 034c4929..766e7129 100644 --- a/Cavern/Channels/ChannelPrototype.cs +++ b/Cavern/Channels/ChannelPrototype.cs @@ -3,6 +3,8 @@ using System.Numerics; using System.Runtime.CompilerServices; +using Cavern.Utilities; + namespace Cavern.Channels { /// /// Light audio channel information structure. @@ -192,13 +194,24 @@ public static ReferenceChannel GetReference(Channel source) { } Vector3 cubicalPos = source.CubicalPos; for (int i = 0; i < AlternativePositions.Length; i++) { - if (AlternativePositions[i] == cubicalPos) { + if (AlternativePositions[i].CloseTo(cubicalPos, .05f)) { return (ReferenceChannel)i; } } return ReferenceChannel.Unknown; } + /// + /// Get which channels this layout represents. + /// + public static ReferenceChannel[] GetReferences(Channel[] source) { + ReferenceChannel[] result = new ReferenceChannel[source.Length]; + for (int i = 0; i < source.Length; i++) { + result[i] = GetReference(source[i]); + } + return result; + } + /// /// Convert a mapping of s to channel name initials. /// diff --git a/Cavern/Internals/Configuration.cs b/Cavern/Internals/Configuration.cs new file mode 100644 index 00000000..43db0a11 --- /dev/null +++ b/Cavern/Internals/Configuration.cs @@ -0,0 +1,82 @@ +using System; +using System.Globalization; +using System.IO; + +namespace Cavern.Internals { + /// + /// Functions to modify Cavern's global configuration. See for warnings. + /// + public static class CavernConfiguration { + /// + /// These are very wild waters and you should really evaluate if you need these functions or not. + /// You are given the power to modify the user's global settings which are reflected in all Cavern + /// products and applications/games built on Cavern. If the user is not properly prompted that for + /// example, the speakers are getting reordered in all applications, they can blame Cavern for bugs + /// it doesn't have. Your page names, prompts, and UIs should completely convey that the user is + /// editing the GLOBAL settings. You should really make it sure they WANT to do EXACTLY what is + /// being called from here. To be safe and always support the Cavern ecosystem properly, just ask + /// the user to use the Cavern Driver. If they never used Cavern Driver, Cavern falls back to 5.1, + /// which is the only safe option to mix to any channel layout with limited system knowledge. + /// If you're extra sure you won't break the user's setup, set this to true to use the class. + /// + public static bool IKnowWhatIAmDoing { get; set; } + + /// + /// Get the path of the folder that contains Cavern's configuration files. + /// + public static string GetPath() { + if (!IKnowWhatIAmDoing) { + throw new DevHasNoIdeaException(); + } + + string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Cavern"); + if (!Directory.Exists(path)) { + Directory.CreateDirectory(path); + } + return path; + } + + /// + /// Overwrite the global environment (including channel layout) used by all applications built on Cavern. + /// + public static void SaveCurrentLayoutAsDefault() => SaveCurrentLayoutAs("Save"); + + /// + /// Save an environment (including channel layout) preset option that can be recalled in the Cavern Driver. + /// + public static void SaveCurrentLayoutAsPreset(string presetName) => SaveCurrentLayoutAs(presetPrefix + presetName); + + /// + /// Delete a preset that was saved with . + /// + public static void DeletePreset(string presetName) => File.Delete(Path.Combine(GetPath(), $"{presetPrefix}{presetName}.dat")); + + /// + /// Save an environment (including channel layout) preset option that can be recalled in the Cavern Driver. + /// + static void SaveCurrentLayoutAs(string presetName) { + Channel[] channels = Listener.Channels; + string[] save = new string[channels.Length * 3 + 7]; + save[0] = channels.Length.ToString(); + int savePos = 1; + for (int i = 0; i < channels.Length; i++) { + save[savePos] = channels[i].X.ToString(CultureInfo.InvariantCulture); + save[savePos + 1] = channels[i].Y.ToString(CultureInfo.InvariantCulture); + save[savePos + 2] = channels[i].LFE.ToString(); + savePos += 3; + } + save[savePos] = ((int)Listener.EnvironmentType).ToString(); + save[savePos + 1] = Listener.EnvironmentSize.X.ToString(CultureInfo.InvariantCulture); + save[savePos + 2] = Listener.EnvironmentSize.Y.ToString(CultureInfo.InvariantCulture); + save[savePos + 3] = Listener.EnvironmentSize.Z.ToString(CultureInfo.InvariantCulture); + save[savePos + 4] = Listener.HeadphoneVirtualizer.ToString(); + save[savePos + 5] = string.Empty; // Was: environment compensation + File.WriteAllLines(Path.Combine(GetPath(), presetName + ".dat"), save); + } + + /// + /// The name of all environment preset files in Cavern's configuration folder start with this. + /// + const string presetPrefix = "CavernPreset_"; + } +} \ No newline at end of file diff --git a/Cavern/Internals/_Exceptions.cs b/Cavern/Internals/_Exceptions.cs new file mode 100644 index 00000000..d358c135 --- /dev/null +++ b/Cavern/Internals/_Exceptions.cs @@ -0,0 +1,15 @@ +using System; + +namespace Cavern.Internals { + /// + /// Tells if the developer used something without properly understanding what it does. + /// + public class DevHasNoIdeaException : Exception { + const string message = "The developer is very irresponsible."; + + /// + /// Tells if the developer used something without properly understanding what it does. + /// + public DevHasNoIdeaException() : base(message) { } + } +} \ No newline at end of file diff --git a/Cavern/Utilities/VectorExtensions.cs b/Cavern/Utilities/VectorExtensions.cs index 2bb15649..79e6e737 100644 --- a/Cavern/Utilities/VectorExtensions.cs +++ b/Cavern/Utilities/VectorExtensions.cs @@ -22,6 +22,12 @@ public static class VectorExtensions { /// internal const float Sqrt2p2 = .7071067811f; + /// + /// Check if all 3 components are at max apart for the two vectors. + /// + public static bool CloseTo(this Vector3 vector, Vector3 other, float epsilon) => + Math.Abs(vector.X - other.X) < epsilon && Math.Abs(vector.Y - other.Y) < epsilon && Math.Abs(vector.Z - other.Z) < epsilon; + /// /// Returns a vector that's the same direction as the source vector, but it's on the side of a 2x2x2 cube. /// diff --git a/CavernSamples/CavernPipeServer/CavernPipeRenderer.cs b/CavernSamples/CavernPipeServer/CavernPipeRenderer.cs index 8234cad7..b225f522 100644 --- a/CavernSamples/CavernPipeServer/CavernPipeRenderer.cs +++ b/CavernSamples/CavernPipeServer/CavernPipeRenderer.cs @@ -28,6 +28,11 @@ public class CavernPipeRenderer : IDisposable { /// public event OnMetersAvailable MetersAvailable; + /// + /// Exceptions coming from the rendering thread are passed down from this event. + /// + public event Action OnException; + /// /// Protocol message decoder. /// @@ -96,8 +101,9 @@ void RenderThread() { streamDumper.WriteBlock(reRender, 0, reRender.LongLength); } } - } catch { + } catch (Exception e) { Dispose(); + OnException?.Invoke(e); } } diff --git a/CavernSamples/CavernPipeServer/CavernPipeServer.csproj b/CavernSamples/CavernPipeServer/CavernPipeServer.csproj index ffdc7f04..0eb6d359 100644 --- a/CavernSamples/CavernPipeServer/CavernPipeServer.csproj +++ b/CavernSamples/CavernPipeServer/CavernPipeServer.csproj @@ -19,6 +19,7 @@ + <_DeploymentManifestIconFile Remove="..\Icon.ico" /> diff --git a/CavernSamples/CavernPipeServer/ChannelMeters.cs b/CavernSamples/CavernPipeServer/ChannelMeters.cs index 3cd8092f..034bf5bb 100644 --- a/CavernSamples/CavernPipeServer/ChannelMeters.cs +++ b/CavernSamples/CavernPipeServer/ChannelMeters.cs @@ -4,6 +4,7 @@ using Cavern; using Cavern.Channels; +using Cavern.WPF.Consts; namespace CavernPipeServer { /// @@ -38,7 +39,7 @@ public class ChannelMeters(Panel canvas, TextBlock labelProto, ProgressBar barPr /// so the are what will be rendered. /// public virtual void Enable() { - string[] channels = ChannelPrototype.GetNames(Listener.Channels); + ReferenceChannel[] channels = ChannelPrototype.GetReferences(Listener.Channels); displays = new (TextBlock, ProgressBar)[channels.Length]; movingPeaks = new float[channels.Length]; for (int i = 0; i < channels.Length; i++) { @@ -47,7 +48,7 @@ public virtual void Enable() { Margin = new Thickness(labelProto.Margin.Left, marginTop, labelProto.Margin.Right, 0), HorizontalAlignment = labelProto.HorizontalAlignment, VerticalAlignment = labelProto.VerticalAlignment, - Text = channels[i] + Text = channels[i].Translate() }; ProgressBar progressBar = new ProgressBar { Margin = new Thickness(barProto.Margin.Left, marginTop, barProto.Margin.Right, 0), diff --git a/CavernSamples/CavernPipeServer/Consts/Language.cs b/CavernSamples/CavernPipeServer/Consts/Language.cs index a76fe01d..df6589e3 100644 --- a/CavernSamples/CavernPipeServer/Consts/Language.cs +++ b/CavernSamples/CavernPipeServer/Consts/Language.cs @@ -12,12 +12,6 @@ static class Language { /// public static ResourceDictionary GetMainWindowStrings() => mainWindowCache ??= GetFor("MainWindowStrings"); - /// - /// Show the message in an error dialog with the localized "Error" title. - /// - public static void ShowError(string message) => - MessageBox.Show(message, (string)GetMainWindowStrings()["Error"], MessageBoxButton.OK, MessageBoxImage.Error); - /// /// Get the translation of a resource file in the user's language, or in English if a translation couldn't be found. /// diff --git a/CavernSamples/CavernPipeServer/MainWindow.ContextMenu.cs b/CavernSamples/CavernPipeServer/MainWindow.ContextMenu.cs new file mode 100644 index 00000000..ca52a31b --- /dev/null +++ b/CavernSamples/CavernPipeServer/MainWindow.ContextMenu.cs @@ -0,0 +1,117 @@ +using System; +using System.Windows; +using System.Windows.Forms; + +using Cavern; +using Cavern.Channels; +using Cavern.Internals; +using Cavern.WPF.Consts; + +using Application = System.Windows.Application; +using GLanguage = Cavern.WPF.Consts.Language; +using MessageBox = System.Windows.MessageBox; + +namespace CavernPipeServer { + // Functions related to the tray icon right click menu + partial class MainWindow { + /// + /// Overwrite the layout Cavern is using, globally. + /// + /// Read when using this code anywhere! + void SetLayout(string name, ReferenceChannel[] channels) { + if (GLanguage.Warning(string.Format((string)language["WSLay"], name), MessageBoxButton.YesNo) == MessageBoxResult.No) { + return; + } + + Listener.ReplaceChannels(ChannelPrototype.ToLayoutAlternative(channels)); + CavernConfiguration.IKnowWhatIAmDoing = true; + CavernConfiguration.SaveCurrentLayoutAsDefault(); + GLanguage.Warning((string)language["WSLaC"]); + } + + /// + /// Create a right click menu to be assigned to the tray icon. + /// + ContextMenuStrip CreateContextMenu() { + ContextMenuStrip result = new ContextMenuStrip(); + result.Items.Add((string)language["MOpen"], null, Open); + result.Items.Add(new ToolStripSeparator()); + + ToolStripMenuItem settingsMenu = new ToolStripMenuItem((string)language["MSett"]); + ToolStripMenuItem layouts = new ToolStripMenuItem((string)language["MSLay"]); + for (int i = 0; i < customLayouts.Length; i++) { + (string name, ReferenceChannel[] channels) = customLayouts[i]; + layouts.DropDownItems.Add(customLayouts[i].name, null, (_, __) => SetLayout(name, channels)); + } + settingsMenu.DropDownItems.Add(layouts); + settingsMenu.DropDownItems.Add((string)language["MWiri"], null, ShowWiring); + result.Items.Add(settingsMenu); + result.Items.Add(new ToolStripSeparator()); + + ToolStripMenuItem diagMenu = new ToolStripMenuItem((string)language["MDiag"]); + diagMenu.DropDownItems.Add((string)language["MRest"], null, Restart); + diagMenu.DropDownItems.Add((string)language["MLErr"], null, LastError); + result.Items.Add(diagMenu); + result.Items.Add(new ToolStripSeparator()); + + result.Items.Add((string)language["MExit"], null, Exit); + return result; + } + + /// + /// Show the configuration dialog. + /// + void Open(object _, EventArgs e) { + Show(); + WindowState = WindowState.Normal; + } + + /// + /// Show how the user should wire spatial speakers to standard outputs. + /// + void ShowWiring(object _, EventArgs e) { + new Listener(); // Load current user layout + ChannelPrototype.GetReferences(Listener.Channels).DisplayWiring(); + } + + /// + /// Try to restart the pipe. + /// + void Restart(object _, EventArgs __) { + try { + handler.Start(); + } catch (InvalidOperationException e) { + GLanguage.Error(e.Message); + } + } + + /// + /// Display the cause of the last disconnection. + /// + void LastError(object _, EventArgs __) => + MessageBox.Show(lastError ?? (string)language["NLErr"], (string)language["MLErr"], MessageBoxButton.OK, MessageBoxImage.Information); + + /// + /// Close the CavernPipe server. + /// + void Exit(object _, EventArgs e) { + exiting = true; + handler.Dispose(); + icon.Dispose(); + Application.Current.Shutdown(); + } + + /// + /// Channel layouts that can be used for overriding the Cavern Driver setting. + /// + static readonly (string name, ReferenceChannel[] channels)[] customLayouts = [ + ("3.1.2", ChannelPrototype.ref312), + ("4.0.4", ChannelPrototype.ref404), + ("4.1.1", ChannelPrototype.ref411), + ("4.1.3", ChannelPrototype.ref413), + ("5.1", ChannelPrototype.ref510), + ("5.1.2", ChannelPrototype.ref512), + ("7.1", ChannelPrototype.ref710), + ]; + } +} \ No newline at end of file diff --git a/CavernSamples/CavernPipeServer/MainWindow.xaml.cs b/CavernSamples/CavernPipeServer/MainWindow.xaml.cs index ec6c8c6d..9e549463 100644 --- a/CavernSamples/CavernPipeServer/MainWindow.xaml.cs +++ b/CavernSamples/CavernPipeServer/MainWindow.xaml.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using System.Drawing; +using System.IO; using System.Windows; using System.Windows.Forms; using System.Windows.Media; @@ -13,11 +14,6 @@ namespace CavernPipeServer { /// Main status/configuration window and background operation handler. /// public partial class MainWindow : Window { - /// - /// Right-click menu of the tray icon. - /// - readonly ContextMenuStrip contextMenu; - /// /// CavernPipe tray icon handle. /// @@ -43,31 +39,31 @@ public partial class MainWindow : Window { /// bool exiting; + /// + /// The issue with the last tried playback. + /// + string lastError; + /// /// Main status/configuration window and background operation handler. /// public MainWindow() { InitializeComponent(); - contextMenu = new ContextMenuStrip(); - contextMenu.Items.Add((string)language["MOpen"], null, Open); - contextMenu.Items.Add(new ToolStripSeparator()); - contextMenu.Items.Add((string)language["MRest"], null, Restart); - contextMenu.Items.Add(new ToolStripSeparator()); - contextMenu.Items.Add((string)language["MExit"], null, Exit); - icon = new NotifyIcon { Icon = new Icon(Application.GetResourceStream(new Uri("Resources/Icon.ico", UriKind.Relative)).Stream), Visible = true, - ContextMenuStrip = contextMenu, + ContextMenuStrip = CreateContextMenu(), }; icon.DoubleClick += Open; meters = new ThreadSafeChannelMeters(canvas, chProto, vmProto); handler = new PipeHandler(); - handler.OnRenderingStarted += meters.Enable; + handler.OnRenderingStarted += OnRenderingStarted; handler.StatusChanged += OnServerStatusChange; handler.MetersAvailable += meters.Update; + handler.OnException += StoreErrorMessage; + OnServerStatusChange(); } @@ -80,32 +76,11 @@ protected override void OnClosing(CancelEventArgs e) { } /// - /// Show the configuration dialog. + /// Prepare the UI for handling data coming from networking/rendering threads. /// - void Open(object _, EventArgs e) { - Show(); - WindowState = WindowState.Normal; - } - - /// - /// Try to restart the pipe. - /// - void Restart(object _, EventArgs __) { - try { - handler.Start(); - } catch (InvalidOperationException e) { - Consts.Language.ShowError(e.Message); - } - } - - /// - /// Close the CavernPipe server. - /// - void Exit(object _, EventArgs e) { - exiting = true; - handler.Dispose(); - icon.Dispose(); - Application.Current.Shutdown(); + void OnRenderingStarted() { + meters.Enable(); + lastError = null; } /// @@ -135,5 +110,19 @@ void OnServerStatusChange() { meters.Disable(); } } + + /// + /// Handle exceptions that result in a stopped playback. + /// + void StoreErrorMessage(Exception e) { + if (e.GetType() == typeof(IOException) && e.Source == "System.IO.Pipes") { + lastError = (string)language["NDisc"]; + return; + } else if (e.StackTrace.Contains("QueueStream")) { + return; // Rendering threads are stopped by setting them to null (yeah...) + } else { + lastError = e.ToString(); + } + } } } \ No newline at end of file diff --git a/CavernSamples/CavernPipeServer/PipeHandler.cs b/CavernSamples/CavernPipeServer/PipeHandler.cs index 4a42702d..3dce9a9f 100644 --- a/CavernSamples/CavernPipeServer/PipeHandler.cs +++ b/CavernSamples/CavernPipeServer/PipeHandler.cs @@ -27,6 +27,11 @@ public class PipeHandler : IDisposable { /// public event CavernPipeRenderer.OnMetersAvailable MetersAvailable; + /// + /// Exceptions coming from the named pipe or the rendering thread are passed down from this event. + /// + public event Action OnException; + /// /// The network connection is kept alive. /// @@ -123,6 +128,8 @@ async void ThreadProc() { using CavernPipeRenderer renderer = new CavernPipeRenderer(server); renderer.OnRenderingStarted += OnRenderingStarted; renderer.MetersAvailable += MetersAvailable; + renderer.OnException += OnException; + byte[] inBuffer = [], outBuffer = []; while (Running) { @@ -145,16 +152,17 @@ async void ThreadProc() { server.Write(outBuffer, 0, length); } } catch (TimeoutException) { - Language.ShowError((string)Language.GetMainWindowStrings()["EPipe"]); + Cavern.WPF.Consts.Language.Error((string)Language.GetMainWindowStrings()["EPipe"]); return; - } catch { // Content type change or server/stream closed - if (server.IsConnected) { - server.Flush(); - } + } catch (Exception e) { // Content type change or server/stream closed + OnException?.Invoke(e); } IsConnected = false; lock (locker) { + if (server.IsConnected) { + server.Flush(); + } server.Dispose(); server = null; } diff --git a/CavernSamples/CavernPipeServer/Resources/MainWindowStrings.hu-HU.xaml b/CavernSamples/CavernPipeServer/Resources/MainWindowStrings.hu-HU.xaml index 18eda784..4c832701 100644 --- a/CavernSamples/CavernPipeServer/Resources/MainWindowStrings.hu-HU.xaml +++ b/CavernSamples/CavernPipeServer/Resources/MainWindowStrings.hu-HU.xaml @@ -3,7 +3,12 @@ xmlns:system="clr-namespace:System;assembly=mscorlib"> Megnyitás + Beállítások + Hangszórókiosztás felülírása + Hangszóró bekötési útmutató + Diagnosztika Szerver újraindítása + Utolsó kapcsolatbontás oka Kilépés @@ -12,9 +17,13 @@ A CavernPipe szerver nem fut. - Hiba + A médialejátszó lecsatlakozott. + Nem volt lecsatlakozás az utolsó kapcsolódás óta. A CavernPipe szerver már fut, és készen áll a lejátszásra. A CavernPipe szerver újraindítása már folyamatban van. A CavernPipe szerver nem tudott elindulni 3 másodpercen keresztül. Menj biztosra, hogy nincs CavernPipe nevű named pipe, és használd az értesítési sáv CavernPipe ikonjának jobbklikkes menüjét, hogy újraindítsd a pipe-ot. + Biztos meg akarod változtatni a Cavern csatornakiosztását a rendszeren erre: {0}? + Sikeres változtatás. Ne feledd, a Cavern élményhez úgy kell bekötni a hangszórókat, ahogy a + Beállítások -> Hangszóró bekötési útmutatóban találod! \ No newline at end of file diff --git a/CavernSamples/CavernPipeServer/Resources/MainWindowStrings.xaml b/CavernSamples/CavernPipeServer/Resources/MainWindowStrings.xaml index 959540d2..7ff909b4 100644 --- a/CavernSamples/CavernPipeServer/Resources/MainWindowStrings.xaml +++ b/CavernSamples/CavernPipeServer/Resources/MainWindowStrings.xaml @@ -3,7 +3,12 @@ xmlns:system="clr-namespace:System;assembly=mscorlib"> Open + Settings + Override speaker layout + Speaker wiring guide + Diagnostics Restart server + Cause of last disconnection Exit @@ -12,9 +17,13 @@ The CavernPipe server isn't running. - Error + The media player has disconnected. + There was no disconnection since the last connection. The CavernPipe server is already running and ready for playback. The CavernPipe server is already being restarted. CavernPipe server failed to start for 3 seconds. Make sure no named pipe is called CavernPipe, and use the right click menu of the CavernPipe icon on your system tray to restart the pipe. + Do you really want to change the Cavern channel layout on the system to {0}? + Change successful. Don't forget, for the Cavern experience, you have to wire the speakers as described by + Settings -> Speaker wiring guide! \ No newline at end of file diff --git a/CavernSamples/CavernizeGUI/CavernizeGUI.csproj b/CavernSamples/CavernizeGUI/CavernizeGUI.csproj index f0558a09..c9f0494b 100644 --- a/CavernSamples/CavernizeGUI/CavernizeGUI.csproj +++ b/CavernSamples/CavernizeGUI/CavernizeGUI.csproj @@ -32,6 +32,7 @@ + diff --git a/CavernSamples/CavernizeGUI/Elements/RenderTarget.cs b/CavernSamples/CavernizeGUI/Elements/RenderTarget.cs index e645c9bd..e171169b 100644 --- a/CavernSamples/CavernizeGUI/Elements/RenderTarget.cs +++ b/CavernSamples/CavernizeGUI/Elements/RenderTarget.cs @@ -99,18 +99,10 @@ public virtual void Apply() { /// /// Top rears are used instead of sides for smooth height transitions and WAVEFORMATEXTENSIBLE support. public static readonly RenderTarget[] Targets = [ - new RenderTarget("3.1.2", [ - ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE, - ReferenceChannel.TopFrontLeft, ReferenceChannel.TopFrontRight - ]), - new RenderTarget("4.0.4", [ - ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.RearLeft, ReferenceChannel.RearRight, - ReferenceChannel.TopFrontLeft, ReferenceChannel.TopFrontRight, ReferenceChannel.TopRearLeft, ReferenceChannel.TopRearRight - ]), - new RenderTarget("4.1.1", [ - ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE, - ReferenceChannel.RearCenter, ReferenceChannel.TopFrontCenter - ]), + new RenderTarget("3.1.2", ChannelPrototype.ref312), + new RenderTarget("4.0.4", ChannelPrototype.ref404), + new RenderTarget("4.1.1", ChannelPrototype.ref411), + new RenderTarget("4.1.3", ChannelPrototype.ref413), new RenderTarget("5.1 side", ChannelPrototype.ref510), new RenderTarget("5.1 rear", [ ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE, @@ -122,11 +114,7 @@ public virtual void Apply() { ]), new DownmixedRenderTarget("5.1.2 front", ChannelPrototype.ref514, (8, 4), (9, 5)), new RenderTarget("5.1.4", ChannelPrototype.ref514), - new DownmixedRenderTarget("5.1.4 matrix", [ - ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE, - ReferenceChannel.SideLeft, ReferenceChannel.SideRight, ReferenceChannel.TopFrontLeft, ReferenceChannel.TopFrontRight, - ReferenceChannel.TopRearLeft, ReferenceChannel.TopRearRight - ], (8, 0), (~8, 4), (9, 1), (~9, 5)), + new DownmixedRenderTarget("5.1.4 matrix", ChannelPrototype.ref514, (8, 0), (~8, 4), (9, 1), (~9, 5)), new RenderTarget("5.1.6 with top sides", ChannelPrototype.ref516), new RenderTarget("5.1.6 for WAVE", ChannelPrototype.wav516), new RenderTarget("7.1", ChannelPrototype.ref710), @@ -149,11 +137,7 @@ public virtual void Apply() { new RenderTarget("7.1.4", ChannelPrototype.ref714), new RenderTarget("7.1.6 with top sides", ChannelPrototype.ref716), new RenderTarget("7.1.6 for WAVE", ChannelPrototype.wav716), - new RenderTarget("9.1", [ - ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE, - ReferenceChannel.RearLeft, ReferenceChannel.RearRight, ReferenceChannel.SideLeft, ReferenceChannel.SideRight, - ReferenceChannel.WideLeft, ReferenceChannel.WideRight - ]), + new RenderTarget("9.1", ChannelPrototype.ref910), new RenderTarget("9.1.2 side", [ ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE, ReferenceChannel.RearLeft, ReferenceChannel.RearRight, ReferenceChannel.SideLeft, ReferenceChannel.SideRight, diff --git a/CavernSamples/CavernizeGUI/MainWindow.Popups.cs b/CavernSamples/CavernizeGUI/MainWindow.Popups.cs index 9c9f0a3a..ea44a1ac 100644 --- a/CavernSamples/CavernizeGUI/MainWindow.Popups.cs +++ b/CavernSamples/CavernizeGUI/MainWindow.Popups.cs @@ -1,8 +1,6 @@ using Microsoft.Win32; using System; using System.IO; -using System.Linq; -using System.Text; using System.Windows; using Cavern; @@ -10,6 +8,7 @@ using Cavern.Format; using Cavern.Format.Common; using Cavern.Virtualizer; +using Cavern.WPF.Consts; using CavernizeGUI.Elements; using CavernizeGUI.Windows; @@ -159,27 +158,11 @@ void ShowMetadata(object _, RoutedEventArgs e) { void DisplayWiring(object _, RoutedEventArgs e) { RenderTarget target = (RenderTarget)renderTarget.SelectedItem; ReferenceChannel[] channels = target.GetNameMappedChannels(); - ChannelPrototype[] prototypes = ChannelPrototype.Get(channels); - if (channels.Length > 8) { - MessageBox.Show(string.Format((string)language["Over8"], string.Join(string.Empty, prototypes.Select(x => "\n- " + x.Name)), - (string)language["WrGui"])); - return; - } - - StringBuilder output = new StringBuilder(); - for (int i = 0; i < prototypes.Length; i++) { - output.AppendLine(string.Format((string)language["ChCon"], prototypes[i].Name, - ChannelPrototype.Get(i, prototypes.Length).Name)); - } if (target is DownmixedRenderTarget downmix) { - (ReferenceChannel source, ReferenceChannel posPhase, ReferenceChannel negPhase)[] matrixed = downmix.MatrixWirings; - for (int i = 0; i < matrixed.Length; i++) { - output.AppendLine(string.Format((string)language["ChCMx"], - ChannelPrototype.GetName(matrixed[i].source), ChannelPrototype.GetName(matrixed[i].posPhase), - ChannelPrototype.GetName(matrixed[i].negPhase))); - } + channels.DisplayWiring(downmix.MatrixWirings); + } else { + channels.DisplayWiring(); } - MessageBox.Show(output.ToString(), (string)language["WrGui"]); } } } \ No newline at end of file diff --git a/CavernSamples/CavernizeGUI/MainWindow.xaml.cs b/CavernSamples/CavernizeGUI/MainWindow.xaml.cs index 81555078..577b7fc6 100644 --- a/CavernSamples/CavernizeGUI/MainWindow.xaml.cs +++ b/CavernSamples/CavernizeGUI/MainWindow.xaml.cs @@ -140,7 +140,7 @@ public MainWindow() { }; renderTarget.ItemsSource = RenderTarget.Targets; - renderTarget.SelectedIndex = Math.Clamp(Settings.Default.renderTarget + 3, 0, RenderTarget.Targets.Length - 1); + renderTarget.SelectedIndex = Math.Clamp(Settings.Default.renderTarget + 4, 0, RenderTarget.Targets.Length - 1); renderSettings.IsEnabled = true; // Don't grey out initially queuedJobs.ItemsSource = jobs; taskEngine = new(progress, TaskbarItemInfo, status); @@ -245,7 +245,7 @@ public void RenderContent(string path) { protected override void OnClosed(EventArgs e) { taskEngine?.Dispose(); Settings.Default.ffmpegLocation = ffmpeg.Location; - Settings.Default.renderTarget = renderTarget.SelectedIndex - 3; + Settings.Default.renderTarget = renderTarget.SelectedIndex - 4; Settings.Default.outputCodec = audio.SelectedIndex - 2; Settings.Default.speakerVirtualizer = speakerVirtualizer.IsChecked; Settings.Default.force24Bit = force24Bit.IsChecked; diff --git a/CavernSamples/CavernizeGUI/Resources/MainWindowStrings.hu-HU.xaml b/CavernSamples/CavernizeGUI/Resources/MainWindowStrings.hu-HU.xaml index f74b307f..2aa78378 100644 --- a/CavernSamples/CavernizeGUI/Resources/MainWindowStrings.hu-HU.xaml +++ b/CavernSamples/CavernizeGUI/Resources/MainWindowStrings.hu-HU.xaml @@ -77,11 +77,6 @@ Limitless Audio Format|*.laf A fájlnév kiterjesztése nem támogatott. Ez a kodek nem támogatott exportáláshoz. - Bekötési útmutató - Kösd a {0} csatornát a rendszered {1} kimenetére. - Kösd a {0} csatorna + csatlakozóját a {1} hangszóró +-ára, valamint a - kimenetét a {2} +-ára. - Nincs szabványos bekötés 8 csatorna felett. Masszívan többcsatornás interfészeknél a csatornák - rendje pontosan az, ami a fájlban is:{0} Renderelés indítása... Befejezve! Hiba diff --git a/CavernSamples/CavernizeGUI/Resources/MainWindowStrings.xaml b/CavernSamples/CavernizeGUI/Resources/MainWindowStrings.xaml index 9fad3b4b..3c140fa1 100644 --- a/CavernSamples/CavernizeGUI/Resources/MainWindowStrings.xaml +++ b/CavernSamples/CavernizeGUI/Resources/MainWindowStrings.xaml @@ -77,12 +77,6 @@ Limitless Audio Format|*.laf The file name had an unsupported extension. This codec is not supported for export. - Wiring guide - Connect the {0} channel to your system's {1} output. - Connect the {0} channel's + terminal to the {1} speaker's + - and its - terminal to the {2} speaker's +. - There is no standard wiring over 8 channels. For massively multichannel interfaces, - the channel order is exactly as it's in the file:{0} Starting render... Finished! Error