Editing: —
@@ -4795,6 +4795,7 @@ refreshMiniLB();
var edPiece=null,edMelody=[],edBpm=120,edBars=8,edSelected=-1,edScrollX=0,edDragging=false,edDragNote=-1,edDragOffX=0,edDragOffY=0,edPreviewing=false,edIsClassical=false,edGenre="";
var edResizing=false,edResizeNote=-1; // right-edge duration drag
var edSelRange=null,edSelecting=false,edClipboard=[]; // range select + copy/paste
+var edPlayhead=-1,edPlayheadStart=0,edPlayheadRAF=0; // preview playhead
var ED_LANE_H=50,ED_KICK_H=40,ED_HEAD=20,ED_NOTE_W=14,ED_NOTE_H=16,ED_MIN_DUR=0.05;
var ED_COLORS=["#e53e3e","#d69e2e","#3182ce","#38a169","#805ad5"];
var ED_NAMES=["Red","Yellow","Blue","Green","Kick"];
@@ -4843,7 +4844,7 @@ function openEditor(){
edBpm=edPiece.bpm;
edBars=edPiece.bars||8;
}
- edSelected=-1;edScrollX=0;edSelRange=null;edClipboard=[];
+ edSelected=-1;edScrollX=0;edSelRange=null;edClipboard=[];edPlayhead=-1;
document.getElementById("ed-title").textContent="Editing: "+edPiece.n+(edPiece.composer?" ("+edPiece.composer+")":"");
document.getElementById("ed-bpm").value=edBpm;
document.getElementById("ed-mode-label").textContent=edIsClassical?"Classical (full timeline)":"Pattern (4-beat cycle, repeats "+edBars+"x)";
@@ -4853,7 +4854,7 @@ function openEditor(){
// Need multiple frames for flex layout to settle after display:none→flex
requestAnimationFrame(function(){requestAnimationFrame(function(){edResize();});});
}
-function closeEditor(){stopPreview();edPreviewing=false;document.getElementById("ed-pv-btn").textContent="▶ Preview";show("sm");}
+function closeEditor(){stopPreview();edPreviewing=false;edStopPlayhead();document.getElementById("ed-pv-btn").textContent="▶ Preview";show("sm");}
function edResize(){
var cv=getEdCanvas();
@@ -4861,7 +4862,7 @@ function edResize(){
cv.width=cvRect.width||cv.parentElement.clientWidth||window.innerWidth;
// Height = remaining space from canvas top to window bottom
cv.height=Math.max(200, window.innerHeight-cvRect.top);
- ED_LANE_H=Math.floor((cv.height-ED_KICK_H)/4);
+ ED_LANE_H=Math.floor((cv.height-ED_HEAD-ED_KICK_H)/4);
edDraw();
}
window.addEventListener("resize",function(){if(document.getElementById("sed").classList.contains("active"))edResize();});
@@ -4962,7 +4963,7 @@ function edDraw(){
ctx.setLineDash([]);
}
- // Draw notes (with duration support)
+ // Draw notes (with duration support + playhead highlight)
edMelody.forEach(function(m,idx){
var dur=m.d||0;
var noteW=dur>0?Math.max(ED_NOTE_W, dur*pps):ED_NOTE_W;
@@ -4970,15 +4971,26 @@ function edDraw(){
var ny=edLaneToY(m.l)-ED_NOTE_H/2;
if(nx+noteW<0||nx>W)return;
var inRange=edSelRange&&!edSelecting&&m.t>=Math.min(edSelRange.start,edSelRange.end)&&m.t<=Math.max(edSelRange.start,edSelRange.end);
- ctx.fillStyle=ED_COLORS[m.l]||"#888";
- ctx.globalAlpha=idx===edSelected?1:inRange?0.95:0.8;
+ // Check if this note is "playing" (near the playhead)
+ var isPlaying=edPlayhead>=0&&m.t<=edPlayhead&&m.t+Math.max(dur,0.15)>edPlayhead;
+ ctx.fillStyle=isPlaying?"#fff":ED_COLORS[m.l]||"#888";
+ ctx.globalAlpha=idx===edSelected?1:isPlaying?1:inRange?0.95:0.8;
+ // Glow effect for playing notes
+ if(isPlaying){
+ ctx.shadowColor=ED_COLORS[m.l]||"#fff";
+ ctx.shadowBlur=12;
+ }
ctx.fillRect(nx,ny,noteW,ED_NOTE_H);
+ ctx.shadowBlur=0;
// Duration right-edge handle
if(dur>0){
ctx.fillStyle="rgba(255,255,255,0.3)";
ctx.fillRect(nx+noteW-4,ny,4,ED_NOTE_H);
}
- if(idx===edSelected){
+ if(isPlaying){
+ ctx.strokeStyle=ED_COLORS[m.l]||"#fff";ctx.lineWidth=2.5;
+ ctx.strokeRect(nx-2,ny-2,noteW+4,ED_NOTE_H+4);
+ } else if(idx===edSelected){
ctx.strokeStyle="#fff";ctx.lineWidth=2;
ctx.strokeRect(nx-1,ny-1,noteW+2,ED_NOTE_H+2);
} else if(inRange){
@@ -5001,6 +5013,18 @@ function edDraw(){
}
}
+ // Playhead line during preview
+ if(edPlayhead>=0){
+ var phx=edTimeToX(edPlayhead);
+ if(phx>=0&&phx<=W){
+ ctx.strokeStyle="#ff0";ctx.lineWidth=2;
+ ctx.beginPath();ctx.moveTo(phx,0);ctx.lineTo(phx,H);ctx.stroke();
+ // Triangle marker at top
+ ctx.fillStyle="#ff0";
+ ctx.beginPath();ctx.moveTo(phx,0);ctx.lineTo(phx-5,0);ctx.lineTo(phx,10);ctx.lineTo(phx+5,0);ctx.closePath();ctx.fill();
+ }
+ }
+
// Status
var statusTxt=edMelody.length+" notes";
if(edSelRange&&!edSelecting){
@@ -5281,10 +5305,41 @@ function edKeyDown(e){
function edUpdateBpm(){edBpm=parseInt(document.getElementById("ed-bpm").value)||120;edDraw();}
+function edStopPlayhead(){
+ edPlayhead=-1;
+ if(edPlayheadRAF)cancelAnimationFrame(edPlayheadRAF);
+ edPlayheadRAF=0;
+ edDraw();
+}
+function edStartPlayhead(totalDur){
+ edPlayheadStart=performance.now();
+ var dur=totalDur;
+ function tick(){
+ if(!edPreviewing){edStopPlayhead();return;}
+ var elapsed=(performance.now()-edPlayheadStart)/1000;
+ if(elapsed>dur){
+ edPreviewing=false;
+ document.getElementById("ed-pv-btn").textContent="▶ Preview";
+ edStopPlayhead();
+ return;
+ }
+ edPlayhead=elapsed;
+ // Auto-scroll to keep playhead visible
+ var cv=getEdCanvas();
+ var phx=edTimeToX(edPlayhead);
+ if(phx>cv.width*0.75){
+ edScrollX+=(phx-cv.width*0.5);
+ }
+ edDraw();
+ edPlayheadRAF=requestAnimationFrame(tick);
+ }
+ edPlayheadRAF=requestAnimationFrame(tick);
+}
function edPreview(){
- if(edPreviewing){stopPreview();edPreviewing=false;document.getElementById("ed-pv-btn").textContent="▶ Preview";return;}
+ if(edPreviewing){stopPreview();edPreviewing=false;edStopPlayhead();document.getElementById("ed-pv-btn").textContent="▶ Preview";return;}
var ac=getAc(true);ac.resume();
var piece=edPiece;
+ edScrollX=0; // reset scroll to start
if(edIsClassical){
// Play background orchestration
if(piece.bg)for(var i=0;i
totalDur){
+ edPreviewing=false;
+ document.getElementById("ed-pv-btn").textContent="▶ Preview";
+ edStopPlayhead();
+ return;
+ }
+ // Convert elapsed seconds to beat offset within 4-beat pattern
+ var beatPos=(elapsed/beatDur)%4;
+ edPlayhead=beatPos;
+ edDraw();
+ edPlayheadRAF=requestAnimationFrame(patternTick);
+ }
+ edPlayheadRAF=requestAnimationFrame(patternTick);
}
- edPreviewing=true;
- document.getElementById("ed-pv-btn").textContent="⏹ Stop";
}
// Save/load edits to localStorage