D3+D4: export stage PCM16/24 + TPDF dither + noise shaping, resample 44.1/48/96k, normalize -1dBTP/-14LUFS, mono side/mid check; Export modal mastering options + /plugins/render options (test 9 case, 23 passed)

This commit is contained in:
2026-08-23 07:47:27 +07:00
parent 35076f680b
commit 5d37c0683b
8 changed files with 372 additions and 16 deletions
+29 -1
View File
@@ -607,6 +607,15 @@ async def download_soundfont_asset(sf_id: str):
class RenderRequest(BaseModel):
project_json: dict
output_filename: Optional[str] = "render_output.wav"
# D3 export options: bit-depth 16|24|32 (32 = float), SR 44100|48000|96000,
# dither TPDF+noise shaping (PCM only), normalize target -1dBTP|-14LUFS,
# mono-compat check (side/mid ratio).
bit_depth: Optional[int] = 32
sample_rate: Optional[int] = 0
dither: Optional[bool] = False
normalize: Optional[bool] = False
normalize_target: Optional[str] = "-1dbtp"
mono_check: Optional[bool] = False
class FxGuiRequest(BaseModel):
@@ -1180,7 +1189,14 @@ def render_project(
safe_name += ".wav"
output_path = os.path.join(settings.PROCESSED_DIR, safe_name)
try:
result_path = engine.render_project(req.project_json, output_path)
result_path = engine.render_project(
req.project_json, output_path,
bit_depth=req.bit_depth or 32,
out_sample_rate=req.sample_rate or 0,
dither=bool(req.dither),
normalize=bool(req.normalize),
normalize_target=req.normalize_target or "-1dbtp",
)
# EBU R128 loudness + true peak (D1) — trả kèm response để mastering
# panel hiển thị LUFS/dBTP ngay sau export mà không cần đọc lại file.
data, _sr = sf.read(result_path, dtype="float32", always_2d=True)
@@ -1194,6 +1210,18 @@ def render_project(
_tp = loudness.true_peak_db(audio, _sr)
resp["lufs"] = round(_lufs, 2) if np.isfinite(_lufs) else None
resp["true_peak_db"] = round(_tp, 2) if np.isfinite(_tp) else None
# D3: mono-compat — side/mid RMS ratio, cảnh báo khi sum mono mất nhiều.
if req.mono_check and data.shape[1] >= 2:
_ratio = loudness.mono_side_ratio(data.T)
resp["mono_side_ratio"] = round(_ratio, 3)
if _ratio > 0.85:
resp["mono_warning"] = ("Side/Mid %.2f — mono sum mất gần hết "
"âm thanh, kiểm tra phase!" % _ratio)
elif _ratio > 0.5:
resp["mono_warning"] = ("Side/Mid %.2f — âm thanh phát mono "
"yếu rõ, kiểm tra phase" % _ratio)
else:
resp["mono_warning"] = None
return resp
except Exception as e:
raise HTTPException(status_code=500, detail=f"Render failed: {str(e)}")