Skip to content

Commit

Permalink
Filter property editor UI
Browse files Browse the repository at this point in the history
  • Loading branch information
VoidXH committed Apr 28, 2024
1 parent 858ae40 commit 4079de4
Show file tree
Hide file tree
Showing 18 changed files with 233 additions and 40 deletions.
2 changes: 1 addition & 1 deletion Cavern.QuickEQ.Format/FilterSet/MonolithHTP1FilterSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public override void Export(string path) {
/// <summary>
/// The corresponding JSON label for each supported channel.
/// </summary>
readonly static Dictionary<ReferenceChannel, string> channelLabels = new Dictionary<ReferenceChannel, string> {
static readonly Dictionary<ReferenceChannel, string> channelLabels = new Dictionary<ReferenceChannel, string> {
[ReferenceChannel.FrontLeft] = "lf",
[ReferenceChannel.FrontRight] = "rf",
[ReferenceChannel.FrontCenter] = "c",
Expand Down
4 changes: 4 additions & 0 deletions Cavern/Filters/BiquadFilter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

using Cavern.Filters.Interfaces;
Expand All @@ -18,6 +19,7 @@ public abstract class BiquadFilter : Filter, ICloneable, IEqualizerAPOFilter {
/// <summary>
/// Center frequency (-3 dB point) of the filter.
/// </summary>
[DisplayName("Center frequency (Hz)")]
public double CenterFreq {
get => centerFreq;
set => Reset(value, q, gain);
Expand All @@ -26,6 +28,7 @@ public double CenterFreq {
/// <summary>
/// Q-factor of the filter.
/// </summary>
[DisplayName("Q-factor")]
public double Q {
get => q;
set => Reset(centerFreq, value, gain);
Expand All @@ -34,6 +37,7 @@ public double Q {
/// <summary>
/// Gain of the filter in decibels.
/// </summary>
[DisplayName("Gain (dB)")]
public double Gain {
get => gain;
set => Reset(centerFreq, q, value);
Expand Down
6 changes: 3 additions & 3 deletions Cavern/Filters/BypassFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ public class BypassFilter : Filter {
/// <summary>
/// Name of this filter node.
/// </summary>
readonly string name;
public string Name { get; set; }

/// <summary>
/// A filter that doesn't do anything. Used to display empty filter nodes with custom names, like the beginning of virtual channels.
/// </summary>
/// <param name="name">Name of this filter node</param>
public BypassFilter(string name) => this.name = name;
public BypassFilter(string name) => Name = name;

/// <inheritdoc/>
public override void Process(float[] samples) {
Expand All @@ -25,6 +25,6 @@ public override void Process(float[] samples, int channel, int channels) {
}

/// <inheritdoc/>
public override string ToString() => name;
public override string ToString() => Name;
}
}
4 changes: 4 additions & 0 deletions Cavern/Filters/Cavernize.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;

namespace Cavern.Filters {
/// <summary>
Expand All @@ -8,6 +9,7 @@ public class Cavernize : Filter {
/// <summary>
/// Height separation effect strength.
/// </summary>
[DisplayName("Effect (ratio)")]
public float Effect { get; set; } = .75f;

/// <summary>
Expand All @@ -16,11 +18,13 @@ public class Cavernize : Filter {
/// </summary>
/// <remarks>The default value is calculated with 0.8 smoothness, with an update rate of 240 at
/// 48 kHz sampling.</remarks>
[DisplayName("Smoothing factor (ratio)")]
public float SmoothFactor { get; set; } = .0229349384f;

/// <summary>
/// Keep all frequencies below this on the ground.
/// </summary>
[DisplayName("Ground crossover (Hz)")]
public double GroundCrossover {
get => crossover.Frequency;
set => crossover.Frequency = value;
Expand Down
10 changes: 6 additions & 4 deletions Cavern/Filters/Comb.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;

namespace Cavern.Filters {
/// <summary>
Expand All @@ -9,11 +10,13 @@ public class Comb : Filter {
/// <summary>
/// Wet mix multiplier.
/// </summary>
[DisplayName("Alpha (ratio)")]
public double Alpha { get; set; }

/// <summary>
/// Delay in samples.
/// </summary>
[DisplayName("K (samples)")]
public int K {
get => delay.DelaySamples;
set => delay.DelaySamples = value;
Expand All @@ -22,6 +25,7 @@ public int K {
/// <summary>
/// First minimum point.
/// </summary>
[DisplayName("Frequency (Hz)")]
public double Frequency {
get => sampleRate * .5 / K;
set => K = (int)(.5 / (value / sampleRate) + 1);
Expand Down Expand Up @@ -66,9 +70,7 @@ public Comb(int sampleRate, double frequency, double alpha) {
delay = new Delay((int)(.5 / (frequency / sampleRate) + 1));
}

/// <summary>
/// Apply comb on an array of samples. One filter should be applied to only one continuous stream of samples.
/// </summary>
/// <inheritdoc/>
public override void Process(float[] samples) {
if (cache.Length != samples.Length) {
cache = new float[samples.Length];
Expand All @@ -77,7 +79,7 @@ public override void Process(float[] samples) {
delay.Process(cache);
float alpha = (float)Alpha,
divisor = 1 / (1 + alpha);
for (int sample = 0; sample < samples.Length; ++sample) {
for (int sample = 0; sample < samples.Length; sample++) {
samples[sample] = (samples[sample] + cache[sample] * alpha) * divisor;
}
}
Expand Down
3 changes: 3 additions & 0 deletions Cavern/Filters/Crossover.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;

namespace Cavern.Filters {
/// <summary>
Expand All @@ -13,6 +14,7 @@ public class Crossover : Filter {
/// <summary>
/// Crossover frequency.
/// </summary>
[DisplayName("Frequency (Hz)")]
public double Frequency {
get => lowpasses[0].CenterFreq;
set {
Expand All @@ -27,6 +29,7 @@ public double Frequency {
/// </summary>
/// <remarks>A value of 2 is recommended for notch prevention when mixing
/// <see cref="LowOutput"/> and <see cref="HighOutput"/> back together.</remarks>
[DisplayName("Order")]
public int Order {
get => lowpasses.Length;
set => RecreateFilters(lowpasses[0].CenterFreq, value);
Expand Down
11 changes: 2 additions & 9 deletions Cavern/Filters/DebugCrossover.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,15 @@ public DebugCrossover(int sampleRate, double frequency) : base(sampleRate, frequ
/// <param name="order">Number of filters per pass, 2 is recommended for mixing notch prevention</param>
public DebugCrossover(int sampleRate, double frequency, int order) : base(sampleRate, frequency, order) { }

/// <summary>
/// Apply crossover on an array of samples. One filter should be applied to only one continuous stream of samples.
/// </summary>
/// <inheritdoc/>
public override void Process(float[] samples) {
base.Process(samples);
for (int i = 0; i < samples.Length; ++i) {
samples[i] = LowOutput[i] + HighOutput[i];
}
}

/// <summary>
/// Apply crossover on an array of samples. One filter should be applied to only one continuous stream of samples.
/// </summary>
/// <param name="samples">Input samples</param>
/// <param name="channel">Channel to filter</param>
/// <param name="channels">Total channels</param>
/// <inheritdoc/>
public override void Process(float[] samples, int channel, int channels) {
base.Process(samples, channel, channels);
for (int sample = channel; sample < samples.Length; sample += channels) {
Expand Down
42 changes: 39 additions & 3 deletions Cavern/Filters/Delay.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;

Expand All @@ -13,15 +14,46 @@ public class Delay : Filter, IEqualizerAPOFilter {
/// <summary>
/// Delay in samples.
/// </summary>
[DisplayName("Delay (samples)")]
public int DelaySamples {
get => cache[0].Length;
set {
if (cache[0].Length != value) {
RecreateCaches(value);
delayMs = double.NaN;
}
}
}

/// <summary>
/// Delay in milliseconds.
/// </summary>
[DisplayName("Delay (ms)")]
public double DelayMs {
get {
if (!double.IsNaN(delayMs)) {
return delayMs;
}
if (sampleRate == 0) {
throw new SampleRateNotSetException();
}
return DelaySamples / (double)sampleRate * 1000;
}

set {
if (sampleRate == 0) {
throw new SampleRateNotSetException();
}
DelaySamples = (int)Math.Round(value * sampleRate * .001);
delayMs = value;
}
}

/// <summary>
/// When the filter was created with a precise delay that is not a round value in samples, display this instead.
/// </summary>
double delayMs;

/// <summary>
/// Cached samples for the next block. Alternates between two arrays to prevent memory allocation.
/// </summary>
Expand All @@ -45,14 +77,18 @@ void RecreateCaches(int size) {
/// <summary>
/// Create a delay for a given length in samples.
/// </summary>
public Delay(int samples) => RecreateCaches(samples);
public Delay(int samples) {
delayMs = double.NaN;
RecreateCaches(samples);
}

/// <summary>
/// Create a delay for a given length in seconds.
/// </summary>
public Delay(double time, int sampleRate) {
this.sampleRate = sampleRate;
RecreateCaches((int)(time * sampleRate + .5f));
delayMs = time;
RecreateCaches((int)(time * sampleRate * .001 + .5));
}

/// <summary>
Expand Down Expand Up @@ -110,7 +146,7 @@ public override string ToString() {
if (sampleRate == 0) {
return $"Delay: {DelaySamples} samples";
} else {
string delay = ((double)DelaySamples / sampleRate).ToString(CultureInfo.InvariantCulture);
string delay = DelayMs.ToString(CultureInfo.InvariantCulture);
return $"Delay: {delay} ms";
}
}
Expand Down
1 change: 1 addition & 0 deletions Cavern/Filters/Filter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public abstract class Filter {
/// <summary>
/// Apply this filter on an array of samples. One filter should be applied to only one continuous stream of samples.
/// </summary>
/// <param name="samples">Input samples</param>
public virtual void Process(float[] samples) => Process(samples, 0, 1);

/// <summary>
Expand Down
20 changes: 6 additions & 14 deletions Cavern/Filters/Gain.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;

using Cavern.Filters.Interfaces;
Expand All @@ -12,6 +13,7 @@ public class Gain : Filter, IEqualizerAPOFilter {
/// <summary>
/// Filter gain in decibels.
/// </summary>
[DisplayName("Gain (dB)")]
public double GainValue {
get => 20 * Math.Log10(gainValue);
set => gainValue = (float)Math.Pow(10, value * .05);
Expand All @@ -28,32 +30,22 @@ public double GainValue {
/// <param name="gain">Filter gain in decibels</param>
public Gain(double gain) => GainValue = gain;

/// <summary>
/// Apply gain on an array of samples. This filter can be used on multiple streams.
/// </summary>
/// <inheritdoc/>
public override void Process(float[] samples) {
for (int sample = 0; sample < samples.Length; ++sample) {
for (int sample = 0; sample < samples.Length; sample++) {
samples[sample] *= gainValue;
}
}

/// <summary>
/// Apply gain on an array of samples. This filter can be used on multiple streams.
/// </summary>
/// <param name="samples">Input samples</param>
/// <param name="channel">Channel to filter</param>
/// <param name="channels">Total channels</param>
/// <inheritdoc/>
public override void Process(float[] samples, int channel, int channels) {
for (int sample = channel; sample < samples.Length; sample += channels) {
samples[sample] *= gainValue;
}
}

/// <inheritdoc/>
public override string ToString() {
double rounded = (int)(GainValue * 100 + .5) * .01;
return $"Gain: {rounded.ToString(CultureInfo.InvariantCulture)} dB";
}
public override string ToString() => $"Gain: {GainValue.ToString("0.00", CultureInfo.InvariantCulture)} dB";

/// <inheritdoc/>
public void ExportToEqualizerAPO(List<string> wipConfig) =>
Expand Down
5 changes: 4 additions & 1 deletion Cavern/Filters/PhaseSwappableBiquadFilter.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
namespace Cavern.Filters {
using System.ComponentModel;

namespace Cavern.Filters {
/// <summary>
/// Simple first-order biquad filter with the option to invert the phase response.
/// </summary>
public abstract class PhaseSwappableBiquadFilter : BiquadFilter {
/// <summary>
/// Biquad filters usually achieve their effect by delaying lower frequencies. Phase swapping delays higher frequencies.
/// </summary>
[DisplayName("Phase-swapped")]
public bool PhaseSwapped {
get => phaseSwapped;
set {
Expand Down
4 changes: 1 addition & 3 deletions Cavern/Filters/SpikeConvolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ public static float[] SpikeConvolve(float[] impulse, float[] samples, int delay)
return convolved;
}

/// <summary>
/// Apply convolution on an array of samples. One filter should be applied to only one continuous stream of samples.
/// </summary>
/// <inheritdoc/>
public override void Process(float[] samples) {
float[] convolved;
if (delay == 0) {
Expand Down
15 changes: 15 additions & 0 deletions Cavern/Filters/_Exceptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace Cavern.Filters {
/// <summary>
/// Tells if a property can only be used when the filter was created with a set sample rate.
/// </summary>
public class SampleRateNotSetException : Exception {
const string message = "This property can only be used when the filter was created with a set sample rate.";

/// <summary>
/// Tells if a property can only be used when the filter was created with a set sample rate.
/// </summary>
public SampleRateNotSetException() : base(message) { }
}
}
15 changes: 15 additions & 0 deletions CavernSamples/FilterStudio/Consts/_Exceptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace FilterStudio {
/// <summary>
/// Tells if value can't be edited for a filter, because the type is not currently supported for parsing.
/// </summary>
public class UneditableTypeException : Exception {
const string message = "This value can't be edited, because the type is not currently supported for parsing.";

/// <summary>
/// Tells if value can't be edited for a filter, because the type is not currently supported for parsing.
/// </summary>
public UneditableTypeException() : base(message) { }
}
}
Loading

0 comments on commit 4079de4

Please sign in to comment.