Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to adjust a mixer's volume (and decouple custom audio stores from global volumes) #6326

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
11 changes: 5 additions & 6 deletions osu.Framework/Audio/AudioManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,21 +177,22 @@ public AudioManager(AudioThread audioThread, ResourceStore<byte[]> trackStore, R
};

AddItem(TrackMixer = createAudioMixer(null, nameof(TrackMixer)));
TrackMixer.Volume.BindTo(VolumeTrack);

AddItem(SampleMixer = createAudioMixer(null, nameof(SampleMixer)));
SampleMixer.Volume.BindTo(VolumeSample);

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

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

Expand Down Expand Up @@ -270,11 +271,9 @@ private AudioMixer createAudioMixer(AudioMixer fallbackMixer, string identifier)
return mixer;
}

protected override void ItemAdded(AudioComponent item)
internal void AddActiveMixer(AudioMixer mixer)
{
base.ItemAdded(item);
if (item is AudioMixer mixer)
activeMixers.Add(mixer);
activeMixers.Add(mixer);
}

protected override void ItemRemoved(AudioComponent item)
Expand Down
8 changes: 7 additions & 1 deletion osu.Framework/Audio/Mixing/AudioMixer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace osu.Framework.Audio.Mixing
/// <summary>
/// Mixes together multiple <see cref="IAudioChannel"/>s into one output.
/// </summary>
public abstract class AudioMixer : AudioComponent, IAudioMixer
public abstract class AudioMixer : AdjustableAudioComponent, IAudioMixer
{
public readonly string Identifier;

Expand All @@ -29,6 +29,9 @@ protected AudioMixer(AudioMixer? fallbackMixer, string identifier)

public void Add(IAudioChannel channel)
{
if (channel is IAdjustableAudioComponent adj)
adj.BindAdjustments(this);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is the Remove() one enqueued to the channel, but this one isn't?

Copy link
Author

Choose a reason for hiding this comment

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

peppy made this change, by my local testing, it doesn't matter if the adjustment bind is executed inside or outside of the audio thread... I can at least make it consistent across both methods, is it preferable that both are enqueue'd or not?

Copy link
Member

Choose a reason for hiding this comment

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

Comparing with AudioCollectionManager<T>, binding adjustments there is enqueued to the audio thread, so might as well do the same here.

Copy link
Author

Choose a reason for hiding this comment

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

done, both are now enqueue'd


channel.EnqueueAction(() =>
{
if (channel.Mixer == this)
Expand Down Expand Up @@ -70,6 +73,9 @@ protected void Remove(IAudioChannel channel, bool returnToDefault)
RemoveInternal(channel);
channel.Mixer = null;

if (channel is IAdjustableAudioComponent adj)
adj.UnbindAdjustments(this);

// Add the channel back to the default mixer so audio can always be played.
if (returnToDefault)
fallbackMixer.AsNonNull().Add(channel);
Expand Down
2 changes: 2 additions & 0 deletions osu.Framework/Audio/Mixing/Bass/BassAudioMixer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ private void createMixer()
BassMix.MixerAddChannel(manager.GlobalMixerHandle.Value.Value, Handle, BassFlags.MixerChanBuffer | BassFlags.MixerChanNoRampin);

ManagedBass.Bass.ChannelPlay(Handle);

manager?.AddActiveMixer(this);
}

/// <summary>
Expand Down
114 changes: 81 additions & 33 deletions osu.Framework/Graphics/Visualisation/Audio/AudioChannelDisplay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@ namespace osu.Framework.Graphics.Visualisation.Audio
public partial class AudioChannelDisplay : CompositeDrawable
{
private const int sample_window = 30;
private const int peak_hold_time = 3000;
private const int peak_hold_time = 1000;

public readonly int ChannelHandle;

private readonly Drawable volBarL;
private readonly Drawable volBarR;
private readonly Drawable peakBarL;
private readonly Drawable peakBarR;
private readonly SpriteText peakText;
private readonly SpriteText maxPeakText;
private readonly SpriteText mixerLabel;

private float maxPeak = float.MinValue;
private float peakLevelLR = float.MinValue;
private float peakLevelL = float.MinValue;
private float peakLevelR = float.MinValue;
private double lastMaxPeakTime;
private readonly bool isOutputChannel;
private IBindable<bool> usingGlobalMixer = null!;
Expand Down Expand Up @@ -55,35 +59,72 @@ public AudioChannelDisplay(int channelHandle, bool isOutputChannel = false)
},
Content = new[]
{
new Drawable[]
{
[
new FillFlowContainer
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Children = new[]
{
volBarL = new Box
Spacing = new Vector2(3),
Children =
[
new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Y,
Width = 30,
Colour = isOutputChannel ? FrameworkColour.YellowGreen : FrameworkColour.Green
AutoSizeAxes = Axes.X,
Height = 1,
Children =
[
volBarL = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Y,
Width = 31,
Height = 0,
Colour = isOutputChannel ? FrameworkColour.YellowGreen : FrameworkColour.Green,
},
peakBarL = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
RelativePositionAxes = Axes.Y,
Width = 31,
Height = 5f,
Colour = isOutputChannel ? FrameworkColour.YellowGreen : FrameworkColour.Green,
},
]
},
volBarR = new Box
new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Y,
Width = 30,
Colour = isOutputChannel ? FrameworkColour.YellowGreen : FrameworkColour.Green
AutoSizeAxes = Axes.X,
Height = 1,
Children =
[
volBarR = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Y,
Width = 31,
Height = 0,
Colour = isOutputChannel ? FrameworkColour.YellowGreen : FrameworkColour.Green,
},
peakBarR = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
RelativePositionAxes = Axes.Y,
Width = 31,
Height = 5f,
Colour = isOutputChannel ? FrameworkColour.YellowGreen : FrameworkColour.Green,
},
]
}
}
]
}
},
],
new Drawable[]
{
new FillFlowContainer
Expand Down Expand Up @@ -120,27 +161,34 @@ protected override void Update()
else
BassMix.ChannelGetLevel(ChannelHandle, levels, 1 / 1000f * sample_window, LevelRetrievalFlags.Stereo);

float curPeakL = levels[0];
float curPeakR = levels[1];
float curPeak = (curPeakL + curPeakR) / 2f;
float curLevelL = levels[0];
float curLevelR = levels[1];
float curLevelLR = (curLevelL + curLevelR) / 2f;

if (curPeak > maxPeak || Clock.CurrentTime - lastMaxPeakTime > peak_hold_time)
if (Clock.CurrentTime - lastMaxPeakTime > peak_hold_time)
{
lastMaxPeakTime = Clock.CurrentTime;
maxPeak = float.MinValue;
peakLevelL = 0;
peakLevelR = 0;
peakLevelLR = 0;
}

maxPeak = Math.Max(maxPeak, curPeak);
peakLevelL = Math.Max(peakLevelL, curLevelL);
peakLevelR = Math.Max(peakLevelR, curLevelR);
peakLevelLR = Math.Max(peakLevelL, peakLevelR);

peakBarL.MoveToY(-peakLevelL, peakBarL.Y >= -peakLevelL ? 0 : sample_window * 4);
peakBarR.MoveToY(-peakLevelR, peakBarR.Y >= -peakLevelR ? 0 : sample_window * 4);

volBarL.ResizeHeightTo(curPeakL, sample_window * 4);
volBarR.ResizeHeightTo(curPeakR, sample_window * 4);
volBarL.ResizeHeightTo(curLevelL, volBarL.Height <= curLevelL ? 0 : sample_window * 4);
volBarR.ResizeHeightTo(curLevelR, volBarR.Height <= curLevelR ? 0 : sample_window * 4);

string peakDisplay = curPeak == 0 ? "-∞ " : $"{BassUtils.LevelToDb(curPeak):F}";
string maxPeakDisplay = maxPeak == 0 ? "-∞ " : $"{BassUtils.LevelToDb(maxPeak):F}";
peakText.Text = $"curr: {peakDisplay}dB";
maxPeakText.Text = $"peak: {maxPeakDisplay}dB";
peakText.Colour = BassUtils.LevelToDb(curPeak) > 0 ? Colour4.Red : Colour4.White;
maxPeakText.Colour = BassUtils.LevelToDb(maxPeak) > 0 ? Colour4.Red : Colour4.White;
string curDisplay = curLevelLR == 0 ? "-∞ " : $"{BassUtils.LevelToDb(curLevelLR):F}";
string peakDisplay = peakLevelLR == 0 ? "-∞ " : $"{BassUtils.LevelToDb(peakLevelLR):F}";
peakText.Text = $"curr: {curDisplay}dB";
maxPeakText.Text = $"peak: {peakDisplay}dB";
peakText.Colour = BassUtils.LevelToDb(Math.Max(curLevelL, curLevelR)) > 0 ? Colour4.Red : Colour4.White;
maxPeakText.Colour = BassUtils.LevelToDb(peakLevelLR) > 0 ? Colour4.Red : Colour4.White;
mixerLabel.Text = isOutputChannel ? "MIXER OUT" : " ";
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public AudioMixerVisualiser()
MainHorizontalContent.Add(new BasicScrollContainer(Direction.Horizontal)
{
RelativeSizeAxes = Axes.Y,
Width = WIDTH,
Width = WIDTH * 1.5f,
Children = new[]
{
mixerFlow = new FillFlowContainer<MixerDisplay>
Expand Down
5 changes: 3 additions & 2 deletions osu.Framework/Graphics/Visualisation/Audio/MixerDisplay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@ public MixerDisplay(AudioMixer mixer)
},
new SpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = mixer.Identifier,
Font = FrameworkFont.Condensed.With(size: 14),
Colour = FrameworkColour.Yellow,
Padding = new MarginPadding(2),
Margin = new MarginPadding(10)
},
new FillFlowContainer
{
Expand Down