Files
MeshCore/test/test_gps_voice_generator.py

188 lines
9.6 KiB
Python

"""Validate speech-generation plans, fixed audio identity, and fixture encoding."""
from pathlib import Path
import hashlib
import shutil
import subprocess
import sys
import tempfile
import unittest
from test_button_voice_generator import parse_header, write_wav
import importlib.util
ROOT = Path(__file__).resolve().parents[1]
POWERSHELL = shutil.which("pwsh") or shutil.which("powershell")
BASELINE_FILTER = (
"highpass=f=350,lowpass=f=3400,"
"silenceremove=start_periods=1:start_threshold=-45dB,areverse,"
"silenceremove=start_periods=1:start_threshold=-45dB,areverse,"
"volume=1.4,alimiter=limit=0.8:level=disabled,apad=pad_dur=0.05"
)
SELECTED_FILTER = (
"highpass=f=350,lowpass=f=3400,"
"silenceremove=start_periods=1:start_threshold=-45dB,areverse,"
"silenceremove=start_periods=1:start_threshold=-45dB,areverse,"
"acompressor=threshold=0.08:ratio=6:attack=5:release=70:makeup=4:knee=4:detection=rms,"
"volume=1.4,aresample=8000,alimiter=limit=0.12:level=disabled,apad=pad_dur=0.05"
)
# Independently pinned from approved Zira WAVs (rate -1 / compressed / peak
# 0.12 / 8 kHz), not from the header being tested or its voice-name metadata.
ZIRA_GPS_RECORDINGS = {
'gpsOn': ('8bb2571a94245c9df307857a072f5e8900e72976c81536dc02c25251da42ca7a', 9683),
'gpsOff': ('4dd8519110914aba97d84d2d1b72a20dec8bb8ed573aa167551d8e092af8ffcb', 9237),
}
class AssetTests(unittest.TestCase):
def test_generator_shares_only_profitable_exact_encoded_prefixes(self):
spec = importlib.util.spec_from_file_location('gps_encoder', ROOT/'scripts/generate_gps_voice.py')
encoder = importlib.util.module_from_spec(spec)
spec.loader.exec_module(encoder)
for rate in (8000, 16000):
with self.subTest(rate=rate), tempfile.TemporaryDirectory() as temporary:
directory = Path(temporary)
common = [((i*997)%18001)-9000 for i in range(257)]
phrases = {'gpsOn': common+[1000,-1000]*80,
'gpsOff': common+[-3000,3000]*77}
for name, samples in phrases.items():
write_wav(directory/(name+'.wav'), samples, rate=rate)
output = directory/'shared.h'
command = [sys.executable, str(ROOT/'scripts/generate_gps_voice.py'),
'--on', str(directory/'gpsOn.wav'), '--off', str(directory/'gpsOff.wav'),
'--rate', str(rate), '--output', str(output)]
subprocess.run(command, check=True, capture_output=True, timeout=30)
source = output.read_text()
self.assertEqual(source.count('static const uint8_t '), 3)
arrays, clips = parse_header(source)
for name, samples in phrases.items():
self.assertEqual(arrays[name], encoder.encode(samples))
self.assertEqual(clips[name], (name,name,len(samples),rate))
# A completely different second phrase must not add a useless
# shared/empty blob merely to keep the nominal count at three.
write_wav(directory/'gpsOff.wav', [3000,-3000]*80, rate=rate)
subprocess.run(command, check=True, capture_output=True, timeout=30)
source = output.read_text()
self.assertEqual(source.count('static const uint8_t '), 2)
self.assertNotIn('gpsData', source)
def test_production_gps_recordings_are_the_approved_zira_audio_and_metadata(self):
source = (ROOT / 'src/helpers/ui/GpsVoiceData.h').read_text(encoding='utf-8')
self.assertEqual([line for line in source.splitlines() if line.startswith('// Voice:')],
['// Voice: Microsoft Zira Desktop'])
arrays, clips = parse_header(source)
self.assertEqual(tuple(arrays), tuple(ZIRA_GPS_RECORDINGS))
self.assertEqual(tuple(clips), tuple(ZIRA_GPS_RECORDINGS))
for name, (fingerprint, samples) in ZIRA_GPS_RECORDINGS.items():
with self.subTest(recording=name):
self.assertEqual(hashlib.sha256(arrays[name]).hexdigest(), fingerprint)
self.assertEqual(clips[name], (name, name, samples, 8000))
def test_optional_voice_metadata_preserves_generic_pcm_fixture_encoding(self):
with tempfile.TemporaryDirectory() as temporary:
directory = Path(temporary)
on, off = directory / 'on.wav', directory / 'off.wav'
write_wav(on, [0, 0, 1, -1, 2, -2, 3, -3, 1000])
write_wav(off, [0, 1, -1, 2, -2, 3, -3, -1000])
outputs = []
for arguments in ((), ('--voice', 'Microsoft Zira Desktop')):
target = directory / ('selected.h' if arguments else 'plain.h')
result = subprocess.run(
[sys.executable, str(ROOT / 'scripts/generate_gps_voice.py'),
'--on', str(on), '--off', str(off), '--output', str(target), *arguments],
capture_output=True, text=True, timeout=30,
)
self.assertEqual(result.returncode, 0, result.stderr)
outputs.append(target.read_text(encoding='utf-8'))
self.assertNotIn('// Voice:', outputs[0])
self.assertIn('// Voice: Microsoft Zira Desktop\n', outputs[1])
self.assertEqual(parse_header(outputs[0]), parse_header(outputs[1]))
def test_powershell_forwards_selected_voice_to_encoder_metadata(self):
source = (ROOT / 'scripts/generate_gps_voice.ps1').read_text(encoding='utf-8')
invocation = next(line for line in source.splitlines()
if line.startswith('& python $gpsEncoder '))
self.assertIn('--voice $Voice', invocation)
@unittest.skipUnless(POWERSHELL, "PowerShell is needed for generator plan tests")
class Tests(unittest.TestCase):
def plan(self, *arguments):
return subprocess.run(
[POWERSHELL, "-NoProfile", "-NonInteractive", "-File",
str(ROOT / "scripts/generate_gps_voice.ps1"),
*arguments, "-WhatIf", "-Verbose"],
cwd=ROOT, capture_output=True, text=True, timeout=30,
)
def test_selected_defaults_and_original_profile(self):
result = self.plan()
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn(SELECTED_FILTER, result.stdout)
self.assertIn("Microsoft Zira Desktop; rate: -1", result.stdout)
self.assertIn(str(ROOT / "src/helpers/ui/GpsVoiceData.h"), result.stdout)
result = self.plan(
"-Voice", "Microsoft Zira Desktop", "-Compress:$false", "-PeakLimit", "0.8",
"-OutputHeader", "out/gps-voice/original-zira.h",
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn(BASELINE_FILTER, result.stdout)
self.assertIn("Microsoft Zira Desktop; rate: -1", result.stdout)
self.assertNotIn("acompressor=", result.stdout)
def test_compressed_variant_is_planned_without_writes(self):
with tempfile.TemporaryDirectory() as temporary:
directory = Path(temporary) / "preview"
header = Path(temporary) / "headers/mark.h"
result = self.plan(
"-Voice", "Microsoft Mark", "-Rate", "0", "-Compress",
"-PeakLimit", "0.10", "-OutputDirectory", str(directory),
"-OutputHeader", str(header),
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("Microsoft Mark; rate: 0", result.stdout)
self.assertIn("acompressor=threshold=0.08:ratio=6", result.stdout)
self.assertIn("makeup=4:knee=4:detection=rms", result.stdout)
self.assertIn("aresample=8000,alimiter=limit=0.1:level=disabled", result.stdout)
self.assertIn(str(header), result.stdout)
self.assertFalse(directory.exists())
self.assertFalse(header.parent.exists())
def test_variants_require_an_explicit_header(self):
for arguments in (("-Compress:$false",), ("-Voice", "Microsoft Mark"),
("-Rate", "0"), ("-PeakLimit", "0.10")):
with self.subTest(arguments=arguments):
result = self.plan(*arguments)
self.assertNotEqual(result.returncode, 0)
self.assertIn("explicit -OutputHeader", result.stderr)
def test_invalid_header_extension_is_rejected(self):
result = self.plan("-OutputHeader", "out/gps-voice/test.wav")
self.assertNotEqual(result.returncode, 0)
self.assertIn("must name a .h file", result.stderr)
def test_file_directory_collisions_are_rejected(self):
with tempfile.TemporaryDirectory() as temporary:
path = Path(temporary)
file = path / "existing-file"
file.touch()
result = self.plan("-OutputDirectory", str(file))
self.assertNotEqual(result.returncode, 0)
self.assertIn("names an existing file", result.stderr)
directory = path / "existing-directory.h"
directory.mkdir()
result = self.plan("-OutputHeader", str(directory))
self.assertNotEqual(result.returncode, 0)
self.assertIn("names an existing directory", result.stderr)
def test_rate_and_peak_ranges_are_rejected(self):
for arguments in (("-Rate", "11"), ("-Rate", "-11"),
("-PeakLimit", "0.01"), ("-PeakLimit", "1.1")):
with self.subTest(arguments=arguments):
result = self.plan("-OutputHeader", "out/gps-voice/test.h", *arguments)
self.assertNotEqual(result.returncode, 0)
self.assertIn(arguments[0][1:], result.stderr)
if __name__ == "__main__":
unittest.main()