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
121 lines
4.2 KiB
Python
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()
|