Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions osu.Game/Audio/DecibelScaling.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;

namespace osu.Game.Audio
{
/// <summary>
/// Common functions and constants for implementing decibel scaling into sliders and meters.
/// </summary>
public static class DecibelScaling
{
/// <summary>
/// Arbitrary silence threshold. Required for sliders, since the decibel scale is bottomless.
/// </summary>
public const double DB_MIN = -60;

/// <summary>
/// Decibel equivalent of full volume.
/// </summary>
public const double DB_MAX = 0;

/// <summary>
/// Decibel precision level.
/// </summary>
public const double DB_PRECISION = 0.5;

/// <summary>
/// Linear equivalent of <see cref="DB_MIN"/>
/// </summary>
private static readonly double cutoff = Math.Pow(10, DB_MIN / 20);

/// <summary>
/// Returns the decibel equivalent of a linear value.
/// </summary>
public static double DecibelFromLinear(double linear) => linear <= cutoff ? DB_MIN : 20 * Math.Log10(linear);

/// <summary>
/// Returns the linear equivalent of a decibel value.
/// </summary>
public static double LinearFromDecibel(double decibel) => decibel <= DB_MIN ? 0 : Math.Pow(10, decibel / 20);
}
}
2 changes: 1 addition & 1 deletion osu.Game/Configuration/OsuConfigManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ protected override void InitialiseDefaults()
SetDefault(OsuSetting.NotifyOnFriendPresenceChange, true);

// Audio
SetDefault(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01);
SetDefault(OsuSetting.VolumeInactive, 0.25, 0, 1);

SetDefault(OsuSetting.MenuVoice, true);
SetDefault(OsuSetting.MenuMusic, true);
Expand Down
79 changes: 61 additions & 18 deletions osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using static osu.Game.Audio.DecibelScaling;

namespace osu.Game.Overlays.Settings.Sections.Audio
{
Expand All @@ -23,42 +25,83 @@ private void load(AudioManager audio, OsuConfigManager config)
new VolumeAdjustSlider
{
LabelText = AudioSettingsStrings.MasterVolume,
Current = audio.Volume,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
Volume = audio.Volume,
},
new SettingsSlider<double>
new VolumeAdjustSlider
{
LabelText = AudioSettingsStrings.MasterVolumeInactive,
Current = config.GetBindable<double>(OsuSetting.VolumeInactive),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
Volume = config.GetBindable<double>(OsuSetting.VolumeInactive),
PlaySamplesOnAdjust = true,
},
new VolumeAdjustSlider
{
LabelText = AudioSettingsStrings.EffectVolume,
Current = audio.VolumeSample,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
Volume = audio.VolumeSample,
},

new VolumeAdjustSlider
{
LabelText = AudioSettingsStrings.MusicVolume,
Current = audio.VolumeTrack,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
Volume = audio.VolumeTrack,
},
};
}

private partial class VolumeAdjustSlider : SettingsSlider<double>
{
protected override Drawable CreateControl()
protected override Drawable CreateControl() => new DecibelSliderBar();

public bool PlaySamplesOnAdjust { set => ((DecibelSliderBar)Control).PlaySamplesOnAdjust = value; }

public Bindable<double> Volume { set => ((DecibelSliderBar)Control).Volume = value; }

protected partial class DecibelSliderBar : RoundedSliderBar<double>
{
var sliderBar = (RoundedSliderBar<double>)base.CreateControl();
sliderBar.PlaySamplesOnAdjust = false;
return sliderBar;
public override LocalisableString TooltipText => Current.Value <= DB_MIN ? "-∞ dB" : $"{Current.Value:+#0.0;-#0.0;+0.0} dB";

public DecibelSliderBar()
{
RelativeSizeAxes = Axes.X;
PlaySamplesOnAdjust = false;
KeyboardStep = (float)DB_PRECISION;

Current = new BindableNumber<double>(0)
{
Precision = DB_PRECISION,
MinValue = DB_MIN,
MaxValue = DB_MAX,
};
}

public Bindable<double> Volume = new Bindable<double>(1);

private bool currentFirstInvoked;
private bool volumeFirstInvoked;

protected override void LoadComplete()
{
base.LoadComplete();
Current.Default = DecibelFromLinear(Volume.Default);

Current.ValueChanged += v =>
{
if (!volumeFirstInvoked)
{
currentFirstInvoked = true;
Volume.Value = LinearFromDecibel(v.NewValue);
currentFirstInvoked = false;
}
};

Volume.BindValueChanged(v =>
{
if (!currentFirstInvoked)
{
volumeFirstInvoked = true;
Current.Value = DecibelFromLinear(v.NewValue);
volumeFirstInvoked = false;
}
}, true);
}
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using osu.Game.Input.Bindings;
using osuTK.Graphics;
using osuTK.Input;
using static osu.Game.Audio.DecibelScaling;

namespace osu.Game.Overlays.Toolbar
{
Expand Down Expand Up @@ -78,7 +79,7 @@ protected override void LoadComplete()
base.LoadComplete();

globalVolume = audio.Volume.GetBoundCopy();
globalVolume.BindValueChanged(v => volumeBar.ResizeHeightTo((float)v.NewValue, 200, Easing.OutQuint), true);
globalVolume.BindValueChanged(v => volumeBar.ResizeHeightTo((float)((DecibelFromLinear(v.NewValue) - DB_MIN) / (DB_MAX - DB_MIN)), 200, Easing.OutQuint), true);
}

protected override bool OnKeyDown(KeyDownEvent e)
Expand Down
57 changes: 34 additions & 23 deletions osu.Game/Overlays/Volume/VolumeMeter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
using osu.Game.Input.Bindings;
using osuTK;
using osuTK.Graphics;
using static osu.Game.Audio.DecibelScaling;

namespace osu.Game.Overlays.Volume
{
Expand All @@ -37,7 +38,7 @@ public partial class VolumeMeter : Container, IStateful<SelectionState>

protected static readonly Vector2 LABEL_SIZE = new Vector2(120, 20);

public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1, Precision = 0.01 };
public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 };

protected readonly float CircleSize;

Expand Down Expand Up @@ -237,42 +238,51 @@ private void load(OsuColour colours, AudioManager audio)
}
};

Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true);
Bindable.BindValueChanged(volume =>
{
decibel = DecibelFromLinear(volume.NewValue);
this.TransformTo(nameof(DisplayVolume), decibel, 400, Easing.OutQuint);
}, true);

bgProgress.Progress = 0.75f;
}

private int? displayVolumeInt;
private int currentStep;
private const int step_min = (int)(DB_MIN / DB_PRECISION);
private const int step_max = (int)(DB_MAX / DB_PRECISION);

private double decibel;
private double displayVolume;
private double normalizedVolume;

protected double DisplayVolume
{
get => displayVolume;
set
{
displayVolume = value;
normalizedVolume = (value - DB_MIN) / (DB_MAX - DB_MIN);

int intValue = (int)Math.Round(displayVolume * 100);
bool intVolumeChanged = intValue != displayVolumeInt;
int step = (int)Math.Round(value / DB_PRECISION);
bool stepChanged = step != currentStep;

displayVolumeInt = intValue;
currentStep = step;
displayVolume = currentStep * DB_PRECISION;

if (displayVolume >= 0.995f)
if (currentStep >= step_max)
{
text.Text = "MAX";
maxGlow.EffectColour = meterColour.Opacity(2f);
}
else
{
maxGlow.EffectColour = Color4.Transparent;
text.Text = intValue.ToString(CultureInfo.CurrentCulture);
text.Text = currentStep <= step_min ? "-INF" : displayVolume.ToString("N1", CultureInfo.CurrentCulture);
}

volumeCircle.Progress = displayVolume * 0.75f;
volumeCircleGlow.Progress = displayVolume * 0.75f;
volumeCircle.Progress = normalizedVolume * 0.75f;
volumeCircleGlow.Progress = normalizedVolume * 0.75f;

if (intVolumeChanged && IsLoaded)
if (stepChanged && IsLoaded)
Scheduler.AddOnce(playTickSound);
}
}
Expand All @@ -286,10 +296,10 @@ private void playTickSound()

var channel = notchSample.GetChannel();

channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + displayVolume * 0.1f;
channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + (normalizedVolume * 0.1f);

// intentionally pitched down, even when hitting max.
if (displayVolumeInt == 0 || displayVolumeInt == 100)
if (currentStep == step_min || currentStep == step_max)
channel.Frequency.Value -= 0.5f;

channel.Play();
Expand All @@ -302,8 +312,6 @@ public double Volume
private set => Bindable.Value = value;
}

private const double adjust_step = 0.01;

public void Increase(double amount = 1, bool isPrecise = false) => adjust(amount, isPrecise);
public void Decrease(double amount = 1, bool isPrecise = false) => adjust(-amount, isPrecise);

Expand Down Expand Up @@ -336,13 +344,13 @@ protected override void OnDrag(DragEvent e)

private void adjustFromDrag(Vector2 delta)
{
const float mouse_drag_divisor = 200;
const float mouse_drag_divisor = (float)(2 / DB_PRECISION);

dragDelta += delta.Y / mouse_drag_divisor;

if (Math.Abs(dragDelta) < 0.01) return;
if (Math.Abs(dragDelta) < DB_PRECISION) return;

Volume -= dragDelta;
Volume = LinearFromDecibel(decibel - dragDelta);
dragDelta = 0;
}

Expand All @@ -359,22 +367,25 @@ private void adjust(double delta, bool isPrecise)
delta *= accelerationModifier;
accelerationModifier = Math.Min(max_acceleration, accelerationModifier * acceleration_multiplier);

double precision = Bindable.Precision;
double dB = decibel;
const double precision = DB_PRECISION;

if (isPrecise)
{
scrollAccumulation += delta * adjust_step;
scrollAccumulation += delta * precision;

while (Precision.AlmostBigger(Math.Abs(scrollAccumulation), precision))
{
Volume += Math.Sign(scrollAccumulation) * precision;
dB += Math.Sign(scrollAccumulation) * precision;
scrollAccumulation = scrollAccumulation < 0 ? Math.Min(0, scrollAccumulation + precision) : Math.Max(0, scrollAccumulation - precision);
}
}
else
{
Volume += Math.Sign(delta) * Math.Max(precision, Math.Abs(delta * adjust_step));
dB += Math.Sign(delta) * Math.Max(precision, Math.Abs(delta * precision));
}

Volume = LinearFromDecibel(dB);
}

protected override bool OnScroll(ScrollEvent e)
Expand Down
Loading