@@ -4007,13 +4008,13 @@ const SINGALONG_PIECES=(function(){
// Rhythm: q ee q ee | q q q q
note(A4,q,0,inst,v); note(A4,e,0,inst,v); note(A4,e,0,inst,v);
note(A4,q,0,inst,v); note(A4,e,0,inst,v); note(A4,e,0,inst,v);
- note(B4,q,3,inst,v); note(C5,q,1,inst,v); note(A4,q,0,inst,v); note(A4,q,0,inst,v);
+ note(D4,q,1,inst,v); note(F4,q,1,inst,v); note(A4,q,0,inst,v); note(A4,q,0,inst,v);
// M3-4: "What shall we do with a drunken sailor?" (G)
// Rhythm: q ee q ee | q q q q
note(G4,q,3,inst,v); note(G4,e,3,inst,v); note(G4,e,3,inst,v);
note(G4,q,3,inst,v); note(G4,e,3,inst,v); note(G4,e,3,inst,v);
- note(A4,q,0,inst,v); note(B4,q,3,inst,v); note(G4,q,3,inst,v); note(G4,q,3,inst,v);
+ note(C4,q,2,inst,v); note(E4,q,2,inst,v); note(G4,q,3,inst,v); note(G4,q,3,inst,v);
// M5-6: "What shall we do with a drunken sailor" (Am)
// Rhythm: q ee q ee | q q q q
@@ -4879,10 +4880,38 @@ 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,edPlayheadOffset=0; // preview playhead + start offset
var edShowLabels=false; // toggle note labels on canvas
-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 edShowPitch=false; // toggle pitch names on notes
+var edPlayFromMarker=-1; // click-to-play marker position in seconds
+var ED_LANE_H=50,ED_KICK_H=40,ED_HEAD=32,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"];
+// Frequency to note name lookup
+var ED_NOTE_NAMES=(function(){
+ var names=["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"];
+ var map={};
+ for(var oct=0;oct<=8;oct++){
+ for(var n=0;n<12;n++){
+ var freq=440*Math.pow(2,(n-9)/12+(oct-4));
+ map[Math.round(freq*10)]=names[n]+oct;
+ }
+ }
+ // Add enharmonic aliases for display
+ return map;
+})();
+function edFreqToNote(freq){
+ if(!freq||freq<=0)return"?";
+ var key=Math.round(freq*10);
+ if(ED_NOTE_NAMES[key])return ED_NOTE_NAMES[key];
+ // Find closest
+ var best="?",bestDist=999;
+ for(var k in ED_NOTE_NAMES){
+ var d=Math.abs(parseInt(k)-key);
+ if(d=0){
+ var pmx=edTimeToX(edPlayFromMarker);
+ if(pmx>-2&&pmx=0)hdrTxt+=" | Dbl-click or Preview to play from "+edPlayFromMarker.toFixed(1)+"s";
+ ctx.fillText(hdrTxt,edTimeToX(0)+50,ED_HEAD-3);
+
// Lane separator lines
for(i=1;i<5;i++){
var ly=ED_HEAD+i*ED_LANE_H;
@@ -5109,28 +5159,35 @@ function edDraw(){
ctx.strokeStyle="#805ad5";ctx.lineWidth=1.5;
ctx.strokeRect(nx-1,ny-1,noteW+2,ED_NOTE_H+2);
}
- // Musical notation symbol on note (always shown if duration exists)
- if(dur>0){
- var di=edDurInfo(dur);
- ctx.font="bold 13px sans-serif";
- ctx.fillStyle=isPlaying?"#000":"rgba(255,255,255,0.9)";
+ // Musical notation symbol + pitch name on note
+ if(dur>0||edShowPitch){
ctx.textAlign="center";
- ctx.fillText(di.sym,nx+noteW/2,ny+ED_NOTE_H/2+4);
+ if(dur>0){
+ var di=edDurInfo(dur);
+ ctx.font="bold 11px sans-serif";
+ ctx.fillStyle=isPlaying?"#000":"rgba(255,255,255,0.9)";
+ ctx.fillText(di.sym,nx+noteW/2,ny+ED_NOTE_H/2+1);
+ }
+ if(edShowPitch&&m.f){
+ var noteName=edFreqToNote(m.f);
+ ctx.font="bold 9px sans-serif";
+ ctx.fillStyle=isPlaying?"#000":"#ff0";
+ ctx.fillText(noteName,nx+noteW/2,ny+ED_NOTE_H+10);
+ }
ctx.textAlign="left";
}
// Note label (when Code/Labels toggled on, or for selected note)
if(edShowLabels||idx===edSelected){
ctx.font="9px monospace";
var label="t:"+m.t.toFixed(edIsClassical?2:2)+",l:"+m.l;
+ if(m.f)label+=" "+edFreqToNote(m.f);
if(dur>0){
var di2=edDurInfo(dur);
- label+=",d:"+dur.toFixed(2)+"s ("+di2.name+")";
+ label+=","+di2.name+"("+dur.toFixed(2)+"s)";
}
- // Position label above the note, inside its lane
var lx=nx;
var ly=ny-4;
- // If near top of lane, put below instead
- if(ly=0){
@@ -5464,6 +5525,11 @@ function edKeyDown(e){
cv.addEventListener("mouseleave",edMouseUp);
cv.addEventListener("wheel",edWheel,{passive:false});
document.addEventListener("keydown",edKeyDown);
+ // Auto-resize canvas when code panel textarea is resized
+ var codeTA=document.getElementById("ed-code");
+ if(codeTA&&typeof ResizeObserver!=="undefined"){
+ new ResizeObserver(function(){if(document.getElementById("sed").classList.contains("active"))edResize();}).observe(codeTA);
+ }
})();
function edUpdateBpm(){edBpm=parseInt(document.getElementById("ed-bpm").value)||120;edDraw();}
@@ -5534,7 +5600,7 @@ function edPreview(startFrom){
if(edPreviewing){stopPreview();edPreviewing=false;edStopPlayhead();document.getElementById("ed-pv-btn").textContent="▶ Preview";return;}
var ac=getAc(true);ac.resume();
var piece=edPiece;
- var offset=typeof startFrom==="number"?startFrom:0;
+ var offset=typeof startFrom==="number"?startFrom:(edPlayFromMarker>=0?edPlayFromMarker:0);
edPlayheadOffset=offset;
if(offset===0)edScrollX=0; // reset scroll only when starting from beginning
if(edIsClassical){
@@ -5639,6 +5705,12 @@ function edExport(){
}
// Code view toggle — shows labels on canvas + code panel
+function edTogglePitch(){
+ edShowPitch=!edShowPitch;
+ var btn=document.getElementById("ed-pitch-btn");
+ btn.style.background=edShowPitch?"#38a169":"";
+ edDraw();
+}
function edToggleCode(){
edShowLabels=!edShowLabels;
var btn=document.getElementById("ed-code-btn");