voice_audio_multi/tools/community_models/compare_mira_tts_outputs.py
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

121 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""Compare matched upstream/native MiraTTS WAV outputs."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import librosa
import numpy as np
import soundfile as sf
def mono(path: Path) -> tuple[np.ndarray, int]:
audio, sample_rate = sf.read(path, dtype="float32", always_2d=True)
return np.mean(audio, axis=1, dtype=np.float32), sample_rate
def cosine(left: np.ndarray, right: np.ndarray) -> float:
denominator = float(np.linalg.norm(left) * np.linalg.norm(right))
return 1.0 if denominator == 0.0 else float(np.dot(left, right) / denominator)
def compare(cpp_path: Path, python_path: Path) -> dict[str, object]:
cpp, cpp_rate = mono(cpp_path)
python, python_rate = mono(python_path)
if cpp_rate != python_rate:
raise RuntimeError(
f"sample-rate mismatch for {cpp_path.name}: {cpp_rate} != {python_rate}"
)
common = min(cpp.size, python.size)
cpp_common = cpp[:common].astype(np.float64, copy=False)
python_common = python[:common].astype(np.float64, copy=False)
wav_cosine = cosine(cpp_common, python_common)
mel_kwargs = {
"sr": cpp_rate,
"n_fft": 2048,
"hop_length": 512,
"win_length": 2048,
"n_mels": 128,
"power": 2.0,
}
cpp_mel = np.log(
np.maximum(librosa.feature.melspectrogram(y=cpp, **mel_kwargs), 1.0e-10)
)
python_mel = np.log(
np.maximum(librosa.feature.melspectrogram(y=python, **mel_kwargs), 1.0e-10)
)
mel_frames = min(cpp_mel.shape[1], python_mel.shape[1])
log_mel_cosine = cosine(
cpp_mel[:, :mel_frames].reshape(-1).astype(np.float64, copy=False),
python_mel[:, :mel_frames].reshape(-1).astype(np.float64, copy=False),
)
return {
"cpp_audio": str(cpp_path),
"python_audio": str(python_path),
"sample_rate": cpp_rate,
"cpp_frames": int(cpp.size),
"python_frames": int(python.size),
"exact_frame_count": cpp.size == python.size,
"common_frames": int(common),
"waveform_cosine": wav_cosine,
"log_mel_cosine": log_mel_cosine,
}
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--cpp-summary", type=Path, required=True)
parser.add_argument("--python-summary", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--wav-cosine-min", type=float, default=0.95)
parser.add_argument("--log-mel-cosine-min", type=float, default=0.95)
parser.add_argument("--require-exact-frames", action="store_true")
args = parser.parse_args()
cpp = json.loads(args.cpp_summary.read_text(encoding="utf-8"))
python = json.loads(args.python_summary.read_text(encoding="utf-8"))
cpp_by_name = {item["name"]: item for item in cpp["results"]}
python_by_name = {item["name"]: item for item in python["results"]}
names = sorted(set(cpp_by_name) & set(python_by_name))
if not names:
raise RuntimeError("the summaries contain no matching request names")
comparisons = []
failed = []
for name in names:
item = compare(
Path(cpp_by_name[name]["audio_out"]),
Path(python_by_name[name]["audio_out"]),
)
item["name"] = name
item["passed"] = (
item["waveform_cosine"] >= args.wav_cosine_min
and item["log_mel_cosine"] >= args.log_mel_cosine_min
and (not args.require_exact_frames or item["exact_frame_count"])
)
comparisons.append(item)
if not item["passed"]:
failed.append(name)
print(json.dumps(item))
report = {
"thresholds": {
"waveform_cosine": args.wav_cosine_min,
"log_mel_cosine": args.log_mel_cosine_min,
"require_exact_frames": args.require_exact_frames,
},
"passed": not failed,
"failed": failed,
"comparisons": comparisons,
}
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(report, indent=2), encoding="utf-8")
if failed:
raise SystemExit(f"MiraTTS parity failed: {', '.join(failed)}")
if __name__ == "__main__":
main()