File size: 6,142 Bytes
a2d9995
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f02c67e
 
a2d9995
 
 
 
 
f02c67e
a2d9995
 
 
 
 
 
 
f02c67e
a2d9995
 
 
 
 
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
from __future__ import annotations

import json
import tempfile

import gradio as gr
import numpy as np
import soundfile as sf

from app_state import app_state


def render_full_set(max_iter, progress=gr.Progress()):
    """Render the DJ set through the AutomationIR full-set renderer."""
    if not app_state.transitions:
        return None, "⚠️ Generate a set plan first"

    import app as core

    progress(0.05, desc="Compiling full-set AutomationIR...")
    from render_engine import render_full_set as render_full_set_shared
    from stem_provider import StemProvider
    stem_provider = StemProvider()

    set_audio, set_info, ir = render_full_set_shared(
        app_state.analyses,
        app_state.set_order,
        app_state.transitions,
        load_audio_segment=core.load_audio_segment,
        time_stretch_audio=core.time_stretch_audio,
        stem_resolver=stem_provider.resolver(),
        sr=44100,
    )
    app_state.last_stem_diagnostics = dict(stem_provider.diagnostics)
    app_state.rendered_set = set_audio

    progress(0.82, desc="Running diagnostics...")
    benchmarks = core.run_benchmarks(app_state.analyses, app_state.transitions, set_audio)
    benchmark_text = core.format_benchmarks(benchmarks)

    progress(0.95, desc="Saving audio...")
    tmp = tempfile.NamedTemporaryFile(suffix='.wav', delete=False)
    audio_for_save = set_audio.T if set_audio.ndim == 2 else set_audio
    if audio_for_save.ndim == 2 and audio_for_save.shape[1] > audio_for_save.shape[0]:
        audio_for_save = audio_for_save.T
    audio_for_save = np.clip(audio_for_save, -1.0, 1.0)
    sf.write(tmp.name, audio_for_save.astype(np.float32), 44100)

    summary = "# ✅ DJ Set Rendered via AutomationIR\n\n"
    summary += f"- **Total duration:** {set_info['total_duration']:.1f}s ({set_info['total_duration']/60:.1f} min)\n"
    summary += f"- **Tracks:** {len(set_info['tracks'])}\n"
    summary += f"- **Transitions:** {len(set_info.get('transitions', []))}\n"
    summary += f"- **AutomationIR:** {set_info['automation_ir']['clips']} clips, {set_info['automation_ir']['lanes']} lanes\n"
    summary += f"- **Stem lane method:** `{set_info['automation_ir'].get('component_lane_method', 'n/a')}`\n\n"

    summary += "## Tracklist\n"
    for i, t in enumerate(set_info["tracks"]):
        summary += f"{i+1}. **{t['filename']}** — tl={t['tl_start']:.1f}s\n"

    if set_info.get("transitions"):
        summary += "\n## Transitions Applied\n"
        for i, t in enumerate(set_info["transitions"], start=1):
            score = t.get("score_breakdown", {}).get("overall", "n/a")
            summary += f"- **{i}. `{t['type']}`**: {t['track_a']}{t['track_b']} / score={score}\n"

    summary += "\n## AutomationIR preview\n"
    summary += "```json\n" + json.dumps(ir.to_dict(), indent=2)[:8000] + "\n```\n"
    summary += f"\n{benchmark_text}"

    return tmp.name, summary


def render_single_transition(transition_idx, candidate_rank=0, progress=gr.Progress()):
    """Preview a selected or alternative transition candidate via AutomationIR."""
    if not app_state.transitions:
        return None, "⚠️ Generate a set plan first"

    import app as core

    idx = int(transition_idx) - 1
    if idx < 0 or idx >= len(app_state.transitions):
        return None, f"⚠️ Invalid transition index. Choose 1-{len(app_state.transitions)}"

    rank = max(0, int(candidate_rank or 0))
    trans = app_state.transitions[idx]
    track_a = app_state.analyses[trans.track_a_idx]
    track_b = app_state.analyses[trans.track_b_idx]

    progress(0.15, desc="Compiling automation IR...")
    from render_engine import render_transition_preview
    from stem_provider import StemProvider
    stem_provider = StemProvider()
    audio, ir, candidate = render_transition_preview(
        trans,
        track_a,
        track_b,
        candidate_rank=rank,
        load_audio_segment=core.load_audio_segment,
        time_stretch_audio=core.time_stretch_audio,
        stem_resolver=stem_provider.resolver(),
        sr=44100,
    )
    app_state.last_stem_diagnostics = dict(stem_provider.diagnostics)

    progress(0.85, desc="Saving preview...")
    tmp = tempfile.NamedTemporaryFile(suffix='.wav', delete=False)
    sf.write(tmp.name, audio.T, 44100)

    cue_source = "selected plan" if rank == 0 or not candidate else f"alternative #{rank}"
    edge_score = None
    if candidate:
        edge_score = candidate.get('score')
    else:
        edge_score = trans.score_breakdown.get('overall')

    from transition_diagnostics import diagnose_transition_audio, format_transition_diagnostics
    diag = ir.metadata.get("rendered_diagnostics") or diagnose_transition_audio(audio, sr=44100, anchor_seconds=ir.anchor_seconds)
    rendered_scores = ir.metadata.get("rendered_candidate_scores", {})
    stem_diag = json.dumps(app_state.last_stem_diagnostics, indent=2)[:2500] if app_state.last_stem_diagnostics else "{}"

    info = (
        f"**Transition {idx+1}:** {track_a.filename}{track_b.filename}\n"
        f"**Candidate:** {cue_source}\n"
        f"**Type:** `{getattr(ir, 'transition_type', trans.transition_type)}`\n"
        f"**Automation IR:** {len(ir.clips)} clips, {len(ir.lanes)} lanes, anchor={ir.anchor_seconds:.2f}s\n"
        f"**Cue timing:** A out {ir.metadata['mix_out_point']:.2f}s, "
        f"B in {ir.metadata['mix_in_point']:.2f}s, B drop {ir.metadata['b_drop']:.2f}s\n"
        f"**Duration:** {ir.metadata['duration_seconds']:.2f}s; score={edge_score if edge_score is not None else 'n/a'}\n"
        f"**Tempo policy:** ×{float(ir.metadata.get('bpm_adjustment', 1.0) or 1.0):.3f}; "
        f"{(getattr(trans, 'tempo_policy', {}) or {}).get('reason', 'source tempo unless explicitly forced')}\n"
        f"**Preview file duration:** {audio.shape[-1] / 44100:.1f}s\n\n"
        f"**Rendered candidate scores:** `{json.dumps(rendered_scores)}`\n\n"
        f"{format_transition_diagnostics(diag)}\n\n"
        f"### Stem provider diagnostics\n```json\n{stem_diag}\n```\n\n"
        f"```json\n{json.dumps(ir.to_dict(), indent=2)[:6000]}\n```"
    )
    return tmp.name, info