using Amphora; using System; using System.IO; using static StbVorbisSharp.StbVorbis; namespace Susurrus.UnitTests { public static class TestSounds { public static Stream OpenFixture(string name) { Stream returnMe = typeof(TestSounds).Assembly.GetManifestResourceStream("Susurrus.UnitTests.Fixtures." + name); if (returnMe == null) { throw new InvalidOperationException($"There's no embedded fixture called {name}."); } return returnMe; } public static byte[] LoadFixture(string name) { using (Stream stream = OpenFixture(name)) using (MemoryStream copy = new MemoryStream()) { stream.CopyTo(copy); return copy.ToArray(); } } public static int LastPageStart(byte[] file) { for (int start = file.Length - 4; start >= 0; --start) { if (file[start] == 'O' && file[start + 1] == 'g' && file[start + 2] == 'g' && file[start + 3] == 'S') { return start; } } return -1; } /// /// Rewrites the checksum of the Ogg page at to match its current bytes. /// public static void FixPageChecksum(byte[] file, int pageStart) { int segmentCount = file[pageStart + 26]; int pageSize = 27 + segmentCount; for (int i = 0; i < segmentCount; ++i) { pageSize += file[pageStart + 27 + i]; } uint checksum = OggPages.Checksum(file, pageStart, pageSize); Array.Copy(BitConverter.GetBytes(checksum), 0, file, pageStart + 22, 4); } /// /// Returns the start of the Ogg page that holds the byte at , reading the file's pages /// from its first. /// public static int PageStartHolding(byte[] file, int index) { int start = 0; while (OggPages.TryRead(file, start, file.Length, out OggPage page) && start + page.Size <= index) { start += page.Size; } return start; } /// /// Builds an Ogg page with the given header fields and a patterned body of bytes. /// public static byte[] MakePage(long granule, byte flags, byte[] lacing, int bodySize) { byte[] returnMe = new byte[OggPages.HeaderSize + lacing.Length + bodySize]; Array.Copy(System.Text.Encoding.ASCII.GetBytes("OggS"), returnMe, 4); returnMe[5] = flags; Array.Copy(BitConverter.GetBytes(granule), 0, returnMe, 6, 8); returnMe[26] = (byte)lacing.Length; Array.Copy(lacing, 0, returnMe, OggPages.HeaderSize, lacing.Length); for (int i = 0; i < bodySize; ++i) { returnMe[OggPages.HeaderSize + lacing.Length + i] = (byte)(i % 251); } FixPageChecksum(returnMe, 0); return returnMe; } public static FailureKind KindOf(StatusOr result) { return result.Status.Kind; } /// /// Returns the error code of an Ogg failure, or -1 for anything else, so a test comparing it fails either way. /// public static STBVorbisError OggErrorOf(StatusOr result) { Status status = result.Status; if (status.Kind != SoundFailures.OggFailed) { return (STBVorbisError)(-1); } return SoundFailures.OggFailed.Read(in status); } public static short SampleAt(DecodedSound sound, int frame, int channel) { int index = (frame * sound.ChannelCount + channel) * sizeof(short); return (short)(sound.Samples[index] | sound.Samples[index + 1] << 8); } public static StatusOr DecodeWav(byte[] file) { return WavDecoder.Decode(new ByteView(file)); } public static StatusOr DecodeOgg(byte[] file) { return OggDecoder.Decode(new ByteView(file)); } public static StatusOr DecodeAny(byte[] file) { return SoundDecoder.Decode(new ByteView(file)); } public static ByteView Silence(int byteCount) { return new ByteView(new byte[byteCount]); } public static byte[] BytesOf(ByteView view) { byte[] returnMe = new byte[view.Length]; view.CopyTo(returnMe, destinationIndex: 0); return returnMe; } } }