diff --git a/Audio Router UWP/.editorconfig b/Audio Router UWP/.editorconfig
new file mode 100644
index 0000000..c423c9d
--- /dev/null
+++ b/Audio Router UWP/.editorconfig
@@ -0,0 +1,4 @@
+[*.cs]
+
+# IDE0011: Add braces
+csharp_prefer_braces = false
diff --git a/Audio Router UWP/Audio Router.sln b/Audio Router UWP/Audio Router.sln
new file mode 100644
index 0000000..ce1561b
--- /dev/null
+++ b/Audio Router UWP/Audio Router.sln
@@ -0,0 +1,56 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31710.8
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Audio Router", "Audio Router\Audio Router.csproj", "{92D76120-5578-484A-BB93-24D105B48043}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AAA9AA8A-8F68-425E-B705-B650E4EBA213}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|ARM = Debug|ARM
+ Debug|ARM64 = Debug|ARM64
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|ARM = Release|ARM
+ Release|ARM64 = Release|ARM64
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|ARM.ActiveCfg = Debug|ARM
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|ARM.Build.0 = Debug|ARM
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|ARM.Deploy.0 = Debug|ARM
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|ARM64.Build.0 = Debug|ARM64
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|ARM64.Deploy.0 = Debug|ARM64
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|x64.ActiveCfg = Debug|x64
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|x64.Build.0 = Debug|x64
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|x64.Deploy.0 = Debug|x64
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|x86.ActiveCfg = Debug|x86
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|x86.Build.0 = Debug|x86
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|x86.Deploy.0 = Debug|x86
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|ARM.ActiveCfg = Release|ARM
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|ARM.Build.0 = Release|ARM
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|ARM.Deploy.0 = Release|ARM
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|ARM64.ActiveCfg = Release|ARM64
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|ARM64.Build.0 = Release|ARM64
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|ARM64.Deploy.0 = Release|ARM64
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|x64.ActiveCfg = Release|x64
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|x64.Build.0 = Release|x64
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|x64.Deploy.0 = Release|x64
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|x86.ActiveCfg = Release|x86
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|x86.Build.0 = Release|x86
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|x86.Deploy.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {8831C984-1B2F-462D-916A-68826955596B}
+ EndGlobalSection
+EndGlobal
diff --git a/Audio Router UWP/Audio Router/App.xaml b/Audio Router UWP/Audio Router/App.xaml
new file mode 100644
index 0000000..fca0b72
--- /dev/null
+++ b/Audio Router UWP/Audio Router/App.xaml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/Audio Router UWP/Audio Router/App.xaml.cs b/Audio Router UWP/Audio Router/App.xaml.cs
new file mode 100644
index 0000000..d98089c
--- /dev/null
+++ b/Audio Router UWP/Audio Router/App.xaml.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.Media;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+
+namespace Audio_Router
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ sealed partial class App : Application
+ {
+ public static App appRef;
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ appRef = this;
+ audioGraphConnections = new List();
+
+ this.Suspending += OnSuspending;
+
+ this.EnteredBackground += App_EnteredBackground;
+ this.LeavingBackground += App_LeavingBackground;
+
+ Windows.System.MemoryManager.AppMemoryUsageLimitChanging += MemoryManager_AppMemoryUsageLimitChanging; ;
+ }
+ private void MemoryManager_AppMemoryUsageLimitChanging(object sender, Windows.System.AppMemoryUsageLimitChangingEventArgs e)
+ {
+ // If app memory usage is over the limit, reduce usage within 2 seconds
+ // so that the system does not suspend the app
+ if (Windows.System.MemoryManager.AppMemoryUsage >= e.NewLimit)
+ {
+ Debug.WriteLine(e.NewLimit + " " + Windows.System.MemoryManager.AppMemoryUsage);
+ }
+ }
+
+
+ // The list of audio graphs
+ public List audioGraphConnections;
+
+ private void App_LeavingBackground(object sender, LeavingBackgroundEventArgs e)
+ {
+ isInBackground = false;
+ }
+
+ private void App_EnteredBackground(object sender, EnteredBackgroundEventArgs e)
+ {
+ isInBackground = true;
+ }
+
+ public static Windows.System.Display.DisplayRequest displayRequest = new Windows.System.Display.DisplayRequest();
+ bool isInBackground = false;
+
+ ///
+ /// Invoked when the application is launched normally by the end user. Other entry points
+ /// will be used such as when the application is launched to open a specific file.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(LaunchActivatedEventArgs e)
+ {
+ Frame rootFrame = Window.Current.Content as Frame;
+
+ // Do not repeat app initialization when the Window already has content,
+ // just ensure that the window is active
+ if (rootFrame == null)
+ {
+ // Create a Frame to act as the navigation context and navigate to the first page
+ rootFrame = new Frame();
+
+ rootFrame.NavigationFailed += OnNavigationFailed;
+
+ if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
+ {
+ //TODO: Load state from previously suspended application
+ }
+
+ // Place the frame in the current Window
+ Window.Current.Content = rootFrame;
+ }
+
+ if (e.PrelaunchActivated == false)
+ {
+ if (rootFrame.Content == null)
+ {
+ // When the navigation stack isn't restored navigate to the first page,
+ // configuring the new page by passing required information as a navigation
+ // parameter
+ rootFrame.Navigate(typeof(MainPage), e.Arguments);
+ }
+ // Ensure the current window is active
+ Window.Current.Activate();
+ }
+ }
+
+ ///
+ /// Invoked when Navigation to a certain page fails
+ ///
+ /// The Frame which failed navigation
+ /// Details about the navigation failure
+ void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
+ {
+ throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
+ }
+
+ ///
+ /// Invoked when application execution is being suspended. Application state is saved
+ /// without knowing whether the application will be terminated or resumed with the contents
+ /// of memory still intact.
+ ///
+ /// The source of the suspend request.
+ /// Details about the suspend request.
+ private void OnSuspending(object sender, SuspendingEventArgs e)
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+ //TODO: Save application state and stop any background activity
+ deferral.Complete();
+ }
+ }
+}
diff --git a/Audio Router UWP/Audio Router/Assets/LockScreenLogo.scale-200.png b/Audio Router UWP/Audio Router/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..735f57a
Binary files /dev/null and b/Audio Router UWP/Audio Router/Assets/LockScreenLogo.scale-200.png differ
diff --git a/Audio Router UWP/Audio Router/Assets/SplashScreen.scale-200.png b/Audio Router UWP/Audio Router/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..023e7f1
Binary files /dev/null and b/Audio Router UWP/Audio Router/Assets/SplashScreen.scale-200.png differ
diff --git a/Audio Router UWP/Audio Router/Assets/Square150x150Logo.scale-200.png b/Audio Router UWP/Audio Router/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..af49fec
Binary files /dev/null and b/Audio Router UWP/Audio Router/Assets/Square150x150Logo.scale-200.png differ
diff --git a/Audio Router UWP/Audio Router/Assets/Square44x44Logo.scale-200.png b/Audio Router UWP/Audio Router/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..ce342a2
Binary files /dev/null and b/Audio Router UWP/Audio Router/Assets/Square44x44Logo.scale-200.png differ
diff --git a/Audio Router UWP/Audio Router/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/Audio Router UWP/Audio Router/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..f6c02ce
Binary files /dev/null and b/Audio Router UWP/Audio Router/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/Audio Router UWP/Audio Router/Assets/StoreLogo.png b/Audio Router UWP/Audio Router/Assets/StoreLogo.png
new file mode 100644
index 0000000..7385b56
Binary files /dev/null and b/Audio Router UWP/Audio Router/Assets/StoreLogo.png differ
diff --git a/Audio Router UWP/Audio Router/Assets/Wide310x150Logo.scale-200.png b/Audio Router UWP/Audio Router/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..288995b
Binary files /dev/null and b/Audio Router UWP/Audio Router/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/Audio Router UWP/Audio Router/Audio Router UWP.csproj b/Audio Router UWP/Audio Router/Audio Router UWP.csproj
new file mode 100644
index 0000000..9c42a11
--- /dev/null
+++ b/Audio Router UWP/Audio Router/Audio Router UWP.csproj
@@ -0,0 +1,184 @@
+
+
+
+
+ Debug
+ x86
+ {92D76120-5578-484A-BB93-24D105B48043}
+ AppContainerExe
+ Properties
+ Audio_Router
+ Audio Router
+ en-US
+ UAP
+ 10.0.19041.0
+ 10.0.17763.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ true
+ True
+ False
+ CED012D19342E23CEB15B1111E6E590A9225BF36
+ SHA256
+ True
+ True
+ Always
+ x86|x64|arm
+ 0
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+ true
+
+
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+ true
+
+
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ bin\ARM64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+ true
+ true
+
+
+ PackageReference
+
+
+
+ App.xaml
+
+
+
+
+ MainPage.xaml
+
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ 6.2.12
+
+
+
+
+ .editorconfig
+
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/Audio Router UWP/Audio Router/AudioGraphConnection.cs b/Audio Router UWP/Audio Router/AudioGraphConnection.cs
new file mode 100644
index 0000000..3c08f36
--- /dev/null
+++ b/Audio Router UWP/Audio Router/AudioGraphConnection.cs
@@ -0,0 +1,183 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.Media.Audio;
+using Windows.UI.Core;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Documents;
+using Windows.UI.Xaml.Media;
+
+namespace Audio_Router
+{
+ ///
+ /// Store the data graph in a minimal data class, so that the application meets memory requirements for a
+ /// background task
+ ///
+ 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; }
+
+ CoreDispatcher uiDispatcher { get; set; }
+
+ TextBlock latencyLabel { get; set; }
+
+ int quantaCount = 0;
+
+ public AudioGraphConnection(AudioGraph graph, AudioDeviceOutputNode targetDevice, AudioDeviceInputNode sourceDevice, CoreDispatcher 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,
+ StepFrequency = 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.RunAsync(CoreDispatcherPriority.Low, () =>
+ {
+ try
+ {
+ latencyLabel.Text = result;
+ }
+ catch
+ {
+
+ }
+
+ });
+ quantaCount = 0;
+ }
+ }
+
+ private void VolumeSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs 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 UWP/Audio Router/AudioPlayback.cs b/Audio Router UWP/Audio Router/AudioPlayback.cs
new file mode 100644
index 0000000..0183c93
--- /dev/null
+++ b/Audio Router UWP/Audio Router/AudioPlayback.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.Media.Playback;
+
+namespace Audio_Router
+{
+ class AudioPlayback
+ {
+ public MediaPlayer Player { get; private set; }
+
+
+ static AudioPlayback instance;
+ public static AudioPlayback Instance
+ {
+ get
+ {
+ if (instance is null)
+ {
+ instance = new AudioPlayback();
+ }
+ return instance;
+ }
+ }
+
+ private AudioPlayback()
+ {
+ Player = new MediaPlayer()
+ {
+ AutoPlay = false
+ };
+ }
+ }
+}
diff --git a/Audio Router UWP/Audio Router/MainPage.xaml b/Audio Router UWP/Audio Router/MainPage.xaml
new file mode 100644
index 0000000..3c12f3c
--- /dev/null
+++ b/Audio Router UWP/Audio Router/MainPage.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Audio Router UWP/Audio Router/MainPage.xaml.cs b/Audio Router UWP/Audio Router/MainPage.xaml.cs
new file mode 100644
index 0000000..98924c0
--- /dev/null
+++ b/Audio Router UWP/Audio Router/MainPage.xaml.cs
@@ -0,0 +1,256 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using System.Threading.Tasks;
+using Windows.Devices.Enumeration;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.Media;
+using Windows.Media.Audio;
+using Windows.Media.Capture;
+using Windows.Media.Devices;
+using Windows.Media.Playback;
+using Windows.Media.Render;
+using Windows.UI.Core;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Documents;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+
+// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
+
+namespace Audio_Router
+{
+
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class MainPage : Page
+ {
+ //AudioGraph graph;
+ // The list of audio devices
+ DeviceInformationCollection sourceDevices;
+ DeviceInformationCollection targetDevices;
+ public MainPage()
+ {
+ this.InitializeComponent();
+
+ App.appRef.Suspending += AppRef_Suspending;
+ App.appRef.Resuming += AppRef_Resuming;
+ App.appRef.EnteredBackground += AppRef_EnteredBackground;
+ App.appRef.LeavingBackground += AppRef_LeavingBackground;
+ }
+
+ private void AppRef_LeavingBackground(object sender, Windows.ApplicationModel.LeavingBackgroundEventArgs e)
+ {
+ if (IsLoaded)
+ {
+ BackgroundRestore();
+ }
+
+ }
+
+ private void AppRef_EnteredBackground(object sender, Windows.ApplicationModel.EnteredBackgroundEventArgs e)
+ {
+ BackgroundCleanup();
+ }
+
+ private void AppRef_Resuming(object sender, object e)
+ {
+ BackgroundRestore();
+ }
+
+ ///
+ /// Clear existing elements from this display, to free memory
+ ///
+ ///
+ ///
+ private void AppRef_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e)
+ {
+ BackgroundCleanup();
+ }
+
+
+ private void BackgroundCleanup()
+ {
+ // Clear all items from the lists
+ InputAudioComboBox.Items.Clear();
+ OutputAudioComboBox.Items.Clear();
+ CreatedGraphs.Children.Clear();
+ // Ensure the GC collects freed memory, by explicitly calling the GC
+ GC.Collect();
+ }
+
+ private void BackgroundRestore()
+ {
+ // Start the task that populates the lists
+ if (InputAudioComboBox.Items.Count > 0 || OutputAudioComboBox.Items.Count > 0) return;
+ _ = Task.Run(() => FindAudioSources());
+ // And add existing graphs back to the page
+ foreach (AudioGraphConnection graphConneciton in App.appRef.audioGraphConnections)
+ {
+ Grid renderGrid = graphConneciton.GetAsDisplayItem(out Button visualButton);
+ CreatedGraphs.Children.Add(renderGrid);
+ void handler(object sender, RoutedEventArgs e)
+ {
+ visualButton.Click -= handler;
+ DestroyVisualElement(ref renderGrid);
+ }
+ visualButton.Click += handler;
+ }
+ }
+ ///
+ /// When the page is navigated to, populate the list
+ ///
+ ///
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ // Populate audio device list; as a Task to avoid delaying creation
+ _ = Task.Run(() => FindAudioSources());
+
+ }
+
+ private async Task FindAudioSources()
+ {
+ // First, clear the lists
+ _ = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
+ {
+ InputAudioComboBox.Items.Clear();
+ OutputAudioComboBox.Items.Clear();
+ });
+ // Perform this on our spawned thread, as the operation is asynchronous-synchronous await
+ sourceDevices = await DeviceInformation.FindAllAsync(MediaDevice.GetAudioCaptureSelector());
+ targetDevices = await DeviceInformation.FindAllAsync(MediaDevice.GetAudioRenderSelector());
+
+ DeviceInformation defaultOutput = await DeviceInformation.CreateFromIdAsync(MediaDevice.GetDefaultAudioRenderId(AudioDeviceRole.Default));
+ DeviceInformation defaultInput = await DeviceInformation.CreateFromIdAsync(MediaDevice.GetDefaultAudioCaptureId(AudioDeviceRole.Default));
+
+ _ = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
+ {
+ // Set the Input device
+ for (int i = 0; i < sourceDevices.Count; i++)
+ {
+ if (sourceDevices[i].Id == defaultInput.Id)
+ {
+ InputAudioComboBox.Items.Add(sourceDevices[i].Name + " (Default Device)");
+ InputAudioComboBox.SelectedIndex = i;
+ }
+ else
+ {
+ InputAudioComboBox.Items.Add(sourceDevices[i].Name);
+ }
+ }
+ for (int i = 0; i < targetDevices.Count; i++)
+ {
+ if(targetDevices[i].Id == defaultOutput.Id)
+ {
+ OutputAudioComboBox.Items.Add(targetDevices[i].Name + " (Default Device)");
+ OutputAudioComboBox.SelectedIndex = i;
+ }
+ else
+ {
+ OutputAudioComboBox.Items.Add(targetDevices[i].Name);
+ }
+ }
+ });
+ }
+
+ private async void startStreamButton_Click(object sender, RoutedEventArgs e)
+ {
+ await CreateAudioGraph();
+ }
+
+ private async Task CreateAudioGraph()
+ {
+ App.displayRequest.RequestActive();
+ DeviceInformation outputDevice = targetDevices[OutputAudioComboBox.SelectedIndex];
+ DeviceInformation inputDevice = sourceDevices[InputAudioComboBox.SelectedIndex];
+ AudioGraphSettings settings = LowLatencyCheckbox.IsChecked == true
+ ? new AudioGraphSettings(AudioRenderCategory.Media)
+ {
+ QuantumSizeSelectionMode = QuantumSizeSelectionMode.ClosestToDesired,
+ DesiredSamplesPerQuantum = 10,
+ PrimaryRenderDevice = outputDevice,
+ DesiredRenderDeviceAudioProcessing = Windows.Media.AudioProcessing.Raw,
+ EncodingProperties = new Windows.Media.MediaProperties.AudioEncodingProperties()
+ {
+ Subtype = "Float",
+ SampleRate = 128000, // Manually set to 128 000 samples/second so that the delay is significantly reduced
+ ChannelCount = 2,
+ BitsPerSample = 32,
+ Bitrate = 3072000
+ }
+ }
+ : new AudioGraphSettings(AudioRenderCategory.Media)
+ {
+ PrimaryRenderDevice = outputDevice
+ };
+ CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
+ AudioGraph ag = result.Graph;
+
+ if (result.Status != AudioGraphCreationStatus.Success)
+ {
+ return;
+ }
+
+ CreateAudioDeviceInputNodeResult deviceInputNodeResult = await ag.CreateDeviceInputNodeAsync(MediaCategory.Other, ag.EncodingProperties, inputDevice);
+
+ if (deviceInputNodeResult.Status != AudioDeviceNodeCreationStatus.Success)
+ {
+ // Fail-safe, switch to using the default encoding properties
+ settings = new AudioGraphSettings(AudioRenderCategory.Media)
+ {
+ PrimaryRenderDevice = outputDevice
+ };
+ result = await AudioGraph.CreateAsync(settings);
+ ag = result.Graph;
+
+ if (result.Status != AudioGraphCreationStatus.Success)
+ {
+ return;
+ }
+ deviceInputNodeResult = await ag.CreateDeviceInputNodeAsync(MediaCategory.Other, ag.EncodingProperties, inputDevice);
+ if (deviceInputNodeResult.Status != AudioDeviceNodeCreationStatus.Success)
+ {
+ return;
+ }
+ }
+
+ // Create the device output node connection
+ CreateAudioDeviceOutputNodeResult deviceOutputNodeResult = await ag.CreateDeviceOutputNodeAsync();
+ if (deviceOutputNodeResult.Status != AudioDeviceNodeCreationStatus.Success)
+ {
+ return;
+ }
+ deviceInputNodeResult.DeviceInputNode.AddOutgoingConnection(deviceOutputNodeResult.DeviceOutputNode);
+ // appMediaControls.PlaybackStatus = MediaPlaybackStatus.Playing;
+ ag.Start();
+ AudioGraphConnection graphConnection = new AudioGraphConnection(ag, deviceOutputNodeResult.DeviceOutputNode, deviceInputNodeResult.DeviceInputNode, Dispatcher);
+ App.appRef.audioGraphConnections.Add(graphConnection);
+
+ // Hold a reference to the created button; when clicked we must also ensure we remove this visual element from the grid display
+ Grid renderGrid = graphConnection.GetAsDisplayItem(out Button visualButton);
+ CreatedGraphs.Children.Add(renderGrid);
+ // Inline decl to add a second listener to the click event - will remove the grid from the display
+ void handler(object sender, RoutedEventArgs e)
+ {
+ visualButton.Click -= handler;
+ DestroyVisualElement(ref renderGrid);
+ }
+ visualButton.Click += handler;
+ }
+
+ private void DestroyVisualElement(ref Grid element)
+ {
+ _ = CreatedGraphs.Children.Remove(element);
+ element = null;
+ }
+ }
+}
diff --git a/Audio Router UWP/Audio Router/Package.appxmanifest b/Audio Router UWP/Audio Router/Package.appxmanifest
new file mode 100644
index 0000000..af9f62a
--- /dev/null
+++ b/Audio Router UWP/Audio Router/Package.appxmanifest
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+ Audio Router
+ Brychan Dempsey
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Audio Router UWP/Audio Router/Properties/AssemblyInfo.cs b/Audio Router UWP/Audio Router/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..ae257a6
--- /dev/null
+++ b/Audio Router UWP/Audio Router/Properties/AssemblyInfo.cs
@@ -0,0 +1,29 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Audio Router")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Audio Router")]
+[assembly: AssemblyCopyright("Copyright © 2021")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/Audio Router UWP/Audio Router/Properties/Default.rd.xml b/Audio Router UWP/Audio Router/Properties/Default.rd.xml
new file mode 100644
index 0000000..af00722
--- /dev/null
+++ b/Audio Router UWP/Audio Router/Properties/Default.rd.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Audio Router WPF/App.xaml b/Audio Router WPF/App.xaml
new file mode 100644
index 0000000..0e610fd
--- /dev/null
+++ b/Audio Router WPF/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/Audio Router WPF/App.xaml.cs b/Audio Router WPF/App.xaml.cs
new file mode 100644
index 0000000..cec457a
--- /dev/null
+++ b/Audio Router WPF/App.xaml.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace Audio_Router_WPF
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ }
+}
diff --git a/Audio Router WPF/AssemblyInfo.cs b/Audio Router WPF/AssemblyInfo.cs
new file mode 100644
index 0000000..8b5504e
--- /dev/null
+++ b/Audio Router WPF/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/Audio Router WPF/Audio Router WPF.csproj b/Audio Router WPF/Audio Router WPF.csproj
new file mode 100644
index 0000000..ea206df
--- /dev/null
+++ b/Audio Router WPF/Audio Router WPF.csproj
@@ -0,0 +1,11 @@
+
+
+
+ WinExe
+ net5.0-windows
+ Audio_Router_WPF
+ enable
+ true
+
+
+
diff --git a/Audio Router WPF/MainWindow.xaml b/Audio Router WPF/MainWindow.xaml
new file mode 100644
index 0000000..2747a2e
--- /dev/null
+++ b/Audio Router WPF/MainWindow.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/Audio Router WPF/MainWindow.xaml.cs b/Audio Router WPF/MainWindow.xaml.cs
new file mode 100644
index 0000000..6064f4a
--- /dev/null
+++ b/Audio Router WPF/MainWindow.xaml.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace Audio_Router_WPF
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Audio Router.sln b/Audio Router.sln
new file mode 100644
index 0000000..6f1740f
--- /dev/null
+++ b/Audio Router.sln
@@ -0,0 +1,77 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31710.8
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Audio Router UWP", "Audio Router UWP\Audio Router\Audio Router UWP.csproj", "{92D76120-5578-484A-BB93-24D105B48043}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Audio Router WPF", "Audio Router WPF\Audio Router WPF.csproj", "{E0BEDD27-7800-4434-BA2D-7D71964F557F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|ARM = Debug|ARM
+ Debug|ARM64 = Debug|ARM64
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|ARM = Release|ARM
+ Release|ARM64 = Release|ARM64
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|ARM.ActiveCfg = Debug|ARM
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|ARM.Build.0 = Debug|ARM
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|ARM.Deploy.0 = Debug|ARM
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|ARM64.Build.0 = Debug|ARM64
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|ARM64.Deploy.0 = Debug|ARM64
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|x64.ActiveCfg = Debug|x64
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|x64.Build.0 = Debug|x64
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|x64.Deploy.0 = Debug|x64
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|x86.ActiveCfg = Debug|x86
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|x86.Build.0 = Debug|x86
+ {92D76120-5578-484A-BB93-24D105B48043}.Debug|x86.Deploy.0 = Debug|x86
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|Any CPU.ActiveCfg = Release|x86
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|ARM.ActiveCfg = Release|ARM
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|ARM.Build.0 = Release|ARM
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|ARM.Deploy.0 = Release|ARM
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|ARM64.ActiveCfg = Release|ARM64
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|ARM64.Build.0 = Release|ARM64
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|ARM64.Deploy.0 = Release|ARM64
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|x64.ActiveCfg = Release|x64
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|x64.Build.0 = Release|x64
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|x64.Deploy.0 = Release|x64
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|x86.ActiveCfg = Release|x86
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|x86.Build.0 = Release|x86
+ {92D76120-5578-484A-BB93-24D105B48043}.Release|x86.Deploy.0 = Release|x86
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Debug|ARM.Build.0 = Debug|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Debug|x64.Build.0 = Debug|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Debug|x86.Build.0 = Debug|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Release|ARM.ActiveCfg = Release|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Release|ARM.Build.0 = Release|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Release|ARM64.Build.0 = Release|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Release|x64.ActiveCfg = Release|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Release|x64.Build.0 = Release|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Release|x86.ActiveCfg = Release|Any CPU
+ {E0BEDD27-7800-4434-BA2D-7D71964F557F}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {725876A6-2151-4F75-A3E5-9522B152A17C}
+ EndGlobalSection
+EndGlobal