// Rithmic — 2-line percussion staff renderer (SVG)
// Exports (window): MeasureSVG, SystemSVG, NotePickGlyph, ValuePickGlyph, measureWidthPx

const RG = {
  Y_TOP: 34, Y_BOT: 58, Y_MID: 46,
  STEM: 34, HEAD_RX: 6.6, HEAD_RY: 4.6,
  H: 108, COUNT_Y: 96
};

// Handpan spacing is LINEAR in time (a grid): every half-beat slot is the same
// width, rests included. Flow notation keeps the sqrt curve.
function rgAdvance(q, inst) {return inst === 'handpan' ? q * 56 : 13 + Math.sqrt(q) * 23;}

// Handpan geometry: ONE line, one lane — every glyph writes BELOW the line.
// The glyph itself names the note (D = ding, 1–8 = tone fields, S/T/K =
// slap/tap/knock); a second simultaneous note (s2) stacks under the first.
const HP = { UP: RG.Y_MID - 13, DOWN: RG.Y_MID + 13, TOP: RG.Y_MID - 38, SX: 9, LANE: 26 };
function rgHpUp(ev) {return false;}

// ---- handpan hand signs -----------------------------------------------------
// An event carries up to 4 simultaneous notes (s, s2, s3, s4), max two per
// hand; ev.h aligns a hand ('R' | 'L') to each. Documents saved before hand
// signs existed have no ev.h — they fall back to tone-field parity (odd
// fields 1/3/5/7 → right, even 2/4/6/8 → left) and D/S/T/K alternate R, L.
function rithmicHpNotes(ev) {
  const ns = [ev.s, ev.s2, ev.s3, ev.s4].filter((x) => x && x !== 'R');
  return ns.map((n, i) => ({ n, h: ev.h && (ev.h[i] === 'R' || ev.h[i] === 'L') ? ev.h[i] : /^[1-8]$/.test(n) ? (+n % 2 ? 'R' : 'L') : (i % 2 ? 'L' : 'R') }));
}
// write a [{n, h}] list back onto an event's s/s2/s3/s4 + h fields
function rithmicHpWrite(ev, arr) {
  delete ev.s2;delete ev.s3;delete ev.s4;delete ev.h;
  if (!arr.length) {ev.s = 'R';return ev;}
  ev.s = arr[0].n;
  if (arr[1]) ev.s2 = arr[1].n;
  if (arr[2]) ev.s3 = arr[2].n;
  if (arr[3]) ev.s4 = arr[3].n;
  ev.h = arr.map((x) => x.h);
  return ev;
}
// R / L legend at a staff-line start (hand-sign mode only): LEFT hand above
// the line, RIGHT hand below
// Air between the L/R gutter and the start of the staff, so the labels do not
// sit against the opening barline.
const HP_HAND_PAD = 20;

function RgHandLegend() {
  return (
    <g className="rg-hplegend">
      <text x="3" y={HP.UP + 3} textAnchor="start">L</text>
      <text x="3" y={HP.DOWN + 3} textAnchor="start">R</text>
    </g>);
}

// Vertical position of a stroke's notehead, per instrument.
//   bendir (2-line):  Düm = top line,    Tek = bottom line
//   erbane (3-line):  Ka = top line, Tek = middle line, Düm = bottom line
//   handpan (1-line): every note below the line
function rgHeadY(s, inst, ev) {
  if (inst === 'handpan') return HP.DOWN;
  if (inst === 'erbane') {
    if (s === 'K') return RG.Y_TOP;
    if (s === 'D') return RG.Y_BOT;
    return RG.Y_MID; // tek (rest uses mid, but rests render centred regardless)
  }
  return s === 'D' ? RG.Y_TOP : RG.Y_BOT;
}
// Which strokes hang a down-stem (head sits high on the staff).
// Erbane: every stem points downward (house convention). Bendir: only düm.
// Handpan: stems always rise to one shared height above the staff, so a beam
// can span both hands and the lanes stay legible.
function rgStemDown(s, inst, ev) {
  if (inst === 'handpan') return false;
  return inst === 'erbane' ? true : s === 'D';
}

// Extra horizontal lead (px) to insert BEFORE event i, on top of the previous
// note's advance. Two cases need breathing room:
//   • a rest, so it doesn't crowd the preceding note;
//   • a düm right after a tek — the tek's up-stem (and flag) and the düm's
//     high notehead + down-stem both sit at the top of the staff, so without
//     extra space they collide.
function rgLead(events, i, inst = 'bendir') {
  if (i <= 0) return 0;
  if (inst === 'handpan') return 0; // uniform grid — every slot is a half beat
  const ev = events[i],prev = events[i - 1];
  if (ev.s === 'R') return 10;
  if (inst === 'handpan') {
    // text glyphs are wider than noteheads and each carries a stem to its
    // right, so neighbours need a touch more air than the drums do
    return prev && prev.s !== 'R' ? 6 : 3;
  }
  if (inst === 'erbane') {
    // ka sits high with a down-stem; a tek before it has an up-stem + flags at
    // the top, so a ka straight after a tek crowds badly — give it room.
    let lead = 0;
    if (ev.s === 'K' && prev && prev.s === 'T') lead = 13;
    // and a tek straight after a ka: the ka's down-stem/flag leans into the tek
    else if (ev.s === 'T' && prev && prev.s === 'K') lead = 8;
    // chained (zincir) notes carry an open ring on the stem; consecutive
    // chained notes crowd, so give them extra breathing room.
    if (ev.zincir || (prev && prev.zincir)) lead = Math.max(lead, 13);
    return lead;
  }
  if (ev.s === 'D' && prev && prev.s === 'T') return 11;
  return 0;
}

// Effective sounding duration in quarter-units (a dot adds half the value)
function rgDur(ev) {return ev.q * (ev.dot ? 1.5 : 1);}

function rgStartX(showClef, showSig) {
  return 10 + (showClef ? 24 : 0) + (showSig ? 34 : 0);
}

function measureWidthPx(events, showClef, showSig, inst = 'bendir') {
  const inner = events.reduce((a, e, i) => a + rgAdvance(rgDur(e), inst) + rgLead(events, i, inst), 0);
  // handpan: +4 so the closing measure line (drawn at w-4, the true grid
  // boundary) keeps every cell exactly the same width
  return rgStartX(showClef, showSig) + Math.max(inner, 60) + (inst === 'handpan' ? 4 : 12);
}

// ---- atoms ----------------------------------------------------------------

function RgStaffLines({ x0, x1, inst = 'bendir' }) {
  if (inst === 'handpan') {
    return (
      <g className="rg-staff rg-staff-hp">
        <line x1={x0} y1={RG.Y_MID} x2={x1} y2={RG.Y_MID}></line>
      </g>);

  }
  const ys = inst === 'erbane' ? [RG.Y_TOP, RG.Y_MID, RG.Y_BOT] : [RG.Y_TOP, RG.Y_BOT];
  return (
    <g className="rg-staff">
      {ys.map((y, i) =>
      <line key={i} x1={x0} y1={y} x2={x1} y2={y} style={{ stroke: "rgb(194, 187, 172)" }}></line>
      )}
    </g>);

}

function RgClef({ x, clef = 'perc', onClick = null, inst = 'bendir' }) {
  const clickable = !!onClick;
  const hit = clickable ?
  <rect className="rg-clefhit" x={x - 4} y={RG.Y_TOP - 14} width="28"
  height={RG.Y_BOT - RG.Y_TOP + 30}></rect> : null;
  const body = clef === 'treble' ?
  <text className="rg-trebleclef" x={x + 7} y={RG.Y_BOT + 6} textAnchor="middle">{'\uD834\uDD1E'}</text> :
  <React.Fragment>
      <rect x={x} y={RG.Y_TOP - 1} width="4.5" height={RG.Y_BOT - RG.Y_TOP + 2} rx="1"></rect>
      <rect x={x + 8} y={RG.Y_TOP - 1} width="4.5" height={RG.Y_BOT - RG.Y_TOP + 2} rx="1"></rect>
    </React.Fragment>;
  return (
    <g className={'rg-clef' + (clickable ? ' clickable' : '')}
    onClick={clickable ? (e) => {e.stopPropagation();onClick(e);} : undefined}>
      {hit}
      {body}
    </g>);

}

function RgTimeSig({ x, sig, inst = 'bendir' }) {
  return (
    <g className="rg-timesig">
      <text x={x} y={RG.Y_MID - 2} textAnchor="middle">{sig[0]}</text>
      <text x={x} y={RG.Y_MID + 21} textAnchor="middle">{sig[1]}</text>
    </g>);

}

function RgFlag({ sx, ty, n, down }) {
  // A down-stem (düm) flag is the vertical mirror of an up-stem flag: it
  // attaches at the end of the stem and sweeps away-and-right. Each flag is an
  // OPEN hook (it does not curl back toward the stem), and successive flags are
  // parallel copies offset along the stem — so a 16th's two flags read as two
  // separate hooks rather than crossing into a closed loop.
  const SEP = 9; // spacing between stacked flags, along the stem
  const flags = [];
  for (let i = 0; i < n; i++) {
    const s = down ? -1 : 1; // sweep direction (down-stem mirrors up)
    const y = ty + s * i * SEP;
    flags.push(
      <path key={i} d={
      `M ${sx} ${y}` +
      ` C ${sx + 9} ${y + s * 3}, ${sx + 13} ${y + s * 9}, ${sx + 12} ${y + s * 17}`
      }></path>
    );
  }
  return <g className="rg-flag">{flags}</g>;
}

function RgNote({ x, ev, hl, onClick, dimmable, beamed, inst = 'bendir', hands = false }) {
  // ---- zincir (chain) note: headless — a stem crossing the staff with an open
  // ring sitting on the middle line. Carries values like any note (stem +
  // flags) but never draws a notehead.
  if (ev.s === 'Z') {
    const ringCy = RG.Y_MID;
    const hasStem = ev.q < 4; // whole = ring only, no stem
    const nFlags = beamed ? 0 : ev.q === 0.5 ? 1 : ev.q === 0.25 ? 2 : 0;
    const flagExtra = nFlags === 2 ? 12 : nFlags === 1 ? 6 : 0;
    // the half note carries only a short stub below the ring so it reads clearly
    // apart from the quarter (which keeps the full-length stem); whole = ring only.
    const belowLen = ev.q === 2 ? 13 : RG.STEM;
    const tipY = ringCy + belowLen + flagExtra; // down-stem
    const cls = 'rg-note rg-chain' + (hl ? ' hl' : '') + (dimmable ? ' clickable' : '');
    return (
      <g className={cls} onClick={onClick}>
        {dimmable ? <rect className="rg-hit" x={x - 12} y="6" width="28" height="86"></rect> : null}
        {hasStem ? <line className="rg-stem" x1={x} y1={RG.Y_TOP} x2={x} y2={tipY}></line> : null}
        {nFlags > 0 ? <RgFlag sx={x} ty={tipY} n={nFlags} down={true}></RgFlag> : null}
        <circle className="rg-chainring" cx={x} cy={ringCy} r="6.5"></circle>
        {ev.dot ? <circle className="rg-dot" cx={x + 11} cy={ringCy - 2} r="2"></circle> : null}
      </g>);

  }
  // ---- handpan: pure grid tab, no musical-note anatomy. The glyph IS the
  // note — D for the ding, 1–8 for the tone fields, S / T / K for slap, tap and
  // knock. Every glyph writes below the line; each slot is a half beat, so
  // position alone carries the timing (no stems, flags or beams). Two notes
  // struck together: `s2` stacks beneath the first.
  if (inst === 'handpan') {
    const cls = 'rg-note rg-hp hp-down' + (hl ? ' hl' : '') + (dimmable ? ' clickable' : '');
    const notes = rithmicHpNotes(ev);
    let texts;
    if (hands) {
      // hand-sign mode: LEFT hand ABOVE the line, right hand below; a hand's
      // second note stacks outward from the line
      const rh = notes.filter((n) => n.h === 'R'),lh = notes.filter((n) => n.h !== 'R');
      texts = [
      ...lh.map((n, i) => <text key={'l' + i} className={'rg-hphead' + (i ? ' rg-hp2' : '')}
      x={x} y={HP.UP - i * 20} textAnchor="middle" dominantBaseline="central">{n.n}</text>),
      ...rh.map((n, i) => <text key={'r' + i} className={'rg-hphead' + (i ? ' rg-hp2' : '')}
      x={x} y={HP.DOWN + i * 20} textAnchor="middle" dominantBaseline="central">{n.n}</text>)];
    } else {
      // hand signs off: every note stacks below the line (3+ compress to fit)
      const small = notes.length > 2;
      texts = notes.map((n, i) =>
      <text key={i} className={'rg-hphead' + (i ? ' rg-hp2' : '') + (small ? ' rg-hpsmall' : '')}
      x={x} y={small ? 55 + i * 13.5 : HP.DOWN + i * 22}
      textAnchor="middle" dominantBaseline="central">{n.n}</text>);
    }
    return (
      <g className={cls} onClick={onClick}>
        {dimmable ? <rect className="rg-hit" x={x - 12} y="6" width="30" height="86"></rect> : null}
        {texts}
      </g>);

  }
  const isDum = ev.s === 'D';
  const y = rgHeadY(ev.s, inst, ev);
  const open = ev.q >= 2; // whole + half: open head
  const hasStem = ev.q < 4 && !beamed; // whole: no stem; beamed: stem drawn by beam group
  const nFlags = beamed ? 0 : ev.q === 0.5 ? 1 : ev.q === 0.25 ? 2 : 0;
  // A high-sitting head (bendir düm, erbane ka) hangs a downward stem.
  const stemDown = rgStemDown(ev.s, inst, ev) && !beamed && ev.q < 4;
  // a tek carrying a tremolo gets a slightly longer stem for legibility; a
  // flagged note carrying a zincir ring lengthens so the ring fits cleanly
  // between the notehead and the flags
  const zincirSpan = ev.zincir ? (nFlags === 2 ? 26 : nFlags === 1 ? 17 : 0) : 0;
  const zincirExtra = zincirSpan ? Math.max(0, 23 + zincirSpan - RG.STEM) : 0;
  const stemLen = (!isDum && ev.tremolo && hasStem ? RG.STEM + 12 : RG.STEM) + zincirExtra;
  const sx = stemDown ? x - RG.HEAD_RX + 0.6 : x + RG.HEAD_RX - 0.6;
  const ty = stemDown ? y + stemLen : y - stemLen;
  const stemY0 = stemDown ? y + 2 : y - 2;
  const cls = 'rg-note' + (hl ? ' hl' : '') + (dimmable ? ' clickable' : '');

  // ---- articulations ----
  const arts = [];
  // bendir-only articulations (muted düm, tek tenuto/tremolo)
  if (inst !== 'erbane' && isDum && ev.muted) {
    // muted stroke: caret on the side of the head away from the stem
    const baseY = stemDown ? y - 12 : y + 12;
    const tipY = stemDown ? y - 21 : y + 21;
    arts.push(
      <path key="mut" className="rg-artic rg-muted"
      d={`M ${x - 6} ${baseY} L ${x} ${tipY} L ${x + 6} ${baseY}`}></path>
    );
  }
  if (inst !== 'erbane' && !isDum && ev.tenuto) {
    // tenuto: short line just below the tek head
    arts.push(
      <line key="ten" className="rg-artic rg-tenuto"
      x1={x - 6.5} y1={y + 10} x2={x + 6.5} y2={y + 10}></line>
    );
  }
  if (inst !== 'erbane' && !isDum && ev.tremolo) {
    // tremolo: three slashes across the (lengthened) stem
    const tsx = x + RG.HEAD_RX - 0.6;
    const base = y - stemLen + 15;
    for (let i = 0; i < 3; i++) {
      const yy = base + i * 6;
      arts.push(
        <line key={'tr' + i} className="rg-artic rg-trem"
        x1={tsx - 6} y1={yy + 3.2} x2={tsx + 6} y2={yy - 3.2}></line>
      );
    }
  }
  // erbane "zincir": an open ring around the stem (the chain mark), sitting
  // near the head so a flagged note's flags (at the stem tip) stay clear
  if (ev.zincir && ev.q < 4) {
    // centre the ring in the clear span between the notehead's far edge and the
    // flags (or the stem tip when unflagged) so it never touches the head
    const headEdge = stemDown ? y + 5 : y - 5;
    const flagEdge = zincirSpan ? (stemDown ? ty - zincirSpan : ty + zincirSpan) : ty;
    const cy = (headEdge + flagEdge) / 2;
    arts.push(
      <circle key="zin" className="rg-artic rg-zincir" cx={sx} cy={cy} r="7"></circle>
    );
  }

  return (
    <g className={cls} onClick={onClick}>
      {dimmable ? <rect className="rg-hit" x={x - 12} y="6" width="28" height="86"></rect> : null}
      {hasStem ? <line className="rg-stem" x1={sx} y1={stemY0} x2={sx} y2={ty}></line> : null}
      {nFlags > 0 ? <RgFlag sx={sx} ty={ty} n={nFlags} down={stemDown}></RgFlag> : null}
      <ellipse className={open ? 'rg-head open' : 'rg-head'}
      cx={x} cy={y} rx={RG.HEAD_RX} ry={RG.HEAD_RY}
      transform={`rotate(-18 ${x} ${y})`}></ellipse>
      {ev.dot ? <circle className="rg-dot" cx={x + RG.HEAD_RX + 6} cy={y - 4} r="2"></circle> : null}
      {arts}
    </g>);

}

// ---- beaming -------------------------------------------------------------
// Group consecutive beamable notes (eighths/sixteenths, not rests) that fall
// inside the same beat. Returns an array of index-arrays (length >= 2).
function rgBeamGroups(events, beatUnit, inst) {
  const groups = [];
  let cur = [],curBeat = -1,pos = 0,curHand = null;
  const erbane = inst === 'erbane';
  const handpan = inst === 'handpan';
  const beatOf = (p) => Math.floor(p / beatUnit + 1e-6);
  const flush = () => {if (cur.length >= 2) groups.push(cur);cur = [];};
  events.forEach((ev, i) => {
    // Bendir: only tek notes beam together (no articulated notes).
    // Erbane: every stroke (düm / tek / ka) links to its neighbours — a single
    // beam spans the whole run of consecutive short notes, broken only by rests,
    // long notes, or a zincir mark.
    const beamable = handpan ?
    false : // handpan is grid tab — no beams
    erbane ?
    ev.s !== 'R' && ev.s !== 'Z' && ev.q < 0.999 && !ev.zincir :
    ev.s === 'T' && ev.q < 0.999 && !ev.tremolo && !ev.muted && !ev.zincir;
    const b = beatOf(pos);
    if (beamable) {
      if (cur.length && b !== curBeat) flush();
      if (!cur.length) {curBeat = b;curHand = null;}
      cur.push(i);
    } else {
      flush();
    }
    pos += rgDur(ev);
  });
  flush();
  return groups;
}

function rgBeams(ev) {return ev.s !== 'R' ? ev.q === 0.5 ? 1 : ev.q === 0.25 ? 2 : 0 : 0;}

function RgBeamGroup({ notes, down, stem = RG.STEM, fixedBeamY = null }) {
  const BEAM_H = 4,GAP = 3,STUB = 8;
  const beamY = fixedBeamY != null ? fixedBeamY : down ?
  Math.max(...notes.map((n) => n.headY)) + stem :
  Math.min(...notes.map((n) => n.headY)) - stem;
  const x1 = notes[0].sx,x2 = notes[notes.length - 1].sx;
  const els = [];
  notes.forEach((n, i) => {
    els.push(<line key={'s' + i} className="rg-stem" x1={n.sx} y1={down ? n.headY + 2 : n.headY - 2} x2={n.sx} y2={beamY}></line>);
  });
  els.push(<rect key="b1" className="rg-beam" x={x1} y={beamY} width={Math.max(x2 - x1, 1)} height={BEAM_H}></rect>);
  // secondary beams (level 2) for sixteenths — sit between primary beam and heads
  const y2 = down ? beamY - BEAM_H - GAP : beamY + BEAM_H + GAP;
  let i = 0;
  while (i < notes.length) {
    if (notes[i].beams >= 2) {
      let j = i;
      while (j + 1 < notes.length && notes[j + 1].beams >= 2) j++;
      if (j > i) {
        els.push(<rect key={'b2' + i} className="rg-beam" x={notes[i].sx} y={y2}
        width={notes[j].sx - notes[i].sx} height={BEAM_H}></rect>);
      } else {
        const sx = notes[i].sx;
        const right = i === 0; // first note stubs right, others stub left
        els.push(<rect key={'b2' + i} className="rg-beam" x={right ? sx : sx - STUB} y={y2}
        width={STUB} height={BEAM_H}></rect>);
      }
      i = j + 1;
    } else i++;
  }
  return <g className="rg-beamgroup">{els}</g>;
}

function RgRest({ x, ev, hl, onClick, dimmable, inst = 'bendir' }) {
  const m = RG.Y_MID;
  const cls = 'rg-rest' + (hl ? ' hl' : '') + (dimmable ? ' clickable' : '');
  // handpan: an empty slot in the grid — blank space (the staff's grid ticks
  // already mark the subdivisions); keeps its hit zone so it stays selectable
  if (inst === 'handpan') {
    return (
      <g className={cls} onClick={onClick}>
        {dimmable ? <rect className="rg-hit" x={x - 12} y="6" width="28" height="86"></rect> : null}
      </g>);

  }
  const dot = ev.dot ? <circle className="rg-dot" cx={x + 10} cy={m - 4} r="2"></circle> : null;
  let body = null;
  if (ev.q >= 4) {
    body = <rect x={x - 7} y={RG.Y_TOP} width="14" height="6"></rect>;
  } else if (ev.q >= 2) {
    body = <rect x={x - 7} y={RG.Y_BOT - 6} width="14" height="6"></rect>;
  } else if (ev.q >= 1) {
    body =
    <path className="rg-qrest"
    d={`M ${x - 2} ${m - 11} L ${x + 4} ${m - 4} L ${x - 1} ${m + 1} L ${x + 5} ${m + 7} Q ${x - 2} ${m + 5} ${x + 1} ${m + 12}`}></path>;

  } else {
    const hooks = ev.q === 0.25 ? 2 : 1;
    const kids = [];
    for (let i = 0; i < hooks; i++) {
      const hy = m - 8 + i * 8;
      kids.push(<circle key={'c' + i} cx={x - 4} cy={hy + 1.5} r="2.4"></circle>);
      kids.push(<path key={'p' + i} className="rg-hookline"
      d={`M ${x - 3} ${hy + 3.5} Q ${x + 1} ${hy + 5} ${x + 5} ${hy - 1}`}></path>);
    }
    const x1 = x + 5,y1 = m - 9;
    const x2 = x - 1,y2 = m + 12;
    return (
      <g className={cls} onClick={onClick}>
        {dimmable ? <rect className="rg-hit" x={x - 12} y="6" width="28" height="86"></rect> : null}
        <line className="rg-restline" x1={x1} y1={y1} x2={x2} y2={y2}></line>
        {kids}
        {dot}
      </g>);

  }
  return (
    <g className={cls} onClick={onClick}>
      {dimmable ? <rect className="rg-hit" x={x - 12} y="6" width="28" height="86"></rect> : null}
      {body}
      {dot}
    </g>);

}

function RgEvent(props) {
  return props.ev.s === 'R' ? <RgRest {...props}></RgRest> : <RgNote {...props}></RgNote>;
}

// repeat barline: forward (start ||:), backward (end :||), or both (:||:)
function RgRepeat({ x, dir, inst = 'bendir' }) {
  const yT = inst === 'handpan' ? RG.Y_MID - HP.LANE : RG.Y_TOP;
  const yB = inst === 'handpan' ? RG.Y_MID + HP.LANE : RG.Y_BOT;
  const mid = RG.Y_MID;
  const d1 = mid - 6,d2 = mid + 6;
  const thick = (cx) => <line x1={cx} y1={yT} x2={cx} y2={yB} strokeWidth="4.4"></line>;
  const thin = (cx) => <line x1={cx} y1={yT} x2={cx} y2={yB} strokeWidth="1.5"></line>;
  const dots = (cx, key) => [
  <circle key={key + 'a'} cx={cx} cy={d1} r="2.2"></circle>,
  <circle key={key + 'b'} cx={cx} cy={d2} r="2.2"></circle>];

  if (dir === 'both') {
    return (
      <g className="rg-repeat">
        {dots(x - 12, 'l')}
        {thin(x - 5.5)}
        {thick(x)}
        {thin(x + 5.5)}
        {dots(x + 12, 'r')}
      </g>);

  }
  if (dir === 'forward') {
    return (
      <g className="rg-repeat">
        {thick(x)}
        {thin(x + 5)}
        {dots(x + 11, 'r')}
      </g>);

  }
  return (
    <g className="rg-repeat">
      {dots(x - 11, 'l')}
      {thin(x - 5)}
      {thick(x)}
    </g>);

}

// simile mark: "repeat the previous measure" (slash with two dots).
// An optional `count` draws a dashed number box above the staff — the previous
// measure then sounds (count + 1) times.
function RgSimile({ cx, hl, count = null }) {
  const m = RG.Y_MID;
  const num = count && count >= 1 ? count : null;
  // dashed box sits above the top staff line, centred over the slash
  const boxW = 22, boxH = 22, boxY = 4;
  return (
    <g className={'rg-simile' + (hl ? ' hl' : '')}>
      {num != null ?
      <g className="rg-simile-num">
          <rect className="rg-simile-box" x={cx - boxW / 2} y={boxY} width={boxW} height={boxH} rx="2"></rect>
          <text className="rg-simile-numtext" x={cx} y={boxY + boxH / 2 + 1}
        textAnchor="middle" dominantBaseline="middle">{num}</text>
        </g> : null}
      <line x1={cx - 9} y1={m + 9} x2={cx + 9} y2={m - 9} strokeWidth="4.5" strokeLinecap="round"></line>
      <circle cx={cx - 8} cy={m - 7} r="2.4"></circle>
      <circle cx={cx + 8} cy={m + 7} r="2.4"></circle>
    </g>);

}

function RgBarline({ x, final, inst = 'bendir' }) {
  // handpan: measures are separated by plain TALL lines — the tallest on the
  // staff — with no thick closing barline
  if (inst === 'handpan') {
    return (
      <g className="rg-bar rg-bar-hp">
        <line x1={x} y1={RG.Y_MID - HP.LANE} x2={x} y2={RG.Y_MID + HP.LANE} strokeWidth="1.6"></line>
      </g>);

  }
  const yT = RG.Y_TOP;
  const yB = RG.Y_BOT;
  if (final) {
    return (
      <g className="rg-bar">
        <line x1={x - 5} y1={yT} x2={x - 5} y2={yB} strokeWidth="1.6"></line>
        <line x1={x} y1={yT} x2={x} y2={yB} strokeWidth="4.5"></line>
      </g>);

  }
  return (
    <g className="rg-bar">
      <line x1={x} y1={yT} x2={x} y2={yB} strokeWidth="1.6"></line>
    </g>);

}

// count label for a position (in quarter units) given denominator
function rgCountLabel(posQ, den) {
  const unit = 4 / den;
  const idx = posQ / unit;
  const eps = 0.001;
  if (Math.abs(idx - Math.round(idx)) < eps) return String(Math.round(idx) + 1);
  return '+';
}

// ---- one measure's worth of glyphs (no staff lines) -----------------------

function RgMeasureGlyphs({ events, sig, x0, showCounts, hlIndex, onGlyphClick, dimmable,
  stretch = 1, selIndex = -1, selIndices = null, onInsertAt = null, onEndPlus = null, endPlusX = null, inst = 'bendir', leadLine = false, hands = false }) {
  let x = x0;
  let pos = 0;
  const lay = events.map((ev, i) => {
    if (i > 0) x += rgLead(events, i, inst) * stretch; // breathing room (rests, stem-clash pairs)
    const cx = x + (inst === 'handpan' ? 14 * stretch : 10); // handpan glyphs centre in their grid cell
    const info = {
      i, ev, cx, pos,
      headY: rgHeadY(ev.s, inst, ev),
      sx: inst === 'handpan' ? cx + HP.SX : inst === 'erbane' ? cx - RG.HEAD_RX + 0.6 : cx + RG.HEAD_RX - 0.6,
      beams: rgBeams(ev)
    };
    pos += rgDur(ev);
    x += rgAdvance(rgDur(ev), inst) * stretch;
    return info;
  });
  // Erbane beams by the metre's beat (quarter for x/4, eighth for x/8, …) so a
  // measure shows one group per beat rather than a single beam across everything.
  const den = (sig && sig[1]) || 4;
  const beatUnit = inst === 'erbane' || inst === 'handpan' ? 4 / den : 1;
  const groups = rgBeamGroups(events, beatUnit, inst);
  const beamed = new Set();
  groups.forEach((g) => g.forEach((idx) => beamed.add(idx)));
  const selSet = selIndices ? new Set(selIndices) : selIndex >= 0 ? new Set([selIndex]) : new Set();
  const out = [];
  // handpan grid: gridlines sit at slot BOUNDARIES (never on a note) — a small
  // tick between the two halves of a beat, a taller divider between beats
  // (4/4 → 3 dividers) — so every line is exactly one slot apart and the
  // glyphs sit centred between them
  // handpan grid: gridlines sit at slot BOUNDARIES (never on a note) — a short
  // tick between the two halves of a beat, a longer line after every beat
  // (including the last one before the barline); the full-height barlines mark
  // the measures themselves
  if (inst === 'handpan') {
    const beatQ = 4 / den;
    const totalQ = ((sig && sig[0]) || 4) * beatQ;
    const endQ = totalQ; // the grid always spans the whole measure
    if (leadLine) out.push(<line key="gb0" className="rg-hpbeat" x1={x0} y1={RG.Y_MID - 16} x2={x0} y2={RG.Y_MID + 16}></line>);
    // skip the final beat gridline when it coincides with the closing barline —
    // the (taller) barline IS the measure's closing line
    for (let p = 0.5; p <= endQ + 0.01; p += 0.5) {
      const beat = Math.abs(p / beatQ - Math.round(p / beatQ)) < 1e-6;
      if (p >= totalQ - 0.01) break;
      const tx = x0 + p * 56 * stretch; // boundary between the slots either side of p
      out.push(beat ?
      <line key={'gb' + p} className="rg-hpbeat" x1={tx} y1={RG.Y_MID - 16} x2={tx} y2={RG.Y_MID + 16}></line> :
      <line key={'gt' + p} className="rg-hptick" x1={tx} y1={RG.Y_MID - 8} x2={tx} y2={RG.Y_MID + 8}></line>);
    }
  }
  lay.forEach((info) => {
    if (selSet.has(info.i)) {
      out.push(<rect key={'sb' + info.i} className="rg-selbg"
      x={info.cx - 13} y="11" width="26" height="70" rx="7"></rect>);
    }
    out.push(
      <RgEvent key={info.i} x={info.cx} ev={info.ev} hl={hlIndex === info.i} dimmable={dimmable}
      beamed={beamed.has(info.i)} inst={inst} hands={hands}
      onClick={onGlyphClick ? (e) => onGlyphClick(info.i, !!(e && e.shiftKey)) : undefined}></RgEvent>
    );
    if (showCounts) {
      out.push(
        <text key={'c' + info.i} className="rg-count" x={info.cx} y={RG.COUNT_Y} textAnchor="middle">
          {rgCountLabel(info.pos, sig[1])}
        </text>
      );
    }
  });
  groups.forEach((g, gi) => {
    out.push(<RgBeamGroup key={'bm' + gi} notes={g.map((idx) => lay[idx])}
    down={inst === 'erbane'} fixedBeamY={inst === 'handpan' ? HP.TOP : null}></RgBeamGroup>);
  });
  // hover-plus zones BETWEEN consecutive notes (builder only): clicking inserts
  // a note from the current brush at that position.
  if (onInsertAt && lay.length >= 2) {
    // the zones beside the selected note stay visible (touch has no hover)
    const selAt = selIndices && selIndices.length === 1 ? selIndices[0] : selIndex;
    for (let i = 1; i < lay.length; i++) {
      const ix = (lay[i - 1].cx + lay[i].cx) / 2;
      const pinned = selAt >= 0 && (i === selAt || i === selAt + 1);
      out.push(
        <g key={'ins' + i} className={'rg-noteplus' + (pinned ? ' show' : '')}
        onClick={(e) => {e.stopPropagation();onInsertAt(i);}}>
          <rect className="rg-npzone" x={ix - 7} y="10" width="14" height="72"></rect>
          <g className="rg-npicon" transform={`translate(${ix}, ${RG.Y_MID})`}>
            <circle r="7"></circle>
            <line x1="-3.6" y1="0" x2="3.6" y2="0"></line>
            <line x1="0" y1="-3.6" x2="0" y2="3.6"></line>
          </g>
        </g>
      );
    }
  }
  // always-visible plus AFTER the last note (mobile add-measure page, picker
  // dismissed with room left): tapping it brings the note picker back.
  if (onEndPlus && lay.length) {
    const ix = endPlusX != null ? endPlusX : x + 10;
    out.push(
      <g key="insEnd" className="rg-noteplus show"
      onClick={(e) => {e.stopPropagation();onEndPlus();}}>
        <rect className="rg-npzone" x={ix - 10} y="10" width="20" height="72"></rect>
        <g className="rg-npicon" transform={`translate(${ix}, ${RG.Y_MID})`}>
          <circle r="8"></circle>
          <line x1="-4.2" y1="0" x2="4.2" y2="0"></line>
          <line x1="0" y1="-4.2" x2="0" y2="4.2"></line>
        </g>
      </g>
    );
  }
  return <g>{out}</g>;
}

// ---- standalone single measure (builder, template cards) ------------------

// handpan draws no clef — the perc clef's double bars read as a barline there
function MeasureSVG({ events, sig, showClef = true, showSig = true, showCounts = false,
  hlIndex = -1, selIndex = -1, selIndices = null, onGlyphClick = null, height = null, className = '', clef = 'perc',
  onInsertAt = null, onEndPlus = null, inst = 'bendir', showCaret = false, caretAt = -1, hands = false }) {
  if (inst === 'handpan') showClef = false;
  const handPad = inst === 'handpan' && hands ? HP_HAND_PAD : 0;
  const endPad = onEndPlus && events.length ? 26 : 0;
  const caretOn = showCaret && inst === 'handpan';
  let w = measureWidthPx(events, showClef, showSig, inst) + endPad + (caretOn ? 12 : 0) + handPad;
  // handpan: the grid always spans the FULL measure, notes written or not
  if (inst === 'handpan' && sig) {
    const totalQ = sig[0] * (4 / sig[1]);
    w = Math.max(w, rgStartX(showClef, showSig) + handPad + totalQ * 56 + 4 + endPad + (caretOn ? 12 : 0));
  }
  const x0 = rgStartX(showClef, showSig) + handPad;
  const h = showCounts ? RG.H : RG.H - 8;
  return (
    <svg className={'rg-svg ' + className} viewBox={`0 0 ${w} ${h}`}
    width={w} height={height || h} preserveAspectRatio="xMinYMid meet">
      <RgStaffLines x0={4 + handPad} x1={w - 2} inst={inst}></RgStaffLines>
      {inst === 'handpan' && hands ? <RgHandLegend></RgHandLegend> : null}
      {showClef ? <RgClef x={12 + handPad} clef={clef}></RgClef> : null}
      {showSig ? <RgTimeSig x={(showClef ? 34 : 10) + 12 + handPad} sig={sig} inst={inst}></RgTimeSig> : null}
      <RgMeasureGlyphs events={events} sig={sig} x0={x0} showCounts={showCounts} leadLine={showSig && inst === 'handpan'} hands={hands}
      hlIndex={hlIndex} selIndex={selIndex} selIndices={selIndices} onGlyphClick={onGlyphClick} dimmable={!!onGlyphClick}
      onInsertAt={onInsertAt} onEndPlus={onEndPlus} endPlusX={endPad ? w - 4 - 13 : null} inst={inst}></RgMeasureGlyphs>
      <RgBarline x={w - 4 - (caretOn ? 12 : 0)} final={false} inst={inst}></RgBarline>
      {caretOn ? (() => {
        // caret between notes: at the grid boundary before events[caretAt];
        // caret at the end: just past the last written note
        const mid = caretAt >= 0 && caretAt < events.length;
        const cx = x0 + events.slice(0, mid ? caretAt : events.length).reduce((a, e) => a + rgDur(e), 0) * 56 + (mid ? 0 : 8);
        return (
          <g className="rg-caret">
          <line x1={cx} y1={RG.Y_MID - 3} x2={cx} y2={RG.Y_MID + 24}></line>
        </g>);
      })() : null}
    </svg>);

}

// ---- a system: row of measures on one continuous staff ---------------------
// measures: [{ events, sig, showSig, mi }]  (mi = index in document)
// highlight: { mi, ei } | null

function SystemSVG({ measures, showClef = true, showCounts = false, highlight = null,
  isLast = false, onMeasureClick = null, selectedMi = -1, fillWidth = 0,
  repeatStarts = null, repeatEnds = null, onBoundaryClick = null,
  selectedBoundary = -1, editingMi = -1, voltas = null, onSigClick = null,
  clef = 'perc', onClefClick = null, voltas2 = null, showEndings = false, inst = 'bendir',
  plusPad = 0, plusR = 9.5, onBoundaryPlus = null, hands = false }) {
  const voltaSet = voltas ? new Set(voltas) : null;
  const volta2Set = voltas2 ? new Set(voltas2) : null;
  const hasLabels = measures.some((m) => m.label);
  const hasVolta = showEndings && (
  voltaSet && measures.some((m) => voltaSet.has(m.mi)) ||
  volta2Set && measures.some((m) => volta2Set.has(m.mi)));
  const voltaBand = hasVolta ? 18 : 0;
  const labelBand = hasLabels ? 18 : 0;
  const band = voltaBand + labelBand;
  const handPad = inst === 'handpan' && hands ? HP_HAND_PAD : 0;
  const startX = 10 + (showClef ? 24 : 0) + handPad;
  // ---- justify: stretch note spacing so a full row fills its width ----
  const noteW = measures.map((m) => m.events.reduce((a, e, i) =>
  a + rgAdvance(rgDur(e), inst) + rgLead(m.events, i, inst), 0));
  const fixed = startX + 4 + measures.reduce((a, m) => a + (m.showSig ? 34 : 0) + 18 + plusPad, 0);
  const natSum = measures.reduce((a, w, i) => a + Math.max(noteW[i], 60), 0);
  let stretch = 1;
  if (!isLast && fillWidth > 0 && natSum > 0) {
    stretch = (fillWidth - fixed) / natSum;
    stretch = Math.max(1, Math.min(stretch, 2.6));
  }
  let x = startX;
  const parts = [];
  const zones = [];
  const labels = [];
  const voltaList = [];
  const segs = [];
  const startSet = repeatStarts ? new Set(repeatStarts) : null;
  const endSet = repeatEnds ? new Set(repeatEnds) : null;
  const REP_PAD = 13; // breathing room a repeat sign needs on its open (dotted) side
  const plusW = onBoundaryPlus ? plusR * 2 + 14 : 0; // room for the add-plus drawn beside a repeat sign
  const n = measures.length;
  let pendingLead = 0; // extra space to insert before the next measure (after a fwd/both sign)
  measures.forEach((m, k) => {
    if (k === 0 && plusPad) x += plusPad; // room for the row-start plus icon
    if (pendingLead) {x += pendingLead;pendingLead = 0;}
    const segStart = x;
    const hasFwd = startSet && startSet.has(m.mi);
    const hasEnd = endSet && endSet.has(m.mi + 1);
    const nextHasFwd = k + 1 < n && startSet && startSet.has(measures[k + 1].mi);
    // a start-repeat that opens a row is drawn here; mid-row starts are drawn
    // as the previous measure's boundary divider (so no doubled barline)
    if (hasFwd && k === 0) {
      parts.push(<RgRepeat key={'rs0'} x={x + 3} dir="forward" inst={inst}></RgRepeat>);
      x += REP_PAD + 7 + plusW;
    }
    if (m.showSig) {
      const tx = x + 12;
      parts.push(
        <g key={'ts' + k} className={'rg-timesig-wrap' + (onSigClick ? ' clickable' : '')}
        onClick={onSigClick ? (e) => {e.stopPropagation();onSigClick(e);} : undefined}>
          {onSigClick ? <rect className="rg-sighit" x={tx - 13} y="26" width="26" height="40"></rect> : null}
          <RgTimeSig x={tx} sig={m.sig} inst={inst}></RgTimeSig>
        </g>
      );
      x += 34;
    }
    const glyphStart = x;
    const segW = Math.max(noteW[k] * stretch, 60);
    if (m.label) labels.push({ x: segStart, text: m.label, key: 'l' + k });
    if (m.simile) {
      parts.push(<RgSimile key={'sm' + k} cx={glyphStart + segW / 2} count={m.count}
      hl={highlight && highlight.mi === m.mi}></RgSimile>);
    } else {
      const glyphs =
        <RgMeasureGlyphs key={'g' + k} events={m.events} sig={m.sig} x0={x}
        showCounts={showCounts} stretch={stretch} inst={inst} leadLine={!!m.showSig && inst === 'handpan'} hands={hands}
        hlIndex={highlight && highlight.mi === m.mi ? highlight.ei : -1}></RgMeasureGlyphs>;
      // hidden measure (practice): dimmed in edit mode; view mode filters it out upstream
      parts.push(m.hidden ? <g key={'g' + k} className="rg-hiddenm" style={{ opacity: .3 }}>{glyphs}</g> : glyphs);
    }
    x += segW + (inst === 'handpan' ? 0 : 14) + plusPad / 2;
    const last = k === n - 1;
    if (hasEnd) x += REP_PAD; // space before the dots of an end / combined sign
    if (plusW && (hasEnd || nextHasFwd)) x += plusW; // room for the plus drawn LEFT of a repeat sign
    const barX = x;
    if (showEndings && voltaSet && voltaSet.has(m.mi)) voltaList.push({ x0: segStart, x1: barX, key: k, num: 1 });
    if (showEndings && volta2Set && volta2Set.has(m.mi)) voltaList.push({ x0: segStart, x1: barX, key: 'b' + k, num: 2 });
    // one divider per boundary: a repeat sign REPLACES the plain barline
    if (hasEnd && nextHasFwd) {
      parts.push(<RgRepeat key={'rb' + k} x={barX} dir="both" inst={inst}></RgRepeat>);
      pendingLead = REP_PAD + 4 + plusW;
    } else if (hasEnd) {
      parts.push(<RgRepeat key={'re' + k} x={barX} dir="backward" inst={inst}></RgRepeat>);
      pendingLead = plusW;
    } else if (nextHasFwd) {
      parts.push(<RgRepeat key={'rs' + (k + 1)} x={barX} dir="forward" inst={inst}></RgRepeat>);
      pendingLead = REP_PAD + 4 + plusW;
    } else {
      parts.push(<RgBarline key={'b' + k} x={barX} final={last && isLast} inst={inst}></RgBarline>);
    }
    zones.push({ x0: segStart - 4, x1: barX + 4, mi: m.mi });
    segs.push({ mi: m.mi, x0: segStart, x1: barX, empty: !m.simile && (!m.events || m.events.length === 0) });
    x += 10 + plusPad / 2;
  });
  const w = x + 4;
  const W = Math.max(w, fillWidth);
  const h = (showCounts ? RG.H : RG.H - 8) + band;
  const bzones = [];
  if (onBoundaryClick) {
    const repAt = (b) => startSet && startSet.has(b) || endSet && endSet.has(b);
    // A boundary's zone sits at the BARLINE (where its repeat sign is drawn),
    // i.e. the end (x1) of the measure before it — not the next measure's left
    // edge, which is pushed right by the lead space a repeat sign needs.
    segs.forEach((s, idx) => {
      if (idx === 0) bzones.push({ b: s.mi, x: s.x0 - plusPad / 2 - 4, px: s.x0 + 18, hasRep: repAt(s.mi) });
      // an empty measure already has a plus right before it — suppress the
      // immediately-following one so two pluses never sit side by side
      if (s.empty && !repAt(s.mi + 1)) return;
      bzones.push({ b: s.mi + 1, x: s.x1, px: s.x1 + 13, hasRep: repAt(s.mi + 1) });
    });
  }
  return (
    <svg className="rg-svg rg-system" viewBox={`0 0 ${W} ${h}`} width={W} height={h}
    preserveAspectRatio="xMinYMid meet">
      {labels.map((l) =>
      <text key={l.key} className="rg-mlabel" x={l.x} y={band - 5} textAnchor="start">{l.text}</text>
      )}
      {voltaList.map((v) =>
      <g key={'v' + v.key} className="rg-volta">
          <path d={`M ${v.x0 + 2} ${voltaBand - 2} L ${v.x0 + 2} 4 L ${v.x1 - 2} 4` + (
        v.num === 1 ? ` L ${v.x1 - 2} ${voltaBand - 2}` : '')}></path>
          <text x={v.x0 + 9} y={14} textAnchor="start">{v.num}.</text>
        </g>
      )}
      <g transform={band ? `translate(0,${band})` : undefined}>
        {onMeasureClick ? zones.map((z, i) =>
        <rect key={'z' + i}
        className={'rg-zone' + (selectedMi === z.mi ? ' sel' : '') + (editingMi === z.mi ? ' editing' : '')}
        x={z.x0} y="10" width={z.x1 - z.x0} height="72" rx="8"
        onClick={() => onMeasureClick(z.mi)}></rect>
        ) : null}
        <RgStaffLines x0={4 + handPad} x1={W - 2} inst={inst}></RgStaffLines>
        {inst === 'handpan' && hands ? <RgHandLegend></RgHandLegend> : null}
        {showClef ? <RgClef x={12} clef={clef} onClick={onClefClick} inst={inst}></RgClef> : null}
        {parts}
        {bzones.map((bz, i) =>
        <g key={'bz' + i} className={'rg-bplus' + (bz.hasRep ? ' has-rep' + (selectedBoundary === bz.b ? ' sel' : '') : '')}
        onClick={(e) => {e.stopPropagation();onBoundaryClick(bz.b, e);}}>
            <rect className="rg-bzone" x={bz.x - (11 + plusPad / 2)} y="6" width={22 + plusPad} height="80"></rect>
            {bz.hasRep ? null :
          <g className="rg-plusicon" transform={`translate(${bz.x}, ${RG.Y_MID})`}>
              <circle r={plusR}></circle>
              <line x1={-plusR * 0.47} y1="0" x2={plusR * 0.47} y2="0"></line>
              <line x1="0" y1={-plusR * 0.47} x2="0" y2={plusR * 0.47}></line>
            </g>}
          </g>
        )}
        {/* a boundary that carries a repeat sign keeps its own plus on each side of the sign */}
        {onBoundaryPlus ? bzones.filter((bz) => bz.hasRep).map((bz, i) =>
        [bz.px, bz.x - (11 + plusPad / 2) - (plusR + 5) * 2].filter((px) => px >= 0).map((px, j) =>
        <g key={'bzp' + i + '-' + j} className="rg-bplus rg-bplus-add"
        onClick={(e) => {e.stopPropagation();onBoundaryPlus(bz.b, e);}}>
            <rect className="rg-npzone" x={px} y="10" width={(plusR + 5) * 2} height="72" fill="transparent"></rect>
            <g className="rg-plusicon" transform={`translate(${px + plusR + 5}, ${RG.Y_MID})`}>
              <circle r={plusR}></circle>
              <line x1={-plusR * 0.47} y1="0" x2={plusR * 0.47} y2="0"></line>
              <line x1="0" y1={-plusR * 0.47} x2="0" y2={plusR * 0.47}></line>
            </g>
          </g>)) : null}
      </g>
    </svg>);

}

// ---- tiny glyphs for the pickers -------------------------------------------

// Erbane hangs every stem downward, so a single staff-anchored frame strands the
// glyph against one edge (düm low, ka high). For a picker that shows ONE stroke
// across its values we recentre the frame on that stroke's own extent so the
// note sits balanced instead of clinging to an edge with dead space opposite.
function erbaneStrokeVB(s) {
  const VBH = 64;
  let yMin, yMax;
  if (s === 'Z') {yMin = RG.Y_TOP;yMax = RG.Y_MID + RG.STEM + 12;} // ring + stem + 16th flag
  else if (s === 'R') {yMin = RG.Y_TOP;yMax = RG.Y_BOT;} else {
    const hy = rgHeadY(s, 'erbane');
    yMin = Math.min(RG.Y_TOP, hy - 6);
    yMax = Math.max(RG.Y_BOT, hy + RG.STEM);
  }
  const cy = (yMin + yMax) / 2;
  return `6 ${Math.round((cy - VBH / 2) * 10) / 10} 36 ${VBH}`;
}

function NotePickGlyph({ stroke, inst = 'bendir' }) {
  const ev = { s: stroke, q: 1 };
  // The note picker shows several strokes side by side, so the staff must line up
  // across cards — keep ONE shared frame (centred on the whole stroke group)
  // rather than per-stroke recentring.
  const vb = inst === 'handpan' ? '6 30 36 60' : inst === 'erbane' ? '6 27 36 66' : '6 8 36 80';
  return (
    <svg className="rg-svg rg-pick" viewBox={vb} width="36" height="74">
      <RgStaffLines x0={8} x1={40} inst={inst}></RgStaffLines>
      <RgEvent x={24} ev={ev} inst={inst}></RgEvent>
    </svg>);

}

function ValuePickGlyph({ q, rest, stroke, inst = 'bendir' }) {
  const ev = { s: rest ? 'R' : stroke || 'D', q: q };
  // every value chip shares the selected stroke, so recentre on that stroke.
  const vb = inst === 'handpan' ? '6 30 36 60' : inst === 'erbane' ? erbaneStrokeVB(ev.s) : '6 8 36 80';
  return (
    <svg className="rg-svg rg-pick" viewBox={vb} width="26" height="56">
      <RgStaffLines x0={8} x1={40} inst={inst}></RgStaffLines>
      <RgEvent x={22} ev={ev} inst={inst}></RgEvent>
    </svg>);

}

Object.assign(window, { MeasureSVG, SystemSVG, NotePickGlyph, ValuePickGlyph, measureWidthPx, rgAdvance, rgStartX, rithmicHpNotes, rithmicHpWrite });