Skip to content

Commit

Permalink
CavernPipe done
Browse files Browse the repository at this point in the history
  • Loading branch information
VoidXH committed Jan 19, 2025
1 parent a919fdd commit 9f8ddc8
Show file tree
Hide file tree
Showing 15 changed files with 415 additions and 18 deletions.
40 changes: 39 additions & 1 deletion Cavern/Channels/ChannelPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,27 @@ public static ChannelPrototype[] GetAlternative(ReferenceChannel[] source) {
}

/// <summary>
/// Convert a <see cref="ReferenceChannel"/> to the name of the channels.
/// Convert a <see cref="Channel"/> to the name of the channel, if its position is known.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetName(Channel source) => GetName(GetReference(source));

/// <summary>
/// Convert a <see cref="ReferenceChannel"/> to the name of the channel.
/// </summary>
public static string GetName(ReferenceChannel source) => Mapping[(int)source].Name;

/// <summary>
/// Convert a mapping of <see cref="Channel"/>s to the names of the channels.
/// </summary>
public static string[] GetNames(Channel[] source) {
string[] result = new string[source.Length];
for (int i = 0; i < source.Length; i++) {
result[i] = GetName(source[i]);
}
return result;
}

/// <summary>
/// Convert a mapping of <see cref="ReferenceChannel"/>s to the names of the channels.
/// </summary>
Expand All @@ -161,6 +178,27 @@ public static string[] GetNames(ReferenceChannel[] source) {
return result;
}

/// <summary>
/// Get which channel this placement represents.
/// </summary>
public static ReferenceChannel GetReference(Channel source) {
if (source.LFE) {
return ReferenceChannel.ScreenLFE;
}
for (int i = 0; i < Mapping.Length; i++) {
if (Math.Abs(Mapping[i].X - source.X) < .05f && Math.Abs(Mapping[i].Y - source.Y) < .05f) {
return (ReferenceChannel)i;
}
}
Vector3 cubicalPos = source.CubicalPos;
for (int i = 0; i < AlternativePositions.Length; i++) {
if (AlternativePositions[i] == cubicalPos) {
return (ReferenceChannel)i;
}
}
return ReferenceChannel.Unknown;
}

/// <summary>
/// Convert a mapping of <see cref="ReferenceChannel"/>s to channel name initials.
/// </summary>
Expand Down
13 changes: 8 additions & 5 deletions CavernSamples/CavernPipeClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,26 @@

// Sending the file or part to the pipe
using FileStream reader = File.OpenRead(args[0]);
long sent = 0;
long sent = 0,
received = 0;
float[] writeBuffer = [];
byte[] sendBuffer = new byte[1024 * 1024],
receiveBuffer = [];
while (sent < reader.Length) {
while (received < target.Length) {
int toSend = reader.Read(sendBuffer, 0, sendBuffer.Length);
pipe.Write(BitConverter.GetBytes(toSend));
pipe.Write(sendBuffer, 0, toSend);
sent += toSend;

// If there is incoming data, write it to file
int toReceive = pipe.ReadInt32();
int toReceive = pipe.ReadInt32(),
samples = toReceive / sizeof(float);
if (receiveBuffer.Length < toReceive) {
receiveBuffer = new byte[toReceive];
writeBuffer = new float[toReceive / sizeof(float)];
writeBuffer = new float[samples];
}
pipe.ReadAll(receiveBuffer, 0, toReceive);
Buffer.BlockCopy(receiveBuffer, 0, writeBuffer, 0, toReceive);
target.WriteBlock(writeBuffer, 0, toReceive / sizeof(float));
target.WriteBlock(writeBuffer, 0, samples);
received += samples / target.ChannelCount;
}
1 change: 0 additions & 1 deletion CavernSamples/CavernPipeServer/App.xaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<Application x:Class="CavernPipeServer.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CavernPipeServer"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
Expand Down
47 changes: 45 additions & 2 deletions CavernSamples/CavernPipeServer/CavernPipeRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,28 @@
using Cavern.Format;
using Cavern.Format.Renderers;
using Cavern.Format.Utilities;
using Cavern.Utilities;

namespace CavernPipeServer {
/// <summary>
/// Handles rendering of incoming audio content and special protocol additions/transformations.
/// </summary>
public class CavernPipeRenderer : IDisposable {
/// <summary>
/// Rendering of new content has started, the <see cref="Listener.Channels"/> are updated from the latest Cavern user files.
/// </summary>
public event Action OnRenderingStarted;

/// <summary>
/// Provides per-channel metering data. Channel gains are ratios between -50 and 0 dB FS.
/// </summary>
public delegate void OnMetersAvailable(float[] meters);

/// <summary>
/// New output data was rendered, audio meters can be updated. Channel gains are ratios between -50 and 0 dB FS.
/// </summary>
public event OnMetersAvailable MetersAvailable;

/// <summary>
/// Protocol message decoder.
/// </summary>
Expand Down Expand Up @@ -55,20 +71,47 @@ void RenderThread() {
Renderer renderer = reader.GetRenderer();
Listener listener = new Listener {
SampleRate = reader.SampleRate,
UpdateRate = Protocol.UpdateRate
UpdateRate = Protocol.UpdateRate,
AudioQuality = QualityModes.Perfect,
};
OnRenderingStarted?.Invoke();

float[] reRender = null;
if (Listener.Channels.Length != Protocol.OutputChannels) {
reRender = new float[Protocol.OutputChannels * Protocol.UpdateRate];
}
listener.AttachSources(renderer.Objects);

// When this writer is used without writing a header, it's a BitDepth converter from float to anything, and can dump to streams.
RIFFWaveWriter streamDumper = new RIFFWaveWriter(Output, Protocol.OutputChannels, long.MaxValue, reader.SampleRate, Protocol.OutputFormat);

while (Input != null) {
float[] render = listener.Render();
streamDumper.WriteBlock(render, 0, render.LongLength);
UpdateMeters(render);
if (reRender == null) {
streamDumper.WriteBlock(render, 0, render.LongLength);
} else {
Array.Clear(reRender);
WaveformUtils.Downmix(render, reRender, Protocol.OutputChannels);
streamDumper.WriteBlock(reRender, 0, reRender.LongLength);
}
}
} catch {
Dispose();
}
}

/// <summary>
/// Send the <see cref="MetersAvailable"/> event with the rendered channel names and their last rendered gains.
/// </summary>
/// <remarks>Channel gains are ratios between -50 and 0 dB FS.</remarks>
void UpdateMeters(float[] audioOut) {
float[] result = new float[Listener.Channels.Length];
for (int i = 0; i < result.Length; i++) {
float channelGain = QMath.GainToDb(WaveformUtils.GetRMS(audioOut, i, result.Length));
result[i] = QMath.Clamp01(QMath.LerpInverse(-50, 0, channelGain));
}
MetersAvailable?.Invoke(result);
}
}
}
7 changes: 6 additions & 1 deletion CavernSamples/CavernPipeServer/CavernPipeServer.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>disable</Nullable>
<UseWPF>true</UseWPF>
Expand All @@ -23,6 +23,11 @@
<ItemGroup>
<_DeploymentManifestIconFile Remove="..\Icon.ico" />
</ItemGroup>
<ItemGroup>
<Page Include="..\_Common\Styles.xaml" Link="Resources\Styles.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Resource Include="..\Icon.ico" Link="Resources\Icon.ico" />
</ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions CavernSamples/CavernPipeServer/CavernPipeServer.csproj.user
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
</ApplicationDefinition>
</ItemGroup>
<ItemGroup>
<Page Update="..\_Common\Styles.xaml">
<SubType>Designer</SubType>
</Page>
<Page Update="MainWindow.xaml">
<SubType>Designer</SubType>
</Page>
Expand Down
121 changes: 121 additions & 0 deletions CavernSamples/CavernPipeServer/ChannelMeters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using System.Windows;
using System.Windows.Controls;

using Cavern;
using Cavern.Channels;

namespace CavernPipeServer {
/// <summary>
/// Display channel output meters on a <see cref="Panel"/>.
/// </summary>
/// <param name="canvas">Add meters as children of this control</param>
/// <param name="labelProto">Reference channel name display</param>
/// <param name="barProto">Reference meter display</param>
public class ChannelMeters(Panel canvas, TextBlock labelProto, ProgressBar barProto) {
/// <summary>
/// Meters are displayed on this control.
/// </summary>
protected Panel canvas = canvas;

/// <summary>
/// Display the last gain updates of the channels on these controls.
/// </summary>
(TextBlock, ProgressBar)[] displays;

/// <summary>
/// To prevent slowdowns caused by too many UI updates, collect the peaks over longer time intervals and update with said peaks.
/// </summary>
protected float[] movingPeaks;

/// <summary>
/// When to update the meters.
/// </summary>
DateTime updateAt;

/// <summary>
/// Create the UI elements for displaying meter values later. The first call to this function is always after the <see cref="Listener"/>'s creation,
/// so the <see cref="Listener.Channels"/> are what will be rendered.
/// </summary>
public virtual void Enable() {
string[] channels = ChannelPrototype.GetNames(Listener.Channels);
displays = new (TextBlock, ProgressBar)[channels.Length];
movingPeaks = new float[channels.Length];
for (int i = 0; i < channels.Length; i++) {
double marginTop = labelProto.Margin.Top + (labelProto.Height + 5) * i;
TextBlock channelName = new TextBlock {
Margin = new Thickness(labelProto.Margin.Left, marginTop, labelProto.Margin.Right, 0),
HorizontalAlignment = labelProto.HorizontalAlignment,
VerticalAlignment = labelProto.VerticalAlignment,
Text = channels[i]
};
ProgressBar progressBar = new ProgressBar {
Margin = new Thickness(barProto.Margin.Left, marginTop, barProto.Margin.Right, 0),
HorizontalAlignment = barProto.HorizontalAlignment,
VerticalAlignment = barProto.VerticalAlignment,
Width = barProto.Width,
Height = barProto.Height,
Maximum = 1
};
displays[i] = (channelName, progressBar);
canvas.Children.Add(channelName);
canvas.Children.Add(progressBar);
}
}

/// <summary>
/// Update the displayed meter values of each channel if they exist.
/// </summary>
public void Update(float[] meters) {
if (displays == null) {
return;
}

for (int i = 0; i < movingPeaks.Length; i++) {
if (movingPeaks[i] < meters[i]) {
movingPeaks[i] = meters[i];
}
}

if (updateAt < DateTime.Now) {
UpdateUI(movingPeaks);
Array.Clear(movingPeaks);
updateAt = DateTime.Now + updateInterval;
}
}

/// <summary>
/// Remove the channel output meters from the UI.
/// </summary>
public virtual void Disable() {
if (displays == null) {
return;
}

for (int i = 0; i < displays.Length; i++) {
canvas.Children.Remove(displays[i].Item1);
canvas.Children.Remove(displays[i].Item2);
}
displays = null;
}

/// <summary>
/// The part of <see cref="Update(float[])"/> that requires a dispatcher.
/// </summary>
/// <param name="meters"></param>
protected virtual void UpdateUI(float[] meters) {
if (displays == null) {
return;
}

for (int i = 0, c = Math.Min(displays.Length, meters.Length); i < c; i++) {
displays[i].Item2.Value = meters[i];
}
}

/// <summary>
/// How often to update the meters.
/// </summary>
static readonly TimeSpan updateInterval = TimeSpan.FromSeconds(.05);
}
}
12 changes: 9 additions & 3 deletions CavernSamples/CavernPipeServer/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="CavernPipe Server"
Visibility="Hidden" ShowInTaskbar="False" Width="600" Height="300">
<Grid>
Title="CavernPipe Server" Visibility="Hidden" ShowInTaskbar="False" ResizeMode="NoResize" Background="#696969"
Width="300" Height="300">
<Window.Resources>
<ResourceDictionary Source="Resources/Styles.xaml"/>
</Window.Resources>
<Grid x:Name="canvas">
<RadioButton x:Name="status" Margin="10,10,0,0" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<TextBlock x:Name="chProto" Margin="10,30,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Height="16" Visibility="Hidden"/>
<ProgressBar x:Name="vmProto" Margin="0,30,10,0" HorizontalAlignment="Right" VerticalAlignment="Top" Width="140" Height="16" Visibility="Hidden"/>
</Grid>
</Window>
Loading

0 comments on commit 9f8ddc8

Please sign in to comment.