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 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);

View File

@ -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);

View File

@ -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;

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)
{
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);
}

View File

@ -12,57 +12,64 @@ public sealed class StemWaveformService : IStemWaveformService
public async Task<float[]> ComputeWaveformAsync(StemTrack stem, IStemDecoder decoder, int segments = 200)
{
if (segments <= 0)
{
return Array.Empty<float>();
}
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;
}

Binary file not shown.