1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 11:37:28 +08:00

Rewrite AudioFilter to be easier to follow (and fix tests)

This commit is contained in:
Dean Herbert 2021-10-13 15:13:35 +09:00
parent ae4dcbd829
commit 29dfe33465
2 changed files with 119 additions and 87 deletions

View File

@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@ -18,84 +19,112 @@ namespace osu.Game.Tests.Visual.Audio
{
public class TestSceneAudioFilter : OsuTestScene
{
private OsuSpriteText lowpassText;
private AudioFilter lowpassFilter;
private OsuSpriteText lowPassText;
private AudioFilter lowPassFilter;
private OsuSpriteText highpassText;
private AudioFilter highpassFilter;
private OsuSpriteText highPassText;
private AudioFilter highPassFilter;
private Track track;
private WaveformTestBeatmap beatmap;
private OsuSliderBar<int> lowPassSlider;
private OsuSliderBar<int> highPassSlider;
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
beatmap = new WaveformTestBeatmap(audio);
track = beatmap.LoadTrack();
OsuSliderBar<int> lowPassCutoff;
OsuSliderBar<int> highPassCutoff;
Add(new FillFlowContainer
{
Children = new Drawable[]
{
lowpassFilter = new AudioFilter(audio.TrackMixer),
highpassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass),
lowpassText = new OsuSpriteText
lowPassFilter = new AudioFilter(audio.TrackMixer),
highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass),
lowPassText = new OsuSpriteText
{
Padding = new MarginPadding(20),
Text = $"Low Pass: {lowpassFilter.Cutoff}hz",
Text = $"Low Pass: {lowPassFilter.Cutoff}hz",
Font = new FontUsage(size: 40)
},
lowPassCutoff = new OsuSliderBar<int>
lowPassSlider = new OsuSliderBar<int>
{
Width = 500,
Height = 50,
Padding = new MarginPadding(20),
Current = new BindableInt
{
MinValue = 0,
MaxValue = AudioFilter.MAX_LOWPASS_CUTOFF,
}
},
highpassText = new OsuSpriteText
highPassText = new OsuSpriteText
{
Padding = new MarginPadding(20),
Text = $"High Pass: {highpassFilter.Cutoff}hz",
Text = $"High Pass: {highPassFilter.Cutoff}hz",
Font = new FontUsage(size: 40)
},
highPassCutoff = new OsuSliderBar<int>
highPassSlider = new OsuSliderBar<int>
{
Width = 500,
Height = 50,
Padding = new MarginPadding(20),
Current = new BindableInt
{
MinValue = 0,
MaxValue = AudioFilter.MAX_LOWPASS_CUTOFF,
}
}
}
});
lowPassCutoff.Current.ValueChanged += e =>
lowPassSlider.Current.ValueChanged += e =>
{
lowpassText.Text = $"Low Pass: {e.NewValue}hz";
lowpassFilter.Cutoff = e.NewValue;
lowPassText.Text = $"Low Pass: {e.NewValue}hz";
lowPassFilter.Cutoff = e.NewValue;
};
highPassCutoff.Current.ValueChanged += e =>
highPassSlider.Current.ValueChanged += e =>
{
highpassText.Text = $"High Pass: {e.NewValue}hz";
highpassFilter.Cutoff = e.NewValue;
highPassText.Text = $"High Pass: {e.NewValue}hz";
highPassFilter.Cutoff = e.NewValue;
};
}
#region Overrides of Drawable
protected override void Update()
{
base.Update();
highPassSlider.Current.Value = highPassFilter.Cutoff;
lowPassSlider.Current.Value = lowPassFilter.Cutoff;
}
#endregion
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Play Track", () => track.Start());
AddStep("Reset filters", () =>
{
lowPassFilter.Cutoff = AudioFilter.MAX_LOWPASS_CUTOFF;
highPassFilter.Cutoff = 0;
});
waitTrackPlay();
}
[Test]
public void TestLowPass()
public void TestLowPassSweep()
{
AddStep("Filter Sweep", () =>
{
lowpassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF).Then()
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF).Then()
.CutoffTo(0, 2000, Easing.OutCubic);
});
@ -103,7 +132,7 @@ namespace osu.Game.Tests.Visual.Audio
AddStep("Filter Sweep (reverse)", () =>
{
lowpassFilter.CutoffTo(0).Then()
lowPassFilter.CutoffTo(0).Then()
.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 2000, Easing.InCubic);
});
@ -112,11 +141,11 @@ namespace osu.Game.Tests.Visual.Audio
}
[Test]
public void TestHighPass()
public void TestHighPassSweep()
{
AddStep("Filter Sweep", () =>
{
highpassFilter.CutoffTo(0).Then()
highPassFilter.CutoffTo(0).Then()
.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 2000, Easing.InCubic);
});
@ -124,7 +153,7 @@ namespace osu.Game.Tests.Visual.Audio
AddStep("Filter Sweep (reverse)", () =>
{
highpassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF).Then()
highPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF).Then()
.CutoffTo(0, 2000, Easing.OutCubic);
});

View File

@ -20,6 +20,8 @@ namespace osu.Game.Audio.Effects
private readonly BQFParameters filter;
private readonly BQFType type;
private bool isAttached;
private int cutoff;
/// <summary>
@ -33,10 +35,8 @@ namespace osu.Game.Audio.Effects
if (value == cutoff)
return;
int oldValue = cutoff;
cutoff = value;
updateFilter(oldValue, cutoff);
updateFilter(cutoff);
}
}
@ -50,73 +50,58 @@ namespace osu.Game.Audio.Effects
this.mixer = mixer;
this.type = type;
switch (type)
{
case BQFType.HighPass:
cutoff = 1;
break;
case BQFType.LowPass:
cutoff = MAX_LOWPASS_CUTOFF;
break;
default:
cutoff = 500; // A default that should ensure audio remains audible for other filters.
break;
}
filter = new BQFParameters
{
lFilter = type,
fCenter = cutoff,
fBandwidth = 0,
fQ = 0.7f // This allows fCenter to go up to 22049hz (nyquist - 1hz) without overflowing and causing weird filter behaviour (see: https://www.un4seen.com/forum/?topic=19542.0)
// This allows fCenter to go up to 22049hz (nyquist - 1hz) without overflowing and causing weird filter behaviour (see: https://www.un4seen.com/forum/?topic=19542.0)
fQ = 0.7f
};
// Don't start attached if this is low-pass or high-pass filter (as they have special auto-attach/detach logic)
if (type != BQFType.LowPass && type != BQFType.HighPass)
attachFilter();
Cutoff = getInitialCutoff(type);
}
private void attachFilter()
private int getInitialCutoff(BQFType type)
{
Debug.Assert(!mixer.Effects.Contains(filter));
mixer.Effects.Add(filter);
}
private void detachFilter()
{
Debug.Assert(mixer.Effects.Contains(filter));
mixer.Effects.Remove(filter);
}
private void updateFilter(int oldValue, int newValue)
{
// Workaround for weird behaviour when rapidly setting fCenter of a low-pass filter to nyquist - 1hz.
if (type == BQFType.LowPass)
switch (type)
{
if (newValue >= MAX_LOWPASS_CUTOFF)
{
detachFilter();
return;
}
case BQFType.HighPass:
return 1;
if (oldValue >= MAX_LOWPASS_CUTOFF)
attachFilter();
case BQFType.LowPass:
return MAX_LOWPASS_CUTOFF;
default:
return 500; // A default that should ensure audio remains audible for other filters.
}
}
private void updateFilter(int newValue)
{
switch (type)
{
case BQFType.LowPass:
// Workaround for weird behaviour when rapidly setting fCenter of a low-pass filter to nyquist - 1hz.
if (newValue >= MAX_LOWPASS_CUTOFF)
{
ensureDetached();
return;
}
break;
// Workaround for weird behaviour when rapidly setting fCenter of a high-pass filter to 1hz.
case BQFType.HighPass:
if (newValue <= 1)
{
ensureDetached();
return;
}
break;
}
// Workaround for weird behaviour when rapidly setting fCenter of a high-pass filter to 1hz.
if (type == BQFType.HighPass)
{
if (newValue <= 1)
{
detachFilter();
return;
}
if (oldValue <= 1)
attachFilter();
}
ensureAttached();
var filterIndex = mixer.Effects.IndexOf(filter);
@ -131,12 +116,30 @@ namespace osu.Game.Audio.Effects
}
}
private void ensureAttached()
{
if (isAttached)
return;
Debug.Assert(!mixer.Effects.Contains(filter));
mixer.Effects.Add(filter);
isAttached = true;
}
private void ensureDetached()
{
if (!isAttached)
return;
Debug.Assert(mixer.Effects.Contains(filter));
mixer.Effects.Remove(filter);
isAttached = false;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (mixer.Effects.Contains(filter))
detachFilter();
ensureDetached();
}
}
}