FIX: Carla sử dụng bridge có midikeyboard
This commit is contained in:
+84
-12
@@ -472,11 +472,15 @@ class PreviewRequest(BaseModel):
|
|||||||
|
|
||||||
@router.post("/open-in-carla")
|
@router.post("/open-in-carla")
|
||||||
async def open_in_carla(req: OpenInCarlaRequest, current_user: dict = Depends(get_current_user)):
|
async def open_in_carla(req: OpenInCarlaRequest, current_user: dict = Depends(get_current_user)):
|
||||||
"""Mở Carla (native GUI host) trên máy hiện tại — Windows desktop mode.
|
"""Mở Carla với VSTi đã chọn — TỰ ĐỘNG load plugin (native GUI + keyboard).
|
||||||
|
|
||||||
Carla là app ngoài do user tự cài (GPL-2.0+ → không bundle/nhúng). App chỉ
|
Cơ chế: sinh file project .carxs (định dạng XML chính thức của Carla —
|
||||||
spawn tiến trình; user chỉnh preset trong GUI rồi Save → .vstpreset → upload
|
`carla.exe [FILE]` nhận project file) chứa node <Plugin><Info><Type>VST3
|
||||||
vào thư viện preset → gán vào track → render engine tải preset tương ứng."""
|
</Type><Binary>...</Binary></Info> → Carla mở lên là plugin đã load sẵn,
|
||||||
|
kèm on-screen MIDI keyboard (PixmapKeyboard) để preview realtime.
|
||||||
|
|
||||||
|
Carla là app ngoài do user tự giải nén (GPL-2.0+ → không bundle/nhúng);
|
||||||
|
app chỉ spawn tiến trình + trao đổi file preset."""
|
||||||
enforce_password_changed(current_user)
|
enforce_password_changed(current_user)
|
||||||
from app.core.runtime import find_carla
|
from app.core.runtime import find_carla
|
||||||
carla = find_carla()
|
carla = find_carla()
|
||||||
@@ -497,15 +501,12 @@ async def open_in_carla(req: OpenInCarlaRequest, current_user: dict = Depends(ge
|
|||||||
plugin_path = plugins[req.plugin_name]
|
plugin_path = plugins[req.plugin_name]
|
||||||
except Exception:
|
except Exception:
|
||||||
plugin_path = ""
|
plugin_path = ""
|
||||||
cmd = [carla]
|
carxs_path = ""
|
||||||
if plugin_path:
|
if plugin_path:
|
||||||
# carla-single: mở thẳng 1 plugin thành app standalone có native GUI
|
carxs_path = _write_carla_project(req.plugin_name or os.path.basename(plugin_path), plugin_path)
|
||||||
single = os.path.join(
|
cmd = [carla]
|
||||||
os.path.dirname(carla),
|
if carxs_path:
|
||||||
"carla-single" + (".exe" if os.name == "nt" else ""),
|
cmd.append(carxs_path)
|
||||||
)
|
|
||||||
if os.path.isfile(single):
|
|
||||||
cmd = [single, plugin_path]
|
|
||||||
try:
|
try:
|
||||||
cwd = os.path.dirname(carla) or None
|
cwd = os.path.dirname(carla) or None
|
||||||
subprocess.Popen(cmd, cwd=cwd, close_fds=os.name != "nt")
|
subprocess.Popen(cmd, cwd=cwd, close_fds=os.name != "nt")
|
||||||
@@ -514,12 +515,83 @@ async def open_in_carla(req: OpenInCarlaRequest, current_user: dict = Depends(ge
|
|||||||
"started": True,
|
"started": True,
|
||||||
"carla_path": carla,
|
"carla_path": carla,
|
||||||
"plugin_path": plugin_path,
|
"plugin_path": plugin_path,
|
||||||
|
"project_file": carxs_path,
|
||||||
"cmd": cmd,
|
"cmd": cmd,
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=f"Không mở được Carla: {e}")
|
raise HTTPException(status_code=500, detail=f"Không mở được Carla: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def _write_carla_project(plugin_name: str, plugin_path: str) -> str:
|
||||||
|
"""Sinh file project .carxs cho Carla load sẵn VSTi (chỉ VST3 — VST2 cần
|
||||||
|
uniqueID không đoán được → mở Carla trống để user tự Add Plugin).
|
||||||
|
|
||||||
|
Định dạng theo source Carla (CarlaEngine::saveProjectInternal +
|
||||||
|
CarlaStateSave::dumpToMemoryStream): root <CARLA-PROJECT VERSION='2.5'>,
|
||||||
|
<Plugin><Info><Type>VST3</Type><Binary>path</Binary><Label>..</Label>
|
||||||
|
</Info><Data><Active>Yes</Active><ControlChannel>1</ControlChannel>
|
||||||
|
<Options>0x0</Options></Data></Plugin>."""
|
||||||
|
if not plugin_path or not os.path.exists(plugin_path):
|
||||||
|
return ""
|
||||||
|
low = plugin_path.lower()
|
||||||
|
is_vst3 = low.endswith(".vst3") or low.endswith(".vst3/") or "\\" in plugin_path and plugin_path.rstrip("\\/").lower().endswith(".vst3")
|
||||||
|
if not is_vst3:
|
||||||
|
# VST2 (.dll/.so ngoài .vst3): không auto-load được tin cậy → Carla trống
|
||||||
|
return ""
|
||||||
|
from xml.sax.saxutils import escape
|
||||||
|
name = escape(plugin_name or os.path.splitext(os.path.basename(plugin_path))[0])
|
||||||
|
binary = escape(plugin_path)
|
||||||
|
xml = (
|
||||||
|
"<?xml version='1.0' encoding='UTF-8'?>\n"
|
||||||
|
"<!DOCTYPE CARLA-PROJECT>\n"
|
||||||
|
f"<CARLA-PROJECT VERSION='2.5'>\n"
|
||||||
|
" <EngineSettings>\n"
|
||||||
|
" <ForceStereo>false</ForceStereo>\n"
|
||||||
|
" <PreferPluginBridges>false</PreferPluginBridges>\n"
|
||||||
|
" <PreferUiBridges>false</PreferUiBridges>\n"
|
||||||
|
" <UIsAlwaysOnTop>false</UIsAlwaysOnTop>\n"
|
||||||
|
" <MaxParameters>100</MaxParameters>\n"
|
||||||
|
" <UIBridgesTimeout>10000</UIBridgesTimeout>\n"
|
||||||
|
" </EngineSettings>\n"
|
||||||
|
" <Plugin>\n"
|
||||||
|
" <Info>\n"
|
||||||
|
f" <Type>VST3</Type>\n"
|
||||||
|
f" <Name>{name}</Name>\n"
|
||||||
|
f" <Binary>{binary}</Binary>\n"
|
||||||
|
f" <Label>{name}</Label>\n"
|
||||||
|
" </Info>\n"
|
||||||
|
" <Data>\n"
|
||||||
|
" <Active>Yes</Active>\n"
|
||||||
|
" <ControlChannel>1</ControlChannel>\n"
|
||||||
|
" <Options>0x0</Options>\n"
|
||||||
|
" </Data>\n"
|
||||||
|
" </Plugin>\n"
|
||||||
|
"</CARLA-PROJECT>\n"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
proj_dir = os.path.join(settings.STORAGE_DIR, "carla_projects")
|
||||||
|
os.makedirs(proj_dir, exist_ok=True)
|
||||||
|
# Dọn project cũ (quá 1 ngày) — tránh rác
|
||||||
|
try:
|
||||||
|
now = _time.time()
|
||||||
|
for f in os.listdir(proj_dir):
|
||||||
|
fp = os.path.join(proj_dir, f)
|
||||||
|
try:
|
||||||
|
if os.path.isfile(fp) and now - os.path.getmtime(fp) > 86400:
|
||||||
|
os.remove(fp)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
safe = "".join(c for c in plugin_name if c.isalnum() or c in " _-")[:40].strip() or "plugin"
|
||||||
|
carxs = os.path.join(proj_dir, f"{safe}_{uuid.uuid4().hex[:8]}.carxs")
|
||||||
|
with open(carxs, "w", encoding="utf-8") as fh:
|
||||||
|
fh.write(xml)
|
||||||
|
return carxs
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
@router.post("/preview")
|
@router.post("/preview")
|
||||||
async def preview_instrument(req: PreviewRequest):
|
async def preview_instrument(req: PreviewRequest):
|
||||||
"""Quick-render preview VSTi (âm thật, cùng code path với export)."""
|
"""Quick-render preview VSTi (âm thật, cùng code path với export)."""
|
||||||
|
|||||||
+46
-1
@@ -5294,6 +5294,8 @@ const PluginManagerModal = ({ isOpen, onClose, pluginsData, onInsertInstrument }
|
|||||||
const [pmSfLoading, setPmSfLoading] = React.useState({});
|
const [pmSfLoading, setPmSfLoading] = React.useState({});
|
||||||
// Force re-render sau khi định vị Carla (capabilities đổi)
|
// Force re-render sau khi định vị Carla (capabilities đổi)
|
||||||
const [pmCarlaVersion, setPmCarlaVersion] = React.useState(0);
|
const [pmCarlaVersion, setPmCarlaVersion] = React.useState(0);
|
||||||
|
// Khai báo trực tiếp thư mục chứa carla.exe (nhập tay, không cần picker)
|
||||||
|
const [pmCarlaPathInput, setPmCarlaPathInput] = React.useState('');
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
window.SonicAPI.listPlugins()
|
window.SonicAPI.listPlugins()
|
||||||
@@ -5416,6 +5418,24 @@ const PluginManagerModal = ({ isOpen, onClose, pluginsData, onInsertInstrument }
|
|||||||
}
|
}
|
||||||
} catch (err) { showToast('Lỗi định vị Carla: ' + (err.message || err), 'error'); }
|
} catch (err) { showToast('Lỗi định vị Carla: ' + (err.message || err), 'error'); }
|
||||||
};
|
};
|
||||||
|
// Khai báo thư mục Carla trực tiếp (nhập tay) — tương tự Định vị nhưng
|
||||||
|
// không cần hộp thoại chọn thư mục.
|
||||||
|
const saveCarlaInput = async () => {
|
||||||
|
const p = (pmCarlaPathInput || '').trim();
|
||||||
|
if (!p) { showToast('Nhập đường dẫn thư mục chứa carla.exe', 'warning'); return; }
|
||||||
|
try {
|
||||||
|
const r = await window.SonicAPI.setCarlaPath(p);
|
||||||
|
if (r && r.success && r.carla_path) {
|
||||||
|
window.SonicRuntime.capabilities = r;
|
||||||
|
document.documentElement.dataset.carla = r.features && r.features.carla_local ? '1' : '0';
|
||||||
|
setPmCarlaVersion(v => v + 1);
|
||||||
|
setPmCarlaPathInput('');
|
||||||
|
showToast('Đã lưu Carla: ' + r.carla_path, 'success');
|
||||||
|
} else {
|
||||||
|
showToast('Không tìm thấy carla.exe trong đường dẫn đã nhập', 'error');
|
||||||
|
}
|
||||||
|
} catch (err) { showToast('Lỗi: ' + (err.message || err), 'error'); }
|
||||||
|
};
|
||||||
// Lưu dirs (user override) + scan cả 2 thư mục → refresh list + catalog.
|
// Lưu dirs (user override) + scan cả 2 thư mục → refresh list + catalog.
|
||||||
const saveAndScanDirs = async () => {
|
const saveAndScanDirs = async () => {
|
||||||
setPmScanning(true);
|
setPmScanning(true);
|
||||||
@@ -5667,6 +5687,20 @@ const PluginManagerModal = ({ isOpen, onClose, pluginsData, onInsertInstrument }
|
|||||||
onClick: () => { window.SonicAPI.openInCarla().then(function (r) { if (r && r.success) showToast('Đã mở Carla', 'success'); }).catch(function (err) { showToast('Lỗi mở Carla: ' + (err.message || err), 'error'); }); },
|
onClick: () => { window.SonicAPI.openInCarla().then(function (r) { if (r && r.success) showToast('Đã mở Carla', 'success'); }).catch(function (err) { showToast('Lỗi mở Carla: ' + (err.message || err), 'error'); }); },
|
||||||
className: 'px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-teal-300 text-xs font-semibold rounded transition flex items-center gap-1'
|
className: 'px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-teal-300 text-xs font-semibold rounded transition flex items-center gap-1'
|
||||||
}, React.createElement('i', { 'data-lucide': 'play', className: 'w-3 h-3' }), 'Mở Carla')
|
}, React.createElement('i', { 'data-lucide': 'play', className: 'w-3 h-3' }), 'Mở Carla')
|
||||||
|
),
|
||||||
|
React.createElement('div', { className: 'flex gap-2 mt-2' },
|
||||||
|
React.createElement('input', {
|
||||||
|
type: 'text',
|
||||||
|
placeholder: 'Hoặc nhập thư mục chứa carla.exe (VD: D:/Tools/Carla)',
|
||||||
|
value: pmCarlaPathInput,
|
||||||
|
onChange: e => setPmCarlaPathInput(e.target.value),
|
||||||
|
onKeyDown: e => { if (e.key === 'Enter') saveCarlaInput(); },
|
||||||
|
className: 'flex-1 bg-zinc-800 border border-zinc-700 rounded px-2 py-1.5 text-xs text-zinc-300 focus:outline-none focus:border-teal-600'
|
||||||
|
}),
|
||||||
|
React.createElement('button', {
|
||||||
|
onClick: saveCarlaInput,
|
||||||
|
className: 'px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-teal-300 text-xs font-semibold rounded transition shrink-0'
|
||||||
|
}, 'Lưu')
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
// Plugin directories section (folder picker + save + scan)
|
// Plugin directories section (folder picker + save + scan)
|
||||||
@@ -30099,7 +30133,18 @@ STRICT CONSTRAINTS:
|
|||||||
className: "flex items-stretch"
|
className: "flex items-stretch"
|
||||||
},
|
},
|
||||||
/*#__PURE__*/React.createElement("button", {
|
/*#__PURE__*/React.createElement("button", {
|
||||||
onClick: () => { setInstrumentDropdownTrackId(null); setInstrumentDropdownBtnRect(null); setTrackInstrumentWithUndo(instrumentDropdownTrackId, v.id, v.name || v.id); },
|
onClick: () => {
|
||||||
|
setInstrumentDropdownTrackId(null);
|
||||||
|
setInstrumentDropdownBtnRect(null);
|
||||||
|
setTrackInstrumentWithUndo(instrumentDropdownTrackId, v.id, v.name || v.id);
|
||||||
|
// TỰ ĐỘNG mở Carla với VSTi vừa chọn (desktop + Carla local) —
|
||||||
|
// Carla load sẵn plugin, native GUI + keyboard ảo để preview realtime.
|
||||||
|
if (window.SonicRuntime && window.SonicRuntime.capabilities && window.SonicRuntime.capabilities.features && window.SonicRuntime.capabilities.features.carla_local) {
|
||||||
|
window.SonicAPI.openInCarla(v.id).then(function (r) {
|
||||||
|
if (r && r.success) showToast('Đã mở Carla với ' + (v.name || v.id) + ' — chọn preset, bấm keyboard để preview', 'success');
|
||||||
|
}).catch(function (err) { showToast('Lỗi mở Carla: ' + (err.message || err), 'error'); });
|
||||||
|
}
|
||||||
|
},
|
||||||
className: "flex-1 min-w-0 text-left px-3 py-1.5 text-xs bg-zinc-800 hover:bg-violet-800 text-zinc-200 flex items-center justify-between"
|
className: "flex-1 min-w-0 text-left px-3 py-1.5 text-xs bg-zinc-800 hover:bg-violet-800 text-zinc-200 flex items-center justify-between"
|
||||||
}, /*#__PURE__*/React.createElement("span", { className: "truncate" }, v.name || v.id), /*#__PURE__*/React.createElement("span", { className: "text-[10px] text-cyan-400 shrink-0 ml-1" }, v.type || "VST")),
|
}, /*#__PURE__*/React.createElement("span", { className: "truncate" }, v.name || v.id), /*#__PURE__*/React.createElement("span", { className: "text-[10px] text-cyan-400 shrink-0 ml-1" }, v.type || "VST")),
|
||||||
window.SonicRuntime && window.SonicRuntime.capabilities && window.SonicRuntime.capabilities.features && window.SonicRuntime.capabilities.features.carla_local ? /*#__PURE__*/React.createElement("button", {
|
window.SonicRuntime && window.SonicRuntime.capabilities && window.SonicRuntime.capabilities.features && window.SonicRuntime.capabilities.features.carla_local ? /*#__PURE__*/React.createElement("button", {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+19
-8
@@ -93,11 +93,13 @@ xem §8).
|
|||||||
|
|
||||||
1. Tải: <https://github.com/falkTX/Carla/releases> (bản `win64`).
|
1. Tải: <https://github.com/falkTX/Carla/releases> (bản `win64`).
|
||||||
2. Giải nén ra bất kỳ đâu (VD `D:\Tools\Carla\`, chứa `carla.exe`).
|
2. Giải nén ra bất kỳ đâu (VD `D:\Tools\Carla\`, chứa `carla.exe`).
|
||||||
3. Mở app → **Plugin Manager** → section **"Carla Bridge (VSTi native GUI)"** →
|
3. Khai báo 1 lần (2 cách, tùy chọn 1):
|
||||||
nút **"Định vị Carla..."** → chọn thư mục chứa `carla.exe` (hoặc chính file
|
- **Nút bấm**: Plugin Manager → section **"Carla Bridge (VSTi native GUI)"** →
|
||||||
`carla.exe`).
|
**"Định vị Carla..."** → chọn thư mục chứa `carla.exe` (hoặc chính file).
|
||||||
|
- **Nhập tay**: ô text trong section đó → nhập `D:/Tools/Carla` → **Lưu**.
|
||||||
4. App lưu vào `storage/carla_path.json` → cache detect bị xóa → nút
|
4. App lưu vào `storage/carla_path.json` → cache detect bị xóa → nút
|
||||||
**"Carla Bridge"** hiện trong dropdown nút Synth.
|
**"Carla Bridge"** hiện trong dropdown nút Synth + **tự động mở Carla**
|
||||||
|
khi chọn VSTi.
|
||||||
|
|
||||||
### 3.1 Thứ tự phát hiện `carla_local`
|
### 3.1 Thứ tự phát hiện `carla_local`
|
||||||
|
|
||||||
@@ -119,10 +121,19 @@ body: { "carla_path": "D:/Tools/Carla" } (thư mục HOẶC file exe)
|
|||||||
|
|
||||||
## 4. Luồng sử dụng end-to-end (Windows)
|
## 4. Luồng sử dụng end-to-end (Windows)
|
||||||
|
|
||||||
1. **Nút Synth** (track strip / btnSynth) → chọn **"🎛 Carla Bridge (mở Carla.exe)"**
|
1. **Nút Synth** → chọn **VSTi** trong danh sách → app **TỰ ĐỘNG gọi Carla**:
|
||||||
→ app spawn `carla.exe` (ưu tiên `carla-single <plugin>` nếu chọn kèm plugin).
|
sinh file project `.carxs` (định dạng XML chính thức của Carla —
|
||||||
2. Trong Carla: **Add Plugin** → chọn VSTi (Kontakt, Nexus, Vital...) → native GUI
|
`carla.exe [FILE]` nhận project file) chứa node
|
||||||
hiện ra → chỉnh âm, chọn bank/preset của plugin.
|
`<Plugin><Info><Type>VST3</Type><Binary>path</Binary>...</Info></Plugin>`
|
||||||
|
→ `carla.exe <project.carxs>` → Carla mở lên **plugin đã load sẵn** kèm
|
||||||
|
**on-screen MIDI keyboard** (`PixmapKeyboard`).
|
||||||
|
- VST3 (file `.vst3` hoặc folder `X.vst3` Windows): auto-load qua `.carxs`.
|
||||||
|
- VST2 (`.dll`/`.so` ngoài `.vst3`): không auto-load tin cậy (cần uniqueID)
|
||||||
|
→ mở Carla trống để user **Add Plugin** thủ công.
|
||||||
|
- Project `.carxs` nằm `{STORAGE_DIR}/carla_projects/`, tự dọn sau 1 ngày.
|
||||||
|
2. Trong Carla: native GUI của VSTi hiện ra → **chọn instrument/preset** của
|
||||||
|
plugin → **bật ARM** trên track trong app (tùy chọn) → **bấm phím trên
|
||||||
|
keyboard ảo của Carla** để preview realtime (âm thật qua audio device).
|
||||||
3. **Save preset** bằng nút của CHÍNH plugin (không dùng project save của Carla)
|
3. **Save preset** bằng nút của CHÍNH plugin (không dùng project save của Carla)
|
||||||
→ file `.vstpreset`.
|
→ file `.vstpreset`.
|
||||||
4. Trong app: dropdown Synth → **"⬆ Upload preset (từ Carla...)"** → chọn file
|
4. Trong app: dropdown Synth → **"⬆ Upload preset (từ Carla...)"** → chọn file
|
||||||
|
|||||||
@@ -3036,3 +3036,8 @@
|
|||||||
- **Tóm tắt thay đổi:** (1) Runtime tự phát hiện môi trường `app/core/runtime.py` (desktop Windows / docker headless; override `SF_RUNTIME`/`SF_DOCKER`) + `GET /api/v1/system/capabilities` (public) — frontend bật/tắt tính năng theo môi trường. (2) Carla Bridge: mục "🎛 Carla Bridge (mở Carla.exe)" trong dropdown nút Synth (chỉ hiện khi desktop + có Carla local) → spawn `carla.exe` qua `POST /api/v1/plugins/open-in-carla`; vì bản Windows là zip portable (không installer, không PATH) → Plugin Manager thêm section "Carla Bridge" + nút "Định vị Carla..." (`POST /api/v1/system/carla-path`, lưu `storage/carla_path.json` ưu tiên cao nhất; kèm registry + quét nông Downloads/Desktop/Documents giới hạn độ sâu). (3) Thư viện preset `app/api/v1/presets.py` (storage/presets: list/upload/download/delete, chống path traversal) + `apply_preset_to_plugin()` trong `vst_engine.py` (preset_data base64 → preset_id → preset_path) → render_engine nạp preset trước khi render VST3 → âm render = âm đã chỉnh trong Carla. (4) `POST /api/v1/plugins/preview` — quick-render preview (cùng code path pedalboard với export → âm thật). (5) Plugin Manager: bấm soundfont expand → liệt kê instrument (bank/program/name) + nút "Chèn vào Synth" gán vào track đang chọn. (6) `config.py` default VST_DIR/SOUNDFONT_DIR theo platform + `PRESET_DIR`; docker-compose `SF_DOCKER=1`; service `runtime.js` (capabilities lúc boot, cache preset) + api.js methods mới (getCapabilities/setCarlaPath/openInCarla/previewInstrument/listPresets/uploadPreset/deletePreset).
|
- **Tóm tắt thay đổi:** (1) Runtime tự phát hiện môi trường `app/core/runtime.py` (desktop Windows / docker headless; override `SF_RUNTIME`/`SF_DOCKER`) + `GET /api/v1/system/capabilities` (public) — frontend bật/tắt tính năng theo môi trường. (2) Carla Bridge: mục "🎛 Carla Bridge (mở Carla.exe)" trong dropdown nút Synth (chỉ hiện khi desktop + có Carla local) → spawn `carla.exe` qua `POST /api/v1/plugins/open-in-carla`; vì bản Windows là zip portable (không installer, không PATH) → Plugin Manager thêm section "Carla Bridge" + nút "Định vị Carla..." (`POST /api/v1/system/carla-path`, lưu `storage/carla_path.json` ưu tiên cao nhất; kèm registry + quét nông Downloads/Desktop/Documents giới hạn độ sâu). (3) Thư viện preset `app/api/v1/presets.py` (storage/presets: list/upload/download/delete, chống path traversal) + `apply_preset_to_plugin()` trong `vst_engine.py` (preset_data base64 → preset_id → preset_path) → render_engine nạp preset trước khi render VST3 → âm render = âm đã chỉnh trong Carla. (4) `POST /api/v1/plugins/preview` — quick-render preview (cùng code path pedalboard với export → âm thật). (5) Plugin Manager: bấm soundfont expand → liệt kê instrument (bank/program/name) + nút "Chèn vào Synth" gán vào track đang chọn. (6) `config.py` default VST_DIR/SOUNDFONT_DIR theo platform + `PRESET_DIR`; docker-compose `SF_DOCKER=1`; service `runtime.js` (capabilities lúc boot, cache preset) + api.js methods mới (getCapabilities/setCarlaPath/openInCarla/previewInstrument/listPresets/uploadPreset/deletePreset).
|
||||||
- **Các file ảnh hưởng:** `app/core/runtime.py` (mới), `app/api/v1/system.py` (mới), `app/api/v1/presets.py` (mới), `app/api/v1/plugins.py`, `app/core/vst_engine.py`, `app/core/render_engine.py`, `app/config.py`, `app/main.py`, `app/static/js/services/runtime.js` (mới), `app/static/js/services/api.js`, `app/static/js/app.jsx`, `app/static/js/app.precompiled.js` (rebuild), `app/templates/index.html`, `.env.example`, `docker-compose.prod.yml`, `md/52_CARLA_BRIDGE.md` (mới), `wiki.md`
|
- **Các file ảnh hưởng:** `app/core/runtime.py` (mới), `app/api/v1/system.py` (mới), `app/api/v1/presets.py` (mới), `app/api/v1/plugins.py`, `app/core/vst_engine.py`, `app/core/render_engine.py`, `app/config.py`, `app/main.py`, `app/static/js/services/runtime.js` (mới), `app/static/js/services/api.js`, `app/static/js/app.jsx`, `app/static/js/app.precompiled.js` (rebuild), `app/templates/index.html`, `.env.example`, `docker-compose.prod.yml`, `md/52_CARLA_BRIDGE.md` (mới), `wiki.md`
|
||||||
- **Ghi chú/Test (nếu có):** `pytest`: 86 passed, 7 skipped. Test API: capabilities 200; preset CRUD + chặn path traversal; carla-path (thư mục/exe → resolve exe, invalid → 400); open-in-carla → 409 kèm hướng dẫn khi chưa có Carla; preview → 501 khi thiếu pedalboard. Rebuild bundle BUILD OK. Lưu ý: pedalboard 0.10+ đã bỏ VST2 (chỉ preset VST3 `.vstpreset` round-trip); render cùng sample rate với Carla để preview = export; Carla GPL-2.0+ → không bundle/nhúng, chỉ spawn tiến trình ngoài.
|
- **Ghi chú/Test (nếu có):** `pytest`: 86 passed, 7 skipped. Test API: capabilities 200; preset CRUD + chặn path traversal; carla-path (thư mục/exe → resolve exe, invalid → 400); open-in-carla → 409 kèm hướng dẫn khi chưa có Carla; preview → 501 khi thiếu pedalboard. Rebuild bundle BUILD OK. Lưu ý: pedalboard 0.10+ đã bỏ VST2 (chỉ preset VST3 `.vstpreset` round-trip); render cùng sample rate với Carla để preview = export; Carla GPL-2.0+ → không bundle/nhúng, chỉ spawn tiến trình ngoài.
|
||||||
|
|
||||||
|
### [2026-08-09] Task: Carla Bridge — auto-load VSTi qua .carxs + khai báo thư mục Carla
|
||||||
|
- **Tóm tắt thay đổi:** (1) `POST /api/v1/plugins/open-in-carla` viết lại: sinh file project `.carxs` (định dạng XML chính thức từ source Carla — `carla.exe [FILE]` nhận project file) chứa `<Plugin><Info><Type>VST3</Type><Binary>path</Binary>...` → Carla mở lên **plugin đã load sẵn** kèm on-screen MIDI keyboard (PixmapKeyboard) để preview realtime. VST3 (file `.vst3`/folder `X.vst3` Windows) auto-load; VST2 (`.dll`/`.so` ngoài `.vst3`) → Carla trống để Add Plugin thủ công (cần uniqueID). Project lưu `{STORAGE_DIR}/carla_projects/`, tự dọn sau 1 ngày. (2) Frontend: chọn VSTi trong dropdown Synth → **tự động** `openInCarla(v.id)` (chỉ khi desktop + carla_local) — nhấn nút Synth, thêm VSTi là Carla tự gọi và load luôn VSTi đó. (3) Plugin Manager section "Carla Bridge": thêm **ô nhập tay thư mục chứa carla.exe + nút Lưu** (bên cạnh "Định vị Carla..." dùng picker) — khai báo 1 lần là xong.
|
||||||
|
- **Các file ảnh hưởng:** `app/api/v1/plugins.py` (+ `_write_carla_project`), `app/static/js/app.jsx`, `app/static/js/app.precompiled.js` (rebuild), `md/52_CARLA_BRIDGE.md`, `wiki.md`
|
||||||
|
- **Ghi chú/Test (nếu có):** `pytest`: 86 passed, 7 skipped. Test `_write_carla_project`: VST3 file → carxs hợp lệ (CARLA-PROJECT VERSION='2.5', Type VST3, Binary, Active Yes, ControlChannel 1); Windows VST3 folder → OK; VST2 .dll → '' (không sinh); XML escape tên đặc biệt (A&B <C>) parse OK. End-to-end endpoint với Carla giả: 200, project_file sinh + chứa Binary, cmd = [carla.exe, carxs]. Rebuild bundle BUILD OK (node qua PATH nvm v24).
|
||||||
|
|||||||
Reference in New Issue
Block a user