Skip to content
Closed
23 changes: 11 additions & 12 deletions osu.Framework/Audio/AudioManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,23 +90,20 @@ public class AudioManager : AudioCollectionManager<AudioComponent>
/// </summary>
public readonly Bindable<string> AudioDevice = new Bindable<string>();

/// <summary>
/// Volume of all audio game-wide.
/// </summary>
public new readonly VolumeScaler Volume;

/// <summary>
/// Volume of all samples played game-wide.
/// </summary>
public readonly BindableDouble VolumeSample = new BindableDouble(1)
{
MinValue = 0,
MaxValue = 1
};
public readonly VolumeScaler VolumeSample = new VolumeScaler();

/// <summary>
/// Volume of all tracks played game-wide.
/// </summary>
public readonly BindableDouble VolumeTrack = new BindableDouble(1)
{
MinValue = 0,
MaxValue = 1
};
public readonly VolumeScaler VolumeTrack = new VolumeScaler();

/// <summary>
/// Whether a global mixer is being used for audio routing.
Expand Down Expand Up @@ -179,19 +176,21 @@ public AudioManager(AudioThread audioThread, ResourceStore<byte[]> trackStore, R
AddItem(TrackMixer = createAudioMixer(null, nameof(TrackMixer)));
AddItem(SampleMixer = createAudioMixer(null, nameof(SampleMixer)));

Volume = new VolumeScaler(base.Volume);

globalTrackStore = new Lazy<TrackStore>(() =>
{
var store = new TrackStore(trackStore, TrackMixer);
AddItem(store);
store.AddAdjustment(AdjustableProperty.Volume, VolumeTrack);
store.AddAdjustment(AdjustableProperty.Volume, VolumeTrack.Real);
return store;
});

globalSampleStore = new Lazy<SampleStore>(() =>
{
var store = new SampleStore(sampleStore, SampleMixer);
AddItem(store);
store.AddAdjustment(AdjustableProperty.Volume, VolumeSample);
store.AddAdjustment(AdjustableProperty.Volume, VolumeSample.Real);
return store;
});

Expand Down
46 changes: 46 additions & 0 deletions osu.Framework/Audio/VolumeScaler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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;
using osu.Framework.Bindables;

namespace osu.Framework.Audio
{
public class VolumeScaler
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be renamed to BindableVolume or similar. VolumeScaler reads really bad as a class name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also please xmldoc this whole class. Every public property and the class itself.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also probably seal it. I don't think a framework consumer should be attempting to make a custom implementation of this ever.

{
public const double MIN = -60;
public const double STEP = 0.5;

private const double k = Math.Log(10) / 20;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constant declarations must use compile-time constants on the rhs of the assignment. this can't work with const, it must be static readonly. are you even compiling this code?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kinda still want k to be const. I guess that's why I went with the numeric literal to begin with.

const will get embedded into the IL code wherever it's used, whereas static readonly requires a memory lookup each time it's accessed.

Copy link
Collaborator

@bdach bdach Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please actually profile this and get back to me. i am 99% confident this will not be measurable. i prefer non-obfuscated code over pedantically marginal performance 'gains'.

Copy link
Author

@myQwil myQwil Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you like, I can change the name of the variable from ln_ten to something that's more readable.

But if the code is decently readable either way, why use the slower version, regardless of how pedantically marginal the difference may be?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no solution with the const that i would consider more readable than just writing out what the number is

but i also have no time nor patience to continue this discussion

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make is static readonly or close the PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certainly. I apologize if I came across as confrontational. I'm just trying to have a discussion about your coding philosophies and the reasons behind them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's pretty simple: storing one value to memory that is going to take up 8 bytes is not a concern and shouldn't lead to discussion. By using it we don't have to figure out what a random constant decimal number is and check whether it's correct.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I see what you're saying. I figured if I just gave the variable a good enough name, describing what it's supposed to represent, then everything would be fine, but at the end of the day, there's still this big ugly numeric literal sitting in the middle of the code and it's a bit unsightly.


public readonly BindableNumber<double> Real;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call these Linear and Logarithmic?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this "real" one looks like it should not be externally mutable. should either be IBindableNumber<double>, or if you're gonna keep it mutable, it should be synced both ways (changing "real" should also change "scaled")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this "real" one looks like it should not be externally mutable

I was hoping it would remain mutable so that we aren't deciding on a developers' behalf which they choose to use. Both may have valid use cases, depending on how you're audio engineeringing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, as long as it's not possible to break the instances of this into complete nonsense by setting both to random values and putting it in a completely invalid state

public readonly BindableNumber<double> Scaled = new BindableNumber<double>(1)
{
MinValue = MIN,
MaxValue = 0,
Precision = STEP,
};

private double scaledToReal(double x) => x <= MIN ? 0 : Math.Exp(k * x);

private double realToScaled(double x) => x <= 0 ? MIN : Math.Log(x) / k;

public VolumeScaler(BindableNumber<double>? real = null)
{
Real = real ?? new BindableNumber<double>(1) { MinValue = 0, MaxValue = 1 };
Scaled.BindValueChanged(x => Real.Value = scaledToReal(x.NewValue));
}

public double Value
{
get => Real.Value;
set => Scaled.Value = realToScaled(value);
}

public void Scale()
{
Scaled.Value = realToScaled(Real.Value);
Scaled.Default = realToScaled(Real.Default);
}
}
}
6 changes: 3 additions & 3 deletions osu.Framework/Configuration/FrameworkConfigManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ protected override void InitialiseDefaults()
SetDefault(FrameworkSetting.WindowedPositionY, 0.5, -0.5, 1.5);
SetDefault(FrameworkSetting.LastDisplayDevice, DisplayIndex.Default);
SetDefault(FrameworkSetting.AudioDevice, string.Empty);
SetDefault(FrameworkSetting.VolumeUniversal, 1.0, 0.0, 1.0, 0.01);
SetDefault(FrameworkSetting.VolumeMusic, 1.0, 0.0, 1.0, 0.01);
SetDefault(FrameworkSetting.VolumeEffect, 1.0, 0.0, 1.0, 0.01);
SetDefault(FrameworkSetting.VolumeUniversal, 1.0, 0.0, 1.0);
SetDefault(FrameworkSetting.VolumeMusic, 1.0, 0.0, 1.0);
SetDefault(FrameworkSetting.VolumeEffect, 1.0, 0.0, 1.0);
SetDefault(FrameworkSetting.HardwareVideoDecoder, HardwareVideoDecoder.Any);
SetDefault(FrameworkSetting.SizeFullscreen, new Size(9999, 9999), new Size(320, 240));
SetDefault(FrameworkSetting.MinimiseOnFocusLossInFullscreen, RuntimeInfo.IsDesktop);
Expand Down
10 changes: 7 additions & 3 deletions osu.Framework/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,13 @@ private void load(FrameworkConfigManager config)

// attach our bindables to the audio subsystem.
config.BindWith(FrameworkSetting.AudioDevice, Audio.AudioDevice);
config.BindWith(FrameworkSetting.VolumeUniversal, Audio.Volume);
config.BindWith(FrameworkSetting.VolumeEffect, Audio.VolumeSample);
config.BindWith(FrameworkSetting.VolumeMusic, Audio.VolumeTrack);
config.BindWith(FrameworkSetting.VolumeUniversal, Audio.Volume.Real);
config.BindWith(FrameworkSetting.VolumeEffect, Audio.VolumeSample.Real);
config.BindWith(FrameworkSetting.VolumeMusic, Audio.VolumeTrack.Real);

Audio.Volume.Scale();
Audio.VolumeSample.Scale();
Audio.VolumeTrack.Scale();
Comment on lines +180 to +182
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be required. It will happen on BindWith.


Shaders = new ShaderManager(Host.Renderer, new NamespacedResourceStore<byte[]>(Resources, @"Shaders"));
dependencies.Cache(Shaders);
Expand Down