New test mp3. Much faster StemWaveformService

This commit is contained in:
Alexander Shabarshov 2026-07-19 10:19:36 +01:00
parent 5142bb61f2
commit 7efd223a6c
6 changed files with 62 additions and 51 deletions

View File

@ -254,9 +254,7 @@ public sealed partial class PlaybackViewModel : ObservableObject
var decoder = _decoderFactory.Create(stem); var decoder = _decoderFactory.Create(stem);
var waveform = await _waveformService.ComputeWaveformAsync(stem, decoder, 200); stem.Waveform = await _waveformService.ComputeWaveformAsync(stem, decoder, 200).ConfigureAwait(false);
stem.Waveform = waveform;
// Update progress safely // Update progress safely
var done = Interlocked.Increment(ref completed); var done = Interlocked.Increment(ref completed);

View File

@ -27,8 +27,8 @@ public sealed class Htdemucs6sSeparator : IStemSeparator
return existingStems; return existingStems;
var mix = LoadStereoFloatWave(request.SourceFilePath, out var sr); var mix = LoadStereoFloatWave(request.SourceFilePath, out var sr);
if (sr != _sampleRate) //if (sr != _sampleRate)
throw new InvalidOperationException($"Input must be {_sampleRate} Hz"); // throw new InvalidOperationException($"Input must be {_sampleRate} Hz");
var totalSamples = mix.GetLength(1); var totalSamples = mix.GetLength(1);

View File

@ -152,17 +152,19 @@ public sealed class RubberBandTimeStretchEngine : ITimeStretchEngine, IAsyncDisp
try { _stdin ?.Close(); } catch { } try { _stdin ?.Close(); } catch { }
try { _ff ?.Dispose(); } catch { } try { _ff ?.Dispose(); } catch { }
if (_readerTask != null) if (_cts != null)
{ {
Debug.Assert(_cts != null);
_cts.Cancel(); _cts.Cancel();
try { await _readerTask.ConfigureAwait(false); } catch { }
_readerTask = null;
_cts.Dispose(); _cts.Dispose();
_cts = null; _cts = null;
} }
if (_readerTask != null)
{
try { await _readerTask.ConfigureAwait(false); } catch { }
_readerTask = null;
}
_ff = null; _ff = null;
_stdin = null; _stdin = null;
_stdout = null; _stdout = null;

View File

@ -104,16 +104,17 @@ public sealed class StemPlaybackEngine : IStemPlaybackEngine, IDisposable
} }
} }
public Task PlayAsync() public async Task PlayAsync()
{ {
// TODO: fix the pause mode
lock (_stateLock) lock (_stateLock)
{ {
if (IsPlaying || _session is null) if (IsPlaying || _session is null)
return Task.CompletedTask; return;
if (_pipeline is not null) if (_pipeline is not null)
{ {
return Task.CompletedTask; return;
} }
_pipeline = new PipelineState _pipeline = new PipelineState
@ -130,13 +131,14 @@ public sealed class StemPlaybackEngine : IStemPlaybackEngine, IDisposable
d.Seek(_pendingSeekFrames); d.Seek(_pendingSeekFrames);
} }
_decodedFramePosition = _pendingSeekFrames; _decodedFramePosition = _pendingSeekFrames;
_outputFramesWritten = (long)(_decodedFramePosition / Math.Max(_currentSpeed, 0.0001f)); _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() public Task PauseAsync()
@ -223,7 +225,9 @@ public sealed class StemPlaybackEngine : IStemPlaybackEngine, IDisposable
_outputFramesWritten = (long)(_decodedFramePosition / Math.Max(_currentSpeed, 0.0001f)); _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); await _timeStretchEngine.Configure(settings, _pipeline!.Cts!.Token).ConfigureAwait(false);
} }

View File

@ -12,56 +12,63 @@ public sealed class StemWaveformService : IStemWaveformService
public async Task<float[]> ComputeWaveformAsync(StemTrack stem, IStemDecoder decoder, int segments = 200) public async Task<float[]> ComputeWaveformAsync(StemTrack stem, IStemDecoder decoder, int segments = 200)
{ {
if (segments <= 0) if (segments <= 0)
{
return Array.Empty<float>(); return Array.Empty<float>();
}
var value = await TryReadingFromCache(stem, segments); var cached = await TryReadingFromCache(stem, segments);
if (value != null) if (cached != null)
return value; return cached;
var totalFrames = (long)(decoder.Stem.Duration.TotalSeconds * decoder.Stem.SampleRate); var totalFrames = (long)(stem.Duration.TotalSeconds * stem.SampleRate);
var framesPerSegment = Math.Max(1, totalFrames / segments); var framesPerSegment = Math.Max(1, totalFrames / segments);
var result = new float[segments]; var sums = new float[segments];
var counts = new int[segments];
decoder.Reset(); 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); var block = await decoder.DecodeNextBlockAsync(CancellationToken.None);
if (block != null) if (block == null)
{ break;
try try
{ {
var span = block.Value.Span; var span = block.Value.Span;
var channels = decoder.Stem.Channels; var channels = stem.Channels;
var frames = block.Value.Frames;
for (var s = 0; s < span.Length; s++) for (int f = 0; f < frames; f++)
{ {
var v = span[s]; long frameIndex = globalFramePos + f;
sum += Math.Abs(v); int segmentIndex = (int)(frameIndex / framesPerSegment);
}
count = span.Length; 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;
}
} }
finally finally
{ {
globalFramePos += block.Value.Frames;
block.Value.Dispose(); block.Value.Dispose();
} }
} }
result[i] = count > 0 ? sum / count : 0f; var result = new float[segments];
for (int i = 0; i < segments; i++)
await Task.Yield(); result[i] = counts[i] > 0 ? sums[i] / counts[i] : 0f;
}
await SaveToCache(stem, result); await SaveToCache(stem, result);
return result; return result;

Binary file not shown.