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:
parent
ae4dcbd829
commit
29dfe33465
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user