From 24109f8bc491e5fd338de6d91c58871fb44a09c1 Mon Sep 17 00:00:00 2001 From: locphamtran Date: Wed, 26 Aug 2026 16:45:42 +0700 Subject: [PATCH] v15b: fix CC7 to VSTi (legacy tag kHostMidiCC+cc + volume-param fallback), CC lane grid + 0/+- labels --- app/static/js/app.jsx | 73 +++++++++++++++--- app/static/js/app.precompiled.js | 14 +++- app/templates/index.html | 2 +- native_bridge/src/Vst3Instrument.cpp | 57 +++++++++++++- .../daw_vst_bridge-x86_64-pc-windows-msvc.exe | Bin 1857024 -> 1859584 bytes src-tauri/binaries/daw_vst_bridge.exe | Bin 1857024 -> 1859584 bytes 6 files changed, 132 insertions(+), 14 deletions(-) diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 8a3ccd0..1fbdef6 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -9643,6 +9643,44 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos // ponytail: chi khoi phuc khi project cu co curve; chua hop nhat migration }, [notes]); + // CC lane grid: vẽ cột beat/bar GIỐNG HỆT piano roll (cùng pixelsPerBeat + + // snapBeats + timeSigNum) → không lệch khi zoom. snapBeats copy nguyên từ + // notes canvas để hai lane trùng khít. + const paintCCLaneGrid = (ctx, h) => { + let snapBeats = 0.25; + if (snapValue === '1') snapBeats = 1.0; + else if (snapValue === '1/2') snapBeats = 0.5; + else if (snapValue === '1/4') snapBeats = 0.25; + else if (snapValue === '1/8') snapBeats = 0.125; + else if (snapValue === '1/16') snapBeats = 0.0625; + else if (snapValue === '4') snapBeats = 4.0; + else if (snapValue === '1/32') snapBeats = 0.03125; + for (let beat = 0; beat <= viewBeats; beat += snapBeats) { + const x = beat * pixelsPerBeat; + if (x > viewWidth) break; + const isBar = beat % timeSigNum === 0; + ctx.strokeStyle = isBar ? '#444450' : '#2d2d35'; + ctx.lineWidth = isBar ? 1.2 : 0.6; + ctx.beginPath(); + ctx.moveTo(x, 0); + ctx.lineTo(x, h); + ctx.stroke(); + } + }; + // Nhan 0/+-: CC 0=min (velocity, expression...) ghi "0" o day; CC luong cuc + // (pan) ghi "+" dinh, "0" giua, "-" day. + const paintCCLaneZeroLabel = (ctx, h, bipolar) => { + ctx.fillStyle = '#8b8b95'; + ctx.font = '10px sans-serif'; + if (bipolar) { + ctx.fillText('+', 5, 12); + ctx.fillText('0', 5, h / 2 + 3); + ctx.fillText('-', 5, h - 5); + } else { + ctx.fillText('0', 5, h - 5); + } + }; + // Ve CC11 lane (graph thread). Tach rieng de drag ve preview truc tiep // (truoc day setNotes tung mousemove + doc notes tu closure stale -> node // cu/moi xen ke -> flicker). previewNoteId != null: chi ve note do voi @@ -9658,12 +9696,15 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos ctx.setTransform(dpr, 0, 0, dpr, 0, 0); ctx.fillStyle = '#161616'; ctx.fillRect(0, 0, viewWidth, h); - ctx.strokeStyle = '#252525'; + paintCCLaneGrid(ctx, h); + // CC11 0=min → ghi "0" o day (khong ve duong giua nua). + ctx.strokeStyle = '#2f2f38'; ctx.lineWidth = 1; ctx.beginPath(); - ctx.moveTo(0, h / 2); - ctx.lineTo(viewWidth, h / 2); + ctx.moveTo(0, h - 10); + ctx.lineTo(viewWidth, h - 10); ctx.stroke(); + paintCCLaneZeroLabel(ctx, h, false); const yOf = v => h - ((v / 127) * (h - 20) + 10); if (notes.length === 0) return; let minS = Infinity, maxE = -Infinity; @@ -9719,13 +9760,27 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos ctx.fillStyle = '#161616'; ctx.fillRect(0, 0, viewWidth, h); + paintCCLaneGrid(ctx, h); - ctx.strokeStyle = '#252525'; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(0, h / 2); - ctx.lineTo(viewWidth, h / 2); - ctx.stroke(); + if (ccMode === 'pan') { + // Luong cuc: 0 o giua (+ dinh / - day) — giu nguyen nhu cu. + ctx.strokeStyle = '#252525'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(0, h / 2); + ctx.lineTo(viewWidth, h / 2); + ctx.stroke(); + paintCCLaneZeroLabel(ctx, h, true); + } else { + // 0=min: ve duong day tai gia tri 0 + nhan "0". + ctx.strokeStyle = '#2f2f38'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(0, h - 10); + ctx.lineTo(viewWidth, h - 10); + ctx.stroke(); + paintCCLaneZeroLabel(ctx, h, false); + } if (ccMode === 'cc11') { // CC11 expression: graph thread cho note dang chon; khi dang drag ve diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index 6ea0f80..84963f8 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -745,12 +745,20 @@ const[showCC,setShowCC]=React.useState(true);const[ccHeight,setCcHeight]=React.u // sinh lai per-note expression_curve (engine doc per-note) bang cach sample // global tai vung tung note. const[cc11GlobalCurve,setCc11GlobalCurve]=React.useState([]);React.useEffect(()=>{if(cc11GlobalCurve&&cc11GlobalCurve.length)return;const n=notes.find(x=>Array.isArray(x.expression_curve)&&x.expression_curve.length);if(n)setCc11GlobalCurve(n.expression_curve.slice());// ponytail: chi khoi phuc khi project cu co curve; chua hop nhat migration -},[notes]);// Ve CC11 lane (graph thread). Tach rieng de drag ve preview truc tiep +},[notes]);// CC lane grid: vẽ cột beat/bar GIỐNG HỆT piano roll (cùng pixelsPerBeat + +// snapBeats + timeSigNum) → không lệch khi zoom. snapBeats copy nguyên từ +// notes canvas để hai lane trùng khít. +const paintCCLaneGrid=(ctx,h)=>{let snapBeats=0.25;if(snapValue==='1')snapBeats=1.0;else if(snapValue==='1/2')snapBeats=0.5;else if(snapValue==='1/4')snapBeats=0.25;else if(snapValue==='1/8')snapBeats=0.125;else if(snapValue==='1/16')snapBeats=0.0625;else if(snapValue==='4')snapBeats=4.0;else if(snapValue==='1/32')snapBeats=0.03125;for(let beat=0;beat<=viewBeats;beat+=snapBeats){const x=beat*pixelsPerBeat;if(x>viewWidth)break;const isBar=beat%timeSigNum===0;ctx.strokeStyle=isBar?'#444450':'#2d2d35';ctx.lineWidth=isBar?1.2:0.6;ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,h);ctx.stroke();}};// Nhan 0/+-: CC 0=min (velocity, expression...) ghi "0" o day; CC luong cuc +// (pan) ghi "+" dinh, "0" giua, "-" day. +const paintCCLaneZeroLabel=(ctx,h,bipolar)=>{ctx.fillStyle='#8b8b95';ctx.font='10px sans-serif';if(bipolar){ctx.fillText('+',5,12);ctx.fillText('0',5,h/2+3);ctx.fillText('-',5,h-5);}else{ctx.fillText('0',5,h-5);}};// Ve CC11 lane (graph thread). Tach rieng de drag ve preview truc tiep // (truoc day setNotes tung mousemove + doc notes tu closure stale -> node // cu/moi xen ke -> flicker). previewNoteId != null: chi ve note do voi // previewCurve thay cho expression_curve da commit. -const paintCC11Lane=previewCurve=>{const canvas=ccCanvasRef.current;if(!canvas)return;const ctx=canvas.getContext('2d');const dpr=window.devicePixelRatio||1;const h=ccHeight;canvas.width=viewWidth*dpr;canvas.height=h*dpr;ctx.setTransform(dpr,0,0,dpr,0,0);ctx.fillStyle='#161616';ctx.fillRect(0,0,viewWidth,h);ctx.strokeStyle='#252525';ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(0,h/2);ctx.lineTo(viewWidth,h/2);ctx.stroke();const yOf=v=>h-(v/127*(h-20)+10);if(notes.length===0)return;let minS=Infinity,maxE=-Infinity;notes.forEach(n=>{minS=Math.min(minS,n.start_beat);maxE=Math.max(maxE,n.start_beat+(n.duration_beats||1));});const x0=(renderBeatOffset+minS)*pixelsPerBeat;const x1=(renderBeatOffset+maxE)*pixelsPerBeat;// Dashed lien tuc tu note dau den note cuoi (default line) -ctx.strokeStyle='#2f6b57';ctx.lineWidth=1.5;ctx.setLineDash([4,4]);ctx.beginPath();ctx.moveTo(x0,yOf(127));ctx.lineTo(x1,yOf(127));ctx.stroke();ctx.setLineDash([]);const curve=(previewCurve||cc11GlobalCurve||[]).slice().sort((a,b)=>a.position_ratio-b.position_ratio);if(!curve.length)return;ctx.strokeStyle='#34d399';ctx.lineWidth=2;ctx.beginPath();if(curve.length===1){ctx.moveTo(x0,yOf(curve[0].value));ctx.lineTo(x1,yOf(curve[0].value));}else{curve.forEach((p,i)=>{const x=x0+p.position_ratio*(x1-x0);const y=yOf(p.value);if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);});}ctx.stroke();curve.forEach(p=>{const x=x0+p.position_ratio*(x1-x0);ctx.fillStyle='#a7f3d0';ctx.beginPath();ctx.arc(x,yOf(p.value),4,0,2*Math.PI);ctx.fill();});};React.useLayoutEffect(()=>{const canvas=ccCanvasRef.current;if(!canvas)return;const ctx=canvas.getContext('2d');const dpr=window.devicePixelRatio||1;const h=ccHeight;canvas.width=viewWidth*dpr;canvas.height=h*dpr;ctx.scale(dpr,dpr);ctx.fillStyle='#161616';ctx.fillRect(0,0,viewWidth,h);ctx.strokeStyle='#252525';ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(0,h/2);ctx.lineTo(viewWidth,h/2);ctx.stroke();if(ccMode==='cc11'){// CC11 expression: graph thread cho note dang chon; khi dang drag ve +const paintCC11Lane=previewCurve=>{const canvas=ccCanvasRef.current;if(!canvas)return;const ctx=canvas.getContext('2d');const dpr=window.devicePixelRatio||1;const h=ccHeight;canvas.width=viewWidth*dpr;canvas.height=h*dpr;ctx.setTransform(dpr,0,0,dpr,0,0);ctx.fillStyle='#161616';ctx.fillRect(0,0,viewWidth,h);paintCCLaneGrid(ctx,h);// CC11 0=min → ghi "0" o day (khong ve duong giua nua). +ctx.strokeStyle='#2f2f38';ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(0,h-10);ctx.lineTo(viewWidth,h-10);ctx.stroke();paintCCLaneZeroLabel(ctx,h,false);const yOf=v=>h-(v/127*(h-20)+10);if(notes.length===0)return;let minS=Infinity,maxE=-Infinity;notes.forEach(n=>{minS=Math.min(minS,n.start_beat);maxE=Math.max(maxE,n.start_beat+(n.duration_beats||1));});const x0=(renderBeatOffset+minS)*pixelsPerBeat;const x1=(renderBeatOffset+maxE)*pixelsPerBeat;// Dashed lien tuc tu note dau den note cuoi (default line) +ctx.strokeStyle='#2f6b57';ctx.lineWidth=1.5;ctx.setLineDash([4,4]);ctx.beginPath();ctx.moveTo(x0,yOf(127));ctx.lineTo(x1,yOf(127));ctx.stroke();ctx.setLineDash([]);const curve=(previewCurve||cc11GlobalCurve||[]).slice().sort((a,b)=>a.position_ratio-b.position_ratio);if(!curve.length)return;ctx.strokeStyle='#34d399';ctx.lineWidth=2;ctx.beginPath();if(curve.length===1){ctx.moveTo(x0,yOf(curve[0].value));ctx.lineTo(x1,yOf(curve[0].value));}else{curve.forEach((p,i)=>{const x=x0+p.position_ratio*(x1-x0);const y=yOf(p.value);if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);});}ctx.stroke();curve.forEach(p=>{const x=x0+p.position_ratio*(x1-x0);ctx.fillStyle='#a7f3d0';ctx.beginPath();ctx.arc(x,yOf(p.value),4,0,2*Math.PI);ctx.fill();});};React.useLayoutEffect(()=>{const canvas=ccCanvasRef.current;if(!canvas)return;const ctx=canvas.getContext('2d');const dpr=window.devicePixelRatio||1;const h=ccHeight;canvas.width=viewWidth*dpr;canvas.height=h*dpr;ctx.scale(dpr,dpr);ctx.fillStyle='#161616';ctx.fillRect(0,0,viewWidth,h);paintCCLaneGrid(ctx,h);if(ccMode==='pan'){// Luong cuc: 0 o giua (+ dinh / - day) — giu nguyen nhu cu. +ctx.strokeStyle='#252525';ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(0,h/2);ctx.lineTo(viewWidth,h/2);ctx.stroke();paintCCLaneZeroLabel(ctx,h,true);}else{// 0=min: ve duong day tai gia tri 0 + nhan "0". +ctx.strokeStyle='#2f2f38';ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(0,h-10);ctx.lineTo(viewWidth,h-10);ctx.stroke();paintCCLaneZeroLabel(ctx,h,false);}if(ccMode==='cc11'){// CC11 expression: graph thread cho note dang chon; khi dang drag ve // preview tu ccDragRef (khong phu thuoc state da commit). const drag=ccDragRef.current;if(drag&&drag.mode==='cc11'&&drag.previewCurve){paintCC11Lane(drag.previewCurve);}else{paintCC11Lane();}return;}else{notes.forEach(note=>{const x=(renderBeatOffset+note.start_beat)*pixelsPerBeat;const isSelected=selectedNoteIds.includes(note.id);let val=note.velocity!==undefined?note.velocity:0.8;if(ccMode==='pan'){val=(note.pan!==undefined?note.pan:0.0)*0.5+0.5;}const stemH=val*(h-20)+10;const y=h-stemH;ctx.strokeStyle=ccMode==='pan'?isSelected?'#60a5fa':'#a78bfa':isSelected?'#3b82f6':'#fbbf24';ctx.lineWidth=2.5;ctx.beginPath();ctx.moveTo(x,h);ctx.lineTo(x,y);ctx.stroke();ctx.fillStyle=ccMode==='pan'?isSelected?'#3b82f6':'#c084fc':isSelected?'#3b82f6':'#fbbf24';ctx.beginPath();ctx.arc(x,y,3.5,0,2*Math.PI);ctx.fill();});}},[notes,ccMode,rollZoom,viewWidth,selectedNoteIds,renderBeatOffset,ccHeight,showCC]);// Follow playhead: khi PLAY — playhead luôn ở GIỮA view, notes trôi sang // trái (scroll theo st.currentTime); khi STOP — scroll về đầu (playhead ở diff --git a/app/templates/index.html b/app/templates/index.html index 031aa28..b261919 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -51,7 +51,7 @@ - +