202 lines
7.1 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|