File size: 1,943 Bytes
a9536c4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | import tempfile
import unittest
from pathlib import Path
import numpy as np
import soundfile as sf
from lib.mixer import mix_vocals_and_accompaniment
def _rms(audio: np.ndarray) -> float:
return float(np.sqrt(np.mean(np.square(audio)) + 1e-12))
class MixStabilityTests(unittest.TestCase):
def test_default_mix_does_not_duck_accompaniment_when_vocal_enters(self):
sr = 48000
duration = 3.0
t = np.arange(int(sr * duration), dtype=np.float32) / sr
accompaniment = 0.07 * np.sin(2.0 * np.pi * 220.0 * t)
vocals = np.zeros_like(accompaniment)
active = (t >= 1.10) & (t < 2.00)
vocals[active] = 0.045 * np.sin(2.0 * np.pi * 440.0 * t[active])
with tempfile.TemporaryDirectory() as tmp_dir:
tmp = Path(tmp_dir)
vocals_path = tmp / "vocals.wav"
accompaniment_path = tmp / "accompaniment.wav"
output_path = tmp / "mix.wav"
sf.write(vocals_path, vocals, sr)
sf.write(accompaniment_path, accompaniment, sr)
mix_vocals_and_accompaniment(
str(vocals_path),
str(accompaniment_path),
str(output_path),
target_sr=sr,
)
mixed, out_sr = sf.read(output_path)
self.assertEqual(out_sr, sr)
if mixed.ndim > 1:
mixed = mixed.mean(axis=1)
residual = mixed.astype(np.float32) - vocals.astype(np.float32)
before = residual[int(0.40 * sr): int(0.90 * sr)]
during = residual[int(1.30 * sr): int(1.80 * sr)]
drop_db = 20.0 * np.log10((_rms(during) + 1e-12) / (_rms(before) + 1e-12))
self.assertGreater(
drop_db,
-0.25,
f"default mix should keep accompaniment steady, got {drop_db:.2f} dB drop",
)
if __name__ == "__main__":
unittest.main()
|