Spaces:
Running
Running
File size: 4,105 Bytes
985e22d 33cbdc9 985e22d 33cbdc9 b259003 33cbdc9 e2c6ce1 b259003 e2c6ce1 33cbdc9 e2c6ce1 33cbdc9 b677e1b 33cbdc9 b259003 33cbdc9 b198075 33cbdc9 b259003 33cbdc9 e2c6ce1 b259003 33cbdc9 b259003 33cbdc9 b198075 33cbdc9 e2c6ce1 33cbdc9 e2c6ce1 33cbdc9 b677e1b 33cbdc9 e2c6ce1 985e22d e2c6ce1 33cbdc9 e2c6ce1 33cbdc9 e2c6ce1 33cbdc9 e2c6ce1 33cbdc9 e2c6ce1 33cbdc9 e2c6ce1 33cbdc9 e2c6ce1 33cbdc9 | 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 | # stem_render.py — Stem-based rendering with demucs
import numpy as np
import scipy.io.wavfile
import tempfile
import logging
import gradio as gr
logger = logging.getLogger("dj_engine")
def render_full_set_with_stems(app_state, max_iter=20, stem_backend="spleeter", progress=gr.Progress()):
"""Render the DJ set using stem separation.
max_iter: number of refinement iterations (from the UI slider)
stem_backend: specify 'demucs', 'demucs-mlx', or 'spleeter'. Defaults to preferred_stem_backend()
"""
if not app_state.transitions:
return None, "⚠️ Generate a set plan first"
progress(0.02, desc="Starting stem-based render...")
sr = 44100
try:
from stem_mixer import mix_stems
from stem_separator import preferred_stem_backend, separate_stems_with_backend
actual_backend = stem_backend if stem_backend else preferred_stem_backend()
progress(0.03, desc=f"Loading stem separator ({actual_backend})...")
# Separate each track into stems (cache-aware — skips separation on cache hit)
all_stems = {}
backend_used = actual_backend
n_tracks = len(app_state.set_order)
for i, tidx in enumerate(app_state.set_order):
track = app_state.analyses[tidx]
progress(0.03 + (i / n_tracks) * 0.50,
desc=f"Separating stems ({actual_backend}, {i+1}/{n_tracks}): {track.filename[:30]}...")
stems, backend_used = separate_stems_with_backend(track.path, 0.0, None, sr, backend=stem_backend)
all_stems[tidx] = stems
logger.info(f"Stems for {track.filename} via {backend_used}: {list(stems.keys())}")
# Mix using stem mixer
progress(0.55, desc="Mixing with stems (surgical drum/bass swap)...")
set_audio, set_info = mix_stems(
all_stems, app_state.analyses, app_state.set_order,
progress_cb=lambda p, m: progress(0.55 + p * 0.35, desc=m)
)
method = f"{backend_used} htdemucs → surgical drum/bass swap on downbeats"
except Exception as e:
logger.warning(f"Stem separation failed: {e}")
import traceback
traceback.print_exc()
# Fallback to the original filter-based mixer with refinement loop
from mixer import mix_set
from quality_analyzer import run_refinement_loop, format_analysis_log
progress(0.10, desc="Fallback: filter-based mixing with refinement...")
set_audio, set_info, _ = run_refinement_loop(
mix_fn=mix_set,
tracks=app_state.analyses,
order=app_state.set_order,
transitions=app_state.transitions,
max_iter=int(max_iter),
progress_cb=lambda p, m: progress(0.10 + p * 0.80, desc=m)
)
method = f"Filter-based with {int(max_iter)} refinement iterations (demucs failed: {e})"
app_state.rendered_set = set_audio
progress(0.92, desc="Saving audio...")
tmp = tempfile.NamedTemporaryFile(suffix='.wav', delete=False)
audio_int16 = (set_audio.T * 32767).astype(np.int16)
scipy.io.wavfile.write(tmp.name, sr, audio_int16)
# Summary
summary = f"# ✅ DJ Set Rendered\n\n"
summary += f"- **Total duration:** {set_info.get('total_duration', 0):.1f}s "
summary += f"({set_info.get('total_duration', 0)/60:.1f} min)\n"
summary += f"- **Tracks:** {len(set_info.get('tracks', []))}\n"
summary += f"- **Method:** {method}\n\n"
summary += "## Tracklist\n"
for i, t in enumerate(set_info.get('tracks', [])):
fn = t.get('filename', '?')
tl = t.get('tl_start', 0)
stretch = t.get('stretch', 1.0)
extra = f" (×{stretch:.3f})" if abs(stretch - 1.0) > 0.003 else ""
summary += f"{i+1}. **{fn}** — starts at {tl:.0f}s{extra}\n"
if set_info.get('transitions'):
summary += "\n## Transitions\n"
for t in set_info['transitions']:
if isinstance(t, dict):
summary += f"- {t}\n"
else:
summary += f"- {t}\n"
return tmp.name, summary
|