feat(api): derive Mix waveform datum density from duration (~333 samples/sec, capped/floored) instead of fixed 2048 buckets

This commit is contained in:
daniel-c-harvey
2026-06-14 16:21:57 -04:00
parent da08ac4efb
commit 09a980ba2a
4 changed files with 158 additions and 9 deletions
@@ -0,0 +1,93 @@
using DeepDrftContent.Processors;
namespace DeepDrftTests;
/// <summary>
/// Behavioral tests for the duration-derived Mix bucket-count derivation. The contract: capture at a
/// constant time resolution (≈333 samples/sec) so a 333 ms max-zoom window holds enough samples on any
/// mix length, clamped to a sane floor (short/degenerate mixes) and an upper cap (extreme outliers).
/// </summary>
[TestFixture]
public class MixWaveformResolutionTests
{
[Test]
public void BucketCountForDuration_TypicalMix_CapturesAtTargetDensity()
{
// 3 minutes × 333/s = 59,940 — a typical short mix, comfortably inside [floor, cap].
var buckets = MixWaveformResolution.BucketCountForDuration(180.0);
Assert.That(buckets, Is.EqualTo((int)Math.Ceiling(180.0 * MixWaveformResolution.SamplesPerSecond)));
Assert.That(buckets, Is.EqualTo(59_940));
}
[Test]
public void BucketCountForDuration_SixtyMinuteMix_ProducesAboutOnePointTwoMillion()
{
// 60 min × 333/s = 1,198,800 ≈ 1.2M samples (≈1.2 MB datum), still under the cap.
var buckets = MixWaveformResolution.BucketCountForDuration(3600.0);
Assert.That(buckets, Is.EqualTo(1_198_800));
Assert.That(buckets, Is.LessThan(MixWaveformResolution.MaxBucketCount));
}
[Test]
public void BucketCountForDuration_OverHundredMinutes_ClampsToCap()
{
// 120 min × 333/s = 2,397,600 > cap → clamps to the cap.
var buckets = MixWaveformResolution.BucketCountForDuration(7200.0);
Assert.That(buckets, Is.EqualTo(MixWaveformResolution.MaxBucketCount));
}
[Test]
public void BucketCountForDuration_NearZeroDuration_HitsFloor()
{
// 0.1 s × 333/s = 34 buckets, far below the floor → clamps up to the floor.
var buckets = MixWaveformResolution.BucketCountForDuration(0.1);
Assert.That(buckets, Is.EqualTo(MixWaveformResolution.MinBucketCount));
}
[Test]
public void BucketCountForDuration_ZeroDuration_HitsFloor()
{
Assert.That(MixWaveformResolution.BucketCountForDuration(0.0), Is.EqualTo(MixWaveformResolution.MinBucketCount));
}
[Test]
public void BucketCountForDuration_NegativeOrNaN_HitsFloor()
{
Assert.Multiple(() =>
{
Assert.That(MixWaveformResolution.BucketCountForDuration(-5.0), Is.EqualTo(MixWaveformResolution.MinBucketCount));
Assert.That(MixWaveformResolution.BucketCountForDuration(double.NaN), Is.EqualTo(MixWaveformResolution.MinBucketCount));
});
}
[Test]
public void BucketCountForDuration_DurationAtFloorBoundary_ReturnsFloorThenGrows()
{
// floor / 333 = 6.15 s is the duration where the derived count meets the floor exactly.
const double floorBoundarySeconds = (double)MixWaveformResolution.MinBucketCount / MixWaveformResolution.SamplesPerSecond;
// Just below the boundary clamps to the floor; just above derives above the floor.
Assert.Multiple(() =>
{
Assert.That(MixWaveformResolution.BucketCountForDuration(floorBoundarySeconds - 0.1), Is.EqualTo(MixWaveformResolution.MinBucketCount));
Assert.That(MixWaveformResolution.BucketCountForDuration(floorBoundarySeconds + 1.0), Is.GreaterThan(MixWaveformResolution.MinBucketCount));
});
}
[Test]
public void BucketCountForDuration_DurationAtCapBoundary_ReturnsCap()
{
// cap / 333 = 6006.006 s is the duration where the derived count meets the cap exactly.
const double capBoundarySeconds = (double)MixWaveformResolution.MaxBucketCount / MixWaveformResolution.SamplesPerSecond;
Assert.Multiple(() =>
{
Assert.That(MixWaveformResolution.BucketCountForDuration(capBoundarySeconds + 1.0), Is.EqualTo(MixWaveformResolution.MaxBucketCount));
Assert.That(MixWaveformResolution.BucketCountForDuration(capBoundarySeconds - 10.0), Is.LessThan(MixWaveformResolution.MaxBucketCount));
});
}
}