Audio-Router/Audio Router WPF/AudioGraphConnection.cs

202 lines
7.1 KiB
C#

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; } = new TextBlock();
int quantaCount;
#pragma warning disable CS8618 // Cannot convert null literal to non-nullable reference type.
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);
}
/// <summary>
/// Creates a grid populated with UI controls containing information about this item, i.e. for rendering
/// </summary>
/// <returns></returns>
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 == 0)
{
if (latencyLabel is null) return;
string result = (Graph.LatencyInSamples / (double)Graph.SamplesPerQuantum * SamplesPerMS).ToString("0.#") + " ms";
uiDispatcher.Invoke(() =>
{
try
{
// Try to set the label, otherwise it may have already been destroyed
latencyLabel.Text = result;
}
catch (NullReferenceException)
{
}
}, DispatcherPriority.Background);
result = null;
}
/// ----------------------
/// || About this: ||
/// ----------------------
/// Running AudioGraph in WPF causes what seems to be the captured audio data to
/// never go out of scope (this does not happen in UWP, as shown in the near-identical code from the
/// UWP project), so the memory allocations for the project keep increasing.
/// Stopping the AudioGraph causes these allocations to finalise, so frees up the space.
/// we can call ResetAllNodes() periodically (i.e.: every 10s (1000 * 10 ms)) to reset the
/// nodes
///
if (quantaCount == 1000)
{
//SourceDevice.Reset();
Graph.ResetAllNodes();
// GC.Collect(); // We can let the GC handle itself
}
quantaCount++;
quantaCount %= 1001;
}
private void VolumeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
TargetDevice.OutgoingGain = e.NewValue;
SourceDevice.OutgoingGain = e.NewValue;
}
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
private void DestroyButton_Click(object sender, RoutedEventArgs e)
{
Graph.QuantumProcessed -= Graph_QuantumProcessed;
Graph.ResetAllNodes();
latencyLabel = null;
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;
Graph.QuantumProcessed -= Graph_QuantumProcessed;
}
}
}