FEAT: thêm tính năng MasteringModules Modal
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
# 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 74–885.**
|
||||
|
||||
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 74–885) thành `MasteringModal` React component trong `app.jsx`.
|
||||
|
||||
## Ánh xạ HTML → React (chi tiết từ spec)
|
||||
|
||||
### HTML lines 74–118: 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 121–161: 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 163–213: 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 216–325: 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 794–843)**: port pointerdown/move/up logic vào `useEffect` với `.knob-container` querySelectorAll, update `ozState[param]` + gọi `updateAudioGraphValues`.
|
||||
|
||||
---
|
||||
|
||||
### HTML lines 327–363: 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 366–421: 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 425–480: 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 484–585: 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 586–791: Canvas Renderers (JavaScript → React)
|
||||
|
||||
| Spec Canvas | React Mapping |
|
||||
|---|---|
|
||||
| `eqCanvas` (lines 680–738) | `useEffect` với `requestAnimationFrame` loop: draw freq grid lines, FFT spectrum bar fill từ `outputAnalyser.getByteFrequencyData`, draw composite EQ curve |
|
||||
| `imagerCanvas` (lines 742–765) | `requestAnimationFrame`: draw polar circle guides + random scatter dots with `ozState.w3` width |
|
||||
| `inMeterCanvas`/`outMeterCanvas` (lines 769–787) | `requestAnimationFrame`: `renderMeterBar()` with linear gradient green→yellow→red, dBFS numeric readout |
|
||||
| `resizeAll()` | `useEffect` + `window.addEventListener('resize', resizeAll)` |
|
||||
|
||||
---
|
||||
|
||||
### HTML lines 793–882: Interactions & Init (JavaScript → React)
|
||||
|
||||
| Spec | React |
|
||||
|---|---|
|
||||
| `setupKnobs()` (lines 794–843) | `useEffect` on mount: query `.knob-container`, attach pointerdown/move/up, update UI rotation + `ozState` + `updateAudioGraphValues` |
|
||||
| `setupModuleNavigation()` (lines 846–868) | Inline JSX click handlers thay vì event listeners |
|
||||
| `window.addEventListener('load', ...)` (lines 871–882) | `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.
|
||||
Reference in New Issue
Block a user