diff --git a/engine.spec b/engine.spec
index 0f8c36b..c7750a6 100644
--- a/engine.spec
+++ b/engine.spec
@@ -137,6 +137,13 @@ _excludes = [
'matplotlib', 'pandas', 'IPython', 'jupyter', 'pytest', 'setuptools',
# keo vao nham boi hooks_contrib (app khong import bao gio)
'PIL', 'Pillow', 'cairosvg', 'zstandard', 'imageio',
+ # P0 (2026-09): torch/torchvision (~1.5GB) bi keo vao boi
+ # scipy._lib.array_api_compat.common._helpers ('import torch' trong try/except).
+ # App KHONG import torch o bat ky dau - da kiem chung: block import torch ->
+ # scipy.signal + moi module app.core import van OK. Loai ca cay AI nang.
+ 'torch', 'torchvision', 'torchaudio', 'triton',
+ 'sympy', 'networkx', 'tqdm', 'einops', 'optree',
+ 'tensorflow', 'keras', 'jax', 'jaxlib',
]
a = Analysis(
diff --git a/md/BASELINE_P0.md b/md/BASELINE_P0.md
new file mode 100644
index 0000000..5a75b68
--- /dev/null
+++ b/md/BASELINE_P0.md
@@ -0,0 +1,46 @@
+# Baseline P0 - SonicForgeStudio (Python engine)
+
+Sinh boi `tools/p0_baseline.py` ngay 2026-10-12 21:00. Dung lam moc so sanh khi port Rust.
+
+```json
+{
+ "date": "2026-10-12 21:00",
+ "bundle": {
+ "dist/daw_engine": {
+ "mb": 146.6,
+ "files": 403
+ },
+ "src-tauri/resources/daw_engine": {
+ "mb": 1841.8,
+ "files": 4187
+ },
+ "has:torch": false,
+ "has:torchvision": false,
+ "has:sympy": false,
+ "has:networkx": false,
+ "has:tensorflow": false
+ },
+ "cold_start": {
+ "startup_ms": 5700.4,
+ "port": 8000,
+ "rss_mb": 68.3
+ },
+ "import": {
+ "import_app_main_ms": 730.9,
+ "subprocess_wall_ms": 822.0
+ },
+ "render_mixdown": {
+ "tracks": 4,
+ "seconds_each": 30.0,
+ "sr": 48000,
+ "runs_ms": [
+ 1808.2,
+ 515.9,
+ 529.0
+ ],
+ "min_ms": 515.9,
+ "median_ms": 529.0,
+ "realtime_factor": 0.0172
+ }
+}
+```
diff --git a/md/RUST_PORT_PLAN.md b/md/RUST_PORT_PLAN.md
index eb07a51..318318b 100644
--- a/md/RUST_PORT_PLAN.md
+++ b/md/RUST_PORT_PLAN.md
@@ -128,7 +128,7 @@ Chia hệ thống thành 5 tầng, mỗi tầng có "hệ số port" khác nhau:
### 4.1 Việc tối ưu "miễn phí", làm được NGAY (không cần port)
-1. **Xoá `torch` + `torchvision` khỏi bundle** (không được import ở đâu trong source) → −1,5 GB, giảm thời gian build/install ngay lập tức. Chỉ cần loại khỏi môi trường build / `engine.spec` và kiểm tra lại bằng test suite.
+1. ✅ **Đã làm (P0)**: xoá `torch` + `torchvision` khỏi bundle → **1.842 MB → 146,6 MB**, không cần port. Chỉ cần thêm `excludes` trong `engine.spec`; đã kiểm chứng app không import torch (`tools/p0_baseline.py`, `md/BASELINE_P0.md`).
2. Vá `app/static/js/app.jsx` 37k dòng bằng bundler thật (esbuild/vite) thay vì Babel 1-file → `app.precompiled.js` nhỏ hơn, build nhanh hơn, có source map.
3. Tách `app.jsx` thành module (đang là 1 component khổng lồ) → giảm rủi ro regression, tiền đề bắt buộc nếu sau này port UI.
4. Thay relay `WebView → WS → Python → SHM → C++` bằng **Tauri IPC trực tiếp tới Rust → SHM** cho phần control: bỏ 1 hop mạng loopback, bỏ khả năng treo do uvicorn, giảm jitter điều khiển. Đây là bước rẻ nhất trong hướng T4.
@@ -170,11 +170,13 @@ Chia hệ thống thành 5 tầng, mỗi tầng có "hệ số port" khác nhau:
## 5. Kế hoạch triển khai theo giai đoạn
-### P0 — Dọn dẹp trước khi port (1-3 ngày, không rủi ro)
-- [ ] Xác nhận `torch`/`torchvision` không dùng → loại khỏi build, đo lại bundle (mục tiêu <400 MB ngay).
-- [ ] Chốt "hợp đồng API": export OpenAPI JSON + danh sách message WS (`set_chain`, `set_param`, `flush`, `started`, `stats`…) làm đặc tả cho bản Rust.
-- [ ] Dựng **golden test**: bộ WAV/JSON mẫu cho từng endpoint DSP; chạy trên Python hiện tại, lưu hash → đây là tiêu chuẩn đúng cho Rust.
-- [ ] Đo baseline: thời gian start, RAM, thời gian scan plugin, thời gian render (ghi vào `md/`).
+### P0 — Dọn dẹp trước khi port — ✅ ĐÃ XONG (2026-10-12)
+- [x] Xác nhận `torch`/`torchvision` không dùng (block import torch → `scipy.signal` + mọi `app.core.*` import OK) → thêm exclude vào `engine.spec` (torch/torchvision/torchaudio/triton/sympy/networkx/tqdm/einops/optree/tensorflow/keras/jax).
+ **Kết quả: bundle engine 1.841,8 MB / 4.187 file → 146,6 MB / 403 file (−92%)**, đã thay cả bản ship trong `src-tauri/resources/daw_engine`.
+- [x] Chốt "hợp đồng API": `md/api_contract/openapi.json` (86 path / 95 operation / 53 schema) + `md/api_contract/ws_protocol.md` (WS realtime + worklet).
+- [x] Golden test: `tools/golden_conformance.py` → `md/api_contract/golden/manifest.json`, 10 case DSP (zero-crossing, micro-crossfade, peak/RMS waveform, cut/loop, multitrack mixdown, analyze, LUFS/true-peak/mono-side). Tái lập ổn định (`--check` = 0 mismatch giữa các tiến trình); mixdown hash theo mẫu audio vì header WAV không ổn định.
+- [x] Đo baseline: `tools/p0_baseline.py` → `md/BASELINE_P0.md` (cold start **5.700 ms**, RSS **68 MB**, import `app.main` 731 ms, mixdown 4 track × 30 s: min **516 ms** = 0,017× realtime).
+
### P1 — Rust service khung, chạy song song (2-4 tuần)
- [ ] Crate `engine/` (axum): router, auth, health, `/api/v1/system`, `/tasks`.
diff --git a/md/api_contract/golden/manifest.json b/md/api_contract/golden/manifest.json
new file mode 100644
index 0000000..a80ab46
--- /dev/null
+++ b/md/api_contract/golden/manifest.json
@@ -0,0 +1,22 @@
+{
+ "cases": {
+ "analyzer.analyze_audio": "20fb044c076bc180810a92edb0cf247f62b2dea5e16f769208b634deeb6f36ef",
+ "audio_editor.cut_and_loop_segment": "16848af659a11994904d4d08d3b91ba58224ce0f057cc950b645c0c3a8417226",
+ "audio_editor.mix_multitrack_session": "46dcd4da24a0765cc78707eb4079fc289cc1e23bc741ecd12309683c59ce15bc",
+ "dsp_utils.apply_micro_crossfade": "c43756937a0e5a2e285c9af1d8d848041033fbb3396057d8ca3bfc513bdca758",
+ "dsp_utils.find_zero_crossing": "8ee6913ca5066eb6b4949040e6cb0f3d47589802de67b886d905b2ea79049dba",
+ "dsp_utils.generate_peak_waveform": "1a629e10dd4207bb8faafee747eac4955b05d3e624236e0a13bb51bd0f2a5f82",
+ "dsp_utils.generate_rms_waveform": "6ecf9662cf77a6a3e8b193dfef9b32d32bdff14c355a8915cd8f81ee44b80f5b",
+ "loudness.integrated_lufs": "0914a33306e2832bba22f7bfed82a8c30c785752b3a55dab3bb5151354709972",
+ "loudness.mono_side_ratio": "6ab9135f88cfd6092ccd2c3f0eb3285debd7227f9675b7e8a6cfe86b473b3c0c",
+ "loudness.true_peak_db": "4726ed91ff71f4c2fd9f66bef790916cb7574bd715598af0ff146fa582520e56"
+ },
+ "env": {
+ "numpy": "1.26.4",
+ "python": "3.13.7",
+ "sample_rate": 48000,
+ "scipy": "1.17.1"
+ },
+ "generated_by": "tools/golden_conformance.py",
+ "schema": 1
+}
\ No newline at end of file
diff --git a/md/api_contract/openapi.json b/md/api_contract/openapi.json
new file mode 100644
index 0000000..b7256e7
--- /dev/null
+++ b/md/api_contract/openapi.json
@@ -0,0 +1,7233 @@
+{
+ "openapi": "3.1.0",
+ "info": {
+ "title": "ScoreFX API Engine",
+ "version": "0.1.0"
+ },
+ "paths": {
+ "/api/v1/audio/upload": {
+ "post": {
+ "tags": [
+ "audio"
+ ],
+ "summary": "Upload Audio",
+ "operationId": "upload_audio_api_v1_audio_upload_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_upload_audio_api_v1_audio_upload_post"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/audio/edit": {
+ "post": {
+ "tags": [
+ "audio"
+ ],
+ "summary": "Edit Audio",
+ "operationId": "edit_audio_api_v1_audio_edit_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/EditRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/audio/download/{file_id}": {
+ "get": {
+ "tags": [
+ "audio"
+ ],
+ "summary": "Download Audio",
+ "operationId": "download_audio_api_v1_audio_download__file_id__get",
+ "parameters": [
+ {
+ "name": "file_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "File Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/audio/waveform/{file_id}": {
+ "get": {
+ "tags": [
+ "audio"
+ ],
+ "summary": "Get Waveform",
+ "description": "API endpoint vẽ Peak Waveform đồng bộ (Week 2).\nTrả về dữ liệu peak waveform cho hiển thị đồ thị sóng âm trên Frontend.",
+ "operationId": "get_waveform_api_v1_audio_waveform__file_id__get",
+ "parameters": [
+ {
+ "name": "file_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "File Id"
+ }
+ },
+ {
+ "name": "num_peaks",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "maximum": 4000,
+ "minimum": 50,
+ "default": 800,
+ "title": "Num Peaks"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/audio/waveform-rms/{file_id}": {
+ "get": {
+ "tags": [
+ "audio"
+ ],
+ "summary": "Get Waveform Rms",
+ "description": "API endpoint vẽ RMS Waveform (mượt hơn peak).",
+ "operationId": "get_waveform_rms_api_v1_audio_waveform_rms__file_id__get",
+ "parameters": [
+ {
+ "name": "file_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "File Id"
+ }
+ },
+ {
+ "name": "num_points",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "maximum": 4000,
+ "minimum": 50,
+ "default": 800,
+ "title": "Num Points"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/audio/analyze-ai": {
+ "post": {
+ "tags": [
+ "audio"
+ ],
+ "summary": "Analyze Audio With Ai",
+ "description": "API endpoint phân tích cấu trúc khuôn nhạc bằng AI (Week 4).\nGọi OpenAI Compatible API (DeepSeek/Ollama) để phân đoạn bố cục.",
+ "operationId": "analyze_audio_with_ai_api_v1_audio_analyze_ai_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/AIAnalysisRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/audio/export": {
+ "post": {
+ "tags": [
+ "audio"
+ ],
+ "summary": "Export Audio",
+ "description": "API endpoint xuất tệp âm thanh sang nhiều định dạng (WAV/MP3/OGG).",
+ "operationId": "export_audio_api_v1_audio_export_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExportRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/audio/export/midi": {
+ "post": {
+ "tags": [
+ "audio"
+ ],
+ "summary": "Export Midi",
+ "operationId": "export_midi_api_v1_audio_export_midi_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/MidiExportRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/audio/ai-scan": {
+ "post": {
+ "tags": [
+ "audio"
+ ],
+ "summary": "Ai Scan Audio",
+ "description": "17_AI_SCAN.md Feature 1: AI Loop Scan & Automated Marker Labeling.\nUses AIDSPEngine to find optimal recurring loop region with zero-crossing alignment.",
+ "operationId": "ai_scan_audio_api_v1_audio_ai_scan_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/AIScanRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/audio/ai-cut": {
+ "post": {
+ "tags": [
+ "audio"
+ ],
+ "summary": "Ai Cut Audio",
+ "description": "17_AI_SCAN.md Feature 2: Fade-Free AI Cut (Zero-Crossing Aligned Slicing).\nExecutes raw binary sample slice at exact zero-crossing coordinates.",
+ "operationId": "ai_cut_audio_api_v1_audio_ai_cut_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/AICutRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/audio/python-tool": {
+ "post": {
+ "tags": [
+ "audio"
+ ],
+ "summary": "Run Python Dsp Tool",
+ "description": "Non-AI Python DSP Tools endpoint.\nHandles normalize peak, invert phase, swap channels, zero-crossing align, and synth wave generation.",
+ "operationId": "run_python_dsp_tool_api_v1_audio_python_tool_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PythonToolRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/audio/my-files": {
+ "post": {
+ "tags": [
+ "audio"
+ ],
+ "summary": "List User Files",
+ "operationId": "list_user_files_api_v1_audio_my_files_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/MyFilesRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/audio/reference-analyze": {
+ "post": {
+ "tags": [
+ "audio"
+ ],
+ "summary": "Analyze Reference",
+ "description": "Gap 11: A/B reference track - analyze LUFS/dBTP/centroid of a\nreference file (local path from media/browse, or uploaded file_id),\nreturn compare metrics + playback url for the A/B toggle.",
+ "operationId": "analyze_reference_api_v1_audio_reference_analyze_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ReferenceAnalyzeRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/audio/my-files/{file_id}": {
+ "delete": {
+ "tags": [
+ "audio"
+ ],
+ "summary": "Delete User File",
+ "operationId": "delete_user_file_api_v1_audio_my_files__file_id__delete",
+ "parameters": [
+ {
+ "name": "file_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "File Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/audio/tasks/{task_id}": {
+ "get": {
+ "tags": [
+ "tasks"
+ ],
+ "summary": "Get Task Status",
+ "operationId": "get_task_status_api_v1_audio_tasks__task_id__get",
+ "parameters": [
+ {
+ "name": "task_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Task Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/multitrack/mix": {
+ "post": {
+ "tags": [
+ "multitrack"
+ ],
+ "summary": "Mix Multitrack Session",
+ "description": "API endpoint để xử lý hòa âm đa kênh (Multitrack Mixdown).\nNhận cấu hình JSON từ Client và gửi task xuống Celery Worker.",
+ "operationId": "mix_multitrack_session_api_v1_multitrack_mix_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/MultitrackSessionRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/multitrack/process-session": {
+ "post": {
+ "tags": [
+ "multitrack"
+ ],
+ "summary": "Process Session",
+ "description": "API endpoint để xử lý toàn bộ session với nhiều tracks và clips.\nXử lý từng clip, sau đó hòa âm tất cả tracks lại với nhau.",
+ "operationId": "process_session_api_v1_multitrack_process_session_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/MultitrackSessionRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/auth/login": {
+ "post": {
+ "tags": [
+ "auth"
+ ],
+ "summary": "Login",
+ "operationId": "login_api_v1_auth_login_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LoginRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/auth/local": {
+ "post": {
+ "tags": [
+ "auth"
+ ],
+ "summary": "Local Login",
+ "description": "Auto-login standalone: chỉ cho phép từ loopback (máy chạy engine).\nClient LAN nhận 403 → buộc đăng nhập mật khẩu qua /login.",
+ "operationId": "local_login_api_v1_auth_local_post",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/auth/register": {
+ "post": {
+ "tags": [
+ "auth"
+ ],
+ "summary": "Register",
+ "operationId": "register_api_v1_auth_register_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RegisterRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/auth/change-password": {
+ "post": {
+ "tags": [
+ "auth"
+ ],
+ "summary": "Change Password",
+ "operationId": "change_password_api_v1_auth_change_password_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ChangePasswordRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/auth/profile": {
+ "get": {
+ "tags": [
+ "auth"
+ ],
+ "summary": "Get Profile",
+ "operationId": "get_profile_api_v1_auth_profile_get",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/auth/first-time": {
+ "get": {
+ "tags": [
+ "auth"
+ ],
+ "summary": "Auth First Time",
+ "operationId": "auth_first_time_api_v1_auth_first_time_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/admin/users": {
+ "get": {
+ "tags": [
+ "admin"
+ ],
+ "summary": "List Users",
+ "operationId": "list_users_api_v1_admin_users_get",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "tags": [
+ "admin"
+ ],
+ "summary": "Create User",
+ "operationId": "create_user_api_v1_admin_users_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateUserRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/admin/users/{user_id}/role": {
+ "put": {
+ "tags": [
+ "admin"
+ ],
+ "summary": "Update User Role",
+ "operationId": "update_user_role_api_v1_admin_users__user_id__role_put",
+ "parameters": [
+ {
+ "name": "user_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "User Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserRoleRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/admin/quotas/{user_id}": {
+ "put": {
+ "tags": [
+ "admin"
+ ],
+ "summary": "Update User Quota",
+ "operationId": "update_user_quota_api_v1_admin_quotas__user_id__put",
+ "parameters": [
+ {
+ "name": "user_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "User Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateUserQuotaRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/admin/users/{user_id}": {
+ "delete": {
+ "tags": [
+ "admin"
+ ],
+ "summary": "Delete User",
+ "operationId": "delete_user_api_v1_admin_users__user_id__delete",
+ "parameters": [
+ {
+ "name": "user_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "User Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/projects/temp": {
+ "post": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "Save Temp Project",
+ "operationId": "save_temp_project_api_v1_projects_temp_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SaveTempProjectRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "get": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "Get Temp Project",
+ "operationId": "get_temp_project_api_v1_projects_temp_get",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/projects/temp/revision": {
+ "get": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "Temp Project Revision",
+ "description": "Revision nhẹ của bản temp: updated_at + client_id — frontend poll 2-3s\nđể realtime sync giữa LAN browser và Tauri standalone (không tải full\ndata_json mỗi lần). client_id = client ghi bản cuối → client khác bỏ qua\nbản do CHÍNH NÓ ghi (chống ping-pong).",
+ "operationId": "temp_project_revision_api_v1_projects_temp_revision_get",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/projects/save-to-disk": {
+ "post": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "Save Project To Disk",
+ "description": "Ctrl-S trên desktop: lưu project ra THƯ MỤC CỦA HỆ ĐIỀU HÀNH\n(Documents/SonicForgeDAW/Projects — bản desktop). Docker/headless KHÔNG\ndùng endpoint này (frontend lưu Cloud). Auth optional — desktop có thể\nchưa login.",
+ "operationId": "save_project_to_disk_api_v1_projects_save_to_disk_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SaveProjectRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/projects/disk": {
+ "get": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "List Disk Projects",
+ "description": "Danh sách file Ctrl-S desktop (Documents/SonicForgeDAW/Projects),\nmới nhất trước. Auth optional.",
+ "operationId": "list_disk_projects_api_v1_projects_disk_get",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/projects/disk/{filename}": {
+ "get": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "Get Disk Project",
+ "description": "Đọc nội dung 1 file dự án trên đĩa (mở lại sau khi restart).",
+ "operationId": "get_disk_project_api_v1_projects_disk__filename__get",
+ "parameters": [
+ {
+ "name": "filename",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Filename"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/projects/cloud": {
+ "post": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "Save Cloud Project",
+ "operationId": "save_cloud_project_api_v1_projects_cloud_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SaveProjectRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "get": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "List Cloud Projects",
+ "operationId": "list_cloud_projects_api_v1_projects_cloud_get",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/projects/cloud/{project_id}": {
+ "get": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "Get Cloud Project",
+ "operationId": "get_cloud_project_api_v1_projects_cloud__project_id__get",
+ "parameters": [
+ {
+ "name": "project_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Project Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "Delete Cloud Project",
+ "operationId": "delete_cloud_project_api_v1_projects_cloud__project_id__delete",
+ "parameters": [
+ {
+ "name": "project_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Project Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "put": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "Update Cloud Project",
+ "operationId": "update_cloud_project_api_v1_projects_cloud__project_id__put",
+ "parameters": [
+ {
+ "name": "project_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Project Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SaveProjectRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/projects/cloud/{project_id}/backup": {
+ "post": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "Create Project Backup",
+ "operationId": "create_project_backup_api_v1_projects_cloud__project_id__backup_post",
+ "parameters": [
+ {
+ "name": "project_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Project Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/projects/cloud/{project_id}/backups": {
+ "get": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "List Project Backups",
+ "operationId": "list_project_backups_api_v1_projects_cloud__project_id__backups_get",
+ "parameters": [
+ {
+ "name": "project_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Project Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/projects/cloud/backups/{backup_id}": {
+ "delete": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "Delete Project Backup",
+ "operationId": "delete_project_backup_api_v1_projects_cloud_backups__backup_id__delete",
+ "parameters": [
+ {
+ "name": "backup_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Backup Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/projects/cloud/backups/cleanup": {
+ "post": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "Cleanup All Backups",
+ "operationId": "cleanup_all_backups_api_v1_projects_cloud_backups_cleanup_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CleanupBackupsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/projects/cloud/{project_id}/render": {
+ "post": {
+ "tags": [
+ "projects"
+ ],
+ "summary": "Render Project Endpoint",
+ "operationId": "render_project_endpoint_api_v1_projects_cloud__project_id__render_post",
+ "parameters": [
+ {
+ "name": "project_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Project Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RenderProjectRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/user/preferences": {
+ "get": {
+ "tags": [
+ "user_config"
+ ],
+ "summary": "Get User Preferences",
+ "operationId": "get_user_preferences_api_v1_user_preferences_get",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "tags": [
+ "user_config"
+ ],
+ "summary": "Save User Preferences",
+ "operationId": "save_user_preferences_api_v1_user_preferences_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SavePreferencesRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/user/config/ai": {
+ "get": {
+ "tags": [
+ "user_config"
+ ],
+ "summary": "Get User Ai Config",
+ "operationId": "get_user_ai_config_api_v1_user_config_ai_get",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "tags": [
+ "user_config"
+ ],
+ "summary": "Save User Ai Config",
+ "operationId": "save_user_ai_config_api_v1_user_config_ai_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SaveAIConfigRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/ai/proxy": {
+ "post": {
+ "tags": [
+ "ai"
+ ],
+ "summary": "Proxy Llm",
+ "operationId": "proxy_llm_api_v1_ai_proxy_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ProxyRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/ai/presets": {
+ "get": {
+ "tags": [
+ "ai"
+ ],
+ "summary": "List Presets",
+ "operationId": "list_presets_api_v1_ai_presets_get",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "tags": [
+ "ai"
+ ],
+ "summary": "Save Preset",
+ "operationId": "save_preset_api_v1_ai_presets_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/AIPresetSchema"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/ai/presets/{preset_id}": {
+ "delete": {
+ "tags": [
+ "ai"
+ ],
+ "summary": "Delete Preset",
+ "operationId": "delete_preset_api_v1_ai_presets__preset_id__delete",
+ "parameters": [
+ {
+ "name": "preset_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Preset Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/dirs": {
+ "get": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Get Plugin Dirs",
+ "operationId": "get_plugin_dirs_api_v1_plugins_dirs_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Save Plugin Dirs",
+ "operationId": "save_plugin_dirs_api_v1_plugins_dirs_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DirsRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/scan": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Scan Plugin Dirs",
+ "description": "Scan các dir hiệu lực (env + user override): cập nhật catalog\nsoundfont (inspector/scanner) + liệt kê VST. Trả về danh sách riêng rẽ\nVST (vst_found) + SoundFont (soundfonts) theo từng thư mục user khai báo.",
+ "operationId": "scan_plugin_dirs_api_v1_plugins_scan_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/available": {
+ "get": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "List Plugins",
+ "operationId": "list_plugins_api_v1_plugins_available_get",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/pick-dir": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Pick Plugin Directory",
+ "description": "Mở NATIVE folder picker. Không cần auth (desktop local, chỉ mở dialog).\nTrả {\"path\": \"
\"} hoặc {\"path\": None} (hủy/không có dialog).\nĐịnh nghĩa def (sync) → FastAPI chạy trong threadpool — không block loop\ntrong lúc user chọn thư mục (có thể mất phút).",
+ "operationId": "pick_plugin_directory_api_v1_plugins_pick_dir_post",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/default-soundfonts": {
+ "get": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "List Default Soundfonts",
+ "operationId": "list_default_soundfonts_api_v1_plugins_default_soundfonts_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/soundfonts/catalog": {
+ "get": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Soundfont Catalog",
+ "operationId": "soundfont_catalog_api_v1_plugins_soundfonts_catalog_get",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/soundfont-instruments/{sf_id}": {
+ "get": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "List Soundfont Instruments",
+ "operationId": "list_soundfont_instruments_api_v1_plugins_soundfont_instruments__sf_id__get",
+ "parameters": [
+ {
+ "name": "sf_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Sf Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/upload-soundfont": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Upload Soundfont",
+ "operationId": "upload_soundfont_api_v1_plugins_upload_soundfont_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_upload_soundfont_api_v1_plugins_upload_soundfont_post"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/soundfont/{sf_id}": {
+ "delete": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Delete Soundfont",
+ "operationId": "delete_soundfont_api_v1_plugins_soundfont__sf_id__delete",
+ "parameters": [
+ {
+ "name": "sf_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Sf Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/soundfonts/download/{sf_id}": {
+ "get": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Download Soundfont Asset",
+ "operationId": "download_soundfont_asset_api_v1_plugins_soundfonts_download__sf_id__get",
+ "parameters": [
+ {
+ "name": "sf_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Sf Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/fx-gui": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Open Fx Gui",
+ "description": "Mở native GUI của VST FX (mastering chain / FX rack) qua bridge.\n\nBridge có mode `--open-fx-gui ` (Windows): spawn tiến trình riêng\ngiữ vòng lặp message pump, tạo window 800x600 + attach IPlugView của plugin.\nĐây là GUI tách rời — user chỉnh thông số, đóng window là bridge thoát\n(exit 0). Không spawn 2 lần cho cùng plugin đang mở (tránh trùng GUI).",
+ "operationId": "open_fx_gui_api_v1_plugins_fx_gui_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FxGuiRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/fx-gui/close": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Close Fx Gui",
+ "description": "Đóng bridge fx-gui đang chạy cho plugin (embed mode) — frontend gọi khi\nuser đóng panel embed. Không lỗi nếu plugin chưa mở (closed=False).",
+ "operationId": "close_fx_gui_api_v1_plugins_fx_gui_close_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FxGuiCloseRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/fx-gui/hide": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Hide Fx Gui",
+ "description": "Ẩn GUI native (ed.hide — giữ editor + instance, mở lại nhanh). Chỉ\náp dụng editor in-process fxrt; GUI spawn mode (HTTP bridge) tự có /hide\nriêng — trả applied=False không lỗi.",
+ "operationId": "hide_fx_gui_api_v1_plugins_fx_gui_hide_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FxGuiNativeRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/fx-gui/show": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Show Fx Gui",
+ "description": "Hiện lại GUI native (ed.show).",
+ "operationId": "show_fx_gui_api_v1_plugins_fx_gui_show_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FxGuiNativeRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/fx-gui/rect": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Fx Gui Rect",
+ "description": "M7: anchor PluginWindow native vào rect panel embed (SetWindowPos phía\nC++ khi rect đổi — mirror FxGuiServer /show của float/embed). Frontend đo\nrect panel mỗi 250ms và gửi; chỉ áp dụng editor in-process fxrt — GUI spawn\nmode (HTTP bridge) tự có /show riêng, trả applied=False không lỗi.",
+ "operationId": "fx_gui_rect_api_v1_plugins_fx_gui_rect_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FxGuiRectRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/preview": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Preview Instrument",
+ "description": "Quick-render preview VSTi (âm thật, cùng code path với export).",
+ "operationId": "preview_instrument_api_v1_plugins_preview_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PreviewRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/midi-render": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Midi Render",
+ "description": "Export MIDI notes → WAV với âm của VSTi instrument (lưu vào processed).\n\nPreview/export MIDI notes với âm VSTi trước đây KHÔNG thực hiện được:\n- preview realtime chỉ phát qua loa hệ thống (không vào audio graph DAW)\n- clientSideExport chỉ render soundfont (midiCache = FluidSynth WASM)\n- /plugins/preview có sẵn nhưng frontend không gọi\nEndpoint này render offline bằng pedalboard (đúng plugin + preset như\nexport) → trả file_id để UI preview / gán clip vào project / download.",
+ "operationId": "midi_render_api_v1_plugins_midi_render_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/MidiRenderRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/fx": {
+ "get": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "List Fx Plugins",
+ "description": "List VST effect plugins (is_fx) — native_bridge --scan trên các dir.\n\nChỉ plugin VST3 effect (Ozone, Scaler Audio, ...); instrument/VST2 bị\nbridge loại. refresh=true → quét lại (Plugins Manager → Scan).",
+ "operationId": "list_fx_plugins_api_v1_plugins_fx_get",
+ "parameters": [
+ {
+ "name": "refresh",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false,
+ "title": "Refresh"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/fx-render": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Fx Render",
+ "description": "Áp fx_chain lên WAV đầu vào → WAV mới trong PROCESSED_DIR.",
+ "operationId": "fx_render_api_v1_plugins_fx_render_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FxRenderRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/fx-realtime/start": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Fx Realtime Start",
+ "description": "Start bridge realtime FX loop + SHM. Client mở WS /ws/fx-realtime/{id}\n(token qua query param 'token' hoặc Authorization Bearer) rồi gửi block\nstereo float32 interleaved (block_size*2 float = 2048B), nhận block đã xử lý.",
+ "operationId": "fx_realtime_start_api_v1_plugins_fx_realtime_start_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FxRealtimeStartRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/fx-realtime/stop": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Fx Realtime Stop",
+ "description": "Dừng session realtime (running=0 → bridge thoát, SHM unlink).",
+ "operationId": "fx_realtime_stop_api_v1_plugins_fx_realtime_stop_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FxRealtimeStopRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/fx-realtime/set-chain": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Fx Realtime Set Chain",
+ "description": "Cập nhật chain trên session realtime ĐANG CHẠY — KHÔNG restart session.\nEngine ghi lại job file (seq++); bridge poll seq → RealtimeFxChain::setChain\n(worker load chain mới, swap atomic dưới mutex — audio thread copy\nshared_ptr, không dừng) → load/đổi VST FX + đổi preset GUI không ngắt âm/\ncrackle. Session/WS/SHM giữ nguyên.",
+ "operationId": "fx_realtime_set_chain_api_v1_plugins_fx_realtime_set_chain_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FxRealtimeSetChainRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/soundfont-render": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Soundfont Render",
+ "description": "Preview/play MIDI notes bằng âm soundfont thật — native FluidSynth trên OS.\n\nKhác FluidSynthWASM (docker): âm render server-side bằng pyfluidsynth, trả\nWAV để client play. Dùng cho: preview/keyboard, play MIDI item khi chạy\ntransport ở môi trường standalone.",
+ "operationId": "soundfont_render_api_v1_plugins_soundfont_render_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SoundfontRenderRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/render": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Render Project",
+ "operationId": "render_project_api_v1_plugins_render_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RenderRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/bridge/status": {
+ "get": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Bridge Status",
+ "description": "E1: trạng thái native bridge. Rust ghi bridge_status (JSON) khi spawn/\nhealth-check; nếu chưa có → probe tail bridge.log (dòng started/exists).",
+ "operationId": "bridge_status_api_v1_plugins_bridge_status_get",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/bridge/load": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Bridge Load",
+ "description": "E2: resolve path thật của asset rồi (a) ghi bridge_load.request cho Rust\nwatcher (nếu có) và (b) trả path để JS gọi invoke load_native_instrument\ntrực tiếp — pipeline hoạt động ngay, không chờ watcher.",
+ "operationId": "bridge_load_api_v1_plugins_bridge_load_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/BridgeLoadRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/bridge/log": {
+ "get": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Bridge Log",
+ "description": "E5: tail bridge.log cho UI debug. Log Rust ghi tại %APPDATA%/SonicForgeDAW/logs/bridge.log.",
+ "operationId": "bridge_log_api_v1_plugins_bridge_log_get",
+ "parameters": [
+ {
+ "name": "lines",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "maximum": 2000,
+ "minimum": 1,
+ "default": 100,
+ "title": "Lines"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/plugins/bridge/gui": {
+ "post": {
+ "tags": [
+ "plugins"
+ ],
+ "summary": "Bridge Gui",
+ "description": "E6: mở native GUI VSTi trên máy chạy server — fallback cho browser LAN\nkhông có Tauri. Pipeline giống invoke open_vst_gui: LOAD_INSTRUMENT rồi\nOPEN_GUI qua SHM daw_vst_bridge.",
+ "operationId": "bridge_gui_api_v1_plugins_bridge_gui_post",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/BridgeGuiRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/files/save-dialog": {
+ "post": {
+ "tags": [
+ "files"
+ ],
+ "summary": "Save File Dialog",
+ "description": "Mở NATIVE Save As dialog. Không cần auth (desktop local, chỉ mở dialog).\nTrả {\"path\": \"<đường dẫn user chọn>\"} hoặc {\"path\": None} (hủy/không có\ndialog). def (sync) → FastAPI chạy threadpool — không block loop.",
+ "operationId": "save_file_dialog_api_v1_files_save_dialog_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SaveDialogRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/files/write": {
+ "post": {
+ "tags": [
+ "files"
+ ],
+ "summary": "Write Export File",
+ "description": "Ghi bytes đã export vào đường dẫn tuyệt đối user chọn qua save dialog.\nTrả {\"path\": ..., \"bytes\": N} — frontend toast kèm đường dẫn thật.",
+ "operationId": "write_export_file_api_v1_files_write_post",
+ "requestBody": {
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_write_export_file_api_v1_files_write_post"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/media/computer": {
+ "get": {
+ "tags": [
+ "media"
+ ],
+ "summary": "List Computer Roots",
+ "description": "Liệt kê các ổ đĩa / mount point thật của máy (My Computer).",
+ "operationId": "list_computer_roots_api_v1_media_computer_get",
+ "parameters": [
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/media/browse": {
+ "get": {
+ "tags": [
+ "media"
+ ],
+ "summary": "Browse Directory",
+ "description": "Liệt kê nội dung một thư mục trên máy: thư mục con + file audio/MIDI.",
+ "operationId": "browse_directory_api_v1_media_browse_get",
+ "parameters": [
+ {
+ "name": "path",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Path"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/media/file": {
+ "get": {
+ "tags": [
+ "media"
+ ],
+ "summary": "Serve Local File",
+ "description": "Phục vụ file audio/MIDI cục bộ để preview.",
+ "operationId": "serve_local_file_api_v1_media_file_get",
+ "parameters": [
+ {
+ "name": "path",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Path"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/system/capabilities": {
+ "get": {
+ "tags": [
+ "system"
+ ],
+ "summary": "Get Capabilities",
+ "description": "Khả năng của môi trường hiện tại (public — cần trước khi đăng nhập).\n\n- runtime: \"desktop\" (server + client cùng 1 máy Windows/macOS) |\n \"headless\" (docker server + browser UI)\n- features.preset_upload: luôn True (upload .vstpreset qua web UI)\n- features.preview_mode: \"quick_render\" (pedalboard render clip ngắn —\n âm thật giống export) | \"wasm\" (Preview Synth trong browser)",
+ "operationId": "get_capabilities_api_v1_system_capabilities_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/presets": {
+ "get": {
+ "tags": [
+ "presets"
+ ],
+ "summary": "List Presets",
+ "description": "Danh sách preset trong thư viện (public — frontend cần trước login).",
+ "operationId": "list_presets_api_v1_presets_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/presets/upload": {
+ "post": {
+ "tags": [
+ "presets"
+ ],
+ "summary": "Upload Preset",
+ "description": "Upload preset (.vstpreset / .fxp / .fxb / .dspreset) vào thư viện.",
+ "operationId": "upload_preset_api_v1_presets_upload_post",
+ "parameters": [
+ {
+ "name": "plugin_hint",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Plugin Hint"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_upload_preset_api_v1_presets_upload_post"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/presets/{preset_id}/download": {
+ "get": {
+ "tags": [
+ "presets"
+ ],
+ "summary": "Download Preset",
+ "operationId": "download_preset_api_v1_presets__preset_id__download_get",
+ "parameters": [
+ {
+ "name": "preset_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Preset Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/presets/{preset_id}": {
+ "delete": {
+ "tags": [
+ "presets"
+ ],
+ "summary": "Delete Preset",
+ "operationId": "delete_preset_api_v1_presets__preset_id__delete",
+ "parameters": [
+ {
+ "name": "preset_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Preset Id"
+ }
+ },
+ {
+ "name": "authorization",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Authorization"
+ }
+ },
+ {
+ "name": "x-auth-token",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "X-Auth-Token"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/health": {
+ "get": {
+ "summary": "Health",
+ "operationId": "health_health_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/": {
+ "get": {
+ "summary": "Get Index",
+ "operationId": "get_index__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "text/html": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/favicon.svg": {
+ "get": {
+ "summary": "Get Favicon",
+ "operationId": "get_favicon_favicon_svg_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/ai-prompt-generator": {
+ "get": {
+ "summary": "Get Ai Prompt Generator",
+ "operationId": "get_ai_prompt_generator_ai_prompt_generator_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "text/html": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "AIAnalysisRequest": {
+ "properties": {
+ "file_id": {
+ "type": "string",
+ "title": "File Id"
+ },
+ "api_base_url": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Api Base Url"
+ },
+ "model": {
+ "type": "string",
+ "title": "Model",
+ "default": "deepseek-chat"
+ }
+ },
+ "type": "object",
+ "required": [
+ "file_id"
+ ],
+ "title": "AIAnalysisRequest"
+ },
+ "AICutRequest": {
+ "properties": {
+ "source_track_id": {
+ "type": "string",
+ "title": "Source Track Id"
+ },
+ "file_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "File Id"
+ },
+ "selection_start": {
+ "type": "number",
+ "title": "Selection Start"
+ },
+ "selection_end": {
+ "type": "number",
+ "title": "Selection End"
+ }
+ },
+ "type": "object",
+ "required": [
+ "source_track_id",
+ "selection_start",
+ "selection_end"
+ ],
+ "title": "AICutRequest"
+ },
+ "AIPresetSchema": {
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "Id"
+ },
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "keywords": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Keywords"
+ },
+ "category": {
+ "type": "string",
+ "title": "Category",
+ "default": "General"
+ },
+ "default_bars": {
+ "type": "integer",
+ "title": "Default Bars",
+ "default": 8
+ },
+ "default_bpm": {
+ "type": "integer",
+ "title": "Default Bpm",
+ "default": 120
+ },
+ "default_scale": {
+ "type": "string",
+ "title": "Default Scale",
+ "default": "C Major"
+ },
+ "system_instruction_template": {
+ "type": "string",
+ "title": "System Instruction Template"
+ },
+ "is_user_defined": {
+ "type": "boolean",
+ "title": "Is User Defined",
+ "default": true
+ },
+ "is_favorite": {
+ "type": "boolean",
+ "title": "Is Favorite",
+ "default": false
+ },
+ "created_at": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Created At"
+ }
+ },
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "keywords",
+ "system_instruction_template"
+ ],
+ "title": "AIPresetSchema"
+ },
+ "AIProviderSetting": {
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "Id"
+ },
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "provider_type": {
+ "type": "string",
+ "title": "Provider Type"
+ },
+ "api_base_url": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Api Base Url",
+ "default": "https://api.openai.com/v1"
+ },
+ "api_key": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Api Key",
+ "default": ""
+ },
+ "model_name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Model Name",
+ "default": "gpt-4o"
+ },
+ "temperature": {
+ "type": "number",
+ "title": "Temperature",
+ "default": 0.7
+ },
+ "is_active": {
+ "type": "boolean",
+ "title": "Is Active",
+ "default": true
+ }
+ },
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "provider_type"
+ ],
+ "title": "AIProviderSetting"
+ },
+ "AIScanRequest": {
+ "properties": {
+ "track_id": {
+ "type": "string",
+ "title": "Track Id"
+ },
+ "file_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "File Id"
+ },
+ "min_loop_duration": {
+ "type": "number",
+ "title": "Min Loop Duration",
+ "default": 2.0
+ },
+ "max_loop_duration": {
+ "type": "number",
+ "title": "Max Loop Duration",
+ "default": 6.0
+ }
+ },
+ "type": "object",
+ "required": [
+ "track_id"
+ ],
+ "title": "AIScanRequest"
+ },
+ "Body_upload_audio_api_v1_audio_upload_post": {
+ "properties": {
+ "file": {
+ "type": "string",
+ "contentMediaType": "application/octet-stream",
+ "title": "File"
+ }
+ },
+ "type": "object",
+ "required": [
+ "file"
+ ],
+ "title": "Body_upload_audio_api_v1_audio_upload_post"
+ },
+ "Body_upload_preset_api_v1_presets_upload_post": {
+ "properties": {
+ "file": {
+ "type": "string",
+ "contentMediaType": "application/octet-stream",
+ "title": "File"
+ }
+ },
+ "type": "object",
+ "required": [
+ "file"
+ ],
+ "title": "Body_upload_preset_api_v1_presets_upload_post"
+ },
+ "Body_upload_soundfont_api_v1_plugins_upload_soundfont_post": {
+ "properties": {
+ "file": {
+ "type": "string",
+ "contentMediaType": "application/octet-stream",
+ "title": "File"
+ }
+ },
+ "type": "object",
+ "required": [
+ "file"
+ ],
+ "title": "Body_upload_soundfont_api_v1_plugins_upload_soundfont_post"
+ },
+ "Body_write_export_file_api_v1_files_write_post": {
+ "properties": {
+ "path": {
+ "type": "string",
+ "title": "Path"
+ },
+ "file": {
+ "type": "string",
+ "contentMediaType": "application/octet-stream",
+ "title": "File"
+ }
+ },
+ "type": "object",
+ "required": [
+ "path",
+ "file"
+ ],
+ "title": "Body_write_export_file_api_v1_files_write_post"
+ },
+ "BridgeGuiRequest": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "path": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Path"
+ },
+ "instrumentType": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Instrumenttype"
+ },
+ "channel": {
+ "type": "integer",
+ "title": "Channel",
+ "default": 0
+ }
+ },
+ "type": "object",
+ "required": [
+ "name"
+ ],
+ "title": "BridgeGuiRequest"
+ },
+ "BridgeLoadRequest": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "path": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Path"
+ },
+ "instrumentType": {
+ "type": "string",
+ "title": "Instrumenttype",
+ "default": "SF2"
+ },
+ "channel": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Channel"
+ }
+ },
+ "type": "object",
+ "required": [
+ "name"
+ ],
+ "title": "BridgeLoadRequest"
+ },
+ "ChangePasswordRequest": {
+ "properties": {
+ "old_password": {
+ "type": "string",
+ "title": "Old Password"
+ },
+ "new_password": {
+ "type": "string",
+ "title": "New Password"
+ }
+ },
+ "type": "object",
+ "required": [
+ "old_password",
+ "new_password"
+ ],
+ "title": "ChangePasswordRequest"
+ },
+ "CleanupBackupsRequest": {
+ "properties": {
+ "keep": {
+ "type": "integer",
+ "title": "Keep",
+ "default": 10
+ }
+ },
+ "type": "object",
+ "title": "CleanupBackupsRequest"
+ },
+ "ClipConfig": {
+ "properties": {
+ "clip_id": {
+ "type": "string",
+ "title": "Clip Id"
+ },
+ "start_time_seconds": {
+ "type": "number",
+ "title": "Start Time Seconds"
+ },
+ "end_time_seconds": {
+ "type": "number",
+ "title": "End Time Seconds"
+ },
+ "loop_count": {
+ "type": "integer",
+ "title": "Loop Count",
+ "default": 1
+ },
+ "apply_zero_crossing": {
+ "type": "boolean",
+ "title": "Apply Zero Crossing",
+ "default": true
+ },
+ "fade_in_ms": {
+ "type": "integer",
+ "title": "Fade In Ms",
+ "default": 150
+ },
+ "fade_out_ms": {
+ "type": "integer",
+ "title": "Fade Out Ms",
+ "default": 150
+ }
+ },
+ "type": "object",
+ "required": [
+ "clip_id",
+ "start_time_seconds",
+ "end_time_seconds"
+ ],
+ "title": "ClipConfig"
+ },
+ "CreateUserRequest": {
+ "properties": {
+ "username": {
+ "type": "string",
+ "title": "Username"
+ },
+ "email": {
+ "type": "string",
+ "title": "Email"
+ },
+ "password": {
+ "type": "string",
+ "title": "Password"
+ },
+ "role": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Role",
+ "default": "standard"
+ }
+ },
+ "type": "object",
+ "required": [
+ "username",
+ "email",
+ "password"
+ ],
+ "title": "CreateUserRequest"
+ },
+ "DirsRequest": {
+ "properties": {
+ "vst_dir": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Vst Dir"
+ },
+ "soundfont_dir": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Soundfont Dir"
+ },
+ "plugin_dirs": {
+ "anyOf": [
+ {
+ "items": {},
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Plugin Dirs"
+ }
+ },
+ "type": "object",
+ "title": "DirsRequest"
+ },
+ "EditRequest": {
+ "properties": {
+ "file_id": {
+ "type": "string",
+ "title": "File Id"
+ },
+ "cut_start_ms": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Cut Start Ms"
+ },
+ "cut_end_ms": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Cut End Ms"
+ },
+ "zero_crossing_align": {
+ "type": "boolean",
+ "title": "Zero Crossing Align",
+ "default": true
+ },
+ "loop_count": {
+ "type": "integer",
+ "title": "Loop Count",
+ "default": 1
+ },
+ "fade_in_ms": {
+ "type": "number",
+ "title": "Fade In Ms",
+ "default": 0.0
+ },
+ "fade_out_ms": {
+ "type": "number",
+ "title": "Fade Out Ms",
+ "default": 0.0
+ },
+ "volume_change_db": {
+ "type": "number",
+ "title": "Volume Change Db",
+ "default": 0.0
+ }
+ },
+ "type": "object",
+ "required": [
+ "file_id"
+ ],
+ "title": "EditRequest"
+ },
+ "ExportRequest": {
+ "properties": {
+ "file_id": {
+ "type": "string",
+ "title": "File Id"
+ },
+ "format": {
+ "type": "string",
+ "title": "Format",
+ "default": "wav"
+ },
+ "sample_rate": {
+ "type": "integer",
+ "title": "Sample Rate",
+ "default": 44100
+ },
+ "bit_depth": {
+ "type": "integer",
+ "title": "Bit Depth",
+ "default": 16
+ }
+ },
+ "type": "object",
+ "required": [
+ "file_id"
+ ],
+ "title": "ExportRequest"
+ },
+ "ExportSettings": {
+ "properties": {
+ "sample_rate": {
+ "type": "integer",
+ "title": "Sample Rate",
+ "default": 44100
+ },
+ "bit_depth": {
+ "type": "integer",
+ "title": "Bit Depth",
+ "default": 16
+ },
+ "format": {
+ "type": "string",
+ "title": "Format",
+ "default": "wav"
+ }
+ },
+ "type": "object",
+ "title": "ExportSettings"
+ },
+ "FxGuiCloseRequest": {
+ "properties": {
+ "path": {
+ "type": "string",
+ "title": "Path"
+ }
+ },
+ "type": "object",
+ "required": [
+ "path"
+ ],
+ "title": "FxGuiCloseRequest",
+ "description": "Đóng bridge fx-gui đã mở cho plugin (embed mode)."
+ },
+ "FxGuiNativeRequest": {
+ "properties": {
+ "path": {
+ "type": "string",
+ "title": "Path"
+ }
+ },
+ "type": "object",
+ "required": [
+ "path"
+ ],
+ "title": "FxGuiNativeRequest",
+ "description": "Hide/Show GUI native (editor instance DSP trong session fxrt)."
+ },
+ "FxGuiRectBody": {
+ "properties": {
+ "x": {
+ "type": "integer",
+ "title": "X",
+ "default": 0
+ },
+ "y": {
+ "type": "integer",
+ "title": "Y",
+ "default": 0
+ },
+ "w": {
+ "type": "integer",
+ "title": "W",
+ "default": 0
+ },
+ "h": {
+ "type": "integer",
+ "title": "H",
+ "default": 0
+ }
+ },
+ "type": "object",
+ "title": "FxGuiRectBody",
+ "description": "Rect màn hình (px vật lý, gốc trên-trái; x/y âm = màn hình trái)."
+ },
+ "FxGuiRectRequest": {
+ "properties": {
+ "path": {
+ "type": "string",
+ "title": "Path"
+ },
+ "rect": {
+ "$ref": "#/components/schemas/FxGuiRectBody",
+ "default": {
+ "x": 0,
+ "y": 0,
+ "w": 0,
+ "h": 0
+ }
+ }
+ },
+ "type": "object",
+ "required": [
+ "path"
+ ],
+ "title": "FxGuiRectRequest",
+ "description": "M7: anchor rect cho GUI native — rect panel nơi GUI embed xuất hiện."
+ },
+ "FxGuiRequest": {
+ "properties": {
+ "path": {
+ "type": "string",
+ "title": "Path"
+ },
+ "name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Name"
+ },
+ "embed": {
+ "type": "boolean",
+ "title": "Embed",
+ "default": false
+ },
+ "shm": {
+ "type": "string",
+ "title": "Shm",
+ "default": ""
+ },
+ "preset_b64": {
+ "type": "string",
+ "title": "Preset B64",
+ "default": ""
+ },
+ "master": {
+ "type": "boolean",
+ "title": "Master",
+ "default": false
+ }
+ },
+ "type": "object",
+ "required": [
+ "path"
+ ],
+ "title": "FxGuiRequest",
+ "description": "Mở native GUI của VST FX qua bridge (Windows desktop)."
+ },
+ "FxRealtimeSetChainRequest": {
+ "properties": {
+ "session_id": {
+ "type": "string",
+ "title": "Session Id",
+ "default": ""
+ },
+ "fx_chain": {
+ "items": {},
+ "type": "array",
+ "title": "Fx Chain",
+ "default": []
+ }
+ },
+ "type": "object",
+ "title": "FxRealtimeSetChainRequest"
+ },
+ "FxRealtimeStartRequest": {
+ "properties": {
+ "fx_chain": {
+ "items": {},
+ "type": "array",
+ "title": "Fx Chain",
+ "default": []
+ },
+ "sample_rate": {
+ "type": "integer",
+ "title": "Sample Rate",
+ "default": 44100
+ }
+ },
+ "type": "object",
+ "title": "FxRealtimeStartRequest",
+ "description": "Start một session FX realtime (master chain qua fx_vst_bridge\n--realtime-fx + SHM ring). Trả session_id để client mở WebSocket\n/ws/fx-realtime/{session_id} đẩy/kéo block audio."
+ },
+ "FxRealtimeStopRequest": {
+ "properties": {
+ "session_id": {
+ "type": "string",
+ "title": "Session Id",
+ "default": ""
+ }
+ },
+ "type": "object",
+ "title": "FxRealtimeStopRequest"
+ },
+ "FxRenderRequest": {
+ "properties": {
+ "input_file_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Input File Id"
+ },
+ "input_path": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Input Path"
+ },
+ "fx_chain": {
+ "items": {},
+ "type": "array",
+ "title": "Fx Chain",
+ "default": []
+ },
+ "sample_rate": {
+ "type": "integer",
+ "title": "Sample Rate",
+ "default": 44100
+ }
+ },
+ "type": "object",
+ "title": "FxRenderRequest",
+ "description": "Render WAV qua chuỗi FX (VST3 effect + builtin gain/normalize).\n\nDùng cho masterbus (render xong project → Ozone trên bus) và FX Rack\nper-track (preview offline qua bridge; realtime vẫn WebAudio)."
+ },
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ "type": "array",
+ "title": "Detail"
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError"
+ },
+ "LoginRequest": {
+ "properties": {
+ "username": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Username",
+ "default": "admin"
+ },
+ "password": {
+ "type": "string",
+ "title": "Password"
+ }
+ },
+ "type": "object",
+ "required": [
+ "password"
+ ],
+ "title": "LoginRequest"
+ },
+ "MidiExportRequest": {
+ "properties": {
+ "bpm": {
+ "type": "number",
+ "title": "Bpm",
+ "default": 120.0
+ },
+ "ppq": {
+ "type": "integer",
+ "title": "Ppq",
+ "default": 480
+ },
+ "tracks": {
+ "items": {
+ "$ref": "#/components/schemas/MidiExportTrackModel"
+ },
+ "type": "array",
+ "title": "Tracks",
+ "default": []
+ }
+ },
+ "type": "object",
+ "title": "MidiExportRequest"
+ },
+ "MidiExportTrackModel": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Name",
+ "default": "Track"
+ },
+ "notes": {
+ "items": {
+ "$ref": "#/components/schemas/MidiNoteModel"
+ },
+ "type": "array",
+ "title": "Notes",
+ "default": []
+ }
+ },
+ "type": "object",
+ "title": "MidiExportTrackModel"
+ },
+ "MidiNoteModel": {
+ "properties": {
+ "pitch": {
+ "type": "integer",
+ "title": "Pitch",
+ "default": 60
+ },
+ "start_beat": {
+ "type": "number",
+ "title": "Start Beat",
+ "default": 0.0
+ },
+ "duration_beats": {
+ "type": "number",
+ "title": "Duration Beats",
+ "default": 1.0
+ },
+ "velocity": {
+ "type": "number",
+ "title": "Velocity",
+ "default": 0.8
+ }
+ },
+ "type": "object",
+ "title": "MidiNoteModel"
+ },
+ "MidiRenderRequest": {
+ "properties": {
+ "instrument_id": {
+ "type": "string",
+ "title": "Instrument Id"
+ },
+ "notes": {
+ "items": {},
+ "type": "array",
+ "title": "Notes",
+ "default": []
+ },
+ "bpm": {
+ "type": "number",
+ "title": "Bpm",
+ "default": 120.0
+ },
+ "sample_rate": {
+ "type": "integer",
+ "title": "Sample Rate",
+ "default": 44100
+ },
+ "preset_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Preset Id"
+ },
+ "preset_path": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Preset Path"
+ },
+ "preset_data": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Preset Data"
+ }
+ },
+ "type": "object",
+ "required": [
+ "instrument_id"
+ ],
+ "title": "MidiRenderRequest",
+ "description": "Render MIDI notes → audio qua VSTi (âm thật, cùng code path với export).\n\ninstrument_id = plugin_id của track synth_engine (khớp key scan của\nPluginManager). Đây là cầu nối preset → pedalboard: preset chỉnh trong\nnative GUI (.vstpreset) được áp vào pedalboard trước khi render."
+ },
+ "MultitrackSessionRequest": {
+ "properties": {
+ "session_id": {
+ "type": "string",
+ "title": "Session Id"
+ },
+ "export_settings": {
+ "$ref": "#/components/schemas/ExportSettings"
+ },
+ "tracks": {
+ "items": {
+ "$ref": "#/components/schemas/TrackConfig"
+ },
+ "type": "array",
+ "title": "Tracks"
+ }
+ },
+ "type": "object",
+ "required": [
+ "session_id",
+ "export_settings",
+ "tracks"
+ ],
+ "title": "MultitrackSessionRequest"
+ },
+ "MyFilesRequest": {
+ "properties": {
+ "active_file_ids": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Active File Ids",
+ "default": []
+ }
+ },
+ "type": "object",
+ "title": "MyFilesRequest"
+ },
+ "PreviewRequest": {
+ "properties": {
+ "instrument_id": {
+ "type": "string",
+ "title": "Instrument Id"
+ },
+ "notes": {
+ "items": {},
+ "type": "array",
+ "title": "Notes",
+ "default": []
+ },
+ "bpm": {
+ "type": "number",
+ "title": "Bpm",
+ "default": 120.0
+ },
+ "sample_rate": {
+ "type": "integer",
+ "title": "Sample Rate",
+ "default": 44100
+ },
+ "soundfont_bank": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Soundfont Bank",
+ "default": 0
+ },
+ "soundfont_program": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Soundfont Program",
+ "default": 0
+ },
+ "preset_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Preset Id"
+ },
+ "preset_path": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Preset Path"
+ },
+ "preset_data": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Preset Data"
+ }
+ },
+ "type": "object",
+ "required": [
+ "instrument_id"
+ ],
+ "title": "PreviewRequest",
+ "description": "Quick-render preview: pedalboard render clip ngắn bằng ĐÚNG plugin +\npreset (cùng code path với export) → trả wav để browser phát.\nÂm preview = âm export (khác Preview Synth WASM hiện tại)."
+ },
+ "ProxyRequest": {
+ "properties": {
+ "url": {
+ "type": "string",
+ "title": "Url"
+ },
+ "headers": {
+ "additionalProperties": {
+ "type": "string"
+ },
+ "type": "object",
+ "title": "Headers",
+ "default": {}
+ },
+ "body": {
+ "additionalProperties": true,
+ "type": "object",
+ "title": "Body",
+ "default": {}
+ }
+ },
+ "type": "object",
+ "required": [
+ "url"
+ ],
+ "title": "ProxyRequest"
+ },
+ "PythonToolRequest": {
+ "properties": {
+ "tool_type": {
+ "type": "string",
+ "title": "Tool Type"
+ },
+ "track_id": {
+ "type": "string",
+ "title": "Track Id"
+ },
+ "file_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "File Id"
+ },
+ "time_pos": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Time Pos",
+ "default": 0.0
+ },
+ "freq": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Freq",
+ "default": 440.0
+ },
+ "duration": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Duration",
+ "default": 2.0
+ },
+ "wave_type": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Wave Type",
+ "default": "sine"
+ }
+ },
+ "type": "object",
+ "required": [
+ "tool_type",
+ "track_id"
+ ],
+ "title": "PythonToolRequest"
+ },
+ "ReferenceAnalyzeRequest": {
+ "properties": {
+ "path": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Path"
+ },
+ "file_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "File Id"
+ }
+ },
+ "type": "object",
+ "title": "ReferenceAnalyzeRequest"
+ },
+ "RegisterRequest": {
+ "properties": {
+ "username": {
+ "type": "string",
+ "title": "Username"
+ },
+ "email": {
+ "type": "string",
+ "title": "Email"
+ },
+ "password": {
+ "type": "string",
+ "title": "Password"
+ }
+ },
+ "type": "object",
+ "required": [
+ "username",
+ "email",
+ "password"
+ ],
+ "title": "RegisterRequest"
+ },
+ "RenderProjectRequest": {
+ "properties": {
+ "sample_rate": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Sample Rate",
+ "default": 44100
+ },
+ "metadata": {
+ "anyOf": [
+ {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Metadata"
+ }
+ },
+ "type": "object",
+ "title": "RenderProjectRequest"
+ },
+ "RenderRequest": {
+ "properties": {
+ "project_json": {
+ "additionalProperties": true,
+ "type": "object",
+ "title": "Project Json"
+ },
+ "output_filename": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Output Filename",
+ "default": "render_output.wav"
+ },
+ "bit_depth": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Bit Depth",
+ "default": 32
+ },
+ "sample_rate": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Sample Rate",
+ "default": 0
+ },
+ "dither": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Dither",
+ "default": false
+ },
+ "normalize": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Normalize",
+ "default": false
+ },
+ "normalize_target": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Normalize Target",
+ "default": "-1dbtp"
+ },
+ "mono_check": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Mono Check",
+ "default": false
+ },
+ "metadata": {
+ "anyOf": [
+ {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Metadata"
+ }
+ },
+ "type": "object",
+ "required": [
+ "project_json"
+ ],
+ "title": "RenderRequest"
+ },
+ "SaveAIConfigRequest": {
+ "properties": {
+ "providers": {
+ "items": {
+ "$ref": "#/components/schemas/AIProviderSetting"
+ },
+ "type": "array",
+ "title": "Providers"
+ }
+ },
+ "type": "object",
+ "required": [
+ "providers"
+ ],
+ "title": "SaveAIConfigRequest"
+ },
+ "SaveDialogRequest": {
+ "properties": {
+ "default_name": {
+ "type": "string",
+ "title": "Default Name",
+ "default": "untitled"
+ },
+ "filter_name": {
+ "type": "string",
+ "title": "Filter Name",
+ "default": "File"
+ },
+ "filter_ext": {
+ "type": "string",
+ "title": "Filter Ext",
+ "default": "bin"
+ }
+ },
+ "type": "object",
+ "title": "SaveDialogRequest"
+ },
+ "SavePreferencesRequest": {
+ "properties": {
+ "preferences": {
+ "additionalProperties": true,
+ "type": "object",
+ "title": "Preferences"
+ }
+ },
+ "type": "object",
+ "required": [
+ "preferences"
+ ],
+ "title": "SavePreferencesRequest"
+ },
+ "SaveProjectRequest": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "data_json": {
+ "type": "string",
+ "title": "Data Json"
+ }
+ },
+ "type": "object",
+ "required": [
+ "name",
+ "data_json"
+ ],
+ "title": "SaveProjectRequest"
+ },
+ "SaveTempProjectRequest": {
+ "properties": {
+ "data_json": {
+ "type": "string",
+ "title": "Data Json"
+ },
+ "client_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Client Id"
+ }
+ },
+ "type": "object",
+ "required": [
+ "data_json"
+ ],
+ "title": "SaveTempProjectRequest"
+ },
+ "SoundfontRenderRequest": {
+ "properties": {
+ "soundfont_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Soundfont Id"
+ },
+ "soundfont_path": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Soundfont Path"
+ },
+ "bank": {
+ "type": "integer",
+ "title": "Bank",
+ "default": 0
+ },
+ "program": {
+ "type": "integer",
+ "title": "Program",
+ "default": 0
+ },
+ "notes": {
+ "items": {},
+ "type": "array",
+ "title": "Notes",
+ "default": []
+ },
+ "bpm": {
+ "type": "number",
+ "title": "Bpm",
+ "default": 120.0
+ },
+ "sample_rate": {
+ "type": "integer",
+ "title": "Sample Rate",
+ "default": 44100
+ }
+ },
+ "type": "object",
+ "title": "SoundfontRenderRequest",
+ "description": "Render MIDI notes → WAV bằng soundfont THẬT qua native FluidSynth\n(pyfluidsynth) — luồng âm instrument cho môi trường STANDALONE (xử lí\ntrực tiếp trên OS; docker dùng FluidSynthWASM ở client)."
+ },
+ "TrackConfig": {
+ "properties": {
+ "track_id": {
+ "type": "string",
+ "title": "Track Id"
+ },
+ "file_id": {
+ "type": "string",
+ "title": "File Id"
+ },
+ "volume": {
+ "type": "number",
+ "title": "Volume",
+ "default": 1.0
+ },
+ "muted": {
+ "type": "boolean",
+ "title": "Muted",
+ "default": false
+ },
+ "clips": {
+ "items": {
+ "$ref": "#/components/schemas/ClipConfig"
+ },
+ "type": "array",
+ "title": "Clips",
+ "default": []
+ }
+ },
+ "type": "object",
+ "required": [
+ "track_id",
+ "file_id"
+ ],
+ "title": "TrackConfig"
+ },
+ "UpdateUserQuotaRequest": {
+ "properties": {
+ "storage_limit_mb": {
+ "type": "integer",
+ "title": "Storage Limit Mb"
+ },
+ "max_tracks": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Max Tracks",
+ "default": 16
+ }
+ },
+ "type": "object",
+ "required": [
+ "storage_limit_mb"
+ ],
+ "title": "UpdateUserQuotaRequest"
+ },
+ "UpdateUserRoleRequest": {
+ "properties": {
+ "role": {
+ "type": "string",
+ "title": "Role"
+ },
+ "is_active": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Active",
+ "default": true
+ }
+ },
+ "type": "object",
+ "required": [
+ "role"
+ ],
+ "title": "UpdateUserRoleRequest"
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "integer"
+ }
+ ]
+ },
+ "type": "array",
+ "title": "Location"
+ },
+ "msg": {
+ "type": "string",
+ "title": "Message"
+ },
+ "type": {
+ "type": "string",
+ "title": "Error Type"
+ },
+ "input": {
+ "title": "Input"
+ },
+ "ctx": {
+ "type": "object",
+ "title": "Context"
+ }
+ },
+ "type": "object",
+ "required": [
+ "loc",
+ "msg",
+ "type"
+ ],
+ "title": "ValidationError"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/md/api_contract/ws_protocol.md b/md/api_contract/ws_protocol.md
new file mode 100644
index 0000000..577f516
--- /dev/null
+++ b/md/api_contract/ws_protocol.md
@@ -0,0 +1,72 @@
+# Hợp đồng realtime FX (WS + worklet) — SonicForgeStudio
+
+Trích từ `app/api/v1/plugins.py`, `app/core/fx_realtime.py`,
+`app/static/js/sf-fx-realtime.js`, `app/static/js/fx-rt-worker.js`, `app/static/js/app.jsx`
+tại HEAD `7c236d4`. Đây là đặc tả để port sang Rust (P1/P4).
+
+## 1. HTTP (REST)
+
+| Method | Path | Body | Trả về |
+|---|---|---|---|
+| POST | `/api/v1/plugins/fx-realtime/start` | `{fx_chain: [], sample_rate: 44100}` | `{success, session_id, ws_url, block_size, sample_rate}` |
+| POST | `/api/v1/plugins/fx-realtime/stop` | `{session_id}` | `{success, stopped}` |
+| POST | `/api/v1/plugins/fx-realtime/set-chain` | `{session_id, fx_chain}` | `{success, updated}` |
+| GET | `/api/v1/plugins/bridge/status` | — | trạng thái bridge/SHM |
+| POST | `/api/v1/plugins/render` | job render offline (VST) | file kết quả |
+| POST | `/api/v1/plugins/soundfont-render` | job render SF2 | WAV |
+
+- `block_size` = **256 mẫu** (`FXRT_BLOCK` trong `FxRealtimeIPC.h`).
+- Auth: Bearer token hoặc `?token=`; desktop loopback không token → auto admin.
+
+## 2. WebSocket audio
+
+`GET /api/v1/plugins/ws/fx-realtime/{session_id}?token=...`
+
+| Hướng | Kiểu frame | Nội dung |
+|---|---|---|
+| client → server | **binary** | 2048 B = 256 frame × 2 kênh `float32` interleaved |
+| server → client | **binary** | như trên, đã qua chain FX |
+| client → server | text JSON | `{"cmd":"set_param","slot":n,"key":"...","value":f}` · `{"cmd":"latency","slot":n,"samples":n}` · `{"cmd":"flush"}` |
+| server → client | text JSON | `{"cmd":"latency","slot":n,"samples":n}` · `{"cmd":"flush_done"}` |
+
+Server: `ws_fx_realtime()` (plugins.py ~1344) — vòng lặp receive block → SHM ring →
+`fx_vst_bridge --realtime-fx` (hoặc `--juce-fx`) → SHM → send.
+
+## 3. Worklet ↔ main thread (MessagePort)
+
+`sf-fx-realtime.js` (AudioWorkletProcessor `sf-fx-realtime`).
+
+Hằng số: `BLOCK=256`, `BURST=4` (gom 4 block = 1024 mẫu/frame), `FILL=40` (≈213 ms @48k),
+`RESUME_FILL=24`, `CAP=64` (≈341 ms), `FADE=512`, `HOLD_FADE=4`, `WRAP_FADE=256`.
+
+| type | Hướng | Payload |
+|---|---|---|
+| `__direct` | main → worklet | chuyển `MessagePort` (worker↔worklet trực tiếp, bỏ main thread) |
+| `in` | worklet → main/worker | `{data: ArrayBuffer}` 1024 mẫu stereo interleaved |
+| `out` | worker → worklet | `{l, r: Float32Array}` block đã xử lý |
+| `started` | worklet → main | phát block đầu tiên (đã đủ FILL) |
+| `clear_queue` | main → worklet | `{mode: 'play'\|'seek'\|'loop'}` — wipe queue, `hasStarted=false` (mode `play`) |
+| `flush` | worklet → main | yêu cầu engine xả ring stale |
+| `flush_done` | worker → worklet | engine đã flush |
+| `flush_for_stop` | main → worklet | dừng phát (giữ queue) |
+| `set_param` | worklet → main | forward nguyên văn tới WS |
+| `latency` | main → worklet | `{samples}` — latency chain thật |
+| `stats` | worklet → main | `{underruns, holds, fades, silences, capDrops, fadeIns, queueLen}` (≈2 s/lần) |
+| `dbg` | worklet → main | `{tag, t, ...}`: `fx_in_first`, `fx_out_first`, `fx_play_first`, `fx_seek_first`, `fx_loop_clear`, `fx_seek_clear`, `fx_wrap_xfade` |
+| `dbg_state` | main → worklet | yêu cầu snapshot trạng thái nội bộ |
+| `dbg_state_r` | worklet → main | `{k, t, q, playing, hasStarted, silenced, flushPending, stopped, wrapMode, wrapFadeBlocks, latencySamples, resumeFill, inCount, outPos, fadeLeft, holdFadeLeft}` |
+
+## 4. Đồng bộ playhead / PDC (phía UI, app.jsx)
+
+- `FX_RT_WET_LAG_SAMPLES = 10240` (= FILL 40 × 256) — độ trễ nội dung khi vào FX path.
+- `pdcDelay.delayTime` = 10240/sr khi FX active, 0 khi dry (`fxRtApplyPdc`).
+- Crossfade dry↔wet: `masterFxDryGain` / `masterFxGain` rampe `setTargetAtTime(tc=0.025)`.
+- `fxRt.crossfade(token)` bất biến: `dryGain + fxGain = 1`.
+- Warm bypass: giữ session/node/ws khi PWR OFF giữa lúc play (`fxRtWarmPlayHold`).
+
+## 5. Ghi chú cho bản Rust (P4)
+
+Hợp đồng tối thiểu cần giữ để frontend không phải sửa:
+1. `POST /fx-realtime/start` → `session_id` + `ws_url`, `block_size=256`.
+2. WS binary 2048 B/bản tin, text JSON 3 lệnh (`set_param`, `latency`, `flush`) + 2 phản hồi (`latency`, `flush_done`).
+3. Thay hop Python bằng Rust: `worklet → Tauri IPC/Rust → SHM`, giữ nguyên SHM layout với `fx_vst_bridge`.
diff --git a/tools/golden_conformance.py b/tools/golden_conformance.py
new file mode 100644
index 0000000..c34c3cf
--- /dev/null
+++ b/tools/golden_conformance.py
@@ -0,0 +1,193 @@
+"""P0 golden conformance harness (Python oracle) - SonicForgeStudio.
+
+Muc dich: sinh manifest hash cho cac ham DSP thuan (deterministic) lam CHUAN
+DOI CHIEU khi port sang Rust (P1-P3). Chay:
+
+ python tools/golden_conformance.py --write # ghi manifest
+ python tools/golden_conformance.py --check # so voi manifest da ghi
+
+Moi case tra ve bytes canonical (so thuc lam tron 6 chu so thap phan) -> sha256.
+Cung input (fixture sinh bang seed co dinh trong code) + cung thuat toan =>
+cung hash; lech => in ro case nao.
+"""
+import argparse
+import hashlib
+import io
+import json
+import os
+import sys
+import tempfile
+
+import numpy as np
+
+ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+sys.path.insert(0, ROOT)
+
+MANIFEST = os.path.join(ROOT, "md", "api_contract", "golden", "manifest.json")
+SR = 48000
+
+
+# ── fixtures (seed co dinh, khong dung file ngoai) ───────────────────────────
+
+def _tone(freq, dur, sr=SR, amp=0.5):
+ t = np.linspace(0, dur, int(sr * dur), endpoint=False)
+ return (amp * np.sin(2 * np.pi * freq * t)).astype(np.float32)
+
+
+def _fixture_stereo():
+ rng = np.random.default_rng(1234)
+ n = SR * 2
+ t = np.arange(n) / SR
+ left = (0.4 * np.sin(2 * np.pi * 220 * t) + 0.05 * rng.standard_normal(n)).astype(np.float32)
+ right = (0.4 * np.sin(2 * np.pi * 330 * t + 0.7) + 0.05 * rng.standard_normal(n)).astype(np.float32)
+ return np.column_stack([left, right]).astype(np.float32)
+
+
+def _fixture_click_track():
+ """Click track 120 BPM de analyzer/beat khong bi suy bien."""
+ n = SR * 4
+ y = np.zeros(n, dtype=np.float32)
+ step = SR // 2 # 120 BPM
+ for i in range(0, n, step):
+ y[i:i + 64] = 0.8
+ return y
+
+
+def _write_wav(dirpath, name, data, sr=SR):
+ import soundfile as sf
+ p = os.path.join(dirpath, name)
+ sf.write(p, data, sr, subtype="FLOAT")
+ return p
+
+
+# ── canonical hoa + hash ─────────────────────────────────────────────────────
+
+def _canon(obj):
+ if isinstance(obj, np.ndarray):
+ a = np.asarray(obj, dtype=np.float64)
+ return json.dumps({"dtype": str(obj.dtype), "shape": list(a.shape),
+ "data": np.round(a.ravel(), 6).tolist()},
+ sort_keys=True)
+ if isinstance(obj, dict):
+ return json.dumps({k: _canon(v) for k, v in sorted(obj.items())}, sort_keys=True)
+ if isinstance(obj, (list, tuple)):
+ return json.dumps([_canon(v) for v in obj])
+ if isinstance(obj, (np.floating, float)):
+ return json.dumps(round(float(obj), 6))
+ if isinstance(obj, (np.integer, int)):
+ return json.dumps(int(obj))
+ if isinstance(obj, bytes):
+ return json.dumps(list(obj))
+ return json.dumps(obj, sort_keys=True)
+
+
+def _sha(obj):
+ return hashlib.sha256(_canon(obj).encode("utf-8")).hexdigest()
+
+
+# ── cac case ─────────────────────────────────────────────────────────────────
+
+def collect():
+ cases = {}
+ import soundfile as sf
+ tmp = tempfile.mkdtemp(prefix="sf_golden_")
+ stereo = _fixture_stereo()
+ mono = _tone(440.0, 1.0)
+ p_stereo = _write_wav(tmp, "stereo.wav", stereo)
+ p_mono = _write_wav(tmp, "mono.wav", mono)
+ p_click = _write_wav(tmp, "click.wav", _fixture_click_track())
+
+ from app.core import dsp_utils, audio_editor, analyzer, loudness
+
+ # dsp_utils
+ targets = [0.0, 0.123, 0.5, 1.37, 1.999]
+ cases["dsp_utils.find_zero_crossing"] = [
+ dsp_utils.find_zero_crossing(stereo[:, 0], SR, tt) for tt in targets]
+
+ orig = stereo[:, 0].copy()
+ edited = np.roll(orig, 37)
+ cases["dsp_utils.apply_micro_crossfade"] = dsp_utils.apply_micro_crossfade(
+ orig, edited, start_sample=SR // 2, fade_len_ms=10, sr=SR)
+
+ cases["dsp_utils.generate_peak_waveform"] = dsp_utils.generate_peak_waveform(p_stereo, 400)
+ cases["dsp_utils.generate_rms_waveform"] = dsp_utils.generate_rms_waveform(p_stereo, 400)
+
+ # audio_editor
+ seg = audio_editor.cut_and_loop_segment(
+ p_stereo, 0.25, 0.75, loop_count=2, fade_in_ms=20, fade_out_ms=20,
+ volume_db_change=-3.0)
+ cases["audio_editor.cut_and_loop_segment"] = {
+ "channels": seg.channels, "frame_rate": seg.frame_rate,
+ "sample_width": seg.sample_width, "raw": seg.raw_data}
+
+ # bit_depth=32 (IEEE float) — KHONG dither, bit-exact. 16/24-bit dung TPDF
+ # dither ngau nhien (np.random.default_rng()) nen output KHONG tai lap duoc.
+ out_wav = os.path.join(tmp, "mix.wav")
+ tracks_meta = [
+ {"file_path": p_stereo, "volume": 0.8, "muted": False},
+ {"file_path": p_mono, "volume": 0.6, "muted": False},
+ {"file_path": p_click, "volume": 0.4, "muted": True},
+ ]
+ audio_editor.mix_multitrack_session(tracks_meta, out_wav, sample_rate=SR, bit_depth=32)
+ # Hash theo MAU audio (khong hash byte file): header WAV do pydub/ffmpeg ghi
+ # co metadata khong on dinh giua cac lan chay, du samples giong het nhau.
+ y_mix, sr_mix = sf.read(out_wav, dtype="float64")
+ cases["audio_editor.mix_multitrack_session"] = {"sample_rate": sr_mix, "audio": y_mix}
+
+ # analyzer
+ cases["analyzer.analyze_audio"] = analyzer.analyze_audio(p_click)
+
+ # loudness
+ cases["loudness.integrated_lufs"] = loudness.integrated_lufs(stereo, SR)
+ cases["loudness.true_peak_db"] = loudness.true_peak_db(stereo, SR)
+ cases["loudness.mono_side_ratio"] = loudness.mono_side_ratio(stereo)
+ return cases
+
+
+def build_manifest():
+ import numpy as np
+ import scipy
+ cases = collect()
+ return {
+ "schema": 1,
+ "generated_by": "tools/golden_conformance.py",
+ "env": {"python": sys.version.split()[0], "numpy": np.__version__,
+ "scipy": scipy.__version__, "sample_rate": SR},
+ "cases": {k: _sha(v) for k, v in cases.items()},
+ }
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument("--write", action="store_true")
+ ap.add_argument("--check", action="store_true")
+ args = ap.parse_args()
+
+ manifest = build_manifest()
+ if args.check:
+ with io.open(MANIFEST, "r", encoding="utf-8") as f:
+ ref = json.load(f)
+ bad = []
+ for k, h in ref["cases"].items():
+ cur = manifest["cases"].get(k)
+ if cur != h:
+ bad.append(k)
+ extra = [k for k in manifest["cases"] if k not in ref["cases"]]
+ for k in bad:
+ print("MISMATCH %s\n ref=%s\n cur=%s" % (k, ref["cases"][k], manifest["cases"][k]))
+ for k in extra:
+ print("NEW-CASE %s" % k)
+ print("cases=%d mismatch=%d new=%d" % (len(ref["cases"]), len(bad), len(extra)))
+ return 1 if bad else 0
+
+ os.makedirs(os.path.dirname(MANIFEST), exist_ok=True)
+ with io.open(MANIFEST, "w", encoding="utf-8", newline="\n") as f:
+ json.dump(manifest, f, indent=1, sort_keys=True)
+ print("WROTE %s (%d cases)" % (MANIFEST, len(manifest["cases"])))
+ for k, h in sorted(manifest["cases"].items()):
+ print(" %-45s %s" % (k, h[:16]))
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/tools/p0_baseline.py b/tools/p0_baseline.py
new file mode 100644
index 0000000..ab31edd
--- /dev/null
+++ b/tools/p0_baseline.py
@@ -0,0 +1,168 @@
+"""P0 baseline metrics - SonicForgeStudio (do truoc khi port Rust).
+
+Do: kich thuoc bundle, thoi gian cold start + RAM cua daw_engine.exe,
+thoi gian import app.main, thoi gian mixdown 4 track 30s.
+
+Chay: python tools/p0_baseline.py # in ra stdout + ghi md/BASELINE_P0.md
+ python tools/p0_baseline.py --print # chi in
+"""
+import argparse
+import io
+import json
+import os
+import shutil
+import statistics
+import subprocess
+import sys
+import tempfile
+import time
+import urllib.request
+
+ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+ENGINE_EXE = os.path.join(ROOT, "dist", "daw_engine", "daw_engine.exe")
+ENGINE_DIRS = ["dist/daw_engine", "src-tauri/resources/daw_engine"]
+OUT_MD = os.path.join(ROOT, "md", "BASELINE_P0.md")
+SR = 48000
+
+
+def dir_size_mb(rel):
+ p = os.path.join(ROOT, rel)
+ if not os.path.exists(p):
+ return None
+ total = files = 0
+ for r, _d, fs in os.walk(p):
+ for f in fs:
+ try:
+ total += os.path.getsize(os.path.join(r, f))
+ files += 1
+ except OSError:
+ pass
+ return {"mb": round(total / 1048576.0, 1), "files": files}
+
+
+def measure_bundle():
+ out = {}
+ for d in ENGINE_DIRS:
+ s = dir_size_mb(d)
+ if s:
+ out[d] = s
+ for sub in ("torch", "torchvision", "sympy", "networkx", "tensorflow"):
+ p = os.path.join(ROOT, "dist", "daw_engine", "_internal", sub)
+ out["has:" + sub] = os.path.exists(p)
+ return out
+
+
+def _tasklist_mem_kb(pid):
+ try:
+ r = subprocess.run(["tasklist", "/FI", "PID eq %d" % pid, "/FO", "CSV", "/NH"],
+ capture_output=True, text=True, timeout=15)
+ row = r.stdout.strip().splitlines()[0]
+ cells = [c.strip('"') for c in row.split('","')]
+ return int(cells[-1].replace(",", "").replace(" K", "").strip())
+ except Exception:
+ return None
+
+
+def measure_cold_start(timeout_s=90):
+ if not os.path.exists(ENGINE_EXE):
+ return {"error": "engine exe khong ton tai: " + ENGINE_EXE}
+ env = dict(os.environ)
+ env["SF_PORT"] = "8000"
+ t0 = time.perf_counter()
+ proc = subprocess.Popen([ENGINE_EXE], cwd=os.path.dirname(ENGINE_EXE), env=env,
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ port = None
+ try:
+ while time.perf_counter() - t0 < timeout_s:
+ if proc.poll() is not None:
+ return {"error": "engine thoat som rc=%s" % proc.returncode}
+ for p in range(8000, 8011):
+ try:
+ with urllib.request.urlopen("http://127.0.0.1:%d/health" % p, timeout=0.5) as r:
+ if r.status == 200:
+ port = p
+ break
+ except Exception:
+ continue
+ if port:
+ break
+ time.sleep(0.05)
+ ms = round((time.perf_counter() - t0) * 1000.0, 1)
+ mem = _tasklist_mem_kb(proc.pid)
+ result = {"startup_ms": ms, "port": port, "rss_mb": round(mem / 1024.0, 1) if mem else None}
+ finally:
+ subprocess.run(["taskkill", "/PID", str(proc.pid), "/T", "/F"],
+ capture_output=True, text=True)
+ try:
+ proc.wait(timeout=10)
+ except Exception:
+ pass
+ return result
+
+
+def measure_import_time():
+ code = ("import time,sys;t=time.perf_counter();import app.main;"
+ "print(round((time.perf_counter()-t)*1000,1))")
+ t0 = time.perf_counter()
+ r = subprocess.run([sys.executable, "-c", code], cwd=ROOT, capture_output=True, text=True)
+ wall = round((time.perf_counter() - t0) * 1000.0, 1)
+ try:
+ imp = float(r.stdout.strip().splitlines()[-1])
+ except Exception:
+ imp = None
+ return {"import_app_main_ms": imp, "subprocess_wall_ms": wall}
+
+
+def measure_render(reps=3):
+ import numpy as np
+ sys.path.insert(0, ROOT)
+ from app.core import audio_editor
+ tmp = tempfile.mkdtemp(prefix="sf_bench_")
+ dur = 30.0
+ n = int(SR * dur)
+ t = np.arange(n) / SR
+ paths = []
+ for i, f in enumerate((110.0, 220.0, 330.0, 440.0)):
+ y = (0.3 * np.sin(2 * np.pi * f * t)).astype(np.float32)
+ p = os.path.join(tmp, "t%d.wav" % i)
+ import soundfile as sf
+ sf.write(p, np.column_stack([y, y]), SR, subtype="FLOAT")
+ paths.append(p)
+ meta = [{"file_path": p, "volume": 0.8, "muted": False} for p in paths]
+ times = []
+ for i in range(reps):
+ out = os.path.join(tmp, "mix%d.wav" % i)
+ t0 = time.perf_counter()
+ audio_editor.mix_multitrack_session(meta, out, sample_rate=SR, bit_depth=32)
+ times.append(round((time.perf_counter() - t0) * 1000.0, 1))
+ shutil.rmtree(tmp, ignore_errors=True)
+ return {"tracks": 4, "seconds_each": dur, "sr": SR,
+ "runs_ms": times, "min_ms": min(times), "median_ms": round(statistics.median(times), 1),
+ "realtime_factor": round(min(times) / (dur * 1000.0), 4)}
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument("--print", action="store_true")
+ args = ap.parse_args()
+ metrics = {
+ "date": time.strftime("%Y-%m-%d %H:%M"),
+ "bundle": measure_bundle(),
+ "cold_start": measure_cold_start(),
+ "import": measure_import_time(),
+ "render_mixdown": measure_render(),
+ }
+ text = "# Baseline P0 - SonicForgeStudio (Python engine)\n\n" \
+ "Sinh boi `tools/p0_baseline.py` ngay %s. Dung lam moc so sanh khi port Rust.\n\n```json\n%s\n```\n" % (
+ metrics["date"], json.dumps(metrics, indent=1, ensure_ascii=False))
+ print(text)
+ if not args.print:
+ os.makedirs(os.path.dirname(OUT_MD), exist_ok=True)
+ with io.open(OUT_MD, "w", encoding="utf-8", newline="\n") as f:
+ f.write(text)
+ print("WROTE", OUT_MD)
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
|