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
131 lines
4 KiB
Python
131 lines
4 KiB
Python
#!/usr/bin/env python3
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import soundfile as sf
|
|
import torch
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description="Compare audio.cpp STFT against torch.stft.")
|
|
parser.add_argument("wav", type=Path)
|
|
parser.add_argument("--dump-bin", type=Path, default=Path("build/debug/bin/stft_parity_dump"))
|
|
parser.add_argument("--kind", choices=("stft",), default="stft")
|
|
parser.add_argument("--n-fft", type=int, default=512)
|
|
parser.add_argument("--hop-length", type=int, default=160)
|
|
parser.add_argument("--win-length", type=int, default=400)
|
|
parser.add_argument("--pad-mode", choices=("constant", "reflect"), default="constant")
|
|
parser.add_argument("--preemphasis", type=float, default=0.0)
|
|
parser.add_argument("--periodic-window", action="store_true")
|
|
return parser.parse_args()
|
|
|
|
|
|
def run_dump(
|
|
dump_bin: Path,
|
|
wav: Path,
|
|
kind: str,
|
|
preemphasis: float,
|
|
n_fft: int,
|
|
hop_length: int,
|
|
win_length: int,
|
|
pad_mode: str,
|
|
) -> tuple[np.ndarray, tuple[int, ...]]:
|
|
with tempfile.TemporaryDirectory(prefix=f"stft-{kind}-") as tmp:
|
|
tmp_path = Path(tmp)
|
|
f32_path = tmp_path / "spec.f32"
|
|
shape_path = tmp_path / "shape.i64"
|
|
cmd = [str(dump_bin), str(wav), kind, str(f32_path), str(shape_path)]
|
|
if preemphasis != 0.0:
|
|
cmd.extend(["--preemphasis", str(preemphasis)])
|
|
cmd.extend(
|
|
[
|
|
"--n-fft",
|
|
str(n_fft),
|
|
"--hop-length",
|
|
str(hop_length),
|
|
"--win-length",
|
|
str(win_length),
|
|
"--pad-mode",
|
|
pad_mode,
|
|
]
|
|
)
|
|
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
shape = tuple(np.fromfile(shape_path, dtype=np.int64).tolist())
|
|
spec = np.fromfile(f32_path, dtype=np.float32).reshape(shape)
|
|
return spec, shape
|
|
|
|
|
|
def apply_preemphasis(audio: np.ndarray, coeff: float) -> np.ndarray:
|
|
if coeff == 0.0 or audio.size == 0:
|
|
return audio
|
|
out = audio.copy()
|
|
out[1:] = out[1:] - coeff * out[:-1]
|
|
return out
|
|
|
|
|
|
def compare(name: str, cpp_spec: np.ndarray, torch_spec: np.ndarray) -> None:
|
|
diff = cpp_spec - torch_spec
|
|
abs_diff = np.abs(diff)
|
|
idx = np.unravel_index(np.argmax(abs_diff), abs_diff.shape)
|
|
print(f"{name}:")
|
|
print(f" shape={cpp_spec.shape}")
|
|
print(f" max_abs={float(abs_diff.max())}")
|
|
print(f" mean_abs={float(abs_diff.mean())}")
|
|
print(f" rmse={float(np.sqrt(np.mean(diff * diff)))}")
|
|
print(f" worst_idx={idx}")
|
|
print(f" cpp_val={float(cpp_spec[idx])}")
|
|
print(f" torch_val={float(torch_spec[idx])}")
|
|
|
|
|
|
def main() -> int:
|
|
args = parse_args()
|
|
audio, sr = sf.read(args.wav, dtype="float32")
|
|
if sr != 16000:
|
|
raise RuntimeError(f"expected 16000 Hz WAV, got {sr}")
|
|
if audio.ndim != 1:
|
|
raise RuntimeError("expected mono WAV")
|
|
|
|
audio = apply_preemphasis(audio, args.preemphasis)
|
|
x = torch.from_numpy(audio)
|
|
window = torch.hann_window(
|
|
args.win_length,
|
|
periodic=args.periodic_window,
|
|
dtype=torch.float32,
|
|
)
|
|
spec = torch.stft(
|
|
x,
|
|
n_fft=args.n_fft,
|
|
hop_length=args.hop_length,
|
|
win_length=args.win_length,
|
|
window=window,
|
|
center=True,
|
|
pad_mode=args.pad_mode,
|
|
normalized=False,
|
|
onesided=True,
|
|
return_complex=True,
|
|
)
|
|
torch_spec = torch.view_as_real(spec).unsqueeze(0).numpy().astype(np.float32)
|
|
|
|
cpp_spec, _ = run_dump(
|
|
args.dump_bin,
|
|
args.wav,
|
|
args.kind,
|
|
args.preemphasis,
|
|
args.n_fft,
|
|
args.hop_length,
|
|
args.win_length,
|
|
args.pad_mode,
|
|
)
|
|
compare(args.kind, cpp_spec, torch_spec)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|