9.8 KiB
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-paneloz-cardoz-card-active.knob-container.knob-dial.knob-pointer- Scrollbar dark theme
.font-monoJetBrains Mono override- Gradient panel backgrounds
Task 2: Thêm state + menu item trong app.jsx
showMasteringModalstate (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-colcontainer vớih-[90vh] w-[90vw] max-w-[1400px] - Header bar (logo, transport, preset select, LUFS)
- Module chain strip (3 module cards + add slot,
oz-card-activetoggle) - Main workspace:
- Sub toolbar (Delta listen, IRC mode select, Learn Input Gain)
viewDynamicEq(EQ canvas + 4-band knob panel, show khiactiveModule==='eq')viewImager(vectorscope canvas + 4-band sliders, show khiactiveModule==='imager')viewMaximizer(gain knob, upward comp, soft clip, transient knobs, show khiactiveModule==='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
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.