import numpy as np from conftest import band_db, sine, speechish from producer import meters, pipeline from producer.config import Options def test_stage_order_and_names(): opts = Options() opts.denoise = "off" opts.enhance = "off" res = pipeline.run_pipeline(np.zeros(44100, dtype=np.float32), 44100, opts) names = [s.name for s in res.stages] assert names == ["denoise", "enhance", "dsp", "levelling"] by_name = {s.name: s for s in res.stages} assert by_name["denoise"].enabled is False assert by_name["enhance"].enabled is False assert by_name["dsp"].enabled is True assert by_name["levelling"].enabled is True def test_denoise_stage_detail_shows_pf(): opts = Options() stages = pipeline.build_stages(opts) assert "pf=off" in stages[0].detail assert "leveling" not in stages[0].detail opts.denoise_pf = True stages = pipeline.build_stages(opts) assert "pf=on" in stages[0].detail def test_full_chain_profile_bounds(sr): opts = Options() opts.denoise = "off" opts.enhance = "off" x = speechish(8.0, sr, level_dbfs=-35.0) res = pipeline.run_pipeline(x, sr, opts) assert abs(meters.rms_db(res.audio) + 20.0) < 0.6 assert meters.true_peak_db(res.audio, sr) <= -2.9 assert res.timings.get("levelling", 0) >= 0 def test_passthrough_when_disabled(sr): opts = Options() opts.denoise = "off" opts.enhance = "off" opts.dsp = False opts.levelling = False x = speechish(4.0, sr, level_dbfs=-20.0) res = pipeline.run_pipeline(x, sr, opts) assert np.allclose(res.audio, x) def test_knob_zero_disables_eq(sr): base = Options() base.denoise = "off" base.enhance = "off" base.levelling = False base.strengths["warmth"] = 0.0 warm = Options() warm.denoise = "off" warm.enhance = "off" warm.levelling = False x = sine(60, 4.0, sr, -20.0) + sine(3000, 4.0, sr, -20.0) y_flat = pipeline.run_pipeline(x, sr, base).audio y_warm = pipeline.run_pipeline(x, sr, warm).audio d_flat = band_db(y_flat, sr, 50, 70) - band_db(x, sr, 50, 70) d_warm = band_db(y_warm, sr, 50, 70) - band_db(x, sr, 50, 70) assert d_warm - d_flat > 0.8 def test_podcast_profile_bounds(sr): opts = Options() opts.profile = "podcast" opts.denoise = "off" opts.enhance = "off" x = speechish(8.0, sr, level_dbfs=-35.0) res = pipeline.run_pipeline(x, sr, opts) assert abs(meters.lufs(res.audio, sr) + 16.0) < 0.8 assert meters.true_peak_db(res.audio, sr) <= -1.4 def test_dsp_pregain_makes_chain_level_invariant(sr): """The voice chain must treat a quiet and a hot take identically. Denoised files often arrive far below the chain's design level; without the pre-gain the absolute comp thresholds would idle (or slam) depending on input level alone. """ hot = Options() hot.denoise = "off" hot.enhance = "off" hot.levelling = False quiet = Options() quiet.denoise = "off" quiet.enhance = "off" quiet.levelling = False x_hot = speechish(6.0, sr, level_dbfs=-14.0) x_quiet = speechish(6.0, sr, level_dbfs=-38.0) y_hot = pipeline.run_pipeline(x_hot, sr, hot).audio y_quiet = pipeline.run_pipeline(x_quiet, sr, quiet).audio diff = abs(meters.rms_db(y_hot) - meters.rms_db(y_quiet)) assert diff < 1.0 def test_dsp_pregain_note_recorded(sr): opts = Options() opts.denoise = "off" opts.enhance = "off" notes: list[str] = [] stages = pipeline.build_stages(opts, notes) dsp_stage = next(s for s in stages if s.name == "dsp") dsp_stage.fn(speechish(4.0, sr, level_dbfs=-30.0), sr) assert any("pregain" in n for n in notes)