import importlib.util import sys import tempfile import unittest from pathlib import Path import numpy as np ROOT = Path(__file__).resolve().parents[1] SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) import main from adm_atmos import q_to_adm_xyz from binaural_metadata import OamdPositionTimeline from binaural_renderer import ( DEFAULT_PERSONALIZED_HEADPHONE, RosellaBinauralRenderer, resolve_personalized_headphone, ) from oamd_bits import q_of from rosella_direct import BINAURAL_PROFILE_NAMES, direct_and_room_send, special_lfe_direct from rosella_filterbank import HybridAnalysis, HybridSynthesis, QmfAnalysis, QmfSynthesis from rosella_model import load_personalized_headphone from speaker_wav import BinauralPcmSpool, write_pcm_wav _MODEL_CANDIDATES = list( (ROOT / "tests" / "binauraltests" / "evidence").glob( "*/test.personalized_headphone")) TEST_MODEL = _MODEL_CANDIDATES[0] if _MODEL_CANDIDATES else Path() class BinauralProductionTest(unittest.TestCase): def test_cli_exposes_only_near_mid_far_and_defaults_mid_float32(self): parser = main.build_parser() args = parser.parse_args(["input.eac3", "--binaural"]) self.assertEqual(args.binaural_mode, "mid") self.assertEqual(args.binaural_format, "float32") action = next(a for a in parser._actions if a.dest == "binaural_mode") self.assertEqual(tuple(action.choices), ("near", "mid", "far")) self.assertNotIn("off", BINAURAL_PROFILE_NAMES) def test_default_model_path_and_missing_model_message(self): self.assertEqual( DEFAULT_PERSONALIZED_HEADPHONE, ROOT / "HRTF" / "binaural.personalized_headphone") with tempfile.TemporaryDirectory() as directory: missing = Path(directory) / "missing.personalized_headphone" with self.assertRaisesRegex(FileNotFoundError, "未找到双耳模型"): resolve_personalized_headphone(missing) def test_runtime_filterbanks_are_double_precision(self): qmf = QmfAnalysis(1) hybrid = HybridAnalysis(1) hybrid_synthesis = HybridSynthesis(1) qmf_synthesis = QmfSynthesis(1) self.assertEqual(qmf.coefficients.dtype, np.float64) self.assertEqual(qmf.history.dtype, np.float64) self.assertEqual(hybrid.low_kernel.dtype, np.float64) self.assertEqual(hybrid.high_history.dtype, np.complex128) self.assertEqual(qmf_synthesis.basis.dtype, np.float64) source = np.zeros((1, 1, 64), dtype=np.float64) q = qmf.process_chunk(source) h = hybrid.process_chunk(q) self.assertEqual(q.dtype, np.complex128) self.assertEqual(h.dtype, np.complex128) back = hybrid_synthesis.process_chunk(h) time = qmf_synthesis.process_chunk(back) self.assertEqual(back.dtype, np.complex128) self.assertEqual(time.dtype, np.float64) @unittest.skipUnless( (ROOT / "tests" / "binauraltests" / "hqmf.py").is_file(), "research filterbank reference not present") def test_compact_kernel_archive_matches_research_float64_filterbanks(self): reference_root = ROOT / "tests" / "binauraltests" spec = importlib.util.spec_from_file_location( "binaural_reference_hqmf", reference_root / "hqmf.py") reference = importlib.util.module_from_spec(spec) spec.loader.exec_module(reference) evidence = reference_root / "evidence" source = np.fromfile( evidence / "hqmf_kernel" / "hqmf_validation_input.f32le", dtype="