mirror of
https://github.com/unclshura/ABStemPlayer.git
synced 2026-09-21 11:13:38 +00:00
Speed change on the fly is almost working. Still hangs sometime.
This commit is contained in:
parent
a181acb706
commit
5e654f62f6
@ -4,7 +4,7 @@ namespace ABStemPlayer.ViewModels;
|
|||||||
|
|
||||||
public sealed class MixerViewModel
|
public sealed class MixerViewModel
|
||||||
{
|
{
|
||||||
public ObservableCollection<StemChannelViewModel> Stems { get; } = new();
|
public ObservableCollection<StemChannelViewModel> Stems { get; } = [];
|
||||||
|
|
||||||
public MixerViewModel()
|
public MixerViewModel()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -54,8 +54,8 @@ public sealed partial class PlaybackViewModel : ObservableObject
|
|||||||
[ObservableProperty] private TimeSpan _totalTime;
|
[ObservableProperty] private TimeSpan _totalTime;
|
||||||
[ObservableProperty] private MixerViewModel _mixer = null!;
|
[ObservableProperty] private MixerViewModel _mixer = null!;
|
||||||
|
|
||||||
public ObservableCollection<SegmentViewModel> Segments { get; } = new();
|
public ObservableCollection<SegmentViewModel> Segments { get; } = [];
|
||||||
public ObservableCollection<WaveformBandViewModel> Bands { get; } = new();
|
public ObservableCollection<WaveformBandViewModel> Bands { get; } = [];
|
||||||
|
|
||||||
|
|
||||||
private TimeSpan? _loopA;
|
private TimeSpan? _loopA;
|
||||||
|
|||||||
@ -25,11 +25,11 @@ public partial class WaveformBandViewModel : ObservableObject
|
|||||||
[ObservableProperty] private double _canvasHeight;
|
[ObservableProperty] private double _canvasHeight;
|
||||||
[ObservableProperty] private Geometry? _waveformGeometry;
|
[ObservableProperty] private Geometry? _waveformGeometry;
|
||||||
|
|
||||||
public ObservableCollection<WaveformBar> WaveformBars { get; } = new();
|
public ObservableCollection<WaveformBar> WaveformBars { get; } = [];
|
||||||
|
|
||||||
[ObservableProperty] private double _playbackX;
|
[ObservableProperty] private double _playbackX;
|
||||||
|
|
||||||
public ObservableCollection<SegmentViewModel> Segments { get; } = new();
|
public ObservableCollection<SegmentViewModel> Segments { get; } = [];
|
||||||
|
|
||||||
public TimeSpan Duration { get; set; }
|
public TimeSpan Duration { get; set; }
|
||||||
public string DurationFormatted => Duration.ToString("mm\\:ss");
|
public string DurationFormatted => Duration.ToString("mm\\:ss");
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace AudioCore.Impl;
|
namespace AudioCore.Impl;
|
||||||
|
|
||||||
public class AudioBuffer<T> : IDisposable
|
public class AudioBuffer<T> : IDisposable where T : unmanaged
|
||||||
{
|
{
|
||||||
private readonly GenericBufferPool<T> _owner;
|
private readonly GenericBufferPool<T> _owner;
|
||||||
|
private static GenericBufferPool<byte> _bytePool = new GenericBufferPool<byte>();
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public T[] Samples { get; }
|
public T[] Samples { get; }
|
||||||
@ -24,4 +27,19 @@ public class AudioBuffer<T> : IDisposable
|
|||||||
_owner.Return(Samples);
|
_owner.Return(Samples);
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task WriteAsync(Stream stream, CancellationToken token)
|
||||||
|
{
|
||||||
|
using var outBuf = _bytePool.Rent(Samples.Length * sizeof(T));
|
||||||
|
|
||||||
|
var src = Samples.AsSpan(0, Length);
|
||||||
|
var dst = outBuf.Span;
|
||||||
|
|
||||||
|
MemoryMarshal.Cast<T, byte>(src).CopyTo(dst);
|
||||||
|
|
||||||
|
await stream.WriteAsync(outBuf.Samples, 0, dst.Length, token)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using static AudioCore.Models.Tracer;
|
||||||
|
|
||||||
namespace AudioCore.Impl;
|
namespace AudioCore.Impl;
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ public sealed class FfmpegProcess : IDisposable
|
|||||||
|
|
||||||
public void StartProcess()
|
public void StartProcess()
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"{_name}: Starting ffmpeg process: {_commandLine.Replace("\r", "").Replace("\n", " ").Replace("\t", " ")}");
|
Msg($"{_name}: Starting ffmpeg process: {_commandLine.Replace("\r", "").Replace("\n", " ").Replace("\t", " ")}");
|
||||||
|
|
||||||
var psi = new ProcessStartInfo
|
var psi = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
@ -119,7 +120,7 @@ public sealed class FfmpegProcess : IDisposable
|
|||||||
private void DisposeProcessOnly()
|
private void DisposeProcessOnly()
|
||||||
{
|
{
|
||||||
if (Proc != null)
|
if (Proc != null)
|
||||||
Debug.WriteLine($"{_name}: Disposing ffmpeg process");
|
Msg($"{_name}: Disposing ffmpeg process");
|
||||||
|
|
||||||
try { Stdout?.Dispose(); Stdout = null; } catch { }
|
try { Stdout?.Dispose(); Stdout = null; } catch { }
|
||||||
try { Stdin?.Dispose(); Stdin = null; } catch { }
|
try { Stdin?.Dispose(); Stdin = null; } catch { }
|
||||||
|
|||||||
@ -2,7 +2,7 @@ using System.Buffers;
|
|||||||
|
|
||||||
namespace AudioCore.Impl;
|
namespace AudioCore.Impl;
|
||||||
|
|
||||||
public class GenericBufferPool<T>
|
public class GenericBufferPool<T> where T : unmanaged
|
||||||
{
|
{
|
||||||
private readonly ArrayPool<T> _pool = ArrayPool<T>.Shared;
|
private readonly ArrayPool<T> _pool = ArrayPool<T>.Shared;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
using System.Diagnostics;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using static AudioCore.Models.Tracer;
|
using static AudioCore.Models.Tracer;
|
||||||
|
|
||||||
namespace AudioCore.Impl;
|
namespace AudioCore.Impl;
|
||||||
@ -8,7 +7,7 @@ public sealed class RubberBandTimeStretchEngine : ITimeStretchEngine, IAsyncDisp
|
|||||||
{
|
{
|
||||||
private readonly AudioBufferPool _pool;
|
private readonly AudioBufferPool _pool;
|
||||||
private readonly int _sampleRate;
|
private readonly int _sampleRate;
|
||||||
private long[] _sourcePositions;
|
private readonly long[] _sourcePositions;
|
||||||
|
|
||||||
private sealed class SourceChunkInfo
|
private sealed class SourceChunkInfo
|
||||||
{
|
{
|
||||||
@ -17,7 +16,14 @@ public sealed class RubberBandTimeStretchEngine : ITimeStretchEngine, IAsyncDisp
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly Queue<SourceChunkInfo>[] _pendingInput;
|
private readonly Queue<SourceChunkInfo>[] _pendingInput;
|
||||||
private double[] _fractionalInput;
|
private readonly double[] _fractionalInput;
|
||||||
|
private readonly List<StemProcess> _stemProcesses = [];
|
||||||
|
private readonly int _stemCount;
|
||||||
|
private int _activeIo; // Interlocked counter
|
||||||
|
private float _speed = 1.0f;
|
||||||
|
private CancellationTokenSource? _cts;
|
||||||
|
private CancellationToken _token;
|
||||||
|
private Task? _readerTask;
|
||||||
|
|
||||||
// One RubberBand/ffmpeg process per stem (each is stereo: 2 channels)
|
// One RubberBand/ffmpeg process per stem (each is stereo: 2 channels)
|
||||||
private sealed class StemProcess : IDisposable
|
private sealed class StemProcess : IDisposable
|
||||||
@ -45,13 +51,7 @@ public sealed class RubberBandTimeStretchEngine : ITimeStretchEngine, IAsyncDisp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<StemProcess> _stemProcesses = new();
|
|
||||||
private readonly int _stemCount;
|
|
||||||
|
|
||||||
private float _speed = 1.0f;
|
|
||||||
private CancellationTokenSource? _cts;
|
|
||||||
private CancellationToken _token;
|
|
||||||
private Task? _readerTask;
|
|
||||||
|
|
||||||
public RubberBandTimeStretchEngine(AudioBufferPool pool, int sampleRate = 44100, int stemCount = 6)
|
public RubberBandTimeStretchEngine(AudioBufferPool pool, int sampleRate = 44100, int stemCount = 6)
|
||||||
{
|
{
|
||||||
@ -62,22 +62,24 @@ public sealed class RubberBandTimeStretchEngine : ITimeStretchEngine, IAsyncDisp
|
|||||||
_sourcePositions = new long[_stemCount];
|
_sourcePositions = new long[_stemCount];
|
||||||
_fractionalInput = new double[_stemCount];
|
_fractionalInput = new double[_stemCount];
|
||||||
|
|
||||||
_pendingInput = Enumerable.Range(0, _stemCount)
|
_pendingInput = [.. Enumerable.Range(0, _stemCount).Select(_ => new Queue<SourceChunkInfo>())];
|
||||||
.Select(_ => new Queue<SourceChunkInfo>())
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Configure(PlaybackSpeedSettings settings, CancellationToken token)
|
public async Task Configure(PlaybackSpeedSettings settings, CancellationToken globalToken)
|
||||||
{
|
{
|
||||||
Trace(settings);
|
Trace(settings);
|
||||||
|
|
||||||
|
var speedChanged = Math.Abs(_speed - settings.Speed) > 0.01f;
|
||||||
|
|
||||||
_speed = settings.Speed;
|
_speed = settings.Speed;
|
||||||
|
|
||||||
if (_cts != null)
|
if (globalToken != CancellationToken.None)
|
||||||
await DisposeProcesses().ConfigureAwait(false);
|
_token = globalToken;
|
||||||
|
|
||||||
if ( token != CancellationToken.None )
|
if (_cts != null && speedChanged)
|
||||||
_token = token;
|
{
|
||||||
|
await DisposeProcesses().ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -88,7 +90,10 @@ public sealed class RubberBandTimeStretchEngine : ITimeStretchEngine, IAsyncDisp
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SubmitStems(IReadOnlyList<AudioBlock> stemBlocks, CancellationToken token)
|
public async Task SubmitStems(IReadOnlyList<AudioBlock> stemBlocks, CancellationToken token)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref _activeIo);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (stemBlocks.Count != _stemCount)
|
if (stemBlocks.Count != _stemCount)
|
||||||
throw new ArgumentException($"Expected {_stemCount} stems, but got {stemBlocks.Count}.");
|
throw new ArgumentException($"Expected {_stemCount} stems, but got {stemBlocks.Count}.");
|
||||||
@ -100,6 +105,9 @@ public sealed class RubberBandTimeStretchEngine : ITimeStretchEngine, IAsyncDisp
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < stemBlocks.Count; i++)
|
for (int i = 0; i < stemBlocks.Count; i++)
|
||||||
{
|
{
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
var block = stemBlocks[i];
|
var block = stemBlocks[i];
|
||||||
|
|
||||||
// Track source position for passthrough mode
|
// Track source position for passthrough mode
|
||||||
@ -113,7 +121,7 @@ public sealed class RubberBandTimeStretchEngine : ITimeStretchEngine, IAsyncDisp
|
|||||||
_stemProcesses[i].Ring.Write(bytes, bytes.Length, token);
|
_stemProcesses[i].Ring.Write(bytes, bytes.Length, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stretch path: one ffmpeg+rubberband per stem
|
// Stretch path: one ffmpeg+rubberband per stem
|
||||||
@ -131,22 +139,20 @@ public sealed class RubberBandTimeStretchEngine : ITimeStretchEngine, IAsyncDisp
|
|||||||
});
|
});
|
||||||
|
|
||||||
var proc = _stemProcesses[i];
|
var proc = _stemProcesses[i];
|
||||||
var span = block.Buffer.Span;
|
|
||||||
var bytes = MemoryMarshal.AsBytes(span);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
return Task.CompletedTask;
|
return;
|
||||||
|
|
||||||
// Only write if ffmpeg is alive
|
// Only write if ffmpeg is alive
|
||||||
if (proc.Stdin != null && !(proc.Ff?.Proc?.HasExited ?? true))
|
if (proc.Stdin != null && !(proc.Ff?.Proc?.HasExited ?? true))
|
||||||
{
|
{
|
||||||
proc.Stdin.Write(bytes);
|
await block.Buffer.WriteAsync(proc.Stdin, token).ConfigureAwait(false);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
proc.Stdin.Flush();
|
await proc.Stdin.FlushAsync(token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (ObjectDisposedException)
|
catch (ObjectDisposedException)
|
||||||
{
|
{
|
||||||
@ -154,17 +160,27 @@ public sealed class RubberBandTimeStretchEngine : ITimeStretchEngine, IAsyncDisp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// Ignore write errors (ffmpeg may exit early)
|
// Ignore write errors (ffmpeg may exit early)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return Task.CompletedTask;
|
finally
|
||||||
|
{
|
||||||
|
Interlocked.Decrement(ref _activeIo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<TimeStretchedAudioBlock[]> ReceiveStems(CancellationToken token)
|
public async Task<TimeStretchedAudioBlock[]> ReceiveStems(CancellationToken token)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref _activeIo);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
EnsureStemProcesses(_stemCount);
|
EnsureStemProcesses(_stemCount);
|
||||||
|
|
||||||
@ -190,7 +206,7 @@ public sealed class RubberBandTimeStretchEngine : ITimeStretchEngine, IAsyncDisp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
return Array.Empty<TimeStretchedAudioBlock>();
|
return [];
|
||||||
|
|
||||||
// Determine block size (final block may be smaller)
|
// Determine block size (final block may be smaller)
|
||||||
int bytesToRead = Math.Min(bytesPerBlock, available);
|
int bytesToRead = Math.Min(bytesPerBlock, available);
|
||||||
@ -218,7 +234,7 @@ public sealed class RubberBandTimeStretchEngine : ITimeStretchEngine, IAsyncDisp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
return Array.Empty<TimeStretchedAudioBlock>();
|
return [];
|
||||||
|
|
||||||
// Now allocate the float buffer
|
// Now allocate the float buffer
|
||||||
var outBuf = _pool.Rent(samplesToRead);
|
var outBuf = _pool.Rent(samplesToRead);
|
||||||
@ -243,6 +259,11 @@ public sealed class RubberBandTimeStretchEngine : ITimeStretchEngine, IAsyncDisp
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Interlocked.Decrement(ref _activeIo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private long ComputeSourcePosition(int stemIndex, int outputFrames)
|
private long ComputeSourcePosition(int stemIndex, int outputFrames)
|
||||||
{
|
{
|
||||||
@ -341,37 +362,37 @@ private long ComputeSourcePosition(int stemIndex, int outputFrames)
|
|||||||
|
|
||||||
var buf = new byte[4096];
|
var buf = new byte[4096];
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (!token.IsCancellationRequested)
|
while (!token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
bool anyActive = false;
|
try
|
||||||
|
{
|
||||||
foreach (var proc in _stemProcesses)
|
foreach (var proc in _stemProcesses)
|
||||||
{
|
{
|
||||||
if (proc.Stdout == null)
|
if (proc.Stdout == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
anyActive = true;
|
var read = await proc.Stdout.ReadAsync(buf, token).ConfigureAwait(false);
|
||||||
|
|
||||||
var read = await proc.Stdout.ReadAsync(buf, 0, buf.Length, token).ConfigureAwait(false);
|
|
||||||
if (read > 0)
|
if (read > 0)
|
||||||
proc.Ring.Write(buf, read, token);
|
proc.Ring.Write(buf, read, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!anyActive)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// expected on dispose; let outer while exit via token
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task DisposeProcesses()
|
private async Task DisposeProcesses()
|
||||||
{
|
{
|
||||||
|
Trace();
|
||||||
|
|
||||||
if (_cts != null)
|
if (_cts != null)
|
||||||
{
|
{
|
||||||
Msg("Cancelling RubberBand/ffmpeg reader task...");
|
Msg("Cancelling RubberBand/ffmpeg reader task...");
|
||||||
try { _cts.Cancel(); } catch { }
|
try { await _cts.CancelAsync(); } catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_readerTask != null)
|
if (_readerTask != null)
|
||||||
@ -390,16 +411,29 @@ private long ComputeSourcePosition(int stemIndex, int outputFrames)
|
|||||||
_stemProcesses.Clear();
|
_stemProcesses.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_cts != null)
|
_cts?.Dispose();
|
||||||
{
|
|
||||||
_cts.Dispose();
|
|
||||||
_cts = null;
|
_cts = null;
|
||||||
|
|
||||||
|
// Reset position tracking
|
||||||
|
for (int i = 0; i < _stemCount; i++)
|
||||||
|
{
|
||||||
|
_pendingInput[i].Clear();
|
||||||
|
_fractionalInput[i] = 0;
|
||||||
|
_sourcePositions[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_readerTask = null;
|
||||||
|
|
||||||
|
_cts = null;
|
||||||
|
|
||||||
|
// Wait until no active I/O before tearing down
|
||||||
|
while (Interlocked.CompareExchange(ref _activeIo, 0, 0) != 0)
|
||||||
|
await Task.Delay(1, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
Trace();
|
|
||||||
await DisposeProcesses().ConfigureAwait(false);
|
await DisposeProcesses().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,6 +48,9 @@ public sealed class StemPlaybackEngine : IStemPlaybackEngine, IDisposable
|
|||||||
private PipelineState? _pipeline;
|
private PipelineState? _pipeline;
|
||||||
private long _pendingSeekFrames;
|
private long _pendingSeekFrames;
|
||||||
|
|
||||||
|
private PlaybackSpeedSettings _currentSpeed = new(){ Speed = 1.0f };
|
||||||
|
private PlaybackSpeedSettings _prevSpeed = new(){ Speed = 1.0f };
|
||||||
|
|
||||||
public StemPlaybackEngine(
|
public StemPlaybackEngine(
|
||||||
IStemDecoderFactory stemDecoderFactory,
|
IStemDecoderFactory stemDecoderFactory,
|
||||||
IAudioOutputDevice outputDevice,
|
IAudioOutputDevice outputDevice,
|
||||||
@ -168,7 +171,10 @@ public sealed class StemPlaybackEngine : IStemPlaybackEngine, IDisposable
|
|||||||
|
|
||||||
if (pipelineToDispose is not null)
|
if (pipelineToDispose is not null)
|
||||||
{
|
{
|
||||||
try { pipelineToDispose.Cts?.Cancel(); } catch { }
|
if (pipelineToDispose.Cts != null)
|
||||||
|
{
|
||||||
|
try { await pipelineToDispose.Cts.CancelAsync(); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
var task = pipelineToDispose.RenderTask;
|
var task = pipelineToDispose.RenderTask;
|
||||||
if (task is not null && task.Id != Task.CurrentId)
|
if (task is not null && task.Id != Task.CurrentId)
|
||||||
@ -207,7 +213,7 @@ public sealed class StemPlaybackEngine : IStemPlaybackEngine, IDisposable
|
|||||||
{
|
{
|
||||||
Trace(settings);
|
Trace(settings);
|
||||||
|
|
||||||
await _timeStretchEngine.Configure(settings, _pipeline?.Cts?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
_currentSpeed = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task UpdateMixerAsync(MixerSettings settings)
|
public Task UpdateMixerAsync(MixerSettings settings)
|
||||||
@ -392,6 +398,12 @@ public sealed class StemPlaybackEngine : IStemPlaybackEngine, IDisposable
|
|||||||
var gotFirstBlock = false;
|
var gotFirstBlock = false;
|
||||||
while (!token.IsCancellationRequested)
|
while (!token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
if ( _prevSpeed.Speed != _currentSpeed.Speed )
|
||||||
|
{
|
||||||
|
await _timeStretchEngine.Configure(_currentSpeed, token).ConfigureAwait(false);
|
||||||
|
_prevSpeed = _currentSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
var stretchedBlocks = await _timeStretchEngine.ReceiveStems(token).ConfigureAwait(false);
|
var stretchedBlocks = await _timeStretchEngine.ReceiveStems(token).ConfigureAwait(false);
|
||||||
|
|
||||||
if (stretchedBlocks == null || stretchedBlocks.Length == 0 || stretchedBlocks[0].Buffer == null)
|
if (stretchedBlocks == null || stretchedBlocks.Length == 0 || stretchedBlocks[0].Buffer == null)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using System.Runtime.InteropServices;
|
|||||||
using NAudio.CoreAudioApi;
|
using NAudio.CoreAudioApi;
|
||||||
using NAudio.Dmo;
|
using NAudio.Dmo;
|
||||||
using NAudio.Wave;
|
using NAudio.Wave;
|
||||||
|
using static AudioCore.Models.Tracer;
|
||||||
|
|
||||||
namespace AudioCore.Impl;
|
namespace AudioCore.Impl;
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ public sealed class PlaybackSpeedSettings
|
|||||||
|
|
||||||
public interface ITimeStretchEngine
|
public interface ITimeStretchEngine
|
||||||
{
|
{
|
||||||
Task Configure(PlaybackSpeedSettings settings, CancellationToken token);
|
Task Configure(PlaybackSpeedSettings settings, CancellationToken globalToken = default);
|
||||||
|
|
||||||
// Streaming block processing
|
// Streaming block processing
|
||||||
Task IsReadyToAcceptStems(CancellationToken token);
|
Task IsReadyToAcceptStems(CancellationToken token);
|
||||||
|
|||||||
@ -8,15 +8,12 @@ namespace AudioCore.Models;
|
|||||||
|
|
||||||
public static class Tracer
|
public static class Tracer
|
||||||
{
|
{
|
||||||
public static void Trace([CallerMemberName] string method = null!) => Debug.WriteLine($"TRACE: ____________ {method}() called");
|
public static void Trace([CallerFilePath] string path = null!, [CallerMemberName] string method = null!)
|
||||||
public static void Trace<T>(T args, [CallerArgumentExpression("args")] string argsExpression = "", [CallerMemberName] string method = "")
|
=> Debug.WriteLine($"TRACE: ____________ {Path.GetFileNameWithoutExtension(path)}.{method}() called");
|
||||||
{
|
public static void Trace<T>(T args, [CallerArgumentExpression("args")] string argsExpression = "", [CallerFilePath] string path = null!, [CallerMemberName] string method = "")
|
||||||
Debug.WriteLine($"TRACE: ____________ {method}({argsExpression}: {args}) called");
|
=> Debug.WriteLine($"TRACE: ____________ {Path.GetFileNameWithoutExtension(path)}.{method}({argsExpression}: {args}) called");
|
||||||
}
|
public static void Trace<T1, T2>(T1 arg1, T2 arg2, [CallerArgumentExpression("arg1")] string arg1Expression = "", [CallerArgumentExpression("arg2")] string arg2Expression = "", [CallerFilePath] string path = null!, [CallerMemberName] string method = "")
|
||||||
public static void Trace<T1, T2>(T1 arg1, T2 arg2, [CallerArgumentExpression("arg1")] string arg1Expression = "", [CallerArgumentExpression("arg2")] string arg2Expression = "", [CallerMemberName] string method = "")
|
=> Debug.WriteLine($"TRACE: ____________ {Path.GetFileNameWithoutExtension(path)}.{method}({arg1Expression}: {arg1}, {arg2Expression}: {arg2}) called");
|
||||||
{
|
public static void Msg(string message, [CallerFilePath] string path = null!, [CallerMemberName] string method = null!)
|
||||||
Debug.WriteLine($"TRACE: ____________ {method}({arg1Expression}: {arg1}, {arg2Expression}: {arg2}) called");
|
=> Debug.WriteLine($"TRACE: ____________ {Path.GetFileNameWithoutExtension(path)}.{method}(): {message}");
|
||||||
}
|
|
||||||
|
|
||||||
public static void Msg(string message, [CallerMemberName] string method = null!) => Debug.WriteLine($"TRACE: ____________ {method}(): {message}");
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user