mirror of
https://github.com/unclshura/ABStemPlayer.git
synced 2026-08-07 00:43:38 +00:00
Refactor playback speed and mixer settings to be managed via PlaybackSession, removing SetSpeed/SetStem* methods. Update Play command to apply current mixer and speed. Optimize RenderLoop with reusable audio block list and conditional output start. Align playback control icons in XAML. Update tests for new speed handling and add speed decrease coverage. Remove unused IStemPlaybackEngine methods.
160 lines
4.5 KiB
C#
160 lines
4.5 KiB
C#
using System.Diagnostics;
|
||
using AudioCore.Impl;
|
||
using AudioCore.Interfaces;
|
||
using AudioCore.Models;
|
||
|
||
namespace AudioCore_Tests;
|
||
|
||
[TestClass]
|
||
public sealed class TimeStretchEngine_Tests
|
||
{
|
||
private AudioBufferPool _pool = null!;
|
||
|
||
[TestInitialize]
|
||
public void Init()
|
||
{
|
||
_pool = new AudioBufferPool();
|
||
}
|
||
|
||
private MixedAudioBlock MakeBlock(int frames, int channels = 2, int sampleRate = 44100)
|
||
{
|
||
var buf = _pool.Rent(frames * channels);
|
||
buf.Length = frames * channels;
|
||
|
||
// Fill with deterministic ramp
|
||
for (var i = 0; i < buf.Length; i++)
|
||
buf.Samples[i] = i * 0.001f;
|
||
|
||
return new MixedAudioBlock(buf, frames, channels, sampleRate, 0);
|
||
}
|
||
|
||
[TestMethod]
|
||
public void Process_Returns_Output_For_Speed_1()
|
||
{
|
||
using var engine = new RubberBandTimeStretchEngine(_pool, 44100, 2);
|
||
|
||
var input = MakeBlock(5000);
|
||
var output = engine.Process(input);
|
||
|
||
Assert.IsGreaterThan(0, output.Frames, "No frames returned");
|
||
Assert.AreEqual(2, output.Channels);
|
||
Assert.AreEqual(44100, output.SampleRate);
|
||
|
||
// Output should be roughly same size at speed 1.0
|
||
Assert.AreEqual(5000, output.Frames);
|
||
|
||
// Validate PCM
|
||
foreach (var f in output.Buffer.Span)
|
||
{
|
||
Assert.IsFalse(float.IsNaN(f));
|
||
Assert.IsFalse(float.IsInfinity(f));
|
||
}
|
||
|
||
input.Dispose();
|
||
output.Dispose();
|
||
}
|
||
|
||
[TestMethod]
|
||
public void Process_Respects_Speed_Increase()
|
||
{
|
||
using var engine = new RubberBandTimeStretchEngine(_pool, 44100, 2);
|
||
var input = MakeBlock(1000);
|
||
|
||
// Let the engine and FFmpeg warm up with a few calls
|
||
for (var i = 0; i < 5; i++)
|
||
_ = engine.Process(input);
|
||
|
||
var normal = engine.Process(input);
|
||
var normalFrames = normal.Frames;
|
||
|
||
engine.Configure(new PlaybackSpeedSettings { Speed = 1.5f });
|
||
|
||
for (var i = 0; i < 5; i++)
|
||
_ = engine.Process(input);
|
||
|
||
var faster = engine.Process(input);
|
||
|
||
// Don’t insist on > 0; insist on “not more than”
|
||
Assert.IsLessThanOrEqualTo(normalFrames, faster.Frames,
|
||
$"Speed 1.5 should not increase frame count (normal={normalFrames}, faster={faster.Frames})");
|
||
|
||
input.Dispose();
|
||
normal.Dispose();
|
||
faster.Dispose();
|
||
}
|
||
|
||
[TestMethod]
|
||
public void Process_Respects_Speed_Decrease()
|
||
{
|
||
using var engine = new RubberBandTimeStretchEngine(_pool, 44100, 2);
|
||
var input = MakeBlock(1000);
|
||
|
||
// Let the engine and FFmpeg warm up with a few calls
|
||
for (var i = 0; i < 5; i++)
|
||
_ = engine.Process(input);
|
||
|
||
var normal = engine.Process(input);
|
||
var normalFrames = normal.Frames;
|
||
|
||
engine.Configure(new PlaybackSpeedSettings { Speed = 0.5f });
|
||
|
||
for (var i = 0; i < 5; i++)
|
||
_ = engine.Process(input);
|
||
|
||
var slower = engine.Process(input);
|
||
|
||
// Don’t insist on > 0; insist on “not more than”
|
||
Assert.IsGreaterThanOrEqualTo(normalFrames, slower.Frames,
|
||
$"Speed 0.5 should not decrease frame count (normal={normalFrames}, slower={slower.Frames})");
|
||
|
||
input.Dispose();
|
||
normal.Dispose();
|
||
slower.Dispose();
|
||
}
|
||
|
||
|
||
|
||
|
||
[TestMethod]
|
||
public void Engine_Restarts_On_Speed_Change()
|
||
{
|
||
using var engine = new RubberBandTimeStretchEngine(_pool, 44100, 2);
|
||
|
||
var input = MakeBlock(100);
|
||
|
||
var before = engine.Process(input);
|
||
|
||
engine.Configure(new PlaybackSpeedSettings { Speed = 0.75f });
|
||
|
||
var after = engine.Process(input);
|
||
|
||
// After restart, RubberBand has no buffered audio yet → zero frames expected
|
||
Assert.AreEqual(0, after.Frames, "First block after restart must produce zero frames");
|
||
}
|
||
|
||
|
||
[TestMethod]
|
||
public void Dispose_Kills_FFmpeg()
|
||
{
|
||
var engine = new RubberBandTimeStretchEngine(_pool, 44100, 2);
|
||
|
||
// Capture FFmpeg PID
|
||
var ffField = typeof(RubberBandTimeStretchEngine)
|
||
.GetField("_ff", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||
|
||
var ff = (Process)ffField!.GetValue(engine)!;
|
||
var pid = ff.Id;
|
||
|
||
engine.Dispose();
|
||
|
||
// Process should be gone
|
||
var exists = Process.GetProcesses().Any(p =>
|
||
{
|
||
try { return p.Id == pid; }
|
||
catch { return false; }
|
||
});
|
||
|
||
Assert.IsFalse(exists, "FFmpeg process was not terminated");
|
||
}
|
||
}
|