V45: D17 seamless loop wrap + D18 stop solo note-off (Qin) + D18b/c guard + engine VST load fixes

- D17: wrap_info/wrap-skip, fx wrap xfade, playhead-derived scheduling
- D18: solo stop -> NOTE_OFF all sounding timeline pitches + CC123/CC120
- D18b: track audible gain guard, tracksRef fallback
- D18c: timelinePitchesRef per-track Set, kill at applyAllTrackMuteSolo
- bridge ring _MAX 16, index v=202608290928
- plugins.py/vst_engine.py/fx_realtime.py/JuceFxEngine.cpp: VSTi load + Qin fixes (uncommitted 12:18)
This commit is contained in:
2026-08-29 16:39:02 +07:00
parent 2afa828c8f
commit c084ac8ba7
9 changed files with 434 additions and 104 deletions
+7 -3
View File
@@ -1,13 +1,10 @@
import os, sys, uuid, json, tempfile, subprocess, time as _time, threading, asyncio, logging
import numpy as np
import soundfile as sf
from fastapi import APIRouter, HTTPException, Depends, UploadFile, File, BackgroundTasks, Query, WebSocket, WebSocketDisconnect
from fastapi.responses import FileResponse
from pydantic import BaseModel
from typing import Optional, Any
from app.config import settings
from app.core.vst_engine import PluginManager, HAS_PYFLUIDSYNTH, render_soundfont_midi_to_audio, RENDER_LOCK, _render_locked
from app.core.render_engine import PythonRenderEngine, _find_sf2_path, _find_default_sf2
from app.core.soundfont_inspector import SoundFontInspector
from app.core.soundfont_converter import SoundFontConverter
from app.core.soundfont_scanner import SoundFontAutoScanner
@@ -570,6 +567,7 @@ async def delete_soundfont(sf_id: str, current_user: dict = Depends(get_current_
@router.get("/soundfonts/download/{sf_id}")
async def download_soundfont_asset(sf_id: str):
from app.core.render_engine import _find_sf2_path, _find_default_sf2
clean_id = sf_id.replace("sf_", "") if sf_id.startswith("sf_") else sf_id
# Cũng tìm trong static/soundfonts (font bundled theo deployment) — trước
# đây chỉ UPLOAD + SYSTEM → font bundled 404 → incognito (IndexedDB rỗng)
@@ -963,6 +961,7 @@ class FxRenderRequest(BaseModel):
@router.post("/fx-render")
async def fx_render(req: FxRenderRequest, current_user: dict = Depends(get_current_user)):
"""Áp fx_chain lên WAV đầu vào → WAV mới trong PROCESSED_DIR."""
import soundfile as sf
enforce_password_changed(current_user)
src = (req.input_path or "").strip()
if not src:
@@ -1233,6 +1232,8 @@ async def soundfont_render(req: SoundfontRenderRequest, current_user: dict = Dep
Khác FluidSynthWASM (docker): âm render server-side bằng pyfluidsynth, trả
WAV để client play. Dùng cho: preview/keyboard, play MIDI item khi chạy
transport ở môi trường standalone."""
from app.core.render_engine import _find_sf2_path, _find_default_sf2
import soundfile as sf
enforce_password_changed(current_user)
if not req.notes:
raise HTTPException(status_code=400, detail="Chưa có nốt nhạc để render")
@@ -1288,6 +1289,9 @@ def render_project(
current_user: dict = Depends(get_current_user)
):
enforce_password_changed(current_user)
from app.core.render_engine import PythonRenderEngine
import soundfile as sf
import numpy as np
engine = PythonRenderEngine()
# Prevent path traversal: strip directory components; force .wav/.ogg.
safe_name = os.path.basename((req.output_filename or "render_output.wav").replace("\\", "/"))