diff --git a/index.html b/index.html
index b885cfd..23fe8a9 100644
--- a/index.html
+++ b/index.html
@@ -214,6 +214,39 @@ canvas{display:block;}
Click=select | Dbl-click=add note | Drag=move | Drag edge=resize | 1-5=note value | Shift+↑↓=pitch | ↑↓=lane | ←→=nudge time | D=+dur | Shift+D=-dur | Shift+drag=select range | Ctrl+C/V=copy/paste | Del=delete | Scroll=pan | Click header=set start | Dbl-click header=play from there
+
+
JavaScript — edit and click "Apply Code" to update the timeline. Drag bottom edge to resize.
@@ -4912,6 +4945,112 @@ function edFreqToNote(freq){
return best;
}
+// Note name to frequency (reverse of edFreqToNote)
+// Accepts: C4, D#5, Db3, Eb4, F#3, Bb2, etc.
+function edNoteToFreq(name){
+ if(!name||name==="?")return 0;
+ name=name.trim();
+ var match=name.match(/^([A-Ga-g])(#{1,2}|b{1,2}|♯|♭)?(\d)$/);
+ if(!match)return 0;
+ var letter=match[1].toUpperCase();
+ var accidental=match[2]||"";
+ var octave=parseInt(match[3]);
+ var semitones={"C":0,"D":2,"E":4,"F":5,"G":7,"A":9,"B":11}[letter];
+ if(semitones===undefined)return 0;
+ if(accidental==="#"||accidental==="♯")semitones+=1;
+ else if(accidental==="##")semitones+=2;
+ else if(accidental==="b"||accidental==="♭")semitones-=1;
+ else if(accidental==="bb")semitones-=2;
+ // MIDI note number: C4=60, A4=69
+ var midi=(octave+1)*12+semitones;
+ return parseFloat((440*Math.pow(2,(midi-69)/12)).toFixed(2));
+}
+
+// Duration name to seconds
+function edDurNameToSec(name){
+ var q=edQuarterSec();
+ var map={"whole":q*4,"dotted-half":q*3,"half":q*2,"dotted-quarter":q*1.5,"quarter":q,"dotted-eighth":q*0.75,"eighth":q/2,"sixteenth":q/4,"32nd":q/8};
+ return map[name]||0;
+}
+
+// Duration seconds to name (for dropdown selection)
+function edDurSecToName(durSec){
+ if(!durSec||durSec<=0)return "0";
+ var q=edQuarterSec();
+ var ratio=durSec/q;
+ if(ratio>=3.5) return "whole";
+ if(ratio>=2.5) return "dotted-half";
+ if(ratio>=1.8) return "half";
+ if(ratio>=1.3) return "dotted-quarter";
+ if(ratio>=0.8) return "quarter";
+ if(ratio>=0.6) return "dotted-eighth";
+ if(ratio>=0.4) return "eighth";
+ if(ratio>=0.2) return "sixteenth";
+ return "32nd";
+}
+
+// Update note properties panel when selection changes
+function edUpdateNotePanel(){
+ var panel=document.getElementById("ed-note-panel");
+ if(edSelected<0||edSelected>=edMelody.length){
+ panel.style.display="none";
+ return;
+ }
+ panel.style.display="block";
+ var m=edMelody[edSelected];
+ // Pitch input
+ var pitchInput=document.getElementById("ed-note-pitch");
+ pitchInput.value=m.f?edFreqToNote(m.f):"";
+ // Duration dropdown
+ var durSel=document.getElementById("ed-note-dur");
+ durSel.value=edDurSecToName(m.d||0);
+ // Lane dropdown
+ document.getElementById("ed-note-lane").value=m.l;
+ // Info line
+ var info="t="+m.t.toFixed(3)+"s";
+ if(m.f)info+=" | freq="+m.f.toFixed(1)+"Hz";
+ if(m.d>0)info+=" | dur="+m.d.toFixed(3)+"s";
+ document.getElementById("ed-note-info").textContent=info;
+ // Resize canvas since panel may have appeared
+ setTimeout(edResize,30);
+}
+
+// Set pitch from note panel input
+function edSetPitch(val){
+ if(edSelected<0)return;
+ var freq=edNoteToFreq(val);
+ if(freq>0){
+ edMelody[edSelected].f=freq;
+ }else if(val.trim()===""){
+ delete edMelody[edSelected].f;
+ }else{
+ document.getElementById("ed-note-info").textContent="Invalid pitch — use e.g. C4, D#5, Bb3";
+ return;
+ }
+ edDraw();
+ edUpdateNotePanel();
+}
+
+// Set duration from note panel dropdown
+function edSetDuration(val){
+ if(edSelected<0)return;
+ if(val==="0"){
+ delete edMelody[edSelected].d;
+ }else{
+ edMelody[edSelected].d=parseFloat(edDurNameToSec(val).toFixed(4));
+ }
+ edDraw();
+ edUpdateNotePanel();
+}
+
+// Set lane from note panel dropdown
+function edSetLane(val){
+ if(edSelected<0)return;
+ edMelody[edSelected].l=parseInt(val);
+ edDraw();
+ edUpdateNotePanel();
+}
+
function getEdCanvas(){return document.getElementById("ed-canvas");}
function getEdCtx(){return getEdCanvas().getContext("2d");}
function edPxPerUnit(){return parseInt(document.getElementById("ed-zoom").value);}
@@ -5235,6 +5374,7 @@ function edDraw(){
statusTxt+=" | Click=add, Shift+drag=select range";
}
document.getElementById("ed-status").textContent=statusTxt;
+ edUpdateNotePanel();
}
function edHitTest(x,y){
@@ -5733,8 +5873,14 @@ function edReset(){
edSelected=-1;edDraw();
}
function edExport(){
- var exportMelody=edMelody.map(function(m){var n={t:m.t,l:m.l};if(m.d>0)n.d=m.d;return n;});
- var data=JSON.stringify({name:edPiece.n,bpm:edBpm,type:edIsClassical?"classical":"pattern",melody:exportMelody},null,2);
+ var laneNames=["Red","Yellow","Blue","Green","Kick"];
+ var exportMelody=edMelody.map(function(m){
+ var n={t:m.t,l:m.l,lane:laneNames[m.l]};
+ if(m.f){n.f=m.f;n.pitch=edFreqToNote(m.f);}
+ if(m.d>0){n.d=m.d;var di=edDurInfo(m.d);n.duration=di.name;}
+ return n;
+ });
+ var data=JSON.stringify({name:edPiece.n,bpm:edBpm,timeSig:edTimeSig[0]+"/"+edTimeSig[1],type:edIsClassical?"classical":"pattern",melody:exportMelody},null,2);
navigator.clipboard.writeText(data).then(function(){
document.getElementById("ed-status").textContent="Copied to clipboard!";
}).catch(function(){
@@ -5764,26 +5910,38 @@ function edToggleCode(){
}
function edUpdateCode(){
var ta=document.getElementById("ed-code");
+ var laneNames=["Red","Yel","Blu","Grn","Kick"];
if(edIsClassical){
var lines=edMelody.map(function(m){
var s=" {t:"+m.t.toFixed(3)+", l:"+m.l;
+ if(m.f)s+=", f:"+m.f.toFixed(2);
if(m.d>0)s+=", d:"+m.d.toFixed(3);
- return s+"}";
+ s+="}";
+ // Human-readable comment
+ var comment=" // "+laneNames[m.l];
+ if(m.f)comment+=" "+edFreqToNote(m.f);
+ if(m.d>0){var di=edDurInfo(m.d);comment+=" "+di.sym+" "+di.name;}
+ return s+","+comment;
});
ta.value="// "+edPiece.n+" — melody ("+edMelody.length+" notes)\n"+
- "// BPM: "+edBpm+", Duration: "+(edPiece.duration||"?")+"s\n"+
- "// Lanes: 0=Red 1=Yellow 2=Blue 3=Green 4=Kick | d=duration(s)\n"+
- "melody: [\n"+lines.join(",\n")+"\n]";
+ "// BPM: "+edBpm+", Time Sig: "+edTimeSig[0]+"/"+edTimeSig[1]+", Duration: "+(edPiece.duration||"?")+"s\n"+
+ "// Lanes: 0=Red 1=Yellow 2=Blue 3=Green 4=Kick\n"+
+ "// f=frequency(Hz) → pitch name, d=duration(s) → note value\n"+
+ "// Pitch: edit f values or use note names in Apply Code (e.g. f:\"C4\" → converted)\n"+
+ "melody: [\n"+lines.join("\n")+"\n]";
}else{
var lines2=edMelody.map(function(m){
var s=" {t:"+m.t+", l:"+m.l;
if(m.d>0)s+=", d:"+m.d;
- return s+"}";
+ s+="}";
+ var comment=" // "+laneNames[m.l];
+ if(m.d>0){var di=edDurInfo(m.d);comment+=" "+di.sym+" "+di.name;}
+ return s+","+comment;
});
ta.value="// "+edPiece.n+" — pattern ("+edMelody.length+" notes, repeats "+edBars+"x)\n"+
"// BPM: "+edBpm+", Bars: "+edBars+"\n"+
"// t = beat offset (0-4), Lanes: 0=Red 1=Yellow 2=Blue 3=Green 4=Kick | d=duration\n"+
- "p: [\n"+lines2.join(",\n")+"\n]";
+ "p: [\n"+lines2.join("\n")+"\n]";
}
}
function edApplyCode(){
@@ -5797,7 +5955,18 @@ function edApplyCode(){
var arrStr="["+match[1].replace(/(\w+)\s*:/g,'"$1":')+"]";
var arr=JSON.parse(arrStr);
edMelody=arr.filter(function(m){return typeof m.t==="number"&&typeof m.l==="number";})
- .map(function(m){var n={t:m.t,l:m.l};if(m.d>0)n.d=m.d;return n;});
+ .map(function(m){
+ var n={t:m.t,l:m.l};
+ if(m.d>0)n.d=m.d;
+ // Handle f as number (Hz) or string (note name like "C4")
+ if(typeof m.f==="string"){
+ var freq=edNoteToFreq(m.f);
+ if(freq>0)n.f=freq;
+ }else if(typeof m.f==="number"&&m.f>0){
+ n.f=m.f;
+ }
+ return n;
+ });
edMelody.sort(function(a,b){return a.t-b.t;});
edSelected=-1;edDraw();
document.getElementById("ed-status").textContent="Applied! "+edMelody.length+" notes loaded from code.";
@@ -5821,7 +5990,15 @@ function edDoImport(){
var arr=Array.isArray(data)?data:(data.melody||data.p||[]);
if(!Array.isArray(arr)||arr.length===0){document.getElementById("ed-import-status").textContent="No notes found";return;}
edMelody=arr.filter(function(m){return typeof m.t==="number"&&typeof m.l==="number";})
- .map(function(m){var n={t:m.t,l:m.l};if(m.d>0)n.d=m.d;return n;});
+ .map(function(m){
+ var n={t:m.t,l:m.l};
+ if(m.d>0)n.d=m.d;
+ // Handle pitch as note name string or "pitch" field from export
+ if(typeof m.f==="number"&&m.f>0){n.f=m.f;}
+ else if(typeof m.f==="string"){var freq=edNoteToFreq(m.f);if(freq>0)n.f=freq;}
+ else if(typeof m.pitch==="string"){var freq2=edNoteToFreq(m.pitch);if(freq2>0)n.f=freq2;}
+ return n;
+ });
edMelody.sort(function(a,b){return a.t-b.t;});
if(data.bpm)edBpm=data.bpm;
document.getElementById("ed-bpm").value=edBpm;