diff --git a/osu.Framework/Audio/AudioManager.cs b/osu.Framework/Audio/AudioManager.cs
index f6c366271d..28c6433e7e 100644
--- a/osu.Framework/Audio/AudioManager.cs
+++ b/osu.Framework/Audio/AudioManager.cs
@@ -265,7 +265,7 @@ public AudioMixer CreateAudioMixer(string identifier = default) =>
private AudioMixer createAudioMixer(AudioMixer fallbackMixer, string identifier)
{
- var mixer = new BassAudioMixer(this, fallbackMixer, identifier);
+ var mixer = new PassthroughBassAudioMixer(this, fallbackMixer, identifier);
AddItem(mixer);
return mixer;
}
diff --git a/osu.Framework/Audio/Mixing/Bass/BassAudioMixer.cs b/osu.Framework/Audio/Mixing/Bass/BassAudioMixer.cs
index a564a6b20a..0514eeaf5b 100644
--- a/osu.Framework/Audio/Mixing/Bass/BassAudioMixer.cs
+++ b/osu.Framework/Audio/Mixing/Bass/BassAudioMixer.cs
@@ -14,7 +14,7 @@ namespace osu.Framework.Audio.Mixing.Bass
///
/// Mixes together multiple into one output via BASSmix.
///
- internal class BassAudioMixer : AudioMixer, IBassAudio
+ internal class BassAudioMixer : AudioMixer, IBassAudio, IBassAudioMixer
{
private readonly AudioManager? manager;
@@ -100,6 +100,9 @@ protected override void RemoveInternal(IAudioChannel channel)
removeChannelFromBassMix(bassChannel);
}
+ public BassFlags SampleFlags => BassFlags.SampleChannelStream | BassFlags.Decode;
+ public BassFlags TrackFlags => BassFlags.Decode;
+
///
/// Plays a channel.
///
diff --git a/osu.Framework/Audio/Mixing/Bass/IBassAudioChannel.cs b/osu.Framework/Audio/Mixing/Bass/IBassAudioChannel.cs
index 4e6905b183..12e8722fd2 100644
--- a/osu.Framework/Audio/Mixing/Bass/IBassAudioChannel.cs
+++ b/osu.Framework/Audio/Mixing/Bass/IBassAudioChannel.cs
@@ -26,6 +26,6 @@ internal interface IBassAudioChannel : IAudioChannel
///
bool MixerChannelPaused { get; set; }
- new BassAudioMixer Mixer { get; }
+ new IBassAudioMixer Mixer { get; }
}
}
diff --git a/osu.Framework/Audio/Mixing/Bass/IBassAudioMixer.cs b/osu.Framework/Audio/Mixing/Bass/IBassAudioMixer.cs
new file mode 100644
index 0000000000..5641dee430
--- /dev/null
+++ b/osu.Framework/Audio/Mixing/Bass/IBassAudioMixer.cs
@@ -0,0 +1,136 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Runtime.InteropServices;
+using ManagedBass;
+using ManagedBass.Mix;
+
+namespace osu.Framework.Audio.Mixing.Bass
+{
+ internal interface IBassAudioMixer
+ {
+ BassFlags SampleFlags { get; }
+ BassFlags TrackFlags { get; }
+
+ void Add(IAudioChannel channel);
+
+ ///
+ /// Plays a channel.
+ ///
+ /// See: .
+ /// The channel to play.
+ /// Restart playback from the beginning?
+ ///
+ /// If successful, is returned, else is returned.
+ /// Use to get the error code.
+ ///
+ bool ChannelPlay(IBassAudioChannel channel, bool restart = false);
+
+ ///
+ /// Pauses a channel.
+ ///
+ /// See: .
+ /// The channel to pause.
+ /// Set to true to make the pause take effect immediately.
+ ///
+ /// This will change the timing of , so should be used sparingly.
+ ///
+ ///
+ ///
+ /// If successful, is returned, else is returned.
+ /// Use to get the error code.
+ ///
+ bool ChannelPause(IBassAudioChannel channel, bool flushMixer = false);
+
+ ///
+ /// Checks if a channel is active (playing) or stalled.
+ ///
+ /// See: .
+ /// The channel to get the state of.
+ /// indicating the state of the channel.
+ PlaybackState ChannelIsActive(IBassAudioChannel channel);
+
+ ///
+ /// Retrieves the playback position of a channel.
+ ///
+ /// See: .
+ /// The channel to retrieve the position of.
+ /// How to retrieve the position.
+ ///
+ /// If an error occurs, -1 is returned, use to get the error code.
+ /// If successful, the position is returned.
+ ///
+ long ChannelGetPosition(IBassAudioChannel channel, PositionFlags mode = PositionFlags.Bytes);
+
+ ///
+ /// Sets the playback position of a channel.
+ ///
+ /// See: .
+ /// The to set the position of.
+ /// The position, in units determined by the .
+ /// How to set the position.
+ ///
+ /// If successful, then is returned, else is returned.
+ /// Use to get the error code.
+ ///
+ bool ChannelSetPosition(IBassAudioChannel channel, long position, PositionFlags mode = PositionFlags.Bytes);
+
+ ///
+ /// Retrieves the level (peak amplitude) of a channel.
+ ///
+ /// See: .
+ /// The to get the levels of.
+ /// The array in which the levels are to be returned.
+ /// How much data (in seconds) to look at to get the level (limited to 1 second).
+ /// What levels to retrieve.
+ /// true if successful, false otherwise.
+ bool ChannelGetLevel(IBassAudioChannel channel, [In, Out] float[] levels, float length, LevelRetrievalFlags flags);
+
+ ///
+ /// Retrieves the immediate sample data (or an FFT representation of it) of a channel.
+ ///
+ /// See: .
+ /// The to retrieve the data of.
+ /// float[] to write the data to.
+ /// Number of bytes wanted, and/or .
+ /// If an error occurs, -1 is returned, use to get the error code.
+ /// When requesting FFT data, the number of bytes read from the channel (to perform the FFT) is returned.
+ /// When requesting sample data, the number of bytes written to buffer will be returned (not necessarily the same as the number of bytes read when using the or DataFlags.Fixed flag).
+ /// When using the flag, the number of bytes in the channel's buffer is returned.
+ ///
+ int ChannelGetData(IBassAudioChannel channel, float[] buffer, int length);
+
+ ///
+ /// Sets up a synchroniser on a mixer source channel.
+ ///
+ /// See: .
+ /// The to set up the synchroniser for.
+ /// The type of sync.
+ /// The sync parameters, depending on the sync type.
+ /// The callback function which should be invoked with the sync.
+ /// User instance data to pass to the callback function.
+ /// If successful, then the new synchroniser's handle is returned, else 0 is returned. Use to get the error code.
+ int ChannelSetSync(IBassAudioChannel channel, SyncFlags type, long parameter, SyncProcedure procedure, IntPtr user = default);
+
+ ///
+ /// Removes a synchroniser from a mixer source channel.
+ ///
+ /// The to remove the synchroniser for.
+ /// Handle of the synchroniser to remove (return value of a previous call).
+ /// If successful, is returned, else is returned. Use to get the error code.
+ bool ChannelRemoveSync(IBassAudioChannel channel, int sync);
+
+ ///
+ /// Frees a channel's resources.
+ ///
+ /// The to free.
+ /// If successful, is returned, else is returned. Use to get the error code.
+ bool StreamFree(IBassAudioChannel channel);
+
+ ///
+ /// Adds a channel to the native BASS mix.
+ ///
+ void AddChannelToBassMix(IBassAudioChannel channel);
+ }
+}
diff --git a/osu.Framework/Audio/Mixing/Bass/PassthroughBassAudioMixer.cs b/osu.Framework/Audio/Mixing/Bass/PassthroughBassAudioMixer.cs
new file mode 100644
index 0000000000..cc920677dc
--- /dev/null
+++ b/osu.Framework/Audio/Mixing/Bass/PassthroughBassAudioMixer.cs
@@ -0,0 +1,115 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using ManagedBass;
+
+namespace osu.Framework.Audio.Mixing.Bass
+{
+ internal class PassthroughBassAudioMixer : AudioMixer, IBassAudio, IBassAudioMixer
+ {
+ private readonly List activeChannels = new List();
+
+ public PassthroughBassAudioMixer(AudioManager manager, AudioMixer? fallbackMixer, string identifier)
+ : base(fallbackMixer, identifier)
+ {
+ }
+
+ public override void AddEffect(IEffectParameter effect, int priority = 0)
+ {
+ }
+
+ public override void RemoveEffect(IEffectParameter effect)
+ {
+ }
+
+ public override void UpdateEffect(IEffectParameter effect)
+ {
+ }
+
+ protected override void AddInternal(IAudioChannel channel)
+ {
+ if (!(channel is IBassAudioChannel bassChannel))
+ return;
+
+ if (bassChannel.Handle == 0)
+ return;
+
+ activeChannels.Add(bassChannel);
+
+ if (ManagedBass.Bass.ChannelIsActive(bassChannel.Handle) != PlaybackState.Stopped)
+ ManagedBass.Bass.ChannelPlay(bassChannel.Handle);
+ }
+
+ protected override void RemoveInternal(IAudioChannel channel)
+ {
+ if (!(channel is IBassAudioChannel bassChannel))
+ return;
+
+ if (bassChannel.Handle == 0)
+ return;
+
+ activeChannels.Remove(bassChannel);
+ ManagedBass.Bass.ChannelPause(bassChannel.Handle);
+ }
+
+ public void UpdateDevice(int deviceIndex)
+ {
+ }
+
+ public BassFlags SampleFlags => BassFlags.Default;
+ public BassFlags TrackFlags => BassFlags.Default;
+
+ public bool ChannelPlay(IBassAudioChannel channel, bool restart = false)
+ {
+ if (channel.Handle == 0)
+ return false;
+
+ return ManagedBass.Bass.ChannelPlay(channel.Handle, restart);
+ }
+
+ public bool ChannelPause(IBassAudioChannel channel, bool flushMixer = false)
+ => ManagedBass.Bass.ChannelPause(channel.Handle);
+
+ public PlaybackState ChannelIsActive(IBassAudioChannel channel)
+ => ManagedBass.Bass.ChannelIsActive(channel.Handle);
+
+ public long ChannelGetPosition(IBassAudioChannel channel, PositionFlags mode = PositionFlags.Bytes)
+ => ManagedBass.Bass.ChannelGetPosition(channel.Handle, mode);
+
+ public bool ChannelSetPosition(IBassAudioChannel channel, long position, PositionFlags mode = PositionFlags.Bytes)
+ => ManagedBass.Bass.ChannelSetPosition(channel.Handle, position, mode);
+
+ public bool ChannelGetLevel(IBassAudioChannel channel, float[] levels, float length, LevelRetrievalFlags flags)
+ => ManagedBass.Bass.ChannelGetLevel(channel.Handle, levels, length, flags);
+
+ public int ChannelGetData(IBassAudioChannel channel, float[] buffer, int length)
+ => ManagedBass.Bass.ChannelGetData(channel.Handle, buffer, length);
+
+ public int ChannelSetSync(IBassAudioChannel channel, SyncFlags type, long parameter, SyncProcedure procedure, IntPtr user = default)
+ => ManagedBass.Bass.ChannelSetSync(channel.Handle, type, parameter, procedure, user);
+
+ public bool ChannelRemoveSync(IBassAudioChannel channel, int sync)
+ => ManagedBass.Bass.ChannelRemoveSync(channel.Handle, sync);
+
+ public bool StreamFree(IBassAudioChannel channel)
+ {
+ ManagedBass.Bass.ChannelStop(channel.Handle);
+ return ManagedBass.Bass.StreamFree(channel.Handle);
+ }
+
+ public void AddChannelToBassMix(IBassAudioChannel channel)
+ {
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ foreach (var channel in activeChannels.ToArray())
+ ManagedBass.Bass.ChannelPause(channel.Handle);
+ activeChannels.Clear();
+ }
+ }
+}
diff --git a/osu.Framework/Audio/Sample/SampleBass.cs b/osu.Framework/Audio/Sample/SampleBass.cs
index 5052a2cd5e..42b4d559a0 100644
--- a/osu.Framework/Audio/Sample/SampleBass.cs
+++ b/osu.Framework/Audio/Sample/SampleBass.cs
@@ -12,9 +12,9 @@ internal sealed class SampleBass : Sample
public override bool IsLoaded => factory.IsLoaded;
private readonly SampleBassFactory factory;
- private readonly BassAudioMixer mixer;
+ private readonly IBassAudioMixer mixer;
- internal SampleBass(SampleBassFactory factory, BassAudioMixer mixer)
+ internal SampleBass(SampleBassFactory factory, IBassAudioMixer mixer)
: base(factory.Name)
{
this.factory = factory;
@@ -25,7 +25,7 @@ internal SampleBass(SampleBassFactory factory, BassAudioMixer mixer)
protected override SampleChannel CreateChannel()
{
- var channel = new SampleChannelBass(this);
+ var channel = new SampleChannelBass(this, mixer.SampleFlags);
mixer.Add(channel);
return channel;
}
diff --git a/osu.Framework/Audio/Sample/SampleBassFactory.cs b/osu.Framework/Audio/Sample/SampleBassFactory.cs
index 3b863a68b3..65ebb72568 100644
--- a/osu.Framework/Audio/Sample/SampleBassFactory.cs
+++ b/osu.Framework/Audio/Sample/SampleBassFactory.cs
@@ -32,12 +32,12 @@ internal class SampleBassFactory : AudioCollectionManager
internal readonly Bindable PlaybackConcurrency = new Bindable(Sample.DEFAULT_CONCURRENCY);
- private readonly BassAudioMixer mixer;
+ private readonly IBassAudioMixer mixer;
private NativeMemoryTracker.NativeMemoryLease? memoryLease;
private byte[]? data;
- public SampleBassFactory(byte[] data, string name, BassAudioMixer mixer)
+ public SampleBassFactory(byte[] data, string name, IBassAudioMixer mixer)
{
this.data = data;
this.mixer = mixer;
diff --git a/osu.Framework/Audio/Sample/SampleChannelBass.cs b/osu.Framework/Audio/Sample/SampleChannelBass.cs
index f08f5baa49..b8beb0ff4f 100644
--- a/osu.Framework/Audio/Sample/SampleChannelBass.cs
+++ b/osu.Framework/Audio/Sample/SampleChannelBass.cs
@@ -11,6 +11,7 @@ namespace osu.Framework.Audio.Sample
internal sealed class SampleChannelBass : SampleChannel, IBassAudioChannel
{
private readonly SampleBass sample;
+ private readonly BassFlags creationFlags;
private volatile int channel;
///
@@ -66,10 +67,12 @@ public override bool Looping
/// Creates a new .
///
/// The to create the channel from.
- public SampleChannelBass(SampleBass sample)
+ ///
+ public SampleChannelBass(SampleBass sample, BassFlags creationFlags)
: base(sample.Name)
{
this.sample = sample;
+ this.creationFlags = creationFlags;
relativeFrequencyHandler = new BassRelativeFrequencyHandler
{
@@ -199,7 +202,7 @@ private void ensureChannel() => EnqueueAction(() =>
if (hasChannel)
return;
- BassFlags flags = BassFlags.SampleChannelStream | BassFlags.Decode;
+ BassFlags flags = creationFlags;
// While this shouldn't cause issues, we've had a small subset of users reporting issues on windows.
// To keep things working let's only apply to other platforms until we know more.
@@ -219,7 +222,7 @@ private void ensureChannel() => EnqueueAction(() =>
#region Mixing
- private BassAudioMixer bassMixer => (BassAudioMixer)Mixer.AsNonNull();
+ private IBassAudioMixer bassMixer => (IBassAudioMixer)Mixer.AsNonNull();
bool IBassAudioChannel.IsActive => IsAlive;
@@ -227,7 +230,7 @@ private void ensureChannel() => EnqueueAction(() =>
bool IBassAudioChannel.MixerChannelPaused { get; set; } = true;
- BassAudioMixer IBassAudioChannel.Mixer => bassMixer;
+ IBassAudioMixer IBassAudioChannel.Mixer => bassMixer;
#endregion
diff --git a/osu.Framework/Audio/Sample/SampleStore.cs b/osu.Framework/Audio/Sample/SampleStore.cs
index a47a9f15af..6d7ce65644 100644
--- a/osu.Framework/Audio/Sample/SampleStore.cs
+++ b/osu.Framework/Audio/Sample/SampleStore.cs
@@ -49,7 +49,7 @@ public Sample Get(string name)
this.LogIfNonBackgroundThread(name);
byte[] data = store.Get(name);
- factory = factories[name] = data == null ? null : new SampleBassFactory(data, name, (BassAudioMixer)mixer) { PlaybackConcurrency = { Value = PlaybackConcurrency } };
+ factory = factories[name] = data == null ? null : new SampleBassFactory(data, name, (IBassAudioMixer)mixer) { PlaybackConcurrency = { Value = PlaybackConcurrency } };
if (factory != null)
AddItem(factory);
diff --git a/osu.Framework/Audio/Track/TrackBass.cs b/osu.Framework/Audio/Track/TrackBass.cs
index 55088f81d8..9817f14b43 100644
--- a/osu.Framework/Audio/Track/TrackBass.cs
+++ b/osu.Framework/Audio/Track/TrackBass.cs
@@ -20,6 +20,7 @@ namespace osu.Framework.Audio.Track
{
public sealed class TrackBass : Track, IBassAudio, IBassAudioChannel
{
+ private readonly BassFlags creationFlags;
private Stream? dataStream;
///
@@ -66,10 +67,12 @@ public sealed class TrackBass : Track, IBassAudio, IBassAudioChannel
///
/// The sample data stream.
/// A name identifying the track internally.
+ ///
/// If true, the track will not be fully loaded, and should only be used for preview purposes. Defaults to false.
- internal TrackBass(Stream data, string name, bool quick = false)
+ internal TrackBass(Stream data, string name, BassFlags creationFlags, bool quick = false)
: base(name)
{
+ this.creationFlags = creationFlags;
ArgumentNullException.ThrowIfNull(data);
relativeFrequencyHandler = new BassRelativeFrequencyHandler
@@ -179,7 +182,7 @@ private int prepareStream(Stream data, bool quick)
Bass.ChannelSetDevice(stream, bass_nodevice);
tempoAdjustStream = BassFx.TempoCreate(stream, BassFlags.Decode | BassFlags.FxFreeSource);
Bass.ChannelSetDevice(tempoAdjustStream, bass_nodevice);
- stream = BassFx.ReverseCreate(tempoAdjustStream, 5f, BassFlags.Default | BassFlags.FxFreeSource | BassFlags.Decode);
+ stream = BassFx.ReverseCreate(tempoAdjustStream, 5f, BassFlags.Default | BassFlags.FxFreeSource | creationFlags);
Bass.ChannelSetAttribute(stream, ChannelAttribute.TempoUseQuickAlgorithm, 1);
Bass.ChannelSetAttribute(stream, ChannelAttribute.TempoOverlapMilliseconds, 4);
@@ -439,7 +442,7 @@ protected override AudioMixer? Mixer
}
}
- private BassAudioMixer bassMixer => (BassAudioMixer)Mixer.AsNonNull();
+ private IBassAudioMixer bassMixer => (IBassAudioMixer)Mixer.AsNonNull();
bool IBassAudioChannel.IsActive => !IsDisposed;
@@ -447,7 +450,7 @@ protected override AudioMixer? Mixer
bool IBassAudioChannel.MixerChannelPaused { get; set; } = true;
- BassAudioMixer IBassAudioChannel.Mixer => bassMixer;
+ IBassAudioMixer IBassAudioChannel.Mixer => bassMixer;
#endregion
diff --git a/osu.Framework/Audio/Track/TrackStore.cs b/osu.Framework/Audio/Track/TrackStore.cs
index 495e3f0713..c5830f2371 100644
--- a/osu.Framework/Audio/Track/TrackStore.cs
+++ b/osu.Framework/Audio/Track/TrackStore.cs
@@ -10,6 +10,7 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Audio.Mixing;
+using osu.Framework.Audio.Mixing.Bass;
using osu.Framework.IO.Stores;
namespace osu.Framework.Audio.Track
@@ -47,7 +48,7 @@ public Track Get(string name)
if (dataStream == null)
return null;
- TrackBass trackBass = new TrackBass(dataStream, name);
+ TrackBass trackBass = new TrackBass(dataStream, name, ((IBassAudioMixer)mixer).TrackFlags);
mixer.Add(trackBass);
AddItem(trackBass);