Refactor Filter to behave closer to a Transformable

This commit is contained in:
Jamie Taylor
2021-10-02 01:21:55 +09:00
parent 1304b55c41
commit 2a4a376b87
4 changed files with 175 additions and 93 deletions

View File

@ -8,66 +8,87 @@ using osu.Framework.Graphics;
namespace osu.Game.Audio.Effects
{
public class Filter : Component
public class Filter : Component, ITransformableFilter
{
public BQFType FilterType = BQFType.LowPass;
public float SweepCutoffStart = 2000;
public float SweepCutoffEnd = 150;
public float SweepDuration = 100;
public Easing SweepEasing = Easing.None;
public bool IsActive { get; private set; }
private readonly Bindable<float> filterFreq = new Bindable<float>();
public readonly int MaxCutoff;
private readonly AudioMixer mixer;
private BQFParameters filter;
private readonly BQFParameters filter;
private readonly BQFType type;
public BindableNumber<int> Cutoff { get; }
/// <summary>
/// A BiQuad filter that performs a filter-sweep when toggled on or off.
/// </summary>
/// <param name="mixer">The mixer this effect should be attached to.</param>
public Filter(AudioMixer mixer)
/// <param name="type">The type of filter (e.g. LowPass, HighPass, etc)</param>
public Filter(AudioMixer mixer, BQFType type = BQFType.LowPass)
{
this.mixer = mixer;
}
this.type = type;
public void Enable()
{
attachFilter();
this.TransformBindableTo(filterFreq, SweepCutoffEnd, SweepDuration, SweepEasing);
}
var initialCutoff = 1;
public void Disable()
{
this.TransformBindableTo(filterFreq, SweepCutoffStart, SweepDuration, SweepEasing).OnComplete(_ => detachFilter());
}
// These max cutoff values are a work-around for BASS' BiQuad filters behaving weirdly when approaching nyquist.
// Note that these values assume a sample rate of 44100 (as per BassAudioMixer in osu.Framework)
// See also https://www.un4seen.com/forum/?topic=19542.0 for more information.
switch (type)
{
case BQFType.HighPass:
MaxCutoff = 21968; // beyond this value, the high-pass cuts out
break;
private void attachFilter()
{
if (IsActive) return;
case BQFType.LowPass:
MaxCutoff = initialCutoff = 14000; // beyond (roughly) this value, the low-pass filter audibly wraps/reflects
break;
case BQFType.BandPass:
MaxCutoff = 16000; // beyond (roughly) this value, the band-pass filter audibly wraps/reflects
break;
default:
MaxCutoff = 22050; // default to nyquist for other filter types, TODO: handle quirks of other filter types
break;
}
Cutoff = new BindableNumber<int>
{
MinValue = 1,
MaxValue = MaxCutoff
};
filter = new BQFParameters
{
lFilter = FilterType,
fCenter = filterFreq.Value = SweepCutoffStart
lFilter = type,
fCenter = initialCutoff
};
mixer.Effects.Add(filter);
filterFreq.ValueChanged += updateFilter;
IsActive = true;
attachFilter();
Cutoff.ValueChanged += updateFilter;
Cutoff.Value = initialCutoff;
}
private void detachFilter()
{
if (!IsActive) return;
private void attachFilter() => mixer.Effects.Add(filter);
filterFreq.ValueChanged -= updateFilter;
mixer.Effects.Remove(filter);
IsActive = false;
}
private void detachFilter() => mixer.Effects.Remove(filter);
private void updateFilter(ValueChangedEvent<float> cutoff)
private void updateFilter(ValueChangedEvent<int> cutoff)
{
// This is another workaround for quirks in BASS' BiQuad filters.
// Because the cutoff can't be set above ~14khz (i.e. outside of human hearing range) without the aforementioned wrapping/reflecting quirk occuring, we instead
// remove the effect from the mixer when the cutoff is at maximum so that a LowPass filter isn't always attenuating high frequencies just by existing.
if (type == BQFType.LowPass)
{
if (cutoff.NewValue >= MaxCutoff)
{
detachFilter();
return;
}
if (cutoff.OldValue >= MaxCutoff && cutoff.NewValue < MaxCutoff)
attachFilter();
}
var filterIndex = mixer.Effects.IndexOf(filter);
if (filterIndex < 0) return;

View File

@ -0,0 +1,54 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Transforms;
namespace osu.Game.Audio.Effects
{
public interface ITransformableFilter
{
/// <summary>
/// The filter cutoff.
/// </summary>
BindableNumber<int> Cutoff { get; }
}
public static class FilterableAudioComponentExtensions
{
/// <summary>
/// Smoothly adjusts filter cutoff over time.
/// </summary>
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
public static TransformSequence<T> CutoffTo<T>(this T component, int newCutoff, double duration = 0, Easing easing = Easing.None)
where T : class, ITransformableFilter, IDrawable
=> component.CutoffTo(newCutoff, duration, new DefaultEasingFunction(easing));
/// <summary>
/// Smoothly adjusts filter cutoff over time.
/// </summary>
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
public static TransformSequence<T> CutoffTo<T>(this TransformSequence<T> sequence, int newCutoff, double duration = 0, Easing easing = Easing.None)
where T : class, ITransformableFilter, IDrawable
=> sequence.CutoffTo(newCutoff, duration, new DefaultEasingFunction(easing));
/// <summary>
/// Smoothly adjusts filter cutoff over time.
/// </summary>
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
public static TransformSequence<T> CutoffTo<T, TEasing>(this T component, int newCutoff, double duration, TEasing easing)
where T : class, ITransformableFilter, IDrawable
where TEasing : IEasingFunction
=> component.TransformBindableTo(component.Cutoff, newCutoff, duration, easing);
/// <summary>
/// Smoothly adjusts filter cutoff over time.
/// </summary>
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
public static TransformSequence<T> CutoffTo<T, TEasing>(this TransformSequence<T> sequence, int newCutoff, double duration, TEasing easing)
where T : class, ITransformableFilter, IDrawable
where TEasing : IEasingFunction
=> sequence.Append(o => o.TransformBindableTo(o.Cutoff, newCutoff, duration, easing));
}
}