@@ -539,8 +531,7 @@ function laneMetaHTML(m){ const eff=laneNoteValue(m);
const ref=(meters[0]?meters[0].beatsPerBar:m.beatsPerBar);
const poly=m.poly?"
↻"+m.beatsPerBar+":"+ref+"":"";
return "
"+esc(m.sound)+""+rhythmSVG(eff)+""+esc(m.groupsStr)+""+poly; }
-function setLaneMeta(m){ if(!m._meta) return; m._meta.innerHTML=laneMetaHTML(m);
- const rh=m._meta.querySelector(".rh-host"); if(rh) rh.onclick=(e)=>{ e.stopPropagation(); openNotePicker(m._idx); }; }
+function setLaneMeta(m){ if(!m._meta) return; m._meta.innerHTML=laneMetaHTML(m); }
function buildLanes(){
const box=$("lanes"); box.innerHTML="";
meters.forEach((m,i)=>{
@@ -571,9 +562,6 @@ function renderNoteOpts(box, cur, pick){ if(!box) return; box.innerHTML="";
function setLaneSub(i,n){ const m=meters[i]; if(!m) return;
rebuildLane(i,{groupsStr:m.groupsStr,stepsPerBeat:n,swing:m.swing,poly:m.poly,enabled:m.enabled,sound:m.sound,gainDb:m.gainDb,beatsOn:m.beatsOn.slice(),orns:(m.orns||[]).slice()});
laneSig=null; renderAll(); saveState(); }
-function openNotePicker(i){ const m=meters[i]; if(!m) return; editLaneIdx=i;
- renderNoteOpts($("noteOpts"), m.stepsPerBeat, (n)=>{ setLaneSub(i,n); closeSheets(); });
- $("laneSheet").classList.remove("open"); $("saveSheet").classList.remove("open"); $("shareSheet").classList.remove("open"); $("scrim").classList.add("open"); $("noteSheet").classList.add("open"); }
function refreshLaneSheetNotes(){ const m=meters[editLaneIdx]; if(!m) return;
renderNoteOpts($("lsNotes"), m.stepsPerBeat, (n)=>{ setLaneSub(editLaneIdx,n); refreshLaneSheetNotes(); }); }
function renderPadPlayheads(){ meters.forEach(m=>{ if(!m._padEls) return; const cur=state.running?m.currentStep:-1;
@@ -595,8 +583,8 @@ function gainLabel(db){ return (db>0?"+":"")+db+" dB"; }
function openLaneSheet(i){ editLaneIdx=i; const m=meters[i]; if(!m) return;
$("lsSound").value=m.sound; $("lsGroup").value=m.groupsStr; $("lsPoly").checked=!!m.poly; $("lsMute").checked=!m.enabled;
$("lsGain").value=m.gainDb||0; $("lsGainVal").textContent=gainLabel(m.gainDb||0); refreshLaneSheetNotes();
- $("saveSheet").classList.remove("open"); $("noteSheet").classList.remove("open"); $("shareSheet").classList.remove("open"); $("scrim").classList.add("open"); $("laneSheet").classList.add("open"); }
-function closeSheets(){ ["laneSheet","saveSheet","noteSheet","shareSheet","scrim"].forEach(id=>$(id).classList.remove("open")); }
+ $("saveSheet").classList.remove("open"); $("shareSheet").classList.remove("open"); $("scrim").classList.add("open"); $("laneSheet").classList.add("open"); }
+function closeSheets(){ ["laneSheet","saveSheet","shareSheet","scrim"].forEach(id=>$(id).classList.remove("open")); }
const closeLaneSheet=closeSheets;
function applyLane(){ const m=meters[editLaneIdx]; if(!m) return;
let grp=($("lsGroup").value||"").trim()||"4";
@@ -615,9 +603,9 @@ function clampInt(v,lo,hi,def){ v=parseInt(v,10); if(isNaN(v)) return def; retur
function flashIp(msg){ $("ipMsg").textContent=msg; setTimeout(()=>{ if($("ipMsg").textContent===msg) $("ipMsg").textContent=""; },1600); }
function buildTrackPanel(){
if(document.activeElement&&document.activeElement.closest&&document.activeElement.closest("#trackpanel")) return; // don't fight the user mid-edit
- const hasEnd=segBars>0;
- $("ipBars").value=segBars||0;
- $("ipThen").style.display=hasEnd?"":"none"; $("ipLoop").style.display=hasEnd?"none":"";
+ const rep=segBars>0;
+ $("ipRepeat").checked=rep; $("ipRepeatRow").classList.toggle("off",!rep);
+ $("ipBars").value=segBars||4;
$("ipEnd").value = curEnd==="stop"?"stop":(curEnd===-1?"prev":"next");
$("ipRamp").checked=ramp.on; $("ipGap").checked=trainer.on;
$("ipRampStart").value=ramp.startBpm||80; $("ipRampAmt").value=ramp.amount||5; $("ipRampEvery").value=ramp.everyBars||4;
@@ -625,15 +613,15 @@ function buildTrackPanel(){
$("ipRampRow").classList.toggle("off",!ramp.on); $("ipGapRow").classList.toggle("off",!trainer.on);
}
function applyTrackPanel(){
- segBars=Math.max(0,parseInt($("ipBars").value,10)||0);
- if(segBars>0){ const e=$("ipEnd").value; curEnd = e==="stop"?"stop":(e==="prev"?-1:1); } else { curEnd=null; } // 0 bars = loop forever
- $("ipThen").style.display=segBars>0?"":"none"; $("ipLoop").style.display=segBars>0?"none":"";
+ if($("ipRepeat").checked){ segBars=Math.max(1,parseInt($("ipBars").value,10)||4); const e=$("ipEnd").value; curEnd = e==="stop"?"stop":(e==="prev"?-1:1); }
+ else { segBars=0; curEnd=null; } // no Repeat = loop forever
+ $("ipRepeatRow").classList.toggle("off",!$("ipRepeat").checked);
ramp.on=$("ipRamp").checked; ramp.startBpm=clampInt($("ipRampStart").value,30,300,80); ramp.amount=clampInt($("ipRampAmt").value,1,50,5); ramp.everyBars=clampInt($("ipRampEvery").value,1,64,4);
trainer.on=$("ipGap").checked; trainer.playBars=clampInt($("ipGapPlay").value,1,32,2); trainer.muteBars=clampInt($("ipGapMute").value,1,32,2);
$("ipRampRow").classList.toggle("off",!ramp.on); $("ipGapRow").classList.toggle("off",!trainer.on);
saveState();
}
-["ipBars","ipEnd","ipRamp","ipGap","ipRampStart","ipRampAmt","ipRampEvery","ipGapPlay","ipGapMute"].forEach(id=>$(id).addEventListener("change",applyTrackPanel));
+["ipRepeat","ipBars","ipEnd","ipRamp","ipGap","ipRampStart","ipRampAmt","ipRampEvery","ipGapPlay","ipGapMute"].forEach(id=>$(id).addEventListener("change",applyTrackPanel));
/* ---- save & library: user set lists/tracks (same store + format as the editor) ---- */
function userSetlists(){ return lsGet(LS_SETLISTS,[]); }
@@ -708,7 +696,7 @@ function openSaveSheet(){
$("saveName").value=currentName()||"My track"; buildSaveTo();
const upd=$("saveUpd"); if(slKey[0]==="s"){ upd.style.display=""; upd.textContent='Update “'+(currentName()||"track")+'”'; } else upd.style.display="none";
$("saveMsg").textContent=""; renderLibrary();
- $("laneSheet").classList.remove("open"); $("noteSheet").classList.remove("open"); $("shareSheet").classList.remove("open"); $("scrim").classList.add("open"); $("saveSheet").classList.add("open"); }
+ $("laneSheet").classList.remove("open"); $("shareSheet").classList.remove("open"); $("scrim").classList.add("open"); $("saveSheet").classList.add("open"); }
$("saveNew").onclick=doSaveAsNew; $("saveUpd").onclick=doUpdate; $("saveDone").onclick=closeSheets; $("saveBtn").onclick=openSaveSheet;
/* ---- share: a track or set list as a link, or paste a string to load ---- */
@@ -721,7 +709,7 @@ function refreshShare(){ $("shareLink").value=shareUrl(); $("shareSeg").querySel
function flashShare(m){ $("shareMsg").textContent=m; setTimeout(()=>{ if($("shareMsg").textContent===m) $("shareMsg").textContent=""; },1600); }
function copyText(s, ok){ if(navigator.clipboard&&navigator.clipboard.writeText){ navigator.clipboard.writeText(s).then(ok,()=>{}); } else { const t=$("shareLink"); t.value=s; t.select(); try{ document.execCommand("copy"); ok(); }catch(e){} refreshShare(); } }
function openShareSheet(){ shareKind="p"; refreshShare(); $("sharePaste").value=""; $("shareMsg").textContent="";
- ["laneSheet","saveSheet","noteSheet"].forEach(id=>$(id).classList.remove("open")); $("scrim").classList.add("open"); $("shareSheet").classList.add("open"); }
+ ["laneSheet","saveSheet"].forEach(id=>$(id).classList.remove("open")); $("scrim").classList.add("open"); $("shareSheet").classList.add("open"); }
$("shareSeg").querySelectorAll("button").forEach(b=>b.onclick=()=>{ shareKind=b.dataset.k; refreshShare(); });
$("shareCopy").onclick=()=>copyText(shareUrl(),()=>flashShare("Link copied ✓"));
$("shareCopyT").onclick=()=>copyText(shareText(),()=>flashShare("Copied ✓"));
@@ -842,8 +830,8 @@ const TOUR=[
{sel:".sels", title:"Pick what to play", text:"Choose a set list and the track within it. Tracks are your practice items — name them for whatever you're working on, even if two share the same beat."},
{sel:"#saveBtn", title:"Save & library", text:"Save the current track — “Save as new”, or “Update” one of yours. The same sheet is your library: make set lists and rename / reorder / delete tracks. It all lives with the full editor too."},
{sel:"#pulse", title:"Tempo", text:"Tap the BPM to tap-tempo, press-and-hold to type an exact value, or drag up/down to scrub. ±10 / ±1 buttons nudge it."},
- {sel:"#lanes", title:"Edit the beat", text:"Each lane is a row of pads that blink on the beat — tap a pad to cycle rest → beat → accent → ghost. Tap the little note icon to pick the note value (eighths, triplets, sixteenths…); tap the lane name for sound, grouping, mute or polymeter. “+ Add lane” for more."},
- {sel:"#trackpanel", title:"Track settings", text:"How many bars to play and what happens then (loop, stop, or jump to the next/previous track), plus a tempo ramp and practice gaps."},
+ {sel:"#lanes", title:"Edit the beat", text:"Each lane is a row of pads that blink on the beat — tap a pad to cycle rest → beat → accent → ghost. Tap a lane's label to set its note value (eighths, triplets, sixteenths…), sound, grouping, mute or polymeter. “+ Add lane” for more."},
+ {sel:"#trackpanel", title:"Track settings", text:"Optional per-track extras (under the lanes): Repeat for N bars then stop / next / prev track, a tempo ramp, and practice gaps."},
{sel:"#bPrac", title:"Practice = a timed session", text:"Play just runs the metronome. Practice times your playing and logs it (not audio): it starts a session clock and Play becomes Stop — start/pause each track, then Stop to save the session."},
{sel:"#sessbar", title:"Your practice log", text:"Review past sessions, add notes, and compare a track across days."},
];