diff --git a/osu.Framework/Audio/AudioDecoder.cs b/osu.Framework/Audio/AudioDecoder.cs index 1b29966f54..6de197ac49 100644 --- a/osu.Framework/Audio/AudioDecoder.cs +++ b/osu.Framework/Audio/AudioDecoder.cs @@ -118,8 +118,6 @@ public AudioDecoder(SDL.SDL_AudioSpec spec) private readonly CancellationTokenSource tokenSource = new CancellationTokenSource(); - private bool disposedValue; - /// /// Start decoding in the decoding thread. /// @@ -369,6 +367,8 @@ private int loadFromStream(AudioDecoderData job) return 0; } + private bool disposedValue; + protected virtual void Dispose(bool disposing) { if (!disposedValue) @@ -395,12 +395,12 @@ protected virtual void Dispose(bool disposing) ~AudioDecoder() { - Dispose(disposing: false); + Dispose(false); } public void Dispose() { - Dispose(disposing: true); + Dispose(true); GC.SuppressFinalize(this); } } diff --git a/osu.Framework/Audio/Mixing/SDL2/SDL2AudioMixer.cs b/osu.Framework/Audio/Mixing/SDL2/SDL2AudioMixer.cs index 7d5746d6de..a7cc7e4ddc 100644 --- a/osu.Framework/Audio/Mixing/SDL2/SDL2AudioMixer.cs +++ b/osu.Framework/Audio/Mixing/SDL2/SDL2AudioMixer.cs @@ -86,15 +86,13 @@ public void MixChannelsInto(float[] data) { lock (syncRoot) { - int sampleCount = data.Length; - if (ret == null || sampleCount != ret.Length) - ret = new float[sampleCount]; + int sampleCount = data.Length; + if (ret == null || sampleCount != ret.Length) + ret = new float[sampleCount]; - bool useFilters = AudioFilters.Count > 0; - float[] put = useFilters ? new float[sampleCount] : data; + bool useFilters = audioFilters.Count > 0; + float[] put = useFilters ? new float[sampleCount] : data; - lock (activeChannels) - { var node = activeChannels.First; while (node != null) @@ -123,26 +121,22 @@ public void MixChannelsInto(float[] data) } channelCount = activeChannels.Count; - } - if (useFilters) - { - lock (AudioFilters) + if (useFilters) { for (int i = 0; i < sampleCount; i++) { - foreach (var filter in AudioFilters) + foreach (var filter in audioFilters) { if (filter.BiQuadFilter != null) put[i] = filter.BiQuadFilter.Transform(put[i]); } } - } - mixAudio(data, put, sampleCount, SDL.SDL_MIX_MAXVOLUME); + mixAudio(data, put, sampleCount, SDL.SDL_MIX_MAXVOLUME); + } } } - } private void adjustBalance(double balance, float[] audio, int size) { @@ -157,7 +151,7 @@ private void adjustBalance(double balance, float[] audio, int size) } } - internal readonly List AudioFilters = new List(); + private readonly List audioFilters = new List(); private void onEffectsChanged(object? sender, NotifyCollectionChangedEventArgs e) => EnqueueAction(() => { @@ -169,15 +163,15 @@ private void onEffectsChanged(object? sender, NotifyCollectionChangedEventArgs e { Debug.Assert(e.NewItems != null); int startIndex = Math.Max(0, e.NewStartingIndex); - AudioFilters.InsertRange(startIndex, e.NewItems.OfType().Select(eff => new EffectBox(eff))); + audioFilters.InsertRange(startIndex, e.NewItems.OfType().Select(eff => new EffectBox(eff))); break; } case NotifyCollectionChangedAction.Move: { - EffectBox effect = AudioFilters[e.OldStartingIndex]; - AudioFilters.RemoveAt(e.OldStartingIndex); - AudioFilters.Insert(e.NewStartingIndex, effect); + EffectBox effect = audioFilters[e.OldStartingIndex]; + audioFilters.RemoveAt(e.OldStartingIndex); + audioFilters.Insert(e.NewStartingIndex, effect); break; } @@ -185,7 +179,7 @@ private void onEffectsChanged(object? sender, NotifyCollectionChangedEventArgs e { Debug.Assert(e.OldItems != null); - AudioFilters.RemoveRange(e.OldStartingIndex, e.OldItems.Count); + audioFilters.RemoveRange(e.OldStartingIndex, e.OldItems.Count); break; } @@ -194,13 +188,13 @@ private void onEffectsChanged(object? sender, NotifyCollectionChangedEventArgs e Debug.Assert(e.NewItems != null); EffectBox newFilter = new EffectBox((IEffectParameter)e.NewItems[0].AsNonNull()); - AudioFilters[e.NewStartingIndex] = newFilter; + audioFilters[e.NewStartingIndex] = newFilter; break; } case NotifyCollectionChangedAction.Reset: { - AudioFilters.Clear(); + audioFilters.Clear(); break; } } diff --git a/osu.Framework/Audio/ResamplingPlayer.cs b/osu.Framework/Audio/ResamplingPlayer.cs index cac9ef8c88..225eb17b2e 100644 --- a/osu.Framework/Audio/ResamplingPlayer.cs +++ b/osu.Framework/Audio/ResamplingPlayer.cs @@ -10,7 +10,7 @@ namespace osu.Framework.Audio /// Abstract class that's meant to be used with a real player implementation. /// This class provides resampling on the fly for players. /// - internal abstract class ResamplingPlayer : AudioComponent + internal abstract class ResamplingPlayer { /// /// Represents current relative rate. Use to set this variable. @@ -19,8 +19,8 @@ internal abstract class ResamplingPlayer : AudioComponent private WdlResampler? resampler; - private readonly int srcRate; - private readonly byte srcChannels; + protected readonly int SrcRate; + protected readonly byte SrcChannels; /// /// Creates a new . @@ -29,8 +29,8 @@ internal abstract class ResamplingPlayer : AudioComponent /// Channels of audio that's given from or protected ResamplingPlayer(int srcRate, byte srcChannels) { - this.srcRate = srcRate; - this.srcChannels = srcChannels; + SrcRate = srcRate; + SrcChannels = srcChannels; } /// @@ -56,7 +56,7 @@ public void SetRate(double relativeRate) resampler.SetFeedMode(false); } - resampler.SetRates(srcRate, srcRate / relativeRate); + resampler.SetRates(SrcRate, SrcRate / relativeRate); RelativeRate = relativeRate; } @@ -65,11 +65,9 @@ protected double GetResampleLatency() if (resampler == null || RelativeRate == 1) return 0; - return latencyCache; + return resampler.GetCurrentLatency() * 1000.0d; } - private double latencyCache; - /// /// Returns rate adjusted audio samples. It calls a parent method if is 1. /// @@ -83,15 +81,14 @@ public virtual int GetRemainingSamples(float[] data) if (resampler == null || RelativeRate == 1) return GetRemainingRawFloats(data, 0, data.Length); - int requested = data.Length / srcChannels; - int needed = resampler.ResamplePrepare(requested, srcChannels, out float[] inBuffer, out int inBufferOffset); - int rawGot = GetRemainingRawFloats(inBuffer, inBufferOffset, needed * srcChannels); + int requested = data.Length / SrcChannels; + int needed = resampler.ResamplePrepare(requested, SrcChannels, out float[] inBuffer, out int inBufferOffset); + int rawGot = GetRemainingRawFloats(inBuffer, inBufferOffset, needed * SrcChannels); if (rawGot > 0) { - int got = resampler.ResampleOut(data, 0, rawGot / srcChannels, requested, srcChannels); - latencyCache = resampler.GetCurrentLatency() * 1000.0d; - return got * srcChannels; + int got = resampler.ResampleOut(data, 0, rawGot / SrcChannels, requested, SrcChannels); + return got * SrcChannels; } return 0; diff --git a/osu.Framework/Audio/SDL2AudioManager.cs b/osu.Framework/Audio/SDL2AudioManager.cs index e24183d240..a9507af629 100644 --- a/osu.Framework/Audio/SDL2AudioManager.cs +++ b/osu.Framework/Audio/SDL2AudioManager.cs @@ -26,8 +26,6 @@ public class SDL2AudioManager : AudioManager private SDL.SDL_AudioSpec spec; - public SDL.SDL_AudioSpec Spec => spec; - private readonly AudioDecoder decoder; /// @@ -148,11 +146,11 @@ protected override bool SetAudioDevice(string deviceName = null) Logger.Log($@"🔈 SDL Audio initialised Driver: {SDL.SDL_GetCurrentAudioDriver()} Device Name: {currentDeviceName} - Frequency: {Spec.freq} hz - Channels: {Spec.channels} - Format: {(SDL.SDL_AUDIO_ISSIGNED(Spec.format) ? "" : "un")}signed {SDL.SDL_AUDIO_BITSIZE(Spec.format)} bits{(SDL.SDL_AUDIO_ISFLOAT(Spec.format) ? " (float)" : "")} - Samples: {Spec.samples} samples - Buffer size: {Spec.size} bytes"); + Frequency: {spec.freq} hz + Channels: {spec.channels} + Format: {(SDL.SDL_AUDIO_ISSIGNED(spec.format) ? "" : "un")}signed {SDL.SDL_AUDIO_BITSIZE(spec.format)} bits{(SDL.SDL_AUDIO_ISFLOAT(spec.format) ? " (float)" : "")} + Samples: {spec.samples} samples + Buffer size: {spec.size} bytes"); return true; } @@ -169,13 +167,13 @@ protected override bool SetAudioDevice(int deviceIndex) internal override Track.Track GetNewTrack(Stream data, string name) { - TrackSDL2 track = new TrackSDL2(name, Spec.freq, Spec.channels, Spec.samples); + TrackSDL2 track = new TrackSDL2(name, spec.freq, spec.channels, spec.samples); decoder.StartDecodingAsync(data, track.AddToQueue, null); return track; } internal override SampleFactory GetSampleFactory(Stream data, string name, AudioMixer mixer, int playbackConcurrency) - => new SampleSDL2Factory(data, name, (SDL2AudioMixer)mixer, playbackConcurrency, Spec, decoder); + => new SampleSDL2Factory(data, name, (SDL2AudioMixer)mixer, playbackConcurrency, spec, decoder); protected override void Dispose(bool disposing) { diff --git a/osu.Framework/Audio/Sample/SampleChannelSDL2.cs b/osu.Framework/Audio/Sample/SampleChannelSDL2.cs index bcfce97f88..c3dd99fbfd 100644 --- a/osu.Framework/Audio/Sample/SampleChannelSDL2.cs +++ b/osu.Framework/Audio/Sample/SampleChannelSDL2.cs @@ -38,17 +38,6 @@ public SampleChannelSDL2(SampleSDL2 sample) this.sample = sample; } - protected override void UpdateChildren() - { - base.UpdateChildren(); - - if (player != null) - { - lock (syncRoot) - player.Update(); - } - } - public override void Play() { enqueuedPlaybackStart = true; @@ -107,7 +96,7 @@ int ISDL2AudioChannel.GetRemainingSamples(float[] data) ret = player.GetRemainingSamples(data); - if (player.IsDone()) + if (player.Done) playing = false; } @@ -134,12 +123,10 @@ protected override void Dispose(bool disposing) lock (syncRoot) { - player?.Dispose(); + playing = false; player = null; } - playing = false; - base.Dispose(disposing); } } diff --git a/osu.Framework/Audio/Sample/SampleSDL2AudioPlayer.cs b/osu.Framework/Audio/Sample/SampleSDL2AudioPlayer.cs index 3855147723..ab184e7fde 100644 --- a/osu.Framework/Audio/Sample/SampleSDL2AudioPlayer.cs +++ b/osu.Framework/Audio/Sample/SampleSDL2AudioPlayer.cs @@ -7,10 +7,10 @@ namespace osu.Framework.Audio.Sample { internal class SampleSDL2AudioPlayer : ResamplingPlayer { - public override bool IsLoaded => true; - private int position; - private bool done; + + private volatile bool done; + public bool Done => done; private readonly float[] audioData; @@ -67,10 +67,5 @@ public void Reset(bool resetIndex = true) if (resetIndex) position = 0; } - - public bool IsDone() - { - return done; - } } } diff --git a/osu.Framework/Audio/Track/TempoSDL2AudioPlayer.cs b/osu.Framework/Audio/Track/TempoSDL2AudioPlayer.cs index 89a37d0f24..0c76e29784 100644 --- a/osu.Framework/Audio/Track/TempoSDL2AudioPlayer.cs +++ b/osu.Framework/Audio/Track/TempoSDL2AudioPlayer.cs @@ -20,6 +20,8 @@ internal class TempoSDL2AudioPlayer : TrackSDL2AudioPlayer private bool doneFilling; private bool donePlaying; + public override bool Done => base.Done && (soundTouch == null || donePlaying); + /// /// Creates a new . /// @@ -32,9 +34,8 @@ public TempoSDL2AudioPlayer(int rate, byte channels, int samples) samplesize = samples; } - protected override void UpdateState() + public void FillRequiredSamples() { - base.UpdateState(); fillSamples(samplesize); } @@ -47,18 +48,18 @@ private void fillSamples(int samples) if (soundTouch == null) return; - while (!base.IsDone() && soundTouch.AvailableSamples < samples) + while (!base.Done && soundTouch.AvailableSamples < samples) { - int getSamples = (int)Math.Ceiling((samples - soundTouch.AvailableSamples) * Tempo) * Channels; + int getSamples = (int)Math.Ceiling((samples - soundTouch.AvailableSamples) * Tempo) * SrcChannels; float[] src = new float[getSamples]; getSamples = base.GetRemainingSamples(src); if (getSamples <= 0) break; - soundTouch.PutSamples(src, getSamples / Channels); + soundTouch.PutSamples(src, getSamples / SrcChannels); } - if (!doneFilling && base.IsDone()) + if (!doneFilling && base.Done) { soundTouch.Flush(); doneFilling = true; @@ -78,8 +79,8 @@ public void SetTempo(double tempo) { soundTouch = new SoundTouchProcessor { - SampleRate = Rate, - Channels = Channels + SampleRate = SrcRate, + Channels = SrcChannels }; soundTouch.SetSetting(SettingId.UseQuickSeek, 1); soundTouch.SetSetting(SettingId.OverlapDurationMs, 4); @@ -92,7 +93,7 @@ public void SetTempo(double tempo) if (Tempo == 1.0f) { - int latency = GetTempoLatency() * 4 * Channels; + int latency = GetTempoLatency() * 4 * SrcChannels; StreamPosition = !ReversePlayback ? StreamPosition - latency : StreamPosition + latency; Reset(false); soundTouch = null; @@ -118,7 +119,7 @@ public override int GetRemainingSamples(float[] ret) if (RelativeRate == 0) return 0; - int expected = ret.Length / Channels; + int expected = ret.Length / SrcChannels; if (!doneFilling && soundTouch.AvailableSamples < expected) { @@ -130,9 +131,7 @@ public override int GetRemainingSamples(float[] ret) if (got == 0 && doneFilling) donePlaying = true; - timeCache = getCurrentTime(); - - return got * Channels; + return got * SrcChannels; } public override void Reset(bool resetPosition = true) @@ -151,12 +150,13 @@ protected int GetTempoLatency() return (int)(soundTouch.UnprocessedSampleCount + soundTouch.AvailableSamples * Tempo); } - private double timeCache; - - private double getCurrentTime() + public override double GetCurrentTime() { + if (soundTouch == null) + return base.GetCurrentTime(); + double baseTime = base.GetCurrentTime(); - double latency = (double)GetTempoLatency() / Rate * 1000.0d; + double latency = (double)GetTempoLatency() / SrcRate * 1000.0d; // GetCurrentTime is from TrackAudioPlayer, and fillSample retrieves samples from TrackPlayer. // fillSample already moved the position of TrackPlayer past the actual playing position, so subtract passed sample count to get actual current time. @@ -164,24 +164,11 @@ private double getCurrentTime() return !ReversePlayback ? baseTime - latency : baseTime + latency; } - public override double GetCurrentTime() - { - if (soundTouch == null) - return base.GetCurrentTime(); - - return timeCache; - } - public override void Seek(double seek) { base.Seek(seek); if (soundTouch != null) fillSamples(samplesize); } - - public override bool IsDone() - { - return base.IsDone() && (soundTouch == null || donePlaying); - } } } diff --git a/osu.Framework/Audio/Track/TrackSDL2.cs b/osu.Framework/Audio/Track/TrackSDL2.cs index 035f72b2e7..fdc6b44162 100644 --- a/osu.Framework/Audio/Track/TrackSDL2.cs +++ b/osu.Framework/Audio/Track/TrackSDL2.cs @@ -12,30 +12,23 @@ namespace osu.Framework.Audio.Track { public sealed class TrackSDL2 : Track, ISDL2AudioChannel { - public bool Preview { get; private set; } - private readonly TempoSDL2AudioPlayer player; - private volatile bool isLoaded; - public override bool IsDummyDevice => false; + private volatile bool isLoaded; public override bool IsLoaded => isLoaded; private double currentTime; - public override double CurrentTime => currentTime; private volatile bool isRunning; - public override bool IsRunning => isRunning; private volatile bool hasCompleted; - public override bool HasCompleted => hasCompleted; private volatile int bitrate; - public override int? Bitrate => bitrate; public TrackSDL2(string name, int rate, byte channels, int samples) @@ -49,7 +42,6 @@ public TrackSDL2(string name, int rate, byte channels, int samples) throw new ArgumentException($"{nameof(TrackSDL2)} does not support {nameof(Tempo)} specifications below {tempo_minimum_supported}. Use {nameof(Frequency)} instead."); }; - Preview = false; player = new TempoSDL2AudioPlayer(rate, channels, samples); } @@ -80,17 +72,14 @@ protected override void UpdateState() { base.UpdateState(); - if (decodeData != null) + if (decodeData != null && !isLoaded) { - if (!isLoaded) - { - Length = decodeData.Length; - bitrate = decodeData.Bitrate; - isLoaded = true; - } + Length = decodeData.Length; + bitrate = decodeData.Bitrate; + isLoaded = true; } - if (player.IsDone() && isRunning) + if (player.Done && isRunning) { if (Looping) { @@ -104,16 +93,7 @@ protected override void UpdateState() } } - lock (syncRoot) - Interlocked.Exchange(ref currentTime, player.GetCurrentTime()); - } - - protected override void UpdateChildren() - { - base.UpdateChildren(); - - lock (syncRoot) - player.Update(); + // may need to fill soundtouch buffer } internal override void OnStateChanged() @@ -144,7 +124,7 @@ public override async Task SeekAsync(double seek) return conservativeClamped == seek; } - private void seekInternal(double seek) + private void seekInternal(double seek) => EnqueueAction(() => { lock (syncRoot) { @@ -158,7 +138,7 @@ private void seekInternal(double seek) Interlocked.Exchange(ref currentTime, player.GetCurrentTime()); } - } + }); public override void Start() { @@ -177,10 +157,7 @@ public override Task StartAsync() => EnqueueAction(() => hasCompleted = false; }); - public override void Stop() - { - StopAsync().WaitSafely(); - } + public override void Stop() => StopAsync().WaitSafely(); public override Task StopAsync() => EnqueueAction(() => { @@ -196,6 +173,8 @@ int ISDL2AudioChannel.GetRemainingSamples(float[] data) lock (syncRoot) ret = player.GetRemainingSamples(data); + Interlocked.Exchange(ref currentTime, player.GetCurrentTime()); + if (ret < 0) { EnqueueAction(RaiseFailed); @@ -205,7 +184,7 @@ int ISDL2AudioChannel.GetRemainingSamples(float[] data) return ret; } - bool ISDL2AudioChannel.Playing => isRunning && player.IsDone() == false; + bool ISDL2AudioChannel.Playing => isRunning && !player.Done; int ISDL2AudioChannel.Volume => (int)(AggregateVolume.Value * SDL.SDL_MIX_MAXVOLUME); diff --git a/osu.Framework/Audio/Track/TrackSDL2AudioPlayer.cs b/osu.Framework/Audio/Track/TrackSDL2AudioPlayer.cs index 0933ebd9d7..2be64e37f4 100644 --- a/osu.Framework/Audio/Track/TrackSDL2AudioPlayer.cs +++ b/osu.Framework/Audio/Track/TrackSDL2AudioPlayer.cs @@ -10,38 +10,30 @@ namespace osu.Framework.Audio.Track /// /// Mainly returns audio data to . /// - internal class TrackSDL2AudioPlayer : ResamplingPlayer + internal class TrackSDL2AudioPlayer : ResamplingPlayer, IDisposable { private volatile bool isLoaded; - public override bool IsLoaded => isLoaded; + public bool IsLoaded => isLoaded; private volatile bool isLoading; public bool IsLoading => isLoading; private volatile bool done; - - protected int Rate { get; set; } - protected int Channels { get; set; } + public virtual bool Done => done; /// /// Returns a byte position converted into milliseconds with configuration set for this player. /// /// A byte position to convert /// - public double GetMsFromBytes(long bytePos) - { - return bytePos * 1000.0d / Rate / Channels / 4; - } + public double GetMsFromBytes(long bytePos) => bytePos * 1000.0d / SrcRate / SrcChannels / 4; /// /// Returns a position in milliseconds converted from a byte position with configuration set for this player. /// /// A position in milliseconds to convert /// - public long GetBytesFromMs(double seconds) - { - return (long)(seconds / 1000.0d * Rate) * Channels * 4; - } + public long GetBytesFromMs(double seconds) => (long)(seconds / 1000.0d * SrcRate) * SrcChannels * 4; /// /// Stores raw audio data. @@ -107,9 +99,6 @@ public bool ReversePlayback public TrackSDL2AudioPlayer(int rate, byte channels) : base(rate, channels) { - Rate = rate; - Channels = channels; - isLoading = false; isLoaded = false; } @@ -204,7 +193,7 @@ protected override int GetRemainingRawBytes(byte[] data) { if (ReversePlayback) { - int frameSize = Channels * 4; + int frameSize = SrcChannels * 4; byte[] temp = new byte[put]; AudioData.Position -= put; @@ -252,10 +241,7 @@ public virtual void Reset(bool resetPosition = true) /// /// Returns current position converted into milliseconds. /// - public virtual double GetCurrentTime() - { - return !ReversePlayback ? GetMsFromBytes(StreamPosition) - GetResampleLatency() : GetMsFromBytes(StreamPosition) + GetResampleLatency(); - } + public virtual double GetCurrentTime() => !ReversePlayback ? GetMsFromBytes(StreamPosition) - GetResampleLatency() : GetMsFromBytes(StreamPosition) + GetResampleLatency(); protected long SaveSeek; @@ -280,25 +266,26 @@ public virtual void Seek(double seek) } } - public virtual bool IsDone() - { - return done; - } + private bool disposedValue; - ~TrackSDL2AudioPlayer() + protected virtual void Dispose(bool disposing) { - Dispose(false); + if (!disposedValue) + { + if (disposing) + { + AudioData?.Dispose(); + AudioData = null; + } + + disposedValue = true; + } } - protected override void Dispose(bool disposing) + public void Dispose() { - if (IsDisposed) - return; - - AudioData?.Dispose(); - AudioData = null; - - base.Dispose(disposing); + Dispose(true); + GC.SuppressFinalize(this); } } }