Fix Drunken Sailor pitches, add pitch display, fix play-from-position, resizable code panel

- Restored original pitches (D4, F4, A4) for Drunken Sailor M1-2 and
  (C4, E4, G4) for M3-4 while keeping corrected q-ee-q-ee rhythm
- New "Pitch" toggle button shows note names (A4, C#5, etc.) below
  each note on the editor canvas using frequency-to-note-name lookup
- Code labels now also show pitch name when available
- Play-from-position redesigned: single click in header sets a yellow
  marker, double-click starts playback from that position. Preview
  button also starts from marker. Esc clears marker.
- Header area enlarged to 32px for easier clicking
- Code panel textarea now resizable (drag to expand/shrink), removed
  fixed max-height, canvas auto-resizes via ResizeObserver
- Status bar shows note value name (quarter, eighth, etc.) for selection

https://claude.ai/code/session_01AqTWV8hPy2XcGP5KY2Q8zv
This commit is contained in:
Claude
2026-04-02 14:55:53 +00:00
parent 34f65f4ead
commit cc7c940df1
+94 -22
View File
@@ -200,6 +200,7 @@ canvas{display:block;}
<button class="smbtn" onclick="edPreview()" id="ed-pv-btn">▶ Preview</button>
<button class="smbtn" onclick="edSave()" style="background:#38a169;">💾 Save</button>
<button class="smbtn" onclick="edExport()">📋 Copy JSON</button>
<button class="smbtn" onclick="edTogglePitch()" id="ed-pitch-btn">♪ Pitch</button>
<button class="smbtn" onclick="edToggleCode()" id="ed-code-btn">{} Code</button>
<button class="smbtn" onclick="edToggleImport()">📥 Import</button>
<button class="smbtn red" onclick="edReset()">↺ Reset</button>
@@ -213,12 +214,12 @@ canvas{display:block;}
Click=add | Click note=select | Drag=move | Drag right edge=resize duration | 1-5=set note value (whole/half/quarter/eighth/sixteenth) | D=add duration | Shift+D=remove | Shift+drag=select range | Ctrl+C/V=copy/paste | Ctrl+A=select all | Esc=deselect | Del=delete | Scroll=pan | Dbl-click header=play from here
<span id="ed-mode-label" style="margin-left:12px;color:#805ad5;"></span>
</div>
<div id="ed-code-panel" style="display:none;padding:8px 12px;background:#0a0a14;border-bottom:1px solid #333;max-height:200px;overflow:auto;">
<div id="ed-code-panel" style="display:none;padding:8px 12px;background:#0a0a14;border-bottom:1px solid #333;overflow:auto;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">
<span style="color:#888;font-size:11px;">JavaScript — edit and click "Apply Code" to update the timeline</span>
<button class="smbtn" onclick="edApplyCode()" style="font-size:11px;">Apply Code</button>
</div>
<textarea id="ed-code" style="width:100%;height:140px;background:#111;color:#8f8;font-family:monospace;font-size:11px;border:1px solid #333;padding:4px;resize:vertical;" spellcheck="false"></textarea>
<textarea id="ed-code" style="width:100%;height:200px;min-height:80px;max-height:60vh;background:#111;color:#8f8;font-family:monospace;font-size:11px;border:1px solid #333;padding:4px;resize:vertical;" spellcheck="false"></textarea>
</div>
<div id="ed-import-panel" style="display:none;padding:8px 12px;background:#0a0a14;border-bottom:1px solid #333;">
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
@@ -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<bestDist){bestDist=d;best=ED_NOTE_NAMES[k];}
}
return best;
}
function getEdCanvas(){return document.getElementById("ed-canvas");}
function getEdCtx(){return getEdCanvas().getContext("2d");}
function edPxPerUnit(){return parseInt(document.getElementById("ed-zoom").value);}
@@ -5056,6 +5085,27 @@ function edDraw(){
ctx.fillText("START",sx+3,12);
}
// Play-from marker (set by clicking in header)
if(edPlayFromMarker>=0){
var pmx=edTimeToX(edPlayFromMarker);
if(pmx>-2&&pmx<W){
ctx.strokeStyle="#ff0";ctx.lineWidth=2;ctx.setLineDash([2,2]);
ctx.beginPath();ctx.moveTo(pmx,0);ctx.lineTo(pmx,H);ctx.stroke();
ctx.setLineDash([]);
// Triangle marker in header
ctx.fillStyle="#ff0";
ctx.beginPath();ctx.moveTo(pmx-6,0);ctx.lineTo(pmx+6,0);ctx.lineTo(pmx,10);ctx.closePath();ctx.fill();
ctx.font="bold 9px sans-serif";
ctx.fillText("▶ "+edPlayFromMarker.toFixed(1)+"s",pmx+8,10);
}
}
// Header instruction text
ctx.fillStyle="#555";ctx.font="9px sans-serif";
var hdrTxt="Click header to set start position";
if(edPlayFromMarker>=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<ED_HEAD+8)ly=ny+ED_NOTE_H+10;
if(ly<ED_HEAD+8)ly=ny+ED_NOTE_H+10+(edShowPitch&&m.f?10:0);
ctx.fillStyle=idx===edSelected?"#fff":"rgba(255,255,255,0.7)";
ctx.fillText(label,lx,ly);
}
@@ -5206,12 +5263,16 @@ function edMouseDown(e){
var cv=getEdCanvas(),rect=cv.getBoundingClientRect();
var mx=e.clientX-rect.left,my=e.clientY-rect.top;
// Double-click on header = play from this position
if(e.detail===2&&my<ED_HEAD){
// Click on header/timeline area = set play-from marker
if(my<ED_HEAD){
var startT=edXToTime(mx);
if(startT<0)startT=0;
edPlayheadOffset=startT;
edPreview(startT);
edPlayFromMarker=startT;
edDraw();
// Double-click starts playback immediately from this position
if(e.detail===2){
edPreview(startT);
}
return;
}
@@ -5376,9 +5437,9 @@ function edKeyDown(e){
e.preventDefault();return;
}
// Escape — clear selection range
// Escape — clear selection range and play-from marker
if(e.key==="Escape"){
edSelRange=null;edSelected=-1;edDraw();e.preventDefault();return;
edSelRange=null;edSelected=-1;edPlayFromMarker=-1;edDraw();e.preventDefault();return;
}
if((e.key==="Delete"||e.key==="Backspace")&&edSelected>=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");