import array
import math
import struct
import sys
import tempfile
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import audio_report as ar
def wav_bytes(frames, rate=8000, channels=2, data_size=None):
raw = frames.tobytes()
size = len(raw) if data_size is None else data_size
return struct.pack("<4sI4s4sIHHIIHH4sI", b"RIFF", 36 + size, b"WAVE", b"fmt ", 16, 1, channels,
rate, rate * channels * 2, channels * 2, 16, b"data", size) + raw
class AudioReportTests(unittest.TestCase):
def test_reads_to_eof_even_with_placeholder_data_size(self):
frames = array.array("h", [1000, -1000] * 8000) # 1 s stereo at 8 kHz
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "a.wav"
path.write_bytes(wav_bytes(frames, data_size=0)) # killed writer: size never fixed
rate, channels, samples = ar.read_wav(path)
self.assertEqual((rate, channels, len(samples)), (8000, 2, 16000))
def test_levels_per_second_of_mono_mix(self):
# second 0: full-scale square on both channels (0 dBFS); second 1: silence
loud = [16384 if (i // 20) % 2 else -16384 for i in range(8000)]
frames = array.array("h", [v for s in loud for v in (s, s)] + [0, 0] * 8000)
lv = ar.levels(8000, 2, frames)
self.assertEqual(len(lv), 2)
self.assertAlmostEqual(lv[0][1], 20 * math.log10(0.5), places=3) # rms of ±0.5 square
self.assertEqual(lv[1][1], -120.0)
def test_opposite_channels_cancel_in_the_mix(self):
frames = array.array("h", [8000, -8000] * 8000)
self.assertEqual(ar.levels(8000, 2, frames)[0][1], -120.0)
if __name__ == "__main__":
unittest.main()