From 3dc49ed38c21eaa251fc307a328305ead9d33852 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 28 Aug 2026 19:12:28 +0200 Subject: [PATCH] test(core): add audio synth and recorder tests --- tests/Chip8.Tests/AudioTests.cs | 112 ++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 tests/Chip8.Tests/AudioTests.cs diff --git a/tests/Chip8.Tests/AudioTests.cs b/tests/Chip8.Tests/AudioTests.cs new file mode 100644 index 0000000..a8743f7 --- /dev/null +++ b/tests/Chip8.Tests/AudioTests.cs @@ -0,0 +1,112 @@ +using System; +using System.Linq; +using System.Text; +using Chip8.Core; +using Chip8.Core.Audio; +using Xunit; + +namespace Chip8.Tests; + +public class AudioTests +{ + [Fact] + public void wavHeaderDescribesMonoSixteenBitPcm() + { + var samples = new short[] { 1000, -1000, 500, -500 }; + byte[] wav = WavFile.write(samples, 8000); + + Assert.Equal("RIFF", Encoding.ASCII.GetString(wav, 0, 4)); + Assert.Equal("WAVE", Encoding.ASCII.GetString(wav, 8, 4)); + Assert.Equal("fmt ", Encoding.ASCII.GetString(wav, 12, 4)); + Assert.Equal(1, BitConverter.ToInt16(wav, 20)); + Assert.Equal(1, BitConverter.ToInt16(wav, 22)); + Assert.Equal(8000, BitConverter.ToInt32(wav, 24)); + Assert.Equal(16, BitConverter.ToInt16(wav, 34)); + Assert.Equal("data", Encoding.ASCII.GetString(wav, 36, 4)); + Assert.Equal(samples.Length * 2, BitConverter.ToInt32(wav, 40)); + Assert.Equal(44 + samples.Length * 2, wav.Length); + Assert.Equal(1000, BitConverter.ToInt16(wav, 44)); + } + + [Theory] + [InlineData(0.4, 13106)] + [InlineData(2.0, 32767)] + [InlineData(-1.0, 0)] + public void amplitudeIsClampedAndScaled(double amplitude, int expected) + { + Assert.Equal((short)expected, AudioSynth.amplitudeToPcm(amplitude)); + } + + [Fact] + public void squareWaveHasHighThenLowHalfCycle() + { + short[] samples = AudioSynth.square(frequency: 1, sampleRate: 8, sampleCount: 8); + Assert.Equal(8, samples.Length); + Assert.All(samples[..4], sample => Assert.True(sample > 0)); + Assert.All(samples[4..], sample => Assert.True(sample < 0)); + } + + [Fact] + public void decodePatternReadsMostSignificantBitFirst() + { + bool[] bits = AudioSynth.decodePattern(new byte[] { 0xAA }); + Assert.True(bits[0]); + Assert.False(bits[1]); + Assert.True(bits[2]); + Assert.False(bits[7]); + Assert.Equal(AudioSynth.patternBits, bits.Length); + } + + [Fact] + public void renderPatternFollowsBits() + { + var full = new byte[16]; + Array.Fill(full, (byte)0xFF); + short[] loud = AudioSynth.renderPattern(full, playbackRate: 8, sampleRate: 8, sampleCount: 16); + Assert.All(loud, sample => Assert.True(sample > 0)); + + short[] quiet = AudioSynth.renderPattern(new byte[16], playbackRate: 8, sampleRate: 8, sampleCount: 16); + Assert.All(quiet, sample => Assert.True(sample < 0)); + } + + [Fact] + public void trackAccumulatesSamplesAndDuration() + { + var track = new AudioTrack(1000); + track.appendSilence(500); + track.appendSquare(200, 500); + Assert.Equal(1000, track.sampleCount); + Assert.Equal(1.0, track.durationSeconds, 3); + + short[] samples = track.toSamples(); + Assert.All(samples[..500], sample => Assert.Equal(0, sample)); + Assert.Contains(samples[500..], sample => sample != 0); + } + + [Fact] + public void trackProducesPlayableWav() + { + var track = new AudioTrack(8000); + track.appendSquare(440, 8000); + byte[] wav = track.toWav(); + Assert.Equal("RIFF", Encoding.ASCII.GetString(wav, 0, 4)); + Assert.Equal(44 + 8000 * 2, wav.Length); + } + + [Fact] + public void recorderProducesSilenceForASilentProgram() + { + var machine = TestHarness.withProgram(Platform.Chip8, 0x1200); + var track = new AudioRecorder(sampleRate: 8000).record(machine, frames: 5, instructionsPerFrame: 4); + Assert.All(track.toSamples(), sample => Assert.Equal(0, sample)); + } + + [Fact] + public void recorderCapturesTheBeepRom() + { + var machine = new Machine(Quirks.modern(), 1); + machine.loadRom(TestHarness.romBytes("7-beep.ch8")); + var track = new AudioRecorder(sampleRate: 8000).record(machine, frames: 10, instructionsPerFrame: 11); + Assert.Contains(track.toSamples(), sample => sample != 0); + } +}