diff --git a/Audio Router WPF/App.xaml.cs b/Audio Router WPF/App.xaml.cs index cec457a..370df55 100644 --- a/Audio Router WPF/App.xaml.cs +++ b/Audio Router WPF/App.xaml.cs @@ -13,5 +13,16 @@ namespace Audio_Router_WPF /// public partial class App : Application { + public static App appRef; + // The list of audio graphs + public List audioGraphConnections; + public static Windows.System.Display.DisplayRequest displayRequest = new Windows.System.Display.DisplayRequest(); + + + public App() + { + appRef = this; + audioGraphConnections = new List(); + } } } diff --git a/Audio Router WPF/Audio Router WPF.csproj b/Audio Router WPF/Audio Router WPF.csproj index ea206df..a60c611 100644 --- a/Audio Router WPF/Audio Router WPF.csproj +++ b/Audio Router WPF/Audio Router WPF.csproj @@ -2,10 +2,16 @@ WinExe - net5.0-windows + net5.0-windows10.0.19041.0 Audio_Router_WPF enable true + 10.0.17763.0 + 1.2.3.2 + 1.2.3.2 + en-NZ + https://git.software.kauripeak.co.nz/BrychanD/Audio-Router + (C) 2021 Brychan Dempsey diff --git a/Audio Router WPF/AudioGraphConnection.cs b/Audio Router WPF/AudioGraphConnection.cs new file mode 100644 index 0000000..7d39aaa --- /dev/null +++ b/Audio Router WPF/AudioGraphConnection.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Threading; +using System.Threading.Tasks; +using Windows.Media.Audio; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Media; + +namespace Audio_Router_WPF +{ + public class AudioGraphConnection + { + string SourceNames { get; } + string TargetNames { get; } + int SamplesPerMS { get; } + + public AudioGraph Graph { get; private set; } + + AudioDeviceOutputNode TargetDevice { get; set; } + AudioDeviceInputNode SourceDevice { get; set; } + + Dispatcher uiDispatcher { get; set; } + + TextBlock latencyLabel { get; set; } + + int quantaCount = 0; + + public AudioGraphConnection(AudioGraph graph, AudioDeviceOutputNode targetDevice, AudioDeviceInputNode sourceDevice, Dispatcher uiDispatcher) + { + Graph = graph; + TargetDevice = targetDevice; + SourceDevice = sourceDevice; + this.uiDispatcher = uiDispatcher; + SourceNames = sourceDevice.Device.Name; + TargetNames = targetDevice.Device.Name; + + SamplesPerMS = (int)(Graph.SamplesPerQuantum / (double)Graph.EncodingProperties.SampleRate * 1000); + } + + /// + /// Creates a grid populated with UI controls containing information about this item, i.e. for rendering + /// + /// + public Grid GetAsDisplayItem(out Button deletionButton) + { + // Create the display elements + Grid result = new Grid(); + Button destroyButton = new Button() + { + Content = "Stop", + HorizontalAlignment = HorizontalAlignment.Right + }; + + TextBlock label = new TextBlock() + { + HorizontalAlignment = HorizontalAlignment.Left + }; + latencyLabel = new TextBlock() + { + Padding = new Thickness(0, 0, 5, 0) + }; + Slider volumeSlider = new Slider + { + Maximum = 5.0, + Minimum = 0.0, + Value = 1.0, + SmallChange = 0.1, + MinWidth = 250, + Margin = new Thickness(5, 0, 5, 0) + }; + + StackPanel rightSide = new StackPanel + { + Orientation = Orientation.Horizontal + }; + + // Set the base grid to span the full screen width available + result.HorizontalAlignment = HorizontalAlignment.Stretch; + result.Margin = new Thickness(1, 1, 1, 5); + // Add Column definitions + result.ColumnDefinitions.Add(new ColumnDefinition()); // '*' size; fits the maximum space it can, evenly + result.ColumnDefinitions.Add(new ColumnDefinition() // 'auto'; automatically resizes to the total size of the children elements + { + Width = GridLength.Auto + }); + + + Run sourceDev = new Run() + { + Text = SourceNames + }; + Run arrow = new Run + { + Text = "  ", + FontFamily = new FontFamily("Segoe MDL2 Assets") + }; + Run targetDev = new Run() + { + Text = TargetNames + }; + + // Add the text to the label + label.Inlines.Add(sourceDev); + label.Inlines.Add(arrow); + label.Inlines.Add(targetDev); + + // Register the event + destroyButton.Click += DestroyButton_Click; + result.Children.Add(label); + + volumeSlider.ValueChanged += VolumeSlider_ValueChanged; + + Graph.QuantumProcessed += Graph_QuantumProcessed; + rightSide.Children.Add(latencyLabel); + rightSide.Children.Add(volumeSlider); + rightSide.Children.Add(destroyButton); + rightSide.HorizontalAlignment = HorizontalAlignment.Right; + result.Children.Add(rightSide); + Grid.SetColumn(rightSide, 1); + deletionButton = destroyButton; + return result; + } + + private void Graph_QuantumProcessed(AudioGraph sender, object args) + { + // updates aren't urgent, only process after some time (this is 1s, with the Windows default of 10 ms / quanta + if (quantaCount++ == 100) + { + string result = (Graph.LatencyInSamples / (double)Graph.SamplesPerQuantum * SamplesPerMS).ToString("0.#") + " ms"; + if (latencyLabel is null) return; + uiDispatcher.Invoke(() => + { + try + { + latencyLabel.Text = result; + } + catch + { + + } + }, DispatcherPriority.Background); + quantaCount = 0; + } + } + + private void VolumeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) + { + TargetDevice.OutgoingGain = e.NewValue; + SourceDevice.OutgoingGain = e.NewValue; + } + + private void DestroyButton_Click(object sender, RoutedEventArgs e) + { + latencyLabel = null; + Graph.QuantumProcessed -= Graph_QuantumProcessed; + Graph.Stop(); + TargetDevice.Dispose(); + SourceDevice.Dispose(); + Graph.Dispose(); + Graph = null; + TargetDevice = null; + SourceDevice = null; + uiDispatcher = null; + _ = App.appRef.audioGraphConnections.Remove(this); + GC.Collect(); + } + + public void OnHide() + { + latencyLabel = null; + } + } +} diff --git a/Audio Router WPF/MainWindow.xaml b/Audio Router WPF/MainWindow.xaml index 2747a2e..5bc2e3c 100644 --- a/Audio Router WPF/MainWindow.xaml +++ b/Audio Router WPF/MainWindow.xaml @@ -6,7 +6,27 @@ xmlns:local="clr-namespace:Audio_Router_WPF" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> - + + + + + + + + + + + + + + + +