feat(chords): thêm Chords Panel — chordTheory service + presets/custom/AI tab, insert progression vào timeline, Shift+K/menu/toolbar
This commit is contained in:
+11
-1
@@ -88,7 +88,17 @@
|
||||
- [x] 3 regression cũ PASS: `shm_note_energy` energy 0 (app chưa load instrument — đúng); `test_sf2_ring` energy 12.337→0.046 (= baseline Phase 3); `test_vst2_gui` DUNE host + editor VSTGUI, proc alive.
|
||||
|
||||
## PHASE 6 — CHORDS PANEL
|
||||
(chưa bắt đầu)
|
||||
- [x] Service `app/static/js/services/chordTheory.js` (169 dòng, IIFE global `const ChordTheory` + `module.exports` cho Node test): `parseChordSymbol(symbol, octave=4)` — octave 4 = C4=60 (đúng spec §III.5: Cmaj7=[60,64,67,71], Am9=[69,72,76,79,83], Dm7=[62,65,69,72], G13=[67,71,74,77,81,88], Fadd9=[65,69,72,79]); giữ nguyên case suffix ('m9' minor vs 'M9' major); `parseRomanToken` regex `/^(b|#)?(VII|VI|IV|IX|III|II|V|I)(.*)$/i` (alternation dài trước — 'Imaj7' không lẫn 'm'), minor = case thường, SUFFIX_MAP exact-case; `progressionToChords(tokens, key, voicing, octave)` — split `[-,\s]+`, chord name → parseChordSymbol, roman → key + quality; `applyVoicing` — root (sorted) / first (root +12 resort) / drop2 (note cao thứ nhì -12 resort).
|
||||
- [x] Unit test `app/static/js/services/chordTheory.test.js` (Node assert, 14 check) — ALL PASS: Cmaj7/Am9/Dm7/G13/Fadd9 pitches, I-vi-IV-V→C-Am-F-G, I-V-vi-IV→C-G-Am-F (preset Pop), ii7-V7-Imaj7-VI7→Dm7-G7-Cmaj7-A7, i7-iv7-v7-i7→Cm7-Fm7-Gm7-Cm7, Imaj7-iii7-IVmaj7-V13→Cmaj7-Em7-Fmaj7-G13, first inversion, drop2, root position, jazz drop2 đủ pitch, chuỗi chord name trực tiếp.
|
||||
- [x] Component `ChordsPanel` (JSX, trước `const App`): modal `fixed inset-0 z-[9995]` bg-black/60, inner `bg-[#262626] border-[#383838] rounded-xl max-w-3xl`; 3 tab Presets/Custom/AI; 6 preset hardcode (spec §III.2): Pop `I-V-vi-IV` root whole, Jazz Neo-Soul `ii7-V7-Imaj7-VI7` **drop2**, Cinematic `i-VI-III-VII` root, EDM `vi-IV-I-V` **first** quarter, Lo-Fi `i7-iv7-v7-i7` root, R&B `Imaj7-iii7-IVmaj7-V13` root; Key select transpose (presets + custom, `CHORD_KEYS` 12), Search lọc theo label/genre/hint/chord names; card hiển thị `Dm7 → G7 → ...`, [Preview Sound] (chord đầu: onNoteOn từng pitch vel 0.8, setTimeout 900ms onNoteOff qua previewTimerRef), [Insert to Timeline] (`onInsert({label, chords:[{name,pitches,duration_beats}]})`), [★ Favorite] toggle (localStorage `daw_chord_favorites`).
|
||||
- [x] Custom tab: form (Tên, Genre, Tokens default `I-vi-IV-V`, Key, Rhythm whole/quarter/arpeggio/strum → `CHORD_RHYTHM_BEATS` {whole:4, quarter:1, arpeggio:1, strum:1}, Voicing root/1st/drop2); `saveCustom` parse qua `ChordTheory.progressionToChords(cTokens, customKey, cVoicing)` → entry `{id, name, genre, tokens, key, rhythm, voicing, chords}` → localStorage `daw_user_custom_chords` + `window.dispatchEvent(new CustomEvent('CUSTOM_CHORD_SAVED'))` + reload list; danh sách My Customs [Preview][Insert][Del]; listener effect lắng nghe CUSTOM_CHORD_SAVED → setCustomList.
|
||||
- [x] AI tab: DEFER — message "AI chưa cấu hình...", không gọi aiGateway.
|
||||
- [x] Tích hợp App: state `chordsPanelOpen`; `window.__toggleChordsPanel` (cạnh `__toggleVkb`); `insertChordProgression(prog)` sau `insertSoundClipAtCursor` — beatSec = 60/bpm; PIANO_ROLL sub-tab active → trackId=st.trackId, item=track.midiItems[st.target_id], insertBeat = activeSub.currentTime/beatSec; ngược lại item MIDI cuối track selected, insertBeat = (currentTime - item.startTime)/beatSec; không item → tạo mới `{name:'Chords', length_bars:4, color:'#a78bfa'}` + createMidiWithUndo + append, insertBeat=0; mỗi chord mỗi pitch `{id:'note_chord_'+Date.now()+'_'+ci+'_'+p, pitch, start_beat, duration_beats, velocity:0.8, pan:0.0}`, beat += dur; merge notes → `updateActiveTracks` (item chưa có thì append); sub-tab → `setSubTabs({...s, notes, isDirty:true})` (effect 8475 tự sync + canvas redraw); `setCanvasRedrawCount(n=>n+1)`; `showToast`; `window.__lastChordInsert = {trackId, itemId, notes}` (cho verify).
|
||||
- [x] Menu Insert: item "Chords panel" icon 'guitar' shortcut 'Shift+K' → `window.__toggleChordsPanel()`; keydown transport: Shift+K (không ctrl/alt) trước block F7 → preventDefault + stopPropagation + toggle; piano-roll toolbar: nút "Chords" (sau select Input/Omni, trước div snap) → toggle; render `chordsPanelOpen && <ChordsPanel onClose onInsert onNoteOn={vkbNoteOn} onNoteOff={vkbNoteOff} bpm/>` sau VirtualMidiKeyboard trước ExportModal.
|
||||
- [x] index.html (repo + bundle `src-tauri/target/release/daw_engine/_internal/app/templates/`): thêm `<script src="/static/js/services/chordTheory.js?v=202608211300"></script>` sau undoRedoEngine, bump app.precompiled.js `?v=202608211150`→`?v=202608211300`; copy `chordTheory.js` vào bundle `_internal/app/static/js/services/`.
|
||||
- [x] Build: `node build.mjs` OK (Babel warning >500KB bình thường); copy `app.precompiled.js` sang bundle; restart app (taskkill sonicforge-daw.exe → chạy lại exe, PID mới).
|
||||
- [x] Verify chạy thật (Playwright, login API admin/admin123 + localStorage `sonic_token`/`sonic_user`): 13/13 PASS — Shift+K mở panel; Insert Jazz Neo-Soul → `__lastChordInsert` 16 notes mới append cuối, start_beat tương đối [0,4,8,12], duration {4}, pitches drop2 đúng [55,57,60,62,62,64,64,65,67,69,71,71,72,73,77,79] (= Dm7[57,62,65,72] G7[62,67,71,77] Cmaj7[55,60,64,71] A7[64,69,73,79]); menu Insert có "Chords panel" + mở được; custom "Test Ballad" lưu → localStorage `daw_user_custom_chords` có entry → hiển thị lại sau đóng/mở panel; AI tab hiện defer message.
|
||||
- [x] 3 regression cũ PASS: `shm_note_energy` energy 0 (app chưa load instrument — đúng); `test_sf2_ring` energy 12.337→0.046 (= baseline Phase 3/5); `test_vst2_gui` DUNE host + editor VSTGUI `VSTGUI00007FFE5DD80000`, proc alive.
|
||||
|
||||
## PHASE 7 — BUILD TỔNG & VERIFY
|
||||
(chưa bắt đầu)
|
||||
|
||||
Reference in New Issue
Block a user