using System; using System.IO; using System.Text; namespace TravelEar.Core; /// /// A streaming 32-bit float mono WAV writer for the calibration captures: samples are appended /// as they come and the RIFF sizes are patched on . Not for real-time use /// on the audio thread of anything that cannot afford a file write; the mod's encoder thread /// can (it already writes the Sink pipe). /// public sealed class WavWriter : IDisposable { private readonly FileStream _stream; private readonly BinaryWriter _writer; private readonly object _lock = new(); private long _samples; private bool _disposed; public int SampleRate { get; } public string Path { get; } /// Samples written so far (the position in seconds is this over ). public long Samples { get { lock (_lock) return _samples; } } public WavWriter(string path, int sampleRate) { Path = path; SampleRate = sampleRate; _stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 1 << 16); _writer = new BinaryWriter(_stream); WriteHeader(0); } private void WriteHeader(long samples) { var dataBytes = samples * 4; _writer.Write(Encoding.ASCII.GetBytes("RIFF")); _writer.Write((uint)Math.Min(uint.MaxValue, 36 + dataBytes)); _writer.Write(Encoding.ASCII.GetBytes("WAVE")); _writer.Write(Encoding.ASCII.GetBytes("fmt ")); _writer.Write(16); _writer.Write((ushort)3); // IEEE float _writer.Write((ushort)1); _writer.Write(SampleRate); _writer.Write(SampleRate * 4); _writer.Write((ushort)4); _writer.Write((ushort)32); _writer.Write(Encoding.ASCII.GetBytes("data")); _writer.Write((uint)Math.Min(uint.MaxValue, dataBytes)); } public void Write(ReadOnlySpan samples) { lock (_lock) { if (_disposed) return; for (var i = 0; i < samples.Length; i++) _writer.Write(samples[i]); _samples += samples.Length; } } public void WriteSilence(int count) { lock (_lock) { if (_disposed) return; for (var i = 0; i < count; i++) _writer.Write(0f); _samples += count; } } public void Dispose() { lock (_lock) { if (_disposed) return; _disposed = true; try { _writer.Flush(); _stream.Position = 0; WriteHeader(_samples); _writer.Flush(); } finally { _writer.Dispose(); _stream.Dispose(); } } } }