Skip to content

Commit

Permalink
Refactor of the entire upmixing submodule
Browse files Browse the repository at this point in the history
  • Loading branch information
VoidXH committed Mar 30, 2024
1 parent 3c4c9ba commit fac0a1a
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 44 deletions.
43 changes: 13 additions & 30 deletions Cavern/Remapping/CavernizeUpmixer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;

using Cavern.Filters;
Expand All @@ -26,50 +25,35 @@ public class CavernizeUpmixer : Upmixer {
/// </summary>
public bool CenterStays = true;

/// <summary>
/// Mono sources to upconvert. Don't attach these to a <see cref="Listener"/>.
/// </summary>
readonly Source[] sources;

/// <summary>
/// Height separation filters for each channel.
/// </summary>
readonly Cavernize[] filters;

/// <summary>
/// A dummy listener to forefully get source samples.
/// </summary>
readonly Listener pinger;

/// <summary>
/// Creates height information for ground sources with the <see cref="Cavernize"/> filter.
/// The default crossover frequency of 250 Hz will be used.
/// </summary>
/// <param name="sources">Mono sources to upconvert, not attached to any <see cref="Listener"/></param>
/// <param name="sampleRate">Content sample rate</param>
public CavernizeUpmixer(IList<Source> sources, int sampleRate) : this(sources, sampleRate, 250) { }
/// <remarks>Positions of sources are taken on creation and will stay there, regardless of their further movement.</remarks>
public CavernizeUpmixer(IReadOnlyList<Source> sources, int sampleRate) : this(sources, sampleRate, 250) { }

/// <summary>
/// Creates height information for ground sources with the <see cref="Cavernize"/> filter.
/// </summary>
/// <param name="sources">Mono sources to upconvert, not attached to any <see cref="Listener"/></param>
/// <param name="sampleRate">Content sample rate</param>
/// <param name="crossoverFrequency">Keep sounds below this frequency on the ground layer</param>
public CavernizeUpmixer(IList<Source> sources, int sampleRate, int crossoverFrequency) :
/// <remarks>Positions of sources are taken on creation and will stay there, regardless of their further movement.</remarks>
public CavernizeUpmixer(IReadOnlyList<Source> sources, int sampleRate, int crossoverFrequency) :
base(2 * sources.Count, sampleRate) {
this.sources = sources.ToArray();

filters = new Cavernize[sources.Count];
for (int i = 0; i < filters.Length; i++) {
filters[i] = new Cavernize(sampleRate, crossoverFrequency);
IntermediateSources[2 * i].Position = sources[i].Position;
}

pinger = new Listener(false) {
SampleRate = sampleRate
};
for (int i = 0; i < sources.Count; i++) {
pinger.AttachSource(sources[i]);
}
SetupCollection(sources, sampleRate);
}

/// <summary>
Expand All @@ -79,8 +63,9 @@ protected override float[][] UpdateSources(int samplesPerSource) {
filters[0].Effect = Effect;
filters[0].CalculateSmoothingFactor(samplesPerSource, Smoothness);
bool centerToStay = CenterStays;
for (int i = 1; i < filters.Length; i++) {
if (centerToStay && sources[i].Position.X == 0 && sources[i].Position.Y == 0 && sources[i].Position.Z > 0) {
for (int i = 0; i < filters.Length; i++) {
Vector3 position = IntermediateSources[2 * i].Position;
if (centerToStay && position.X == 0 && position.Y == 0 && position.Z > 0) {
filters[i].Effect = 0;
centerToStay = false;
} else {
Expand All @@ -89,16 +74,14 @@ protected override float[][] UpdateSources(int samplesPerSource) {
filters[i].SmoothFactor = filters[0].SmoothFactor;
}

pinger.UpdateRate = samplesPerSource;
pinger.Ping();
for (int i = 0; i < sources.Length; i++) {
float[][] input = OnSamplesNeeded(samplesPerSource);
for (int i = 0; i < input.Length; i++) {
int current = 2 * i,
pair = current + 1;
filters[i].Process(sources[i].Rendered[0]);
filters[i].Process(input[i]);
filters[i].GroundLevel.CopyTo(output[current]);
filters[i].HeightLevel.CopyTo(output[pair]);
IntermediateSources[current].Position = sources[i].Position;
IntermediateSources[pair].Position = sources[i].Position +
IntermediateSources[pair].Position = IntermediateSources[current].Position +
new Vector3(0, filters[i].Height * Listener.EnvironmentSize.Y, 0);
}
return output;
Expand Down
21 changes: 17 additions & 4 deletions Cavern/Remapping/DisassemblerUpmixer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,24 @@ public class DisassemblerUpmixer : PairBasedUpmixer {
/// </summary>
readonly int sampleRate;

/// <summary>
/// Uses <see cref="SpectralDisassembler"/>s to create a better quality upmix than matrixing,
/// with automatically circularly allocated channel pairs for each layer.
/// </summary>
/// <param name="positions">Location of the input channels in the environment, using the position of source input channels,
/// <see cref="Listener.Channels"/> if it's a locally rendered environment</param>
/// <param name="intermediateSourceCount">Number of bands to separate</param>
/// <param name="sampleRate">Content sample rate</param>
public DisassemblerUpmixer(Vector3[] positions, int intermediateSourceCount, int sampleRate) :
this(positions, GetLayeredPairs(positions), intermediateSourceCount, sampleRate) { }

/// <summary>
/// Uses <see cref="SpectralDisassembler"/>s to create a better quality upmix than matrixing.
/// </summary>
/// <param name="positions">Location of the input channels in the environment</param>
/// <param name="pairs">Pairs of indices of later given inputs to recreate the space between</param>
/// <param name="positions">Location of the input channels in the environment, using the position of source input channels,
/// <see cref="Listener.Channels"/> if it's a locally rendered environment</param>
/// <param name="pairs">Pairs of indices of later given inputs to recreate the space between, recommended to map around each
/// channel layer in a circular pattern with <see cref="PairBasedUpmixer.GetLayeredPairs(Vector3[])"/></param>
/// <param name="intermediateSourceCount">Number of bands to separate</param>
/// <param name="sampleRate">Content sample rate</param>
public DisassemblerUpmixer(Vector3[] positions, (int, int)[] pairs, int intermediateSourceCount, int sampleRate) :
Expand All @@ -43,7 +56,7 @@ public DisassemblerUpmixer(Vector3[] positions, (int, int)[] pairs, int intermed
/// </summary>
protected override float[][] UpdateSources(int samplesPerSource) {
float smoothFactor = Cavernize.CalculateSmoothingFactor(sampleRate, samplesPerSource, smoothness);
float[][] inputs = GetNewSamples(samplesPerSource);
float[][] inputs = OnSamplesNeeded(samplesPerSource);
int source = 0;
for (int i = 0; i < disassemblers.Length; i++) {
disassemblers[i].smoothnessFactor = smoothFactor;
Expand All @@ -54,7 +67,7 @@ protected override float[][] UpdateSources(int samplesPerSource) {
for (int j = 0; j < intermediates.Length; j++) {
IntermediateSources[source].Position = Vector3.Lerp(positions[pairA], positions[pairB], intermediates[j].panning);
output[source] = intermediates[j].samples;
++source;
source++;
}
}
return output;
Expand Down
20 changes: 17 additions & 3 deletions Cavern/Remapping/NearestUpmixer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Numerics;

using Cavern.Utilities;
Expand All @@ -8,11 +9,24 @@ namespace Cavern.Remapping {
/// Upmixes channels with interpolated positions between sources.
/// </summary>
public class NearestUpmixer : PairBasedUpmixer {
/// <summary>
/// Upmixes channels with interpolated positions between sources,
/// with automatically circularly allocated channel pairs for each layer.
/// </summary>
/// <param name="positions">Location of the input channels in the environment, using the position of source input channels,
/// <see cref="Listener.Channels"/> if it's a locally rendered environment</param>
/// <param name="intermediateSourceCount">Number of bands to separate</param>
/// <param name="sampleRate">Content sample rate</param>
public NearestUpmixer(Vector3[] positions, int intermediateSourceCount, int sampleRate) :
this(positions, GetLayeredPairs(positions), intermediateSourceCount, sampleRate) { }

/// <summary>
/// Upmixes channels with interpolated positions between sources.
/// </summary>
/// <param name="positions">Location of the input channels in the environment</param>
/// <param name="pairs">Pairs of indices of later given inputs to recreate the space between</param>
/// <param name="positions">Location of the input channels in the environment, using the position of source input channels,
/// <see cref="Listener.Channels"/> if it's a locally rendered environment</param>
/// <param name="pairs">Pairs of indices of later given inputs to recreate the space between, recommended to map around each
/// channel layer in a circular pattern with <see cref="PairBasedUpmixer.GetLayeredPairs(Vector3[])"/></param>
/// <param name="intermediateSourceCount">Number of bands to separate</param>
/// <param name="sampleRate">Content sample rate</param>
public NearestUpmixer(Vector3[] positions, (int, int)[] pairs, int intermediateSourceCount, int sampleRate) :
Expand All @@ -22,7 +36,7 @@ public NearestUpmixer(Vector3[] positions, (int, int)[] pairs, int intermediateS
/// Get the input samples, place the upmixed targets in space, and return their samples.
/// </summary>
protected override float[][] UpdateSources(int samplesPerSource) {
float[][] inputs = GetNewSamples(samplesPerSource);
float[][] inputs = OnSamplesNeeded(samplesPerSource);
int intermediateSourceCount = IntermediateSources.Length / pairs.Length;
int source = 0;
for (int pair = 0; pair < pairs.Length; pair++) {
Expand Down
52 changes: 52 additions & 0 deletions Cavern/Remapping/SourceSetPinger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Linq;

namespace Cavern.Remapping {
/// <summary>
/// Gets the next few samples of multiple related <see cref="Source"/>s.
/// </summary>
internal class SourceSetPinger {
/// <summary>
/// A dummy listener to forcefully get source samples.
/// </summary>
readonly Listener pinger;

/// <summary>
/// Outputs from the <see cref="pinger"/> will be passed through this cache array.
/// </summary>
readonly float[][] input;

/// <summary>
/// Sources to ping. Don't attach these to a <see cref="Listener"/> anywhere else.
/// </summary>
Source[] sources;

/// <summary>
/// Gets the next few samples of multiple related <see cref="Source"/>s.
/// </summary>
/// <param name="sources">Sources to ping, which aren't attached to a <see cref="Listener"/> anywhere else</param>
/// <param name="sampleRate">Sample rate of each of the <paramref name="sources"/></param>
public SourceSetPinger(IReadOnlyList<Source> sources, int sampleRate) {
pinger = new Listener(false) {
SampleRate = sampleRate
};
input = new float[sources.Count][];
this.sources = sources.ToArray();
for (int i = 0; i < input.Length; i++) {
pinger.AttachSource(sources[i]);
}
}

/// <summary>
/// Get the next set of samples.
/// </summary>
public float[][] Update(int samplesPerSource) {
pinger.UpdateRate = samplesPerSource;
pinger.Ping();
for (int i = 0; i < input.Length; i++) {
input[i] = sources[i].Rendered[0];
}
return input;
}
}
}
2 changes: 1 addition & 1 deletion Cavern/Remapping/SurroundUpmixer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public SurroundUpmixer(ReferenceChannel[] sourceChannels, int sampleRate, bool m
/// Get the input samples, place the upmixed targets in space, and return their samples.
/// </summary>
protected override float[][] UpdateSources(int samplesPerSource) {
float[][] input = GetNewSamples(samplesPerSource);
float[][] input = OnSamplesNeeded(samplesPerSource);
for (int i = 0; i < input.Length; i++) {
if (sourceRouting[i] != -1) {
Array.Copy(input[i], output[sourceRouting[i]], input[i].Length);
Expand Down
15 changes: 9 additions & 6 deletions Cavern/Remapping/Upmixer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Runtime.CompilerServices;
using System.Collections.Generic;

using Cavern.SpecialSources;

Expand All @@ -14,11 +14,14 @@ public abstract class Upmixer {

/// <summary>
/// This function is called when new samples are needed for the next frame, it should return a frame for each source.
/// When the upmixer is set up for a set of <see cref="Source"/>s with <see cref="SetupCollection"/>, this is set internally.
/// Upmixer constructors with a list of <see cref="Source"/>s as a parameter call <see cref="SetupCollection"/> with them.
/// Only set this manually when the samples are generated bypassing any <see cref="Source"/> objects.
/// </summary>
public event SampleCollector OnSamplesNeeded;
public SampleCollector OnSamplesNeeded;

/// <summary>
/// Output sources created by the upmixing process.
/// Output sources created by the upmixing process. Attach these to your listening environment to play the upmixed content.
/// </summary>
public Source[] IntermediateSources { get; private set; }

Expand Down Expand Up @@ -46,10 +49,10 @@ protected Upmixer(int sourceCount, int sampleRate) {
}

/// <summary>
/// Uses the sample collector to read new samples.
/// Use a set of <paramref name="sources"/> for input sample generation.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected float[][] GetNewSamples(int samplesPerSource) => OnSamplesNeeded(samplesPerSource);
public void SetupCollection(IReadOnlyList<Source> sources, int sampleRate) =>
OnSamplesNeeded = new SourceSetPinger(sources, sampleRate).Update;

/// <summary>
/// Get the input samples, place the upmixed targets in space, and return their samples.
Expand Down

0 comments on commit fac0a1a

Please sign in to comment.