File size: 3,507 Bytes
e8b2f06
 
 
 
ddef6a2
 
 
e8b2f06
ddef6a2
 
e8b2f06
 
 
ddef6a2
aed07b7
e8b2f06
 
ddef6a2
 
aed07b7
e8b2f06
aed07b7
 
ddef6a2
aed07b7
 
ddef6a2
 
e8b2f06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
"""Tests for app.py wiring: on-upload analysis, demo loading, and the full
finish_song pipeline with the SA3 engine stubbed (no GPU, no weights). Verifies
the real DSP (analyze, enhance, stitch) runs end-to-end on the bundled demo and
produces a valid 44.1 kHz stereo file. Skips if gradio isn't installed."""
import os

import numpy as np
import pytest
import soundfile as sf

pytest.importorskip("gradio")
import app   # noqa: E402
import engine  # noqa: E402

DEMO = app.PUSHBACK_DEMO
_HAVE_DEMO = os.path.exists(DEMO)
need_demo = pytest.mark.skipif(not _HAVE_DEMO, reason="bundled demo clip absent")


class _Prog:
    """stand-in for gr.Progress — records the (frac, desc) stages it's told."""
    def __init__(self):
        self.calls = []

    def __call__(self, frac, desc=None):
        self.calls.append((frac, desc))


@need_demo
def test_load_demo_returns_clip():
    assert app.load_demo() == DEMO
    assert os.path.exists(DEMO)


@need_demo
def test_analyze_on_upload_reports_and_enables():
    info_update, btn_update = app.analyze_on_upload(DEMO)
    md = info_update["value"]
    assert "KEY" in md and "TEMPO" in md and "BPM" in md
    assert info_update["visible"] is True
    assert btn_update["interactive"] is True


def test_analyze_on_upload_empty_disables_button():
    _, btn_update = app.analyze_on_upload(None)
    assert btn_update["interactive"] is False


@need_demo
def test_finish_song_pipeline_with_stubbed_engine(monkeypatch):
    """stub engine.continue_audio with a fast synthetic tail; the rest of the
    pipeline (enhance, analyze, stitch, write) runs for real."""
    def fake_continue_audio(clip_path, total_seconds, prompt="", progress=None,
                            **kw):
        if progress:
            progress("reading")
            progress("composing")
            progress("finalizing")
        new_secs = total_seconds - 29.53          # demo is ~29.53s
        n = int(max(new_secs, 5) * engine.SR)
        t = np.arange(n) / engine.SR
        wave = 0.2 * np.sin(2 * np.pi * 220 * t).astype(np.float32)
        tail = np.stack([wave, wave])
        return tail, 29.53, engine.SR

    monkeypatch.setattr(engine, "continue_audio", fake_continue_audio)

    prog = _Prog()
    out_path, summary = app.finish_song(DEMO, 60, "", False, progress=prog)

    assert os.path.exists(out_path)
    y, sr = sf.read(out_path)
    assert sr == 44100
    assert y.ndim == 2 and y.shape[1] == 2          # 44.1k stereo
    assert len(y) / sr > 50                          # ~60s finished
    assert float(np.abs(y).max()) <= 1.0
    assert float(np.sqrt(np.mean(y ** 2))) > 1e-3    # not silent
    assert "BPM" in summary and "Stable Audio 3" in summary
    # progress streamed several stages and ended at 100%
    assert len(prog.calls) >= 4
    assert prog.calls[-1][0] == 1.0


@need_demo
def test_finish_song_remaster_path(monkeypatch):
    def fake_continue_audio(clip_path, total_seconds, prompt="", progress=None,
                            **kw):
        n = int(15 * engine.SR)
        wave = 0.2 * np.sin(2 * np.pi * 220 * np.arange(n) / engine.SR)
        return np.stack([wave, wave]).astype(np.float32), 29.53, engine.SR

    monkeypatch.setattr(engine, "continue_audio", fake_continue_audio)
    out_path, _ = app.finish_song(DEMO, 45, "", True, progress=_Prog())
    assert os.path.exists(out_path)


def test_finish_song_rejects_empty_input():
    with pytest.raises(Exception):
        app.finish_song(None, 60, "", False, progress=_Prog())