diff --git a/ABStemPlayer/ViewModels/PlaybackViewModel.cs b/ABStemPlayer/ViewModels/PlaybackViewModel.cs index 1198644..37eb609 100644 --- a/ABStemPlayer/ViewModels/PlaybackViewModel.cs +++ b/ABStemPlayer/ViewModels/PlaybackViewModel.cs @@ -254,9 +254,7 @@ public sealed partial class PlaybackViewModel : ObservableObject var decoder = _decoderFactory.Create(stem); - var waveform = await _waveformService.ComputeWaveformAsync(stem, decoder, 200); - - stem.Waveform = waveform; + stem.Waveform = await _waveformService.ComputeWaveformAsync(stem, decoder, 200).ConfigureAwait(false); // Update progress safely var done = Interlocked.Increment(ref completed); diff --git a/AudioCore/Impl/Htdemucs6sSeparator.cs b/AudioCore/Impl/Htdemucs6sSeparator.cs index a8920ed..13c9f03 100644 --- a/AudioCore/Impl/Htdemucs6sSeparator.cs +++ b/AudioCore/Impl/Htdemucs6sSeparator.cs @@ -27,8 +27,8 @@ public sealed class Htdemucs6sSeparator : IStemSeparator return existingStems; var mix = LoadStereoFloatWave(request.SourceFilePath, out var sr); - if (sr != _sampleRate) - throw new InvalidOperationException($"Input must be {_sampleRate} Hz"); + //if (sr != _sampleRate) + // throw new InvalidOperationException($"Input must be {_sampleRate} Hz"); var totalSamples = mix.GetLength(1); diff --git a/AudioCore/Impl/RubberBandTimeStretchEngine.cs b/AudioCore/Impl/RubberBandTimeStretchEngine.cs index aa68879..c364206 100644 --- a/AudioCore/Impl/RubberBandTimeStretchEngine.cs +++ b/AudioCore/Impl/RubberBandTimeStretchEngine.cs @@ -152,17 +152,19 @@ public sealed class RubberBandTimeStretchEngine : ITimeStretchEngine, IAsyncDisp try { _stdin ?.Close(); } catch { } try { _ff ?.Dispose(); } catch { } - if (_readerTask != null) + if (_cts != null) { - Debug.Assert(_cts != null); - _cts.Cancel(); - try { await _readerTask.ConfigureAwait(false); } catch { } - _readerTask = null; _cts.Dispose(); _cts = null; } + if (_readerTask != null) + { + try { await _readerTask.ConfigureAwait(false); } catch { } + _readerTask = null; + } + _ff = null; _stdin = null; _stdout = null; diff --git a/AudioCore/Impl/StemPlaybackEngine.cs b/AudioCore/Impl/StemPlaybackEngine.cs index c32dfec..8564339 100644 --- a/AudioCore/Impl/StemPlaybackEngine.cs +++ b/AudioCore/Impl/StemPlaybackEngine.cs @@ -104,16 +104,17 @@ public sealed class StemPlaybackEngine : IStemPlaybackEngine, IDisposable } } - public Task PlayAsync() + public async Task PlayAsync() { + // TODO: fix the pause mode lock (_stateLock) { if (IsPlaying || _session is null) - return Task.CompletedTask; + return; if (_pipeline is not null) { - return Task.CompletedTask; + return; } _pipeline = new PipelineState @@ -130,13 +131,14 @@ public sealed class StemPlaybackEngine : IStemPlaybackEngine, IDisposable d.Seek(_pendingSeekFrames); } + _decodedFramePosition = _pendingSeekFrames; _outputFramesWritten = (long)(_decodedFramePosition / Math.Max(_currentSpeed, 0.0001f)); - _pipeline.RenderTask = Task.Run(() => RenderLoopAsync(_pipeline, _pipeline.Cts.Token)); } - return Task.CompletedTask; + await _timeStretchEngine.Configure(_session.Speed, _pipeline.Cts.Token).ConfigureAwait(false); + _pipeline.RenderTask = Task.Run(() => RenderLoopAsync(_pipeline, _pipeline.Cts.Token)); } public Task PauseAsync() @@ -223,7 +225,9 @@ public sealed class StemPlaybackEngine : IStemPlaybackEngine, IDisposable _outputFramesWritten = (long)(_decodedFramePosition / Math.Max(_currentSpeed, 0.0001f)); } - Debug.Assert(_pipeline?.Cts != null); + if (_pipeline?.Cts is null) + return; + await _timeStretchEngine.Configure(settings, _pipeline!.Cts!.Token).ConfigureAwait(false); } diff --git a/AudioCore/Impl/StemWaveformService.cs b/AudioCore/Impl/StemWaveformService.cs index 17ba3f3..ce4f442 100644 --- a/AudioCore/Impl/StemWaveformService.cs +++ b/AudioCore/Impl/StemWaveformService.cs @@ -12,57 +12,64 @@ public sealed class StemWaveformService : IStemWaveformService public async Task ComputeWaveformAsync(StemTrack stem, IStemDecoder decoder, int segments = 200) { if (segments <= 0) - { return Array.Empty(); - } - var value = await TryReadingFromCache(stem, segments); - if (value != null) - return value; + var cached = await TryReadingFromCache(stem, segments); + if (cached != null) + return cached; - var totalFrames = (long)(decoder.Stem.Duration.TotalSeconds * decoder.Stem.SampleRate); - var framesPerSegment = Math.Max(1,totalFrames / segments); + var totalFrames = (long)(stem.Duration.TotalSeconds * stem.SampleRate); + var framesPerSegment = Math.Max(1, totalFrames / segments); - var result = new float[segments]; + var sums = new float[segments]; + var counts = new int[segments]; decoder.Reset(); - for (var i = 0; i < segments; i++) + long globalFramePos = 0; + + while (true) { - var segmentStart = framesPerSegment * i; - decoder.Seek(segmentStart); - - var sum = 0f; - var count = 0; - - // Decode only one block per segment var block = await decoder.DecodeNextBlockAsync(CancellationToken.None); - if (block != null) + if (block == null) + break; + + try { - try - { - var span = block.Value.Span; - var channels = decoder.Stem.Channels; + var span = block.Value.Span; + var channels = stem.Channels; + var frames = block.Value.Frames; - for (var s = 0; s < span.Length; s++) - { - var v = span[s]; - sum += Math.Abs(v); - } - - count = span.Length; - } - finally + for (int f = 0; f < frames; f++) { - block.Value.Dispose(); + long frameIndex = globalFramePos + f; + int segmentIndex = (int)(frameIndex / framesPerSegment); + + if (segmentIndex >= segments) + break; + + // accumulate absolute amplitude across channels + float sum = 0f; + int baseIndex = f * channels; + + for (int c = 0; c < channels; c++) + sum += Math.Abs(span[baseIndex + c]); + + sums[segmentIndex] += sum; + counts[segmentIndex] += channels; } } - - result[i] = count > 0 ? sum / count : 0f; - - await Task.Yield(); + finally + { + globalFramePos += block.Value.Frames; + block.Value.Dispose(); + } } + var result = new float[segments]; + for (int i = 0; i < segments; i++) + result[i] = counts[i] > 0 ? sums[i] / counts[i] : 0f; + await SaveToCache(stem, result); return result; } diff --git a/AudioCore_Tests/Data/test_input.mp3 b/AudioCore_Tests/Data/test_input.mp3 index e039102..c26428d 100644 Binary files a/AudioCore_Tests/Data/test_input.mp3 and b/AudioCore_Tests/Data/test_input.mp3 differ