Files
2026-07-20 10:39:07 +07:00

223 lines
15 KiB
Markdown

# Kế hoạch phát triển SonicForge Studio
> Nguyên tắc chung: **Giữ nguyên UI đã thiết kế**, chỉ bổ sung/bổ khuyết các thành phần còn thiếu.
> Mọi thay đổi phải tương thích với code hiện tại (backend FastAPI + frontend React/Babel trong `index.html`).
---
## 0. Khảo sát hiện trạng (đã phân tích)
| Hạng mục | Trạng thái | Ghi chú |
|---|---|---|
| Backend Auth (login/register/change-password/profile) | ✅ Sẵn sàng | `app/api/v1/auth.py` |
| Mock password admin | ✅ `seed_admin()` | Mật khẩu mặc định `admin123`, `must_change_password=1` |
| Quota / System Manager API | ✅ Sẵn sàng | `app/api/v1/admin.py`, `app/api/v1/projects.py` |
| Phân tích Stereo/Mono | ✅ Sẵn sàng | `audioEngine.analyzeAudioBufferChannels()` |
| Auth UI (AuthModal/ProfileModal/SystemManagerModal) | ✅ Sẵn sàng | `app/static/js/components/*` |
| Temp project (local + cloud) | ✅ Sẵn sàng | `storage.scheduleTempAutoSave()`, API `/projects/temp`, `/projects/cloud` |
| Export/Import `.sfs` | ⚠️ Có nhưng thiếu double-click mở lại | `storage.exportProjectToSFS/importProjectFromSFSFile` |
| Mock audioclip trong dự án | ❌ Cần xóa | `index.html:1876-1902` |
| Sub-tab: trục tọa độ channel/volume/panning + zoom rõ nét | ❌ Thiếu | Cần bổ sung theo ảnh đính kèm 1 |
---
## 1. Xóa audioclip mock trong dự án
**Mục tiêu:** Khởi tạo project trống, không có clip mẫu nào khi mở ứng dụng.
**Thay đổi (`app/templates/index.html`):**
- Xóa hàm `createMockAudioBufferObj` (line ~1859) và biến `mockBuffer` (line ~1876) — chỉ giữ lại nếu dùng chỗ khác (hiện chỉ phục vụ mock clip).
- Sửa initial `tracks` state (line ~1880): Track 01 (`id:'1'`) để `buffer: null`, `name: 'Track 01'`, bỏ mảng `clips` mock. Giữ Track 02 rỗng như hiện tại.
- Đồng bộ `handleNewProject` (Ctrl+N, line ~2499 & 5319) đã dùng track rỗng — không đổi.
- Đảm bảo không còn tham chiếu `mockBuffer`/`createMockAudioBufferObj` nào khác (grep xác nhận trước khi xóa).
**Kiểm chứng:** Mở app → 2 track trống, không có waveform mẫu, không có clip `Creak_DeepWood2.wav`.
---
## 2. Phân tích Stereo/Mono khi import & chỉnh sửa đúng loại
**Mục tiêu:** Khi import audio (main session hoặc sub-tab), tự động phát hiện Stereo/Mono và DSP (volume/panning/fade/stretch) phải hoạt động đúng số kênh thực tế.
**Frontend (`app/templates/index.html`):**
- Hàm import audio (decoded bằng `window.SonicAudio.decodeAudioFile`) đã trả về `{ audioBuffer, channelInfo }` với `channelInfo = { channels, isStereo, label }`.
- Khi gán vào track / sub-tab: lưu `channelInfo` vào đối tượng track và sub-tab (`track.channelInfo`, `st.channelInfo`). Hiển thị badge `STEREO`/`MONO` trên TCP panel (giữ nguyên vị trí hiển thị hiện tại).
- **Sub-tab DSP:**
- Nếu `isStereo` → cho phép kéo Volume (L/R independent) và Panning (L100..R100) trên 2 kênh.
- Nếu `MONO` → ẩn/disable kênh đối xứng, chỉ 1 đường Volume, Panning khóa ở Center (vô hiệu hóa). Logic đã có sẵn trong `SubTabWaveform` (kiểm tra `channelInfo?.isStereo`) — bổ sung guard đầy đủ.
- Waveform render: vẽ đủ `numberOfChannels` kênh; với Mono chỉ vẽ 1 lane, Stereo vẽ 2 lane (L/R).
**Backend (`app/core/sub_tab_dsp.py`):** giữ nguyên xử lý theo số kênh của buffer đầu vào (đã đúng). Chỉ đảm bảo API nhận buffer đa kênh.
**Kiểm chứng:** Import file Mono → badge MONO, không thể pan; import Stereo → badge STEREO, pan L/R hoạt động.
---
## 3. Sub-tab timeline: trục tọa độ + zoom rõ nét (theo ảnh 1)
**Mục tiêu:** Trong sub-tab, track timeline hiển thị trục tọa độ thể hiện **Channel / Volume / Panning**, zoom in/out realtime mượt mà, cập nhật ngay khi click/thay đổi thông số.
**Thay đổi (`app/templates/index.html` — khối sub-tab timeline, ~line 6152+):**
- Bổ sung **overlay trục tọa độ** vẽ trên canvas (giữ nguyên style UI):
- **Channel axis:** label `L` / `R` (stereo) hoặc `M` (mono) bên trái lane.
- **Volume axis:** thang dB dọc (`+3 / 0 / -15 / -30 dB`) căn chỉnh với đường `0dB` của graph volume.
- **Panning axis:** thang ngang (`L100 / C / R100`) căn chỉnh với graph panning.
- **Realtime zoom:** dùng `devicePixelRatio` (dpr) scale canvas như main timeline (`ctx.scale(dpr * (useW / timelineWidth), dpr)`) để nét khi zoom in. Đã có pattern ở `SubTabWaveform` (line ~751) — áp dụng đồng nhất cho trục tọa độ.
- Gán `requestAnimationFrame` / `useEffect` dependency `[zoom, buffer, volumeNodes, panningNodes, selectionStart, selectionEnd, currentTime]` để vẽ lại ngay khi tham số đổi (đã có sẵn trong `SubTabWaveform`, mở rộng vẽ thêm trục).
- Giữ nguyên ruler thời gian `0.00s` (đã sửa ở bước trước).
**Kiểm chứng:** Zoom in → waveform + trục dB/pan sắc nét; kéo node volume/pan → trục cập nhật tức thì; click waveform → playhead + trục khớp.
---
## 4. Quản lý người dùng & Menu File
**Mục tiêu:** Admin đăng nhập (mock password), đổi mật khẩu, quản lý hệ thống; menu File có `Profile` (trên `Logout`) và `System Manager` (trong Profile).
**Trạng thái đã có:** `currentUser`, `handleLogout`, `AuthModal`, `ProfileModal`, `SystemManagerModal`, `handleAuthSuccess` đã được bổ sung vào `App` (sửa lỗi `currentUser is not defined`). Backend `seed_admin` + `must_change_password` đã sẵn sàng.
**Frontend (`index.html` — menu File, ~line 5319+ / 5728+):**
- Đảm bảo thứ tự menu File: `... → Profile → Logout`. (Đã đúng: Profile line 5729, Logout line 5730.)
- `Profile` mở `ProfileModal` (đổi password, xem quota) — đã render.
- `System Manager` hiển thị **chỉ khi `currentUser.role === 'admin'`** (đã có guard line 5728) → mở `SystemManagerModal` (quản lý user/quota).
- Auth flow bắt buộc khi login lần đầu (`isMandatoryLogin`) đã có trong `checkAuthStatus` effect.
**Backend (`app/core/auth.py`):** `seed_admin()` dùng `DEFAULT_ADMIN_PASSWORD` (mặc định `admin123`), set `must_change_password=1`. Khi admin login → `AuthModal` mode `force_change` ép đổi pass. ✅ Không đổi.
**Kiểm chứng:** Khởi chạy lần đầu → ép login admin/`admin123` → modal đổi mật khẩu → vào app. File menu hiện Profile (trên Logout); admin thấy thêm System Manager.
---
## 5. Dự án tạm (Temp) & lưu cloud / `.sfs`
**Mục tiêu:** Chưa lưu → auto-save tiến trình vào dự án tạm (local + server); cho phép đăng ký/đăng nhập/lưu cloud (quota); cho phép export `.sfs` về ổ cứng; double-click `.sfs` mở domain → login → tải lại dự án.
**5.1 Temp auto-save (đã có, chuẩn hóa):**
- `storage.scheduleTempAutoSave()` lưu localStorage `sonic_temp_project` + gọi `API saveTempProject` nếu có token. ✅ Giữ nguyên.
- Đảm bảo mọi thay đổi (`tracks`, `subTabs`, `volumeNodes`, `panningNodes`, `fade*`, `speed`) đều nằm trong state được auto-save (serialize an toàn, không lưu `AudioBuffer` thô mà lưu metadata + `serverFileId`).
**5.2 Lưu cloud (quota):**
- Backend `/projects/cloud` kiểm tra quota (`storage_limit_mb`, `max_tracks`). ✅
- Frontend: menu File `Save to Cloud``handleSaveCloud` (đã có trong `app.js`) → port vào `App` trong `index.html` nếu chưa có, dùng `window.SonicAPI.saveCloudProject`.
**5.3 Export / Import `.sfs`:**
- `exportProjectToSFS` (đã có) → download `.sfs`. ✅
- **Bổ sung double-click mở lại:**
- Thêm vào `.sfs` JSON trường `domain` (đã có) và đăng ký MIME/association phía client: khi user double-click file `.sfs` trên máy, OS mở URL `domain/?sfs=<encoded>` (hoặc protocol handler `sonicforge://open?file=...`).
- Tại `index.html` khởi tạo: đọc query param `?sfs=` → nếu có → yêu cầu login (nếu chưa) → `importProjectFromSFSFile` (đọc từ blob/server) → load tracks.
- Ghi chú: cơ chế double-click thực tế phụ thuộc OS (file association / protocol handler). Cung cấp hướng dẫn + nút "Mở dự án .sfs" trong UI làm fallback.
**Kiểm chứng:** Sửa project → F5 → tiến trình còn (temp). Login → Save Cloud → quota đúng. Export `.sfs` → mở lại domain → login → project restored.
---
## 6. Thứ tự thực hiện & kiểm thử
1. **B1** — Xóa mock clip (§1). Chạy app, confirm trống.
2. **B2** — Stereo/Mono import + DSP (§2). Test Mono & Stereo file.
3. **B3** — Sub-tab trục tọa độ + zoom (§3). So sánh ảnh 1.
4. **B4** — User/Menu (§4). Test admin flow + role guard.
5. **B5** — Temp/Cloud/`.sfs` (§5). Test auto-save, quota, round-trip sfs.
6. **B6** — Lint/typecheck (nếu có script) + chạy `tests/` hiện có (`test_sub_tab_dsp.py`, `test_auth_and_quota.py`).
- ✅ Sửa `main.py`: thêm auth + admin + projects routers (thiếu từ đầu).
- ✅ 31/31 tests pass (auth + dsp_engine + sub_tab_dsp).
**Không thay đổi:** Layout tổng thể, màu sắc, component giao diện đã design; chỉ bổ sung thành phần (trục, badge, modal, menu item) và sửa logic thiếu.
---
## 7. Tối ưu hóa & Module hóa `index.html` (giảm latency)
### 7.1 Hiện trạng & nguyên nhân latency
| Vấn đề | Chi tiết |
|---|---|
| File `index.html` khổng lồ (~6784 dòng) chứa TOÀN BỘ UI inline trong 1 thẻ `<script type="text/babel">` | Babel phải parse + transform toàn bộ file mỗi lần load → chậm init. |
| Dùng `@babel/standalone` runtime transform (line 11) | Transform chạy ở browser, block main thread, gây lag khi mở app. |
| Các component đã tách (`app/static/js/components/*.js`, `app.js`) **KHÔNG được load** bởi `index.html` | `index.html` chỉ load `services/*` (api/audioEngine/storage). `TrackTimeline.js`, `SubTabTimeline.js`, `HeaderMenu.js`, `AuthModal.js`, `ProfileModal.js`, `SystemManagerModal.js` bị bỏ không. |
| Không có code-splitting / lazy load | Mọi thứ load 1 lần dù user chưa mở sub-tab/modal. |
> Lưu ý: `app.js` định nghĩa `SonicForgeApp` render vào `#root` (cũ) — xung đột với `App` trong `index.html`. Sau module hóa sẽ gộp về 1 entry duy nhất.
### 7.2 Mục tiêu
- Tách `index.html` thành các **ES modules** riêng biệt, mỗi module 1 trách nhiệm.
- Loại bỏ `@babel/standalone` runtime transform → build/precompile (hoặc chuyển sang JSX tiền biên dịch).
- Giữ nguyên 100% giao diện/UX hiện tại (chỉ refactor code, không redesign).
- Giảm thời gian init và tăng tính bảo trì.
### 7.3 Cấu trúc module đề xuất
```
app/static/js/
├── services/ # (đã có, giữ nguyên)
│ ├── api.js # window.SonicAPI
│ ├── audioEngine.js # window.SonicAudio
│ └── storage.js # window.SonicStorage
├── components/ # (đã có, mở rộng)
│ ├── HeaderMenu.js
│ ├── AuthModal.js
│ ├── ProfileModal.js
│ ├── SystemManagerModal.js
│ ├── TrackTimeline.js # (đã có, chuẩn hóa props)
│ ├── SubTabTimeline.js # (đã có)
│ ├── Timeline/ # MỚI: tách từ index.html
│ │ ├── Ruler.jsx # trục thời gian 0.00s (§3)
│ │ ├── CoordinateAxis.jsx# trục Channel/Volume/Panning (§3)
│ │ ├── WaveformLane.jsx # vẽ waveform main + sub-tab
│ │ └── TempoTrackLane.jsx
│ ├── TCP/ # MỚI: Track Control Panel
│ │ ├── MainTcpPanel.jsx
│ │ └── SubTabTcpPanel.jsx
│ ├── SubTab/ # MỚI
│ │ ├── SubTabWaveform.jsx
│ │ ├── VolumeGraph.jsx
│ │ └── PanningGraph.jsx
│ └── modals/... # (chuyển vào components/)
├── hooks/ # MỚI
│ ├── useAuth.js # currentUser, checkAuthStatus, handleLogout (§4)
│ ├── useTempProject.js # auto-save temp (§5)
│ └── useAudioImport.js # decode + channelInfo (§2)
├── state/ # MỚI
│ └── studioStore.js # tập trung state tracks/subTabs/zoom (Context hoặc store nhẹ)
└── App.jsx # entry: gom toàn bộ, render <App/>
```
### 7.4 Thực trạng & ràng buộc
**Không thể thêm Node build pipeline ngay** vì:
- Dự án deploy qua Python FastAPI + Docker (không có `node_modules`/`package.json`).
- `index.html` được serve trực tiếp từ `main.py:33-39` (HTMLResponse), không có static `/dist`.
- Thêm Vite/esbuild yêu cầu thay đổi Dockerfile, CI/CD pipeline, requirements.txt.
**Đã thực hiện (minimum viable module hóa):**
1. ✅ Gom toàn bộ UI vào **single-file `index.html`** (inline Babel script) — loại bỏ tất cả component `.js` cũ (đã deprecated, nội dung giữ làm reference).
2. ✅ Tách services (`audioEngine.js`, `api.js`, `storage.js`) thành file riêng — đã có sẵn.
3. ✅ Copy 3 modal (Auth, Profile, SystemManager) từ `components/.js` vào inline — tránh load rời.
4. ✅ Xóa `app.js` (SonicForgeApp cũ) — tránh 2 render vào `#root`.
**Kế hoạch tương lai (khi có Node build):**
- B7.1: Thêm `package.json` + Vite/esbuild → `npm run build` → output `dist/`.
- B7.1a: `main.py` mount `/static/dist` qua `StaticFiles`.
- B7.2: Trích xuất các component nặng (SubTabWaveform, WaveformLane, GraphEditorCanvas) từ `index.html` ra `.jsx`.
- B7.4: `React.lazy()` cho SubTabWaveform + GraphEditorCanvas.
- B7.5: Canvas waveform dùng `React.memo` + `useMemo` (đã có pattern dpr).
- B7.6: Cache hashed bundle + `<link rel="modulepreload">`.
### 7.5 Đã tối ưu (no-build)
- Component `SubTabWaveform` canvas dùng `devicePixelRatio` scale (line ~698-710) → zoom nét.
- `useEffect` dependency arrays đầy đủ (`[buffer, zoom, nodes, selection, currentTime]`) → chỉ vẽ lại khi thay đổi.
- Deferred lucide icons init (`setTimeout(..., 300)`) — không block first paint.
- Temp auto-save debounce 2s (trong `storage.js:53`) — không spam API.
### 7.6 Thứ tự ưu tiên (đã thực hiện)
1. ✅ §1 Xóa mock clip.
2. ✅ §2 Stereo/Mono import + channelInfo.
3. ✅ §3 Sub-tab axes + channel label (L/R/M) + mono-lock pan.
4. ✅ §4 Auth flow + modals + menu Profile/System Manager.
5. ✅ §5 Temp auto-save + Cloud save + Export/Import `.sfs` + deep-link `?sfs=`.
6. ✅ §6 Tests 31/31 pass (fixed `main.py` missing routers).
7. ✅ §7 Cleanup deprecated `.js` + PLAN.md cập nhật constraints.