diff options
Diffstat (limited to 'src/Godosa.Core.Tests/Audio')
| -rw-r--r-- | src/Godosa.Core.Tests/Audio/AudioMixTests.cs | 22 | ||||
| -rw-r--r-- | src/Godosa.Core.Tests/Audio/Mp3Tests.cs | 90 | ||||
| -rw-r--r-- | src/Godosa.Core.Tests/Audio/SynthTests.cs | 122 | ||||
| -rw-r--r-- | src/Godosa.Core.Tests/Audio/WavReaderTests.cs | 127 |
4 files changed, 361 insertions, 0 deletions
diff --git a/src/Godosa.Core.Tests/Audio/AudioMixTests.cs b/src/Godosa.Core.Tests/Audio/AudioMixTests.cs new file mode 100644 index 0000000..93a0bed --- /dev/null +++ b/src/Godosa.Core.Tests/Audio/AudioMixTests.cs @@ -0,0 +1,22 @@ +using Godosa.Core.Audio; +using Xunit; + +namespace Godosa.Core.Tests.Audio; + +public class AudioMixTests +{ + private sealed class Plain : IAudioMix + { + public HrtfMode Hrtf => default; + public float ChannelGain(int channel, AudioPauseState state) => 1f; + public bool Pausable(int channel) => true; + public bool WorldPaused(AudioPauseState state) => false; + } + + [Fact] + public void ChannelName_DefaultsToTheNumber() + { + IAudioMix mix = new Plain(); + Assert.Equal("2", mix.ChannelName(2)); + } +} diff --git a/src/Godosa.Core.Tests/Audio/Mp3Tests.cs b/src/Godosa.Core.Tests/Audio/Mp3Tests.cs new file mode 100644 index 0000000..9cdca1e --- /dev/null +++ b/src/Godosa.Core.Tests/Audio/Mp3Tests.cs @@ -0,0 +1,90 @@ +using System; +using Godosa.Core.Audio; +using Xunit; + +namespace Godosa.Core.Tests.Audio; + +/// <summary>Fixtures: our own sine tones (ffmpeg lavfi sine, amplitude 1/8, 0.25 s) encoded with lame 3.100: mono +/// 440 Hz at 22050 Hz (64 kbit/s, --resample 22.05); stereo 440 Hz left / 880 Hz right at 44100 Hz (128 kbit/s, +/// --resample 44.1). Oracle: a sine of f Hz crosses zero 2·f times a second; counted in a 0.1 s window inside the tone +/// (lame's encoder delay, ≈ 1105 samples, and frame padding are near-silent and cross zero at random).</summary> +public class Mp3Tests +{ + private static readonly byte[] Mono = Convert.FromBase64String("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"); + private static readonly byte[] Stereo = Convert.FromBase64String("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"); + + private static (float[] Left, float[] Right) Render(Mp3Stream s, int frames) + { + var buf = new float[2 * frames]; + s.Render(buf, frames); + var (l, r) = (new float[frames], new float[frames]); + for (var i = 0; i < frames; i++) + (l[i], r[i]) = (buf[2 * i], buf[2 * i + 1]); + return (l, r); + } + + [Fact] + public void Reader_decodes_mono_at_the_file_rate() + { + var clip = Mp3Reader.Read(Mono)!.Value; + Assert.Equal(22050, clip.SampleRate); + Assert.InRange(clip.Samples.Length, 5512, 5512 + 3 * 1152); + var f = Array.ConvertAll(clip.Samples, s => s / 32767f); + Assert.InRange(SignalStats.ZeroCrossings(f, 2400, 2400 + 2205), 86, 90); // 0.1 s of 440 Hz → 88 + } + + [Fact] + public void Reader_downmixes_stereo_to_mono() + { + var clip = Mp3Reader.Read(Stereo)!.Value; + Assert.Equal(44100, clip.SampleRate); + Assert.InRange(clip.Samples.Length, 11025, 11025 + 3 * 1152); + } + + [Fact] + public void Reader_rejects_garbage() => Assert.Null(Mp3Reader.Read(new byte[300])); + + /// <summary>Oracle: both tones are 0.25 s; the LAME tag's encoder delay and padding are not counted (the Info header's + /// 12 and 11 whole frames would be 0.313 and 0.287 s). Within 1 sample of rounding.</summary> + [Fact] + public void Seconds_come_from_the_headers_without_encoder_delay() + { + Assert.InRange(Mp3Reader.Seconds(Mono)!.Value, 0.25, 0.25 + 1 / 22050.0); + Assert.InRange(Mp3Reader.Seconds(Stereo)!.Value, 0.25, 0.25 + 1 / 44100.0); + Assert.Null(Mp3Reader.Seconds(new byte[300])); + } + + [Fact] + public void Stream_keeps_channels_apart() + { + using var s = new Mp3Stream(Stereo, loop: false); + var (l, r) = Render(s, 11025); + Assert.InRange(SignalStats.ZeroCrossings(l, 4000, 4000 + 4410), 86, 90); // 440 Hz → 88 in 0.1 s + Assert.InRange(SignalStats.ZeroCrossings(r, 4000, 4000 + 4410), 174, 178); // 880 Hz → 176 + } + + [Fact] + public void Stream_resamples_mono_to_44100_and_duplicates_it() + { + using var s = new Mp3Stream(Mono, loop: false); + var (l, r) = Render(s, 11025); + Assert.InRange(SignalStats.ZeroCrossings(l, 4000, 4000 + 4410), 86, 90); // still 440 Hz (176 if played as 44.1 kHz) + Assert.Equal(l, r); + } + + [Fact] + public void Once_only_goes_silent_and_finishes_looping_does_not() + { + using var once = new Mp3Stream(Mono, loop: false); + Render(once, 44100); // 1 s, past the 0.25 s file + Assert.True(once.Finished); + var tail = new float[200]; + Array.Fill(tail, 1f); // the stream must write the silence, not rely on a cleared buffer + once.Render(tail, 100); + Assert.All(tail, x => Assert.Equal(0f, x)); + using var loop = new Mp3Stream(Mono, loop: true); + var (l, _) = Render(loop, 44100); + Assert.False(loop.Finished); + Assert.InRange(SignalStats.Rms(l, 22050), 0.05f, 0.1f); // second half still sounding (sine 1/8: RMS 0.088 less gaps) + } +} diff --git a/src/Godosa.Core.Tests/Audio/SynthTests.cs b/src/Godosa.Core.Tests/Audio/SynthTests.cs new file mode 100644 index 0000000..7c2c29b --- /dev/null +++ b/src/Godosa.Core.Tests/Audio/SynthTests.cs @@ -0,0 +1,122 @@ +using System; +using System.Linq; +using Godosa.Core.Audio; +using Xunit; + +namespace Godosa.Core.Tests.Audio; + +public class SynthTests +{ + [Fact] + public void Random_IsDeterministicAndInUnitRange() + { + var a = new SynthRandom(42); + var b = new SynthRandom(42); + var values = Enumerable.Range(0, 1000).Select(_ => a.Next01()).ToArray(); + + Assert.Equal(values, Enumerable.Range(0, 1000).Select(_ => b.Next01()).ToArray()); + Assert.All(values, v => Assert.InRange(v, 0f, 0.99999f)); + Assert.InRange(values.Average(), 0.45f, 0.55f); + } + + [Fact] + public void Random_IsXorshift32OfTheMixedSeed() // pinned: seeded songs must sound the same in every run + { + // oracle: independent Python murmur3 fmix32 + xorshift32 — fmix32(1) = 0x514E28B7, first state 0x1F48D1FB + Assert.Equal(0x514E28B7u, SynthRandom.Mix(1)); + Assert.Equal(2050257f / 16777216f, new SynthRandom(1).Next01()); + } + + [Fact] + public void Random_NearbySeeds_StartFarApart() + { + var firsts = Enumerable.Range(0, 20).Select(s => new SynthRandom((uint)s).Next01()).ToList(); + + Assert.True(firsts.Max() - firsts.Min() > 0.5f); + } + + [Fact] + public void Loop_CrossfadesTheTailIntoTheHead() + { + var x = new float[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + var y = Synth.Loop(x, overlapSamples: 2); + + Assert.Equal(new float[] { 8, 5, 2, 3, 4, 5, 6, 7 }, y); // y[^1] = 7 wraps to y[0] = 8, like x[7] → x[8] + } + + [Fact] + public void Hash_IsFnv1a() // stable across runs, unlike string.GetHashCode + { + Assert.Equal(0xE40C292Cu, SynthRandom.Hash("a")); + Assert.Equal(0x1A47E90Bu, SynthRandom.Hash("abc")); + } + + [Fact] + public void Sine_SteadyPitch() + { + var x = Synth.Sine(1f, 100f); + + Assert.Equal(44100, x.Length); + Assert.InRange(SignalStats.ZeroCrossings(x), 198, 201); + } + + [Fact] + public void Sine_SweepIsExponential() + { + var x = Synth.Sine(1f, 100f, 400f); // f = 100·4^t: 144 crossings in the first half, 289 in the second + + Assert.InRange(SignalStats.ZeroCrossings(x, 0, 22050), 141, 148); + Assert.InRange(SignalStats.ZeroCrossings(x, 22050), 285, 292); + } + + [Fact] + public void LowPass_PassesLowCutsHigh() + { + float low = SignalStats.Rms(Synth.Sine(0.5f, 200f).LowPass(1000f), 2000) / SignalStats.Rms(Synth.Sine(0.5f, 200f), 2000); + float high = SignalStats.Rms(Synth.Sine(0.5f, 8000f).LowPass(1000f), 2000) / SignalStats.Rms(Synth.Sine(0.5f, 8000f), 2000); + + Assert.InRange(low, 0.89f, 1.05f); // within 1 dB + Assert.InRange(high, 0f, 0.1f); // −20 dB or more (2nd order: ≈ −36 dB at 3 octaves) + } + + [Fact] + public void HighPass_PassesHighCutsLow() + { + float low = SignalStats.Rms(Synth.Sine(0.5f, 100f).HighPass(1000f), 4000) / SignalStats.Rms(Synth.Sine(0.5f, 100f), 4000); + float high = SignalStats.Rms(Synth.Sine(0.5f, 6000f).HighPass(1000f), 4000) / SignalStats.Rms(Synth.Sine(0.5f, 6000f), 4000); + + Assert.InRange(low, 0f, 0.05f); + Assert.InRange(high, 0.89f, 1.05f); + } + + [Fact] + public void BandPass_PassesCentreOnly() + { + float Ratio(float hz) => SignalStats.Rms(Synth.Sine(0.5f, hz).BandPass(1000f, 2f), 4000) / SignalStats.Rms(Synth.Sine(0.5f, hz), 4000); + + Assert.InRange(Ratio(1000f), 0.85f, 1.05f); + Assert.InRange(Ratio(100f), 0f, 0.1f); + Assert.InRange(Ratio(8000f), 0f, 0.1f); + } + + [Fact] + public void Perc_RisesThenDecaysByTimeConstant() + { + var x = Enumerable.Repeat(1f, 44100).ToArray().Perc(attack: 0.01f, decay: 0.1f); + + Assert.Equal(0f, x[0]); + Assert.InRange(x[441], 0.99f, 1f); // top at the end of the attack + Assert.InRange(x[441 + 4410], 0.36f, 0.375f); // e^-1 one time constant later + } + + [Fact] + public void ToPcm_NormalisesToLevel_AndFadesEnds() + { + var pcm = Synth.ToPcm(Enumerable.Repeat(0.3f, 4410).ToArray(), level: 0.5f); + + Assert.Equal((short)16384, pcm.Max()); + Assert.Equal(0, pcm[0]); + Assert.Equal(0, pcm[^1]); + } +} diff --git a/src/Godosa.Core.Tests/Audio/WavReaderTests.cs b/src/Godosa.Core.Tests/Audio/WavReaderTests.cs new file mode 100644 index 0000000..6ed5485 --- /dev/null +++ b/src/Godosa.Core.Tests/Audio/WavReaderTests.cs @@ -0,0 +1,127 @@ +using System; +using System.IO; +using Godosa.Core.Audio; +using Xunit; + +namespace Godosa.Core.Tests.Audio; + +public class WavReaderTests +{ + private static byte[] Wav(short format, short channels, int rate, short bits, byte[] data, bool extensible = false) + { + using var ms = new MemoryStream(); + using var w = new BinaryWriter(ms); + int fmtSize = extensible ? 40 : 16; + w.Write("RIFF"u8); w.Write(4 + 8 + fmtSize + 8 + data.Length); w.Write("WAVE"u8); + w.Write("fmt "u8); w.Write(fmtSize); + w.Write(extensible ? (short)-2 : format); w.Write(channels); w.Write(rate); + w.Write(rate * channels * bits / 8); w.Write((short)(channels * bits / 8)); w.Write(bits); + if (extensible) + { + w.Write((short)22); w.Write(bits); w.Write(0); + w.Write((int)format); w.Write(new byte[12]); // sub-format GUID: first 4 bytes = format tag + } + w.Write("data"u8); w.Write(data.Length); w.Write(data); + return ms.ToArray(); + } + + private static byte[] Shorts(params short[] s) + { + var b = new byte[s.Length * 2]; + Buffer.BlockCopy(s, 0, b, 0, b.Length); + return b; + } + + [Fact] + public void Pcm16Stereo_DownmixesToMono() + { + var clip = WavReader.Read(Wav(1, 2, 22050, 16, Shorts(1000, 3000, -2000, -4000))); + + Assert.NotNull(clip); + Assert.Equal(22050, clip!.Value.SampleRate); + Assert.Equal(new short[] { 2000, -3000 }, clip.Value.Samples); + } + + [Fact] + public void Pcm24Mono() + { + // 0x400000 = +0.5 FS, 0xC00000 = −0.5 FS (little-endian 3-byte) + var clip = WavReader.Read(Wav(1, 1, 44100, 24, new byte[] { 0, 0, 0x40, 0, 0, 0xC0 })); + + Assert.Equal(new short[] { 16384, -16384 }, clip!.Value.Samples); + } + + [Fact] + public void Float32_Extensible() + { + var data = new byte[8]; + BitConverter.GetBytes(0.5f).CopyTo(data, 0); + BitConverter.GetBytes(-1f).CopyTo(data, 4); + + var clip = WavReader.Read(Wav(3, 1, 48000, 32, data, extensible: true)); + + Assert.Equal(new short[] { 16384, -32767 }, clip!.Value.Samples); + Assert.Equal(48000, clip.Value.SampleRate); + } + + [Theory] + [InlineData(new byte[0])] + [InlineData(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 })] + public void Garbage_IsNull(byte[] bytes) => Assert.Null(WavReader.Read(bytes)); + + [Fact] + public void Pcm8_UnsignedCentredOn128() + { + var clip = WavReader.Read(Wav(1, 1, 8000, 8, new byte[] { 128, 192, 0 })); + Assert.Equal(new short[] { 0, 16384, -32768 }, clip!.Value.Samples); + } + + // MS ADPCM fixtures: our own tones encoded by ffmpeg 7 (adpcm_ms, block 256, standard 7 coefficient pairs). Oracle: + // ffmpeg's own adpcm_ms decode (pcm_s16le), stereo averaged with truncation, cut to the fact chunk's frame count. + private static readonly byte[] AdpcmMono = Convert.FromBase64String("UklGRnQEAABXQVZFZm10IDIAAAACAAEAIlYAAIA+AAAAAQQAIAD0AQcAAAEAAAACAP8AAAAAwABAAPAAAADMATD/iAEY/2ZhY3QEAAAACAYAAExJU1QaAAAASU5GT0lTRlQNAAAATGF2ZjYxLjcuMTAzAABkYXRhAAQAAAAQAPwHAAB3d3MhAQAP/dzNvd3M7d3d3u8BNFNTRDNSMzQjIyEQ/tvM293M3dzd7d7vASREUzUzQ0MzNCIiAf7b283b3c3dzd7e7wEjVEQ1JDM0MzMzIhH+zL3MzdzN3dzt7f/xE1Q1NEM0M0MzMjIQDtrczM3czd3c7d7+ACNFQ0RDNDNDMzMiEQ7bzMzc3N3Nzt7e/gATRTRENDNDQyMzIhEO6829zN3M7c7t7u/xEkREQ0NEJCMjMiIgD+vb3M3Nzdzd3d7f8BNTVDRDQ0MzNCMiEQ7trb3b3c3c3d3d7/8iU1Q0Q0M0NCMjIiAf3bzM3M3czt3d7e7wEkU0REMASgLk/uj2QkIyMyIRH+zMzb3N3Nze3d3u8BI2Q0RDQzQzQyMiIQ/tvM292+3O3d3d7vADNjU0QzUjNCMyMiEP/Nrc3M3Nztzt7u7wETRTRENDNDQjMyIhH/zbzb3czd3N3d3e/xI1REQ0NDQzNCMjEgDtzL3M3Nzdze3d3+8RNUQ1NDQzQzNCIxEQ/czMzczdzc7d3t7/AjRFNTNDQ0MzMzMhEP28zNzM7N7O3e3u/wE0RERDQ0M0IzMzIhDuvbzc293c3dzt7v8BM1RENDRDM0IzMiIQ/sy9zNzc3N3d3d7vASRUNTQ0NDM0MjIiEP7a3Mzczd3N3O3u/wAjVDU0AEkC1PUA7jRCMzMzIhD+zMzM3M3dzc7e7u8AI1NTUzQ0MzQzIyIR/tvMzczc3c3dzu7vARNEU0NEM0M0MjIiEf/b283Nzc3N7c7u//ESREQ1M0M0M0IyIhAP3Mvc2+ztzt3e3u4AIlQ1NEM0NDMzMzIh/9vMzNzczs7e3e7uABNEU0Q0NDNDMzMjEQ7rzbzdvd3N3O3t/vESRERDRDNDQzIzIxEP3Mvczb7c7d3O3v4PEkQ1Q0NDQzQjMjIRD+2tvc3NzO3c7e7v8BFDVDRDQ0M0MjMiIQ/trczNzN3Ozt7t7/ACNUNTQ0NDNDIzIxIP7a3Mzczd3Nzt7e/wAjRTUwDmAfTseOVEMzQzMzIiAOzMzM3M3c3d3O3u8AJ3cQDx8fHx8fHx8fAQ8BDx8B8Q8fAfHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8="); // 440 Hz ×4 gain, 22050 Hz, fact 1544 + private static readonly byte[] AdpcmStereo = Convert.FromBase64String("UklGRnQFAABXQVZFZm10IDIAAAACAAIAIlYAAIA+AAAAAQQAIAD0AAcAAAEAAAACAP8AAAAAwABAAPAAAADMATD/iAEY/2ZhY3QEAAAATwQAAExJU1QaAAAASU5GT0lTRlQNAAAATGF2ZjYxLjcuMTAzAABkYXRhAAUAAAAAEAAQAP8Bs/gAAJLxeHd4RyATGycbEQ8EDeTs0sHAz87RwsLa0NPN39TP4NDi+eDxAh8uQ0JOTj5QPUcwMUtELjIwMT4wPSQgHQ7w5820ztC/38Pew9DN1d7SzN/U793k4v4BESpPQ14+Q08wQT5GPUMxMj9OLz4vJy8PEf/j27LMwt6y38Pa08Dez9Pf1dzU3+zj7g8VIE5RPl80TUAxQjtDPzNOMC8wMDUtLBUOAOHAwsC93cbdxM/S2sHQwu3Q1O3Q4uHwDR8TQExETEQwXzI6Qz8wTjNNITMpMSAQIwD+/dKw0rrB38XRzcLd0dDD3NTcwe7i/vTxHBQ8QUAAAEoACA649VUMRPRlAm4/NlAsQS8zPzExSyEvMR8THv700c2w37LSztHKwt7T38zW39Lf3dLg4w4CKDFjMF4/QUI9RE8/PjZNMDM+Mz8oMREd8fPMws3DzcTC28/R0MHO5N7U3t/e4e/n8RsSMVBNQ0A9UDU/TzJAOjBCPiQ9MiAhHxIN/NKz0rjP0rHhzNDS0s3SzuTd4tvg4uACCxMxQEpQNE5BPkNNPzQ/TjI/QiwhLiUgDgDx48/DysDO1sDO3tDB38PQ3e/X0NHu8vvyIitRP1NAMEBNPkc/MU8/QC4yMC4vIiUQDf/nwMyw0cLN0MLez9XP0tzS0M7j4eHvAABTAH8GBvDJ2Q3wlegXEDFMRE9OQTFCP04+TzEyPT8xQSAnER0MBe3Gz87RsMHS0LDo0cDi3t/U393Q4eXtDxQwTk9SPl81TTNAP0I+Pk0kPDMwJDsSLw7w4sXAvdHCydKx7cDg0dTcz9Tg0ODg7fT8ES80bzFPQUExTE89P0AwN04iPi42ICEeAfL5wMDDwM3UzsLfwdvB0NDS3dDD4uDp8QEQET1RPFM9UjIwTzdMMUM7IUAhIi0gIw8f/vviwr3exMLQuuPfwN/d0cHX7t7V7+7w8BUtM19ATTJUPkAyTTFPMTAwQTwlLz0fJgAA7OK+w9K4083A0t3BwOTd3wAAhACACpH0huoO9tQc1O/g4P31HR8yQ1k0XTBDPUBDPTFOIEMlLD8hIBUtDwTw3dOg0bzfw9LN3cHQ3s3Q19DV39zg8fIPIj5bREBPTUUwTTIwTUIvMTQ9NTwiPh8UAu/hsM3NxtC91N3D3MTc08Df7dLQ7uPqBQ4fMENcQTJRO0ExQTFPPj4wRD8jPCMvHBEA8uvAtt+w0sDQysLQ0M3S0NLQzuHu5O0DCREhMm0+UD9HPk9BMzwzQT5BLT4kLCERHgUP0dG+wMHD2LPf393Ewe/f09Dd0NDb9f3/ICRNUUM8VDBNQzwzQDE/TCMwPDIyLhArBPDdw8Kw2rHTy9IAAIUA0RBwAFYCbwKB6s7SzeHU0dzR3uPs8fAUME9DWjFQPjRRPjE+T0QgPTQ7IjAgIB4O8+zToNW70cDC3s7V3s7Swu/e0OXR7ODwAwskTk5CQ0swUTA/RD1PMz0wNU0jLDQeHxT/7dTAvcLO1MDcv+PQz+Hd1N7UzeHi/fwDBxA7QVAwUT4wUiBBHwAAAAEPAQ8BDwEAH/AR8B/wEf8R8A8AAQAPAQ8AAQ8BAA8BDwEPAQ8AAQ8BDwEPAQ8BDwEPAQ8BDwEPAQ8BDwEPAQ8BDwEPAQ8BDwEPAQ8BDwEPAQ8BDwEPAQ8BDwEPAQ8BDwEPAQ8BDwEPAQ8BDwEPAQ8B"); // 440 Hz left, noise right, fact 1103 + + private static readonly byte[] AdpcmQuiet = Convert.FromBase64String("UklGRnQBAABXQVZFZm10IDIAAAACAAEAESsAAIA+AAAAAQQAIAD0AQcAAAEAAAACAP8AAAAAwABAAPAAAADMATD/iAEY/2ZhY3QEAAAASwEAAExJU1QaAAAASU5GT0lTRlQNAAAATGF2ZjYxLjcuMTAzAABkYXRhAAEAAAAQAAMAAAAAAQAAAADwAAAPAAAAAAAAAQAAAQAAAAAPAAAPAAAAAAAAABAAABAAAAAA8AAA8AAAAAAAAAEAAAEAAAAADwAADwAAAAAAAAEAAAAQAAAADwAAAPAAAAAAAAAQAAAQAAAAAPAAAPAAAAAAAAABAAABAAAAAA8AAA8AAAAAAAAAEAAAEAAAAADwAADwAAAAAAAAEAAAAQAAAADwAAAPAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); // 300 Hz ×0.004, 11025 Hz, fact 331 + + [Fact] + public void MsAdpcm_QuietKeepsTheStepFloorOf16() + { + var s = WavReader.Read(AdpcmQuiet)!.Value.Samples; // ffmpeg: steps stay at the floor, samples move by 16 + Assert.Equal(331, s.Length); + Assert.Equal(new short[] { 0, 3, 3, 3, 3, 19, 19, 19, 19, 19, 19, 19 }, s[..12]); + Assert.Equal((302, 3682), (Sum(s), AbsSum(s))); + } + + private static int Sum(short[] s) { int n = 0; foreach (var x in s) n += x; return n; } + private static int AbsSum(short[] s) { int n = 0; foreach (var x in s) n += Math.Abs(x); return n; } + + [Fact] + public void MsAdpcm_Mono_MatchesFfmpeg() + { + var s = WavReader.Read(AdpcmMono)!.Value.Samples; + Assert.Equal(1544, s.Length); + Assert.Equal(new short[] { 0, 2044, 2156, 2422, 3059, 4585 }, s[..6]); + Assert.Equal(new short[] { -6016, -4057, -2328, -284, 2060, 3464 }, s[498..504]); // across the block edge at 500 + Assert.Equal(new short[] { -16364, -16364, -15876 }, s[^3..]); + Assert.Equal((74278, 16090432), (Sum(s), AbsSum(s))); + } + + [Fact] + public void MsAdpcm_Stereo_MatchesFfmpeg() + { + var clip = WavReader.Read(AdpcmStereo)!.Value; + Assert.Equal(22050, clip.SampleRate); + var s = clip.Samples; + Assert.Equal(1103, s.Length); + Assert.Equal(new short[] { -1847, -679, -687, -386, -527, 1116 }, s[..6]); + Assert.Equal(new short[] { 277, 3542, 2416, 255, -1687, -2380 }, s[498..504]); + Assert.Equal(new short[] { -1115, -937, 606 }, s[^3..]); + Assert.Equal((-183764, 2966742), (Sum(s), AbsSum(s))); + } + + [Fact] + public void Downmix_InterleavedFloat() + { + Assert.Equal(new short[] { 16384, 0 }, Pcm.Downmix(new[] { 0.25f, 0.75f, 1f, -1f }, 2)); + } +} |
