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:
+94
-22
@@ -200,6 +200,7 @@ canvas{display:block;}
|
|||||||
<button class="smbtn" onclick="edPreview()" id="ed-pv-btn">▶ Preview</button>
|
<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="edSave()" style="background:#38a169;">💾 Save</button>
|
||||||
<button class="smbtn" onclick="edExport()">📋 Copy JSON</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="edToggleCode()" id="ed-code-btn">{} Code</button>
|
||||||
<button class="smbtn" onclick="edToggleImport()">📥 Import</button>
|
<button class="smbtn" onclick="edToggleImport()">📥 Import</button>
|
||||||
<button class="smbtn red" onclick="edReset()">↺ Reset</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
|
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>
|
<span id="ed-mode-label" style="margin-left:12px;color:#805ad5;"></span>
|
||||||
</div>
|
</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;">
|
<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>
|
<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>
|
<button class="smbtn" onclick="edApplyCode()" style="font-size:11px;">Apply Code</button>
|
||||||
</div>
|
</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>
|
||||||
<div id="ed-import-panel" style="display:none;padding:8px 12px;background:#0a0a14;border-bottom:1px solid #333;">
|
<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;">
|
<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
|
// 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(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)
|
// M3-4: "What shall we do with a drunken sailor?" (G)
|
||||||
// Rhythm: q ee q ee | q q q q
|
// 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(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)
|
// M5-6: "What shall we do with a drunken sailor" (Am)
|
||||||
// Rhythm: q ee q ee | q q q q
|
// 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 edSelRange=null,edSelecting=false,edClipboard=[]; // range select + copy/paste
|
||||||
var edPlayhead=-1,edPlayheadStart=0,edPlayheadRAF=0,edPlayheadOffset=0; // preview playhead + start offset
|
var edPlayhead=-1,edPlayheadStart=0,edPlayheadRAF=0,edPlayheadOffset=0; // preview playhead + start offset
|
||||||
var edShowLabels=false; // toggle note labels on canvas
|
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_COLORS=["#e53e3e","#d69e2e","#3182ce","#38a169","#805ad5"];
|
||||||
var ED_NAMES=["Red","Yellow","Blue","Green","Kick"];
|
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 getEdCanvas(){return document.getElementById("ed-canvas");}
|
||||||
function getEdCtx(){return getEdCanvas().getContext("2d");}
|
function getEdCtx(){return getEdCanvas().getContext("2d");}
|
||||||
function edPxPerUnit(){return parseInt(document.getElementById("ed-zoom").value);}
|
function edPxPerUnit(){return parseInt(document.getElementById("ed-zoom").value);}
|
||||||
@@ -5056,6 +5085,27 @@ function edDraw(){
|
|||||||
ctx.fillText("START",sx+3,12);
|
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
|
// Lane separator lines
|
||||||
for(i=1;i<5;i++){
|
for(i=1;i<5;i++){
|
||||||
var ly=ED_HEAD+i*ED_LANE_H;
|
var ly=ED_HEAD+i*ED_LANE_H;
|
||||||
@@ -5109,28 +5159,35 @@ function edDraw(){
|
|||||||
ctx.strokeStyle="#805ad5";ctx.lineWidth=1.5;
|
ctx.strokeStyle="#805ad5";ctx.lineWidth=1.5;
|
||||||
ctx.strokeRect(nx-1,ny-1,noteW+2,ED_NOTE_H+2);
|
ctx.strokeRect(nx-1,ny-1,noteW+2,ED_NOTE_H+2);
|
||||||
}
|
}
|
||||||
// Musical notation symbol on note (always shown if duration exists)
|
// Musical notation symbol + pitch name on note
|
||||||
if(dur>0){
|
if(dur>0||edShowPitch){
|
||||||
var di=edDurInfo(dur);
|
|
||||||
ctx.font="bold 13px sans-serif";
|
|
||||||
ctx.fillStyle=isPlaying?"#000":"rgba(255,255,255,0.9)";
|
|
||||||
ctx.textAlign="center";
|
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";
|
ctx.textAlign="left";
|
||||||
}
|
}
|
||||||
// Note label (when Code/Labels toggled on, or for selected note)
|
// Note label (when Code/Labels toggled on, or for selected note)
|
||||||
if(edShowLabels||idx===edSelected){
|
if(edShowLabels||idx===edSelected){
|
||||||
ctx.font="9px monospace";
|
ctx.font="9px monospace";
|
||||||
var label="t:"+m.t.toFixed(edIsClassical?2:2)+",l:"+m.l;
|
var label="t:"+m.t.toFixed(edIsClassical?2:2)+",l:"+m.l;
|
||||||
|
if(m.f)label+=" "+edFreqToNote(m.f);
|
||||||
if(dur>0){
|
if(dur>0){
|
||||||
var di2=edDurInfo(dur);
|
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 lx=nx;
|
||||||
var ly=ny-4;
|
var ly=ny-4;
|
||||||
// If near top of lane, put below instead
|
if(ly<ED_HEAD+8)ly=ny+ED_NOTE_H+10+(edShowPitch&&m.f?10:0);
|
||||||
if(ly<ED_HEAD+8)ly=ny+ED_NOTE_H+10;
|
|
||||||
ctx.fillStyle=idx===edSelected?"#fff":"rgba(255,255,255,0.7)";
|
ctx.fillStyle=idx===edSelected?"#fff":"rgba(255,255,255,0.7)";
|
||||||
ctx.fillText(label,lx,ly);
|
ctx.fillText(label,lx,ly);
|
||||||
}
|
}
|
||||||
@@ -5206,12 +5263,16 @@ function edMouseDown(e){
|
|||||||
var cv=getEdCanvas(),rect=cv.getBoundingClientRect();
|
var cv=getEdCanvas(),rect=cv.getBoundingClientRect();
|
||||||
var mx=e.clientX-rect.left,my=e.clientY-rect.top;
|
var mx=e.clientX-rect.left,my=e.clientY-rect.top;
|
||||||
|
|
||||||
// Double-click on header = play from this position
|
// Click on header/timeline area = set play-from marker
|
||||||
if(e.detail===2&&my<ED_HEAD){
|
if(my<ED_HEAD){
|
||||||
var startT=edXToTime(mx);
|
var startT=edXToTime(mx);
|
||||||
if(startT<0)startT=0;
|
if(startT<0)startT=0;
|
||||||
edPlayheadOffset=startT;
|
edPlayFromMarker=startT;
|
||||||
edPreview(startT);
|
edDraw();
|
||||||
|
// Double-click starts playback immediately from this position
|
||||||
|
if(e.detail===2){
|
||||||
|
edPreview(startT);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5376,9 +5437,9 @@ function edKeyDown(e){
|
|||||||
e.preventDefault();return;
|
e.preventDefault();return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape — clear selection range
|
// Escape — clear selection range and play-from marker
|
||||||
if(e.key==="Escape"){
|
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){
|
if((e.key==="Delete"||e.key==="Backspace")&&edSelected>=0){
|
||||||
@@ -5464,6 +5525,11 @@ function edKeyDown(e){
|
|||||||
cv.addEventListener("mouseleave",edMouseUp);
|
cv.addEventListener("mouseleave",edMouseUp);
|
||||||
cv.addEventListener("wheel",edWheel,{passive:false});
|
cv.addEventListener("wheel",edWheel,{passive:false});
|
||||||
document.addEventListener("keydown",edKeyDown);
|
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();}
|
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;}
|
if(edPreviewing){stopPreview();edPreviewing=false;edStopPlayhead();document.getElementById("ed-pv-btn").textContent="▶ Preview";return;}
|
||||||
var ac=getAc(true);ac.resume();
|
var ac=getAc(true);ac.resume();
|
||||||
var piece=edPiece;
|
var piece=edPiece;
|
||||||
var offset=typeof startFrom==="number"?startFrom:0;
|
var offset=typeof startFrom==="number"?startFrom:(edPlayFromMarker>=0?edPlayFromMarker:0);
|
||||||
edPlayheadOffset=offset;
|
edPlayheadOffset=offset;
|
||||||
if(offset===0)edScrollX=0; // reset scroll only when starting from beginning
|
if(offset===0)edScrollX=0; // reset scroll only when starting from beginning
|
||||||
if(edIsClassical){
|
if(edIsClassical){
|
||||||
@@ -5639,6 +5705,12 @@ function edExport(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Code view toggle — shows labels on canvas + code panel
|
// 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(){
|
function edToggleCode(){
|
||||||
edShowLabels=!edShowLabels;
|
edShowLabels=!edShowLabels;
|
||||||
var btn=document.getElementById("ed-code-btn");
|
var btn=document.getElementById("ed-code-btn");
|
||||||
|
|||||||
Reference in New Issue
Block a user