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 BindableVolume Volume;

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

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

/// <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 BindableVolume(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
47 changes: 47 additions & 0 deletions osu.Framework/Audio/BindableVolume.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// 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 BindableVolume
{
public const double MIN = -60;
public const double STEP = 0.5;

private const double ln_ten = 2.302585092994045684017991454684364208;
private const double k = ln_ten / 20;

public readonly BindableNumber<double> Real;
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 BindableVolume(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);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

in what world is it sane to have a property return values on a different scale than the one used to set the value

it should not be the case that after you set a property's value, you read it back and get a completely different number. that is nonsensical

Copy link
Author

Choose a reason for hiding this comment

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

I should probably be able to get rid of that. I think at first, I wasn't as explicit about whether I was using the real value or the scaled value.

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.

Okay, so it does sorta make sense. We don't want to assign directly to Real. Instead, we want to assign to Scaled only, and whenever we do, that triggers a BindValueChanged function, and that's where Real gets assigned. So assigning to Scaled effectively assigns to both of them.

Real doesn't have an equivalent BindValueChanged function as that would result in an infinite loop.

Also, instead of assigning directly to Scaled, sometimes we want to assign a linear value, but it has to go through Scaled first, so the setter's job is to first translate the linear value into a logarithmic value.

I'll probably still get rid of the getter, as that is a bit confusing, and I think it's just there for convenience sake, but it's better to be explicit.


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