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 @@ + + + + + + + + + + + + + + + + + +