Files
SonicForgeStudio/.kilo/plans/1785323991986-mastering-module-plan.md
T

198 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Implementation Plan: Ozone Mastering Module
## Nguồn DUY NHẤT để tạo giao diện
**Toàn bộ giao diện của Mastering Modal được lấy TRỰC TIẾP từ HTML trong `md/45_MASTERING_MODULE.md` dòng 74885.**
Không thiết kế giao diện mới. Không suy luận UI. Lấy chính xác:
- **Cấu trúc HTML**: copy verbatim từ spec, chuyển sang JSX (className thay class, onChange thay onchange, v.v.)
- **CSS styles**: copy verbatim từ spec (oz-panel, oz-card, oz-card-active, knob-dial, knob-pointer, gradient backgrounds)
- **JavaScript logic**: port verbatim từ spec vào React hooks (useState thay ozState, useRef thay DOM refs, useEffect thay event listeners)
- **Web Audio DSP graph**: copy y hệt node graph + connection từ spec (BiquadFilterNode, GainNode, DynamicsCompressorNode, AnalyserNode)
- **Canvas renderers**: copy y hệt drawing logic từ spec (frequency grid, FFT fill, EQ curve, polar vectorscope, VU meter gradient)
## Goal
Dịch spec HTML `md/45_MASTERING_MODULE.md` (lines 74885) thành `MasteringModal` React component trong `app.jsx`.
## Ánh xạ HTML → React (chi tiết từ spec)
### HTML lines 74118: Dependencies & Styles
| Spec | Action |
|------|--------|
| `<script src="cdn.tailwindcss.com">` | Đã có trong `index.html:9` |
| `<link FA 6.4.0>` | Đã có trong `index.html:10` (dùng lucide thay FA, giữ nguyên) |
| `<link Google Fonts Inter>` | Đã có font-family trong `index.html` style |
| CSS `.oz-panel` `oz-card` `oz-card-active` `knob-*` scrollbar | Thêm vào `index.html` `<style>` block |
| CSS `.font-mono { JetBrains Mono }` | Thêm vào `index.html` `<style>` |
**→ Sửa `app/templates/index.html`**: append ~30 dòng CSS class (oz-panel, oz-card, knob-dial, scrollbar tối)
---
### HTML lines 121161: Header Bar
| Spec Element | React JSX Mapping |
|---|---|
| Logo `.fa-atom` + "OZONE ADVANCED" | `<div>` với cùng class & lucide icon `wand-2` thay FA |
| Play/Stop buttons + preset select | JSX nguyên bản, class giữ nguyên |
| Target LUFS text | JSX nguyên bản |
**→ Trong `MasteringModal` return JSX**, copy verbatim HTML, chỉ thay `.fa-*` → lucide `i` tương ứng.
---
### HTML lines 163213: Module Chain Strip
| Spec Element | React |
|---|---|
| `#cardDynamicEq` + power toggle + icon | JSX với `onClick` → set activeModule('eq') |
| `#cardImager` + power toggle + icon | JSX với `onClick` → set activeModule('imager') |
| `#cardMaximizer` + power toggle + icon | JSX với `onClick` → set activeModule('maximizer') |
| Empty add-module slot `.border-dashed` | JSX verbatim |
| `oz-card-active` class toggle | `className={\`oz-card ${ozState.activeModule==='eq' ? 'oz-card-active' : ''}\`}` |
---
### HTML lines 216325: Dynamic EQ View
| Spec Element | React |
|---|---|
| `#viewDynamicEq` container | `className={ozState.activeModule === 'eq' ? '' : 'hidden'}` |
| `#eqCanvas` | `<canvas ref={el => canvasRefs.current.eq = el}>` |
| 4-band knobs (Low/Mid1/Mid2/High) | Mỗi band là `<div>` với `.knob-container`, `data-param`, pointer events |
| Knob dial HTML `.knob-dial > .knob-pointer` | JSX verbatim + inline `transform: rotate(Xdeg)` |
| Band labels & freq text | JSX verbatim |
**Knob interaction (spec lines 794843)**: port pointerdown/move/up logic vào `useEffect` với `.knob-container` querySelectorAll, update `ozState[param]` + gọi `updateAudioGraphValues`.
---
### HTML lines 327363: Stereo Imager View
| Spec Element | React |
|---|---|
| `#viewImager` | `className={ozState.activeModule === 'imager' ? '' : 'hidden'}` |
| `#imagerCanvas` (polar vectorscope) | `<canvas ref={...}>` |
| 4-band width sliders `#w1Slider``#w4Slider` | `<input type="range" value={ozState.w1} onChange={...}>` |
| Value spans `#w1Val``#w4Val` | `<span>{ozState.w1}%</span>` |
---
### HTML lines 366421: Maximizer View
| Spec Element | React |
|---|---|
| `#viewMaximizer` | `className={ozState.activeModule === 'maximizer' ? '' : 'hidden'}` |
| Maximizer gain knob (`.knob-dial w-24 h-24`) | JSX verbatim, pointer events |
| Ceiling level text | `<span>{ozState.ceiling.toFixed(2)} dB</span>` |
| Upward compress knob | JSX verbatim + pointer events |
| Soft clipper knob | JSX verbatim + pointer events |
| Transient emphasis knob | JSX verbatim + pointer events |
---
### HTML lines 425480: Right Sidebar (I/O Meters + Utilities)
| Spec Element | React |
|---|---|
| `#inPeakText` / `#outPeakText` | `<span>{inPeak.toFixed(1)} dB</span>` etc. |
| `#inMeterCanvas` / `#outMeterCanvas` | `<canvas ref={...}>` |
| Bypass / Gain Match / Codec / Dither buttons | JSX verbatim |
| Module active state checkboxes (power toggles) | Thêm `onClick` set `ozState.eqActive` etc. + gọi `updateAudioGraphValues` |
---
### HTML lines 484585: Audio Engine (JavaScript → React)
| Spec Function | React Mapping |
|---|---|
| `ozState` object | `const [ozState, setOzState] = useState({...})` — values copy verbatim |
| `initOzoneAudioEngine()` | `useEffect` on mount: tạo AudioContext (dùng `window.SonicAudio?.getAudioContext()` nếu có), tạo AnalyserNode (fftSize=2048), BiquadFilter nodes (lowshelf 100Hz, peaking 822Hz Q0.7, peaking 3.2kHz Q1.2, highshelf 10kHz), GainNode boost, DynamicsCompressorNode (threshold -0.1, knee 0, ratio 20, attack 0.001, release 0.05), outputAnalyser |
| Node graph connection | `inputAnalyser → eqLow → eqMid1 → eqMid2 → eqHigh → boostGain → compressor → outputAnalyser → destination` |
| `updateAudioGraphValues()` | Copy verbatim logic: `eqLowFilter.gain.setTargetAtTime(ozState.eqActive ? ozState.eqLowGain : 0, now, 0.01)` etc. |
| `startAudioDemo()` | Copy verbatim: create 4-second stereo buffer, kick/snare/synth oscillator, loop playback |
| `stopAudioDemo()` | Copy verbatim: `sourceNode.stop(); sourceNode.disconnect();` |
---
### HTML lines 586791: Canvas Renderers (JavaScript → React)
| Spec Canvas | React Mapping |
|---|---|
| `eqCanvas` (lines 680738) | `useEffect` với `requestAnimationFrame` loop: draw freq grid lines, FFT spectrum bar fill từ `outputAnalyser.getByteFrequencyData`, draw composite EQ curve |
| `imagerCanvas` (lines 742765) | `requestAnimationFrame`: draw polar circle guides + random scatter dots with `ozState.w3` width |
| `inMeterCanvas`/`outMeterCanvas` (lines 769787) | `requestAnimationFrame`: `renderMeterBar()` with linear gradient green→yellow→red, dBFS numeric readout |
| `resizeAll()` | `useEffect` + `window.addEventListener('resize', resizeAll)` |
---
### HTML lines 793882: Interactions & Init (JavaScript → React)
| Spec | React |
|---|---|
| `setupKnobs()` (lines 794843) | `useEffect` on mount: query `.knob-container`, attach pointerdown/move/up, update UI rotation + `ozState` + `updateAudioGraphValues` |
| `setupModuleNavigation()` (lines 846868) | Inline JSX click handlers thay vì event listeners |
| `window.addEventListener('load', ...)` (lines 871882) | `useEffect` on mount: init audio engine, setup event handlers, start canvas renderers |
---
## Task List
### Task 1: Append CSS classes vào app/templates/index.html
Thêm ~30 dòng CSS vào `<style>` block (`index.html:28-245`):
- `.oz-panel` `oz-card` `oz-card-active`
- `.knob-container` `.knob-dial` `.knob-pointer`
- Scrollbar dark theme
- `.font-mono` JetBrains Mono override
- Gradient panel backgrounds
### Task 2: Thêm state + menu item trong app.jsx
- `showMasteringModal` state (dòng 7708)
- Menu item "Mastering Suite" (dòng 15585)
- Render `MasteringModal` (dòng 18664)
### Task 3: Viết MasteringModal component trong app.jsx (trước dòng 6694)
~500 dòng gồm:
**3a. State**: `ozState` (activeModule, gains, widths, maximizer params), `isPlaying`, `canvasRefs`, `audioRefs`
**3b. useEffect (mount)**: `initOzoneAudioEngine()` — copy verbatim từ spec lines 529-584, dùng `window.SonicAudio?.getAudioContext()` fallback
**3c. useEffect (mount)**: `setupKnobs()` — port pointer events từ spec lines 794-843, gọi `updateAudioGraphValues()` khi giá trị thay đổi
**3d. useEffect (mount + deps)**: Canvas render loop — port `startCanvasRenderers()` từ spec lines 650-791, sử dụng `ozState` thay vì global
**3e. useEffect (cleanup)**: Stop audio + disconnect nodes khi unmount
**3f. JSX return**: ~250 dòng JSX, dịch sát spec HTML lines 121-480:
- Overlay modal container `fixed inset-0 z-[100] bg-black/60 backdrop-blur-sm`
- Bên trong: `flex flex-col` container với `h-[90vh] w-[90vw] max-w-[1400px]`
- Header bar (logo, transport, preset select, LUFS)
- Module chain strip (3 module cards + add slot, `oz-card-active` toggle)
- Main workspace:
- Sub toolbar (Delta listen, IRC mode select, Learn Input Gain)
- `viewDynamicEq` (EQ canvas + 4-band knob panel, show khi `activeModule==='eq'`)
- `viewImager` (vectorscope canvas + 4-band sliders, show khi `activeModule==='imager'`)
- `viewMaximizer` (gain knob, upward comp, soft clip, transient knobs, show khi `activeModule==='maximizer'`)
- Right sidebar I/O meters (in/out peak text, meter canvases, bypass/gain match/codec/dither buttons)
- Close button (góc trên phải)
### Task 4: Build & Verify
```bash
npm run build
```
Kiểm tra: mở app → menu → "Mastering Suite" → modal hiển thị, Play Reference Loop phát âm thanh, canvas vẽ, knobs kéo được.
## Files Affected
| File | Lines | Change |
|------|-------|--------|
| `app/templates/index.html` | +30 | Thêm CSS classes (oz-panel, oz-card, knob-dial, scrollbar) |
| `app/static/js/app.jsx` | ~dòng 6690 + 7708 + 15585 + 18664 | Thêm MasteringModal component + state + menu item + render |
## Skipped (PONYTALL)
- Real M/S processing: phase 1 chỉ UI + vectorscope viz. Thêm actual M/S khi user upload file stereo thật.
- Delta Listen / Gain Match / Codec / Dither: UI button renders, DSP stubbed. Thêm sau.
- Drag-drop module reorder: spec define UX. Phase 1: fixed chain.
- Preset save/load: UI dropdown + defaults. Backend integration sau.