Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
2 changes: 2 additions & 0 deletions osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ private void load()
bodyPiece.CreateProxy(),
tailContainer.CreateProxy(),
});

slidingSample.BindAdjustments(Samples);
}

protected override void LoadComplete()
Expand Down
2 changes: 2 additions & 0 deletions osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ private void load()
foreach (var drawableHitObject in NestedHitObjects)
drawableHitObject.AccentColour.Value = colour.NewValue;
}, true);

slidingSample.BindAdjustments(Samples);
}

protected override JudgementResult CreateResult(Judgement judgement) => new OsuSliderJudgementResult(HitObject, judgement);
Expand Down
3 changes: 3 additions & 0 deletions osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ private void load()
});

PositionBindable.BindValueChanged(pos => Position = pos.NewValue);

spinningSample.BindAdjustments(Samples);
maxBonusSample.BindAdjustments(Samples);
}

protected override void LoadComplete()
Expand Down
63 changes: 6 additions & 57 deletions osu.Game/Audio/AudioNormalization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Linq;
using ManagedBass;
using ManagedBass.Fx;
using osu.Framework.Audio.Mixing;
using osu.Framework.Audio.Track;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Database;
Expand All @@ -16,7 +13,7 @@ namespace osu.Game.Audio
/// <summary>
/// Audio Normalization Manager
/// </summary>
public class AudioNormalization : EmbeddedObject, IAudioNormalization, IEquatable<AudioNormalization>
public class AudioNormalization : EmbeddedObject, IEquatable<AudioNormalization>
{
/// <summary>
/// The target level for audio normalization
Expand Down Expand Up @@ -57,9 +54,9 @@ public AudioNormalization(BeatmapInfo beatmapInfo, BeatmapSetInfo beatmapSetInfo
return;
}

filepath = realmFileStore.Storage.GetFullPath(filepath);
using TrackLoudness loudness = new TrackLoudness(realmFileStore.Storage.GetStream(filepath));

float? integratedLoudness = new BassAudioNormalization(filepath).IntegratedLoudness;
float? integratedLoudness = loudness.GetIntegratedLoudness();

if (integratedLoudness == null)
{
Expand Down Expand Up @@ -93,57 +90,9 @@ public void PopulateSet(BeatmapInfo beatmapInfo, BeatmapSetInfo beatmapSetInfo)
}

/// <summary>
/// Convert integrated loudness to a volume offset
/// A volume offset that can be applied upon audio to reach <see cref="TARGET_LEVEL"/> (converted from integrated loudness).
/// </summary>
/// <param name="integratedLoudness">The integrated loudness value</param>
/// <returns>The volume offset needed to reach <see cref="TARGET_LEVEL"/></returns>
public static float IntegratedLoudnessToVolumeOffset(float integratedLoudness) => (float)Math.Pow(10, (TARGET_LEVEL - integratedLoudness) / 20);

/// <summary>
/// Get the loudness values of a <see cref="BeatmapInfo"/> and apply the audio normalization effect to an <see cref="IAudioMixer"/>
/// </summary>
/// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> to get the loudness values of</param>
/// <param name="mixer">The <see cref="IAudioMixer"/> to apply the effect on</param>
public static void AddAudioNormalization(BeatmapInfo beatmapInfo, IAudioMixer mixer)
{
AudioNormalization? audioNormalizationModule = beatmapInfo.AudioNormalization;

VolumeParameters volumeParameters = new VolumeParameters
{
fTarget = audioNormalizationModule?.IntegratedLoudness != null ? IntegratedLoudnessToVolumeOffset((float)audioNormalizationModule.IntegratedLoudness) : 0.8f,
fCurrent = -1.0f,
fTime = 0.3f,
lCurve = 1,
lChannel = FXChannelFlags.All
};

addFx(volumeParameters, mixer);

Logger.Log("Normalization Status: " + (audioNormalizationModule?.IntegratedLoudness != null ? $"on ({Math.Round(IntegratedLoudnessToVolumeOffset((float)audioNormalizationModule.IntegratedLoudness) * 100)}%)" : "off"));
Comment thread
hwsmm marked this conversation as resolved.
}

/// <summary>
/// Add an effect to a <see cref="IAudioMixer"/>, replacing it if it already exists
/// </summary>
/// <param name="effectParameter">The <see cref="IEffectParameter"/> to add to the <paramref name="mixer"/></param>
/// <param name="mixer">The <see cref="IAudioMixer"/> to add the effect to</param>
private static void addFx(IEffectParameter effectParameter, IAudioMixer mixer)
{
IEffectParameter? effect = mixer.Effects.SingleOrDefault(e => e.FXType == effectParameter.FXType);

if (effect != null)
{
int i = mixer.Effects.IndexOf(effect);
mixer.Effects[i] = effectParameter;
}
else
{
mixer.Effects.Add(effectParameter);
}
}

/// <inheritdoc />
public bool Equals(IAudioNormalization? other) => other is AudioNormalization audioNormalization && Equals(audioNormalization);
public float IntegratedLoudnessInVolumeOffset => IntegratedLoudness != null ? (float)Math.Pow(10, (TARGET_LEVEL - (double)IntegratedLoudness) / 20) : 0.8f;
Comment thread
hwsmm marked this conversation as resolved.
Outdated

@smallketchup82 smallketchup82 May 13, 2024

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I think it'd be better to lower the global volume reduction. In my testing, turning off normalization would blast your ears since 100% volume with 0.8 is much louder than 100% with -14 LUFS.

I'll probably do my own testing to find a proper value for it and consult with the core team & @nekodex about it. You can dismiss this message since its more of a note for myself. Though, nekodex, if you have any thoughts on this then do let me know


/// <inheritdoc />
public bool Equals(AudioNormalization? other) => other?.IntegratedLoudness != null && IntegratedLoudness != null && Math.Abs((float)(IntegratedLoudness - other.IntegratedLoudness)) < 0.0000001;
Expand Down
60 changes: 60 additions & 0 deletions osu.Game/Audio/AudioNormalizationManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// 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.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Overlays;

namespace osu.Game.Audio
{
/// <summary>
/// Provides bindables for track/sample volume.
/// </summary>
public class AudioNormalizationManager
{
private readonly Bindable<bool> beatmapHitsoundBind;

/// <summary>
/// Default <see cref="ITrackStore"/> will bind to this bindable.
/// <see cref="MusicController"/> updates this on every track change.
/// It's set to 0.8 by default to leave some head-room for UI effects / samples.
/// </summary>
public readonly BindableDouble TrackNormalizeVolume = new BindableDouble(0.8);

/// <summary>
/// Samples assigned to hitobjects needs to bind to this bindable to normalize their volume in line with track.
/// </summary>
public readonly BindableDouble SampleNormalizeVolume = new BindableDouble(1.0);

/// <summary>
/// Creates a new <see cref="AudioNormalizationManager"/>.
/// </summary>
/// <param name="audioManager"></param>
/// <param name="config"></param>
Comment thread
hwsmm marked this conversation as resolved.
Outdated
public AudioNormalizationManager(AudioManager audioManager, OsuConfigManager config)
{
audioManager.Tracks.AddAdjustment(AdjustableProperty.Volume, TrackNormalizeVolume);

beatmapHitsoundBind = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds);

normalizeSampleIfTrue(beatmapHitsoundBind.Value);

@smallketchup82 smallketchup82 May 13, 2024

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Why do we only retain the relationship between tracks and hitsounds if beatmap hitsounds are turned on? Unless I'm mistaken and this does something entirely different, I think we'd want the volume offset to apply to skin hitsounds, beatmap hitsounds, and all other gameplay samples (combo sound, slider tick, etc).

Though I want to mention @nekodex and get his opinion on this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I assumed that most players expect consistent hitsounds over maps if they don't have beatmap hitsounds enabled. I guess I should have discussed about this earlier..

beatmapHitsoundBind.BindValueChanged(change => normalizeSampleIfTrue(change.NewValue));
}

private void normalizeSampleIfTrue(bool value)
{
if (value)
{
SampleNormalizeVolume.BindTo(TrackNormalizeVolume);
}
else
{
SampleNormalizeVolume.UnbindFrom(TrackNormalizeVolume);
SampleNormalizeVolume.Value = 1.0;
}
}
}
}
73 changes: 0 additions & 73 deletions osu.Game/Audio/BassAudioNormalization.cs

This file was deleted.

12 changes: 0 additions & 12 deletions osu.Game/Audio/IAudioNormalization.cs

This file was deleted.

4 changes: 4 additions & 0 deletions osu.Game/OsuGameBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ public virtual string Version

protected SafeAreaContainer SafeAreaContainer { get; private set; }

protected AudioNormalizationManager AudioNormalizationManager { get; private set; }

/// <summary>
/// For now, this is used as a source specifically for beat synced components.
/// Going forward, it could potentially be used as the single source-of-truth for beatmap timing.
Expand Down Expand Up @@ -346,6 +348,8 @@ private void load(ReadableKeyCombinationProvider keyCombinationProvider, Framewo
RegisterImportHandler(ScoreManager);
RegisterImportHandler(SkinManager);

dependencies.Cache(AudioNormalizationManager = new AudioNormalizationManager(Audio, LocalConfig));

Beatmap = new NonNullableBindable<WorkingBeatmap>(defaultBeatmap);

dependencies.CacheAs<IBindable<WorkingBeatmap>>(Beatmap);
Expand Down
12 changes: 6 additions & 6 deletions osu.Game/Overlays/MusicController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,13 @@ public partial class MusicController : CompositeDrawable
private RealmAccess realm { get; set; }

[Resolved]
private AudioManager audioManager { get; set; }
private AudioNormalizationManager audioNormalizationManager { get; set; }

protected override void LoadComplete()
{
base.LoadComplete();

beatmap.BindValueChanged(b =>
{
changeBeatmap(b.NewValue);
AudioNormalization.AddAudioNormalization(b.NewValue.BeatmapInfo, audioManager.TrackMixer);
}, true);
beatmap.BindValueChanged(b => changeBeatmap(b.NewValue), true);
mods.BindValueChanged(_ => ResetTrackAdjustments(), true);
}

Expand Down Expand Up @@ -354,6 +350,10 @@ private void changeTrack()
if (queuedTrack == CurrentTrack)
{
AddInternal(queuedTrack);

double normalizedVol = current.BeatmapInfo.AudioNormalization?.IntegratedLoudnessInVolumeOffset ?? 0.8;
this.TransformBindableTo(audioNormalizationManager.TrackNormalizeVolume, normalizedVol, 300, Easing.Out);

Comment thread
hwsmm marked this conversation as resolved.
Outdated
queuedTrack.VolumeTo(0).Then().VolumeTo(1, 300, Easing.Out);
}
else
Expand Down
5 changes: 4 additions & 1 deletion osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ListExtensions;
using osu.Framework.Extensions.ObjectExtensions;
Expand Down Expand Up @@ -197,7 +198,7 @@ protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null)
}

[BackgroundDependencyLoader]
private void load(IGameplaySettings gameplaySettings, ISkinSource skinSource)
private void load(IGameplaySettings gameplaySettings, ISkinSource skinSource, AudioNormalizationManager audioNormalizationManager)
{
positionalHitsoundsLevel.BindTo(gameplaySettings.PositionalHitsoundsLevel);
comboColourBrightness.BindTo(gameplaySettings.ComboColourNormalisationAmount);
Expand All @@ -208,6 +209,8 @@ private void load(IGameplaySettings gameplaySettings, ISkinSource skinSource)
MinimumSampleVolume = MINIMUM_SAMPLE_VOLUME
});

Samples.AddAdjustment(AdjustableProperty.Volume, audioNormalizationManager.SampleNormalizeVolume);

CurrentSkin = skinSource;
CurrentSkin.SourceChanged += skinSourceChanged;
}
Expand Down
4 changes: 0 additions & 4 deletions osu.Game/osu.Game.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="ppy.ManagedBass" Version="2023.0.1" />
<PackageReference Include="ppy.ManagedBass.Fx" Version="2023.0.0" />
<PackageReference Include="ppy.ManagedBass.Loud" Version="3.0.5" />
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2025.0.0" />
<PackageReference Include="Realm" Version="11.5.0" />
<PackageReference Include="ppy.osu.Framework" Version="2024.509.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.510.0" />
Expand Down