(function(global, factory) { typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("tonal"), require("abcjs")) : typeof define === "function" && define.amd ? define(["exports", "tonal", "abcjs"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.ABCEditorCore = {}, global.Tonal, global.ABCJS)); })(this, function(exports2, tonal, abcjs) { "use strict"; var Accidental = /* @__PURE__ */ ((Accidental2) => { Accidental2["None"] = "none"; Accidental2["Sharp"] = "sharp"; Accidental2["Flat"] = "flat"; Accidental2["Natural"] = "natural"; return Accidental2; })(Accidental || {}); var Rhythm = /* @__PURE__ */ ((Rhythm2) => { Rhythm2[Rhythm2["Whole"] = 1] = "Whole"; Rhythm2[Rhythm2["Half"] = 2] = "Half"; Rhythm2[Rhythm2["Quarter"] = 4] = "Quarter"; Rhythm2[Rhythm2["Eighth"] = 8] = "Eighth"; Rhythm2[Rhythm2["Sixteenth"] = 16] = "Sixteenth"; return Rhythm2; })(Rhythm || {}); var Clef = /* @__PURE__ */ ((Clef2) => { Clef2["Treble"] = "treble"; Clef2["Bass"] = "bass"; return Clef2; })(Clef || {}); const kbdKeyToCommandMap = /* @__PURE__ */ new Map([ ["1", { type: "setRhythm", rhythm: Rhythm.Whole }], ["2", { type: "setRhythm", rhythm: Rhythm.Half }], ["3", { type: "setRhythm", rhythm: Rhythm.Quarter }], ["4", { type: "setRhythm", rhythm: Rhythm.Eighth }], ["5", { type: "setRhythm", rhythm: Rhythm.Sixteenth }], ["d", { type: "toggleDotted" }], ["r", { type: "toggleRest" }], ["f", { type: "toggleAccidental", accidental: Accidental.Flat }], ["s", { type: "toggleAccidental", accidental: Accidental.Sharp }], ["n", { type: "toggleAccidental", accidental: Accidental.Natural }], ["t", { type: "toggleTriplet" }], ["b", { type: "toggleBeamed" }], ["Backspace", "backspace"], ["Enter", "newLine"] ]); function setupKeyboardListener(dispatchEditorCommand, onBackspace, onNewLine) { const listener = (e) => { const command = kbdKeyToCommandMap.get(e.key); if (command === "backspace") onBackspace(); else if (command === "newLine") onNewLine(); else if (command !== void 0) dispatchEditorCommand(command); }; window.addEventListener("keydown", listener); return () => window.removeEventListener("keydown", listener); } const EighthNoteIcon = ''; const HalfNoteIcon = ''; const QuarterNoteIcon = ''; const SixteenthNoteIcon = ''; const WholeNoteIcon = ''; const EighthRestIcon = ''; const HalfRestIcon = ''; const QuarterRestIcon = ''; const SixteenthRestIcon = ''; const WholeRestIcon = ''; const BeamingIcon = ''; const DotIcon = ''; const FlatIcon = ''; const NaturalIcon = ''; const PianoIcon = ''; const SharpIcon = ''; const TripletIcon = ''; const BackspaceIcon = ''; const NewLineIcon = ''; var Icon = /* @__PURE__ */ ((Icon2) => { Icon2[Icon2["Backspace"] = 0] = "Backspace"; Icon2[Icon2["Beaming"] = 1] = "Beaming"; Icon2[Icon2["Dot"] = 2] = "Dot"; Icon2[Icon2["EighthNote"] = 3] = "EighthNote"; Icon2[Icon2["EighthRest"] = 4] = "EighthRest"; Icon2[Icon2["Flat"] = 5] = "Flat"; Icon2[Icon2["HalfNote"] = 6] = "HalfNote"; Icon2[Icon2["HalfRest"] = 7] = "HalfRest"; Icon2[Icon2["Natural"] = 8] = "Natural"; Icon2[Icon2["NewLine"] = 9] = "NewLine"; Icon2[Icon2["Piano"] = 10] = "Piano"; Icon2[Icon2["QuarterNote"] = 11] = "QuarterNote"; Icon2[Icon2["QuarterRest"] = 12] = "QuarterRest"; Icon2[Icon2["Sharp"] = 13] = "Sharp"; Icon2[Icon2["SixteenthNote"] = 14] = "SixteenthNote"; Icon2[Icon2["SixteenthRest"] = 15] = "SixteenthRest"; Icon2[Icon2["Triplet"] = 16] = "Triplet"; Icon2[Icon2["WholeNote"] = 17] = "WholeNote"; Icon2[Icon2["WholeRest"] = 18] = "WholeRest"; return Icon2; })(Icon || {}); function getIcon(icon) { switch (icon) { case 0: return BackspaceIcon; case 1: return BeamingIcon; case 2: return DotIcon; case 3: return EighthNoteIcon; case 4: return EighthRestIcon; case 5: return FlatIcon; case 6: return HalfNoteIcon; case 7: return HalfRestIcon; case 8: return NaturalIcon; case 9: return NewLineIcon; case 10: return PianoIcon; case 11: return QuarterNoteIcon; case 12: return QuarterRestIcon; case 13: return SharpIcon; case 14: return SixteenthNoteIcon; case 15: return SixteenthRestIcon; case 16: return TripletIcon; case 17: return WholeNoteIcon; case 18: return WholeRestIcon; } } const setupStaffMouseListeners = ({ renderDiv, numTuneLines, rhythm, dotted = false, accidental = Accidental.None, rest = false, clef, onAddNote, lastMousePos, updateLastMousePos }) => { const topStaffLine = renderDiv.querySelector( `.abcjs-l${numTuneLines - 1} .abcjs-top-line` ); const secondStaffLine = topStaffLine == null ? void 0 : topStaffLine.nextSibling; if (!topStaffLine || !secondStaffLine) return () => { }; const staffClickListener = (e) => { const topStaffLineY2 = topStaffLine.getBoundingClientRect().y; const staffLineGap2 = secondStaffLine.getBoundingClientRect().y - topStaffLineY2; const note = getStaffClickToNoteFn({ clef: clef || "treble", topLineY: topStaffLineY2, lineGap: staffLineGap2 })(e.clientY); if (note) onAddNote(note); }; const CURSOR_TOP_ADJUST = 0.81; const CURSOR_LEFT_ADJUST = 0.5; const topStaffLineY = topStaffLine.getBoundingClientRect().y; const staffLineGap = secondStaffLine.getBoundingClientRect().y - topStaffLineY; const iconSize = staffLineGap * 3.5; const cursorIconDiv = getCursorIcon({ rhythm, rest, size: iconSize, accidental, dotted }); let ledgerLineDivs = []; if (lastMousePos) { cursorIconDiv.style.top = `${lastMousePos.y - iconSize * CURSOR_TOP_ADJUST}px`; cursorIconDiv.style.left = `${lastMousePos.x - iconSize * CURSOR_LEFT_ADJUST}px`; renderDiv.appendChild(cursorIconDiv); if (!rest) { ledgerLineDivs = drawLedgerLines({ mousePos: lastMousePos, topStaffLineY, staffLineGap, renderDiv, cursorSize: iconSize }); } } const staffEnterListener = () => { renderDiv.appendChild(cursorIconDiv); }; const staffLeaveListener = () => { if (renderDiv.contains(cursorIconDiv)) renderDiv.removeChild(cursorIconDiv); ledgerLineDivs.forEach((div) => div.remove()); ledgerLineDivs = []; if (updateLastMousePos) updateLastMousePos(null); }; const staffMoveListener = (e) => { ledgerLineDivs.forEach((div) => div.remove()); cursorIconDiv.style.top = `${e.clientY - iconSize * CURSOR_TOP_ADJUST}px`; cursorIconDiv.style.left = `${e.clientX - iconSize * CURSOR_LEFT_ADJUST}px`; if (updateLastMousePos) updateLastMousePos({ x: e.clientX, y: e.clientY }); if (!rest) { const topStaffLineY2 = topStaffLine.getBoundingClientRect().y; const staffLineGap2 = secondStaffLine.getBoundingClientRect().y - topStaffLineY2; ledgerLineDivs = drawLedgerLines({ topStaffLineY: topStaffLineY2, staffLineGap: staffLineGap2, mousePos: { x: e.clientX, y: e.clientY }, renderDiv, cursorSize: iconSize }); } else { ledgerLineDivs = []; } }; renderDiv.addEventListener("pointerdown", staffClickListener); renderDiv.addEventListener("pointerenter", staffEnterListener); renderDiv.addEventListener("pointerleave", staffLeaveListener); renderDiv.addEventListener("pointermove", staffMoveListener); return () => { renderDiv.removeEventListener("pointerdown", staffClickListener); renderDiv.removeEventListener("pointerenter", staffEnterListener); renderDiv.removeEventListener("pointerleave", staffLeaveListener); renderDiv.removeEventListener("pointermove", staffMoveListener); cursorIconDiv.remove(); ledgerLineDivs.forEach((div) => div.remove()); }; }; const getNoteFromC4MajorDegree = tonal.Scale.steps("C4 major"); function getStaffClickToNoteFn(props) { const getNoteFromDegrees = (degrees) => getNoteFromC4MajorDegree( props.clef === "treble" ? degrees + 10 : degrees - 2 ); return (y) => { const distanceFromTopLine = props.topLineY - y; const estimatedDegreesFromTopLine = Math.round( distanceFromTopLine / (props.lineGap / 2) ); return getNoteFromDegrees(estimatedDegreesFromTopLine); }; } const cursorIconColor = "rgba(0, 0, 0, 0.7)"; const ledgerLineStyle = Object.freeze({ position: "fixed", height: "1px", backgroundColor: "currentColor" }); function drawLedgerLines({ cursorSize, mousePos, topStaffLineY, staffLineGap, renderDiv }) { const ledgerLineDivs = []; const bottomStaffLineY = topStaffLineY + staffLineGap * 4; const maxLedgerDistance = staffLineGap * 6; const drawLedgerAnticipation = 3; if (mousePos.y < topStaffLineY && mousePos.y > topStaffLineY - maxLedgerDistance) { for (let y = topStaffLineY - staffLineGap; y >= mousePos.y - drawLedgerAnticipation; y -= staffLineGap) { const ledgerDiv = document.createElement("div"); Object.assign(ledgerDiv.style, { ...ledgerLineStyle, top: `${y}px`, left: `${mousePos.x - cursorSize / 3}px`, width: `${cursorSize / 1.8}px`, color: cursorIconColor }); ledgerLineDivs.push(ledgerDiv); renderDiv.appendChild(ledgerDiv); } } if (mousePos.y > bottomStaffLineY && mousePos.y < bottomStaffLineY + maxLedgerDistance) { for (let y = bottomStaffLineY + staffLineGap; y <= mousePos.y + drawLedgerAnticipation; y += staffLineGap) { const ledgerDiv = document.createElement("div"); Object.assign(ledgerDiv.style, { ...ledgerLineStyle, top: `${y}px`, left: `${mousePos.x - cursorSize / 3}px`, width: `${cursorSize / 1.8}px`, color: cursorIconColor }); ledgerLineDivs.push(ledgerDiv); renderDiv.appendChild(ledgerDiv); } } return ledgerLineDivs; } function getCursorIcon({ rhythm, size = 36, rest = false, dotted = false, accidental = Accidental.None }) { const svg = (() => { switch (rhythm) { case Rhythm.Eighth: return getIcon(rest ? Icon.EighthRest : Icon.EighthNote); case Rhythm.Whole: return getIcon(rest ? Icon.WholeRest : Icon.WholeNote); case Rhythm.Half: return getIcon(rest ? Icon.HalfRest : Icon.HalfNote); case Rhythm.Quarter: return getIcon(rest ? Icon.QuarterRest : Icon.QuarterNote); case Rhythm.Sixteenth: return getIcon(rest ? Icon.SixteenthRest : Icon.SixteenthNote); } })(); const div = document.createElement("div"); div.style.position = "fixed"; div.style.pointerEvents = "none"; if (svg) { div.innerHTML = svg; const svgEl = div.querySelector("svg"); if (svgEl) { svgEl.setAttribute("height", size.toString()); svgEl.setAttribute("width", size.toString()); svgEl.style.color = cursorIconColor; } } if (!rest && accidental !== Accidental.None) { const accidentalSvg = (() => { switch (accidental) { case Accidental.Sharp: return getIcon(Icon.Sharp); case Accidental.Flat: return getIcon(Icon.Flat); case Accidental.Natural: return getIcon(Icon.Natural); } })(); const accidentalDiv = document.createElement("div"); accidentalDiv.innerHTML = accidentalSvg; const svgEl = accidentalDiv.querySelector("svg"); if (svgEl) Object.assign(svgEl.style, { position: "absolute", right: `${size * 0.65}px`, top: `${size * 0.35}px`, height: `${size * 0.7}px`, width: `${size * 0.7}px`, color: cursorIconColor }); div.appendChild(accidentalDiv); } if (dotted) { const dotDiv = document.createElement("div"); dotDiv.innerHTML = getIcon(Icon.Dot); const svgEl = dotDiv.querySelector("svg"); if (svgEl) Object.assign(svgEl.style, { position: "absolute", left: `${rest ? size * 0.6 : size * 0.4}px`, top: `${rest ? size * 0.1 : size * 0.375}px`, height: `${size * 0.7}px`, width: `${size * 0.7}px`, color: cursorIconColor }); div.appendChild(dotDiv); } return div; } const setupMIDIListener = (onAddNote) => { let inputs = null; const listener = (ev) => { var _a; if (((_a = ev == null ? void 0 : ev.data) == null ? void 0 : _a[0]) === 144 && ev.data[1]) onAddNote(ev.data[1]); }; navigator.requestMIDIAccess({ sysex: true }).then((midiAccess) => { inputs = midiAccess.inputs; inputs.forEach((input) => { input.addEventListener("midimessage", listener); }); }).catch((err) => console.error("Error getting MIDI access:", err)); return () => { inputs == null ? void 0 : inputs.forEach( (input) => input.removeEventListener("midimessage", listener) ); inputs = null; }; }; function editorCommandReducer(state, action) { const newState = { ...state }; switch (action.type) { case "setRhythm": newState.rhythm = action.rhythm; break; case "setAccidental": newState.accidental = action.accidental; break; case "toggleAccidental": newState.accidental = newState.accidental === action.accidental ? Accidental.None : action.accidental; break; case "setBeamed": newState.beamed = action.beamed; break; case "toggleBeamed": newState.beamed = !newState.beamed; break; case "toggleRest": newState.rest = !newState.rest; break; case "toggleTriplet": newState.triplet = !newState.triplet; break; case "toggleShowKeyboard": newState.showKeyboard = !newState.showKeyboard; break; case "toggleDotted": newState.dotted = !newState.dotted; break; case "setDotted": newState.dotted = action.dotted; break; case "toggleMidi": newState.midiEnabled = !newState.midiEnabled; break; } return newState; } const roundToN = (num, decimals = 5) => ( //@ts-expect-error js numbers & rounding weirdness +(Math.round(num + `e+${decimals}`) + `e-${decimals}`) ); const equalUpToN = (a, b, precision = 1e-4) => Math.abs(a - b) < precision; const getMeasureDurationFromTimeSig = (timeSig) => { if (typeof timeSig.upper === "number" && typeof timeSig.lower === "number") return roundToN(timeSig.upper / timeSig.lower); throw new Error("Unsupported time signature: " + timeSig.name); }; const parseMeasuresFromAbcjs = (tuneLines, timeSig) => { const measures = []; const fullMeasureDuration = getMeasureDurationFromTimeSig(timeSig); tuneLines.forEach((line, lineIdx) => { var _a, _b; let currentMeasure = { line: lineIdx, lineStartIdx: 0, lineEndIdx: 0, notes: [], duration: 0 }; measures.push(currentMeasure); let currentTriplet = false; for (let i = 0; i < line.length; i++) { const item = line[i]; currentMeasure.lineEndIdx++; if (item.el_type === "note") { const note = item; if (((_a = note.rest) == null ? void 0 : _a.type) === "spacer") continue; if (note.startTriplet) currentTriplet = true; note.isTriplet = currentTriplet; if (note.endTriplet) currentTriplet = false; currentMeasure.notes.push(note); currentMeasure.duration = roundToN( currentMeasure.duration + note.duration * (note.isTriplet ? 2 / 3 : 1) ); if (currentMeasure.duration >= fullMeasureDuration - 1e-3) { currentMeasure = { notes: [], duration: 0, line: lineIdx, lineStartIdx: currentMeasure.lineEndIdx, lineEndIdx: currentMeasure.lineEndIdx }; measures.push(currentMeasure); } } if (i === line.length - 1 && tuneLines[lineIdx + 1] && ((_b = measures.at(-1)) == null ? void 0 : _b.notes.length) === 0) { measures.splice(measures.length - 1, 1); } } }); return measures; }; function getAbcRhythm(currentRhythm, dotted = false) { return !dotted ? currentRhythm === Rhythm.Whole ? "8" : currentRhythm === Rhythm.Half ? "4" : currentRhythm === Rhythm.Quarter ? "2" : currentRhythm === Rhythm.Eighth ? "" : currentRhythm === Rhythm.Sixteenth ? "/2" : "" : currentRhythm === Rhythm.Half ? "6" : currentRhythm === Rhythm.Quarter ? "3" : currentRhythm === Rhythm.Eighth ? "3/2" : currentRhythm === Rhythm.Sixteenth ? "3/4" : ""; } function getAbcNoteFromMidiNum(midiNum, accidental = Accidental.None) { if (accidental === Accidental.Natural) return "=" + tonal.Midi.midiToNoteName(midiNum); return tonal.AbcNotation.scientificToAbcNotation( tonal.Midi.midiToNoteName(midiNum, { sharps: accidental === Accidental.Sharp }) ); } function getAbcNoteFromNoteName(noteName, accidental = Accidental.None) { if (accidental === Accidental.None) return tonal.AbcNotation.scientificToAbcNotation(noteName); if (accidental === Accidental.Natural) return "=" + tonal.AbcNotation.scientificToAbcNotation(noteName); const note = tonal.Note.get(noteName); const wantedPitchClass = note.letter + (accidental === Accidental.Sharp ? "#" : "b"); const noteNameWithAccidental = tonal.Note.enharmonic( tonal.Note.transpose(noteName, accidental === Accidental.Sharp ? "m2" : "m-2"), wantedPitchClass ); return tonal.AbcNotation.scientificToAbcNotation(noteNameWithAccidental); } const headerRegex = /^.:/; const testIsHeader = (str) => headerRegex.test(str); const getRawValueFromHeader = (header) => header.replace(headerRegex, "").trim(); const supportedClefs = Object.values(Clef); const getSupportedClef = (rawClef) => supportedClefs.includes(rawClef) ? rawClef : Clef.Treble; const isMajorKey = (keyValue) => keyValue.at(-1) !== "m"; const parseAbcHeaders = (abc) => { var _a; const headers = abc.trim().split("\n").filter(testIsHeader); const rawTimeSig = headers.find((h) => h.startsWith("M:")) || "M:4/4"; const [rawKeySig = "K:C", rawClef = "treble"] = ((_a = headers.find((h) => h.startsWith("K:"))) == null ? void 0 : _a.split(" clef=", 2)) || []; const timeSig = tonal.TimeSignature.get(getRawValueFromHeader(rawTimeSig)); const clef = getSupportedClef(rawClef.trim()); const keyValue = getRawValueFromHeader(rawKeySig); const keySig = isMajorKey(keyValue) ? tonal.Key.majorKey(keyValue) : tonal.Key.minorKey(keyValue.replace("m", "")); return { timeSig, keySig, clef }; }; const parseChordTemplate = (abc, timeSig) => { const tuneObject = abcjs.parseOnly(abc); const tuneLines = tuneObject[0].lines.reduce((arr, line) => { var _a, _b, _c; const items = (_c = (_b = (_a = line.staff) == null ? void 0 : _a[0]) == null ? void 0 : _b.voices) == null ? void 0 : _c[0]; if (items) arr.push(items); return arr; }, []); return parseMeasuresFromAbcjs(tuneLines, timeSig).map((measure) => { var _a; let fractionalBeat = 0; const chords = []; for (const note of measure.notes) { if ((_a = note.chord) == null ? void 0 : _a[0]) chords.push({ name: note.chord[0].name, fractionalBeat }); fractionalBeat += note.duration * (note.isTriplet ? 2 / 3 : 1); } return chords; }); }; class EditorState { constructor(initialAbc, options) { var _a, _b; this.tuneLines = []; this.measures = []; if (initialAbc) { this.abc = initialAbc; const { clef, keySig, timeSig } = parseAbcHeaders(initialAbc); this.clef = clef; this.keySig = keySig; this.timeSig = timeSig; if (options == null ? void 0 : options.chordTemplate) { this.chordTemplate = parseChordTemplate( options.chordTemplate, this.timeSig ); } } else { this.clef = Clef.Treble; this.keySig = tonal.Key.majorKey("C"); this.timeSig = tonal.TimeSignature.get("4/4"); this.abc = `%%stretchlast false X:1 L:1/8 M:4/4 K:C clef=treble `; if (options == null ? void 0 : options.chordTemplate) { this.chordTemplate = parseChordTemplate( options.chordTemplate, this.timeSig ); const chordToAdd = (_b = (_a = this.chordTemplate) == null ? void 0 : _a[0]) == null ? void 0 : _b.find( (chord) => equalUpToN(chord.fractionalBeat, 0) ); if (chordToAdd) this.abc += `"^${chordToAdd.name}"`; } } } updateTuneData(lines) { this.tuneLines = lines.reduce((arr, line) => { var _a, _b, _c; const items = (_c = (_b = (_a = line.staff) == null ? void 0 : _a[0]) == null ? void 0 : _b.voices) == null ? void 0 : _c[0]; if (items) arr.push(items); return arr; }, []); this.measures = parseMeasuresFromAbcjs(this.tuneLines, this.timeSig); } addNote(note, rhythm, options) { var _a, _b, _c, _d; const abcNote = typeof note === "number" ? getAbcNoteFromMidiNum(note, options == null ? void 0 : options.accidental) : getAbcNoteFromNoteName(note, options == null ? void 0 : options.accidental); const currentMeasure = this.measures.at(-1); let abcToAdd = ""; if (!(options == null ? void 0 : options.beamed)) abcToAdd += " "; if ((options == null ? void 0 : options.triplet) && currentMeasure) { const startTripletIdx = currentMeasure.notes.findLastIndex( (n) => !!n.startTriplet ); const endTripletIdx = currentMeasure.notes.findLastIndex( (n) => !!n.endTriplet ); if (startTripletIdx === -1 || startTripletIdx < endTripletIdx) { abcToAdd += "(3"; } } abcToAdd += `${!(options == null ? void 0 : options.rest) ? abcNote : "z"}${getAbcRhythm( rhythm, options == null ? void 0 : options.dotted )}`; if (currentMeasure) { const measureTotalDuration = getMeasureDurationFromTimeSig(this.timeSig); const durationWithAddedNote = currentMeasure.duration + 1 / rhythm * ((options == null ? void 0 : options.dotted) ? 3 / 2 : 1) * ((options == null ? void 0 : options.triplet) ? 2 / 3 : 1); if (durationWithAddedNote >= measureTotalDuration - 1e-3) { abcToAdd += " |"; const chordToAdd = (_b = (_a = this.chordTemplate) == null ? void 0 : _a.at(this.measures.length)) == null ? void 0 : _b.find((chord) => equalUpToN(chord.fractionalBeat, 0)); if (chordToAdd) abcToAdd += ` "^${chordToAdd.name}"`; } else { const chordToAdd = (_d = (_c = this.chordTemplate) == null ? void 0 : _c.at(this.measures.length - 1)) == null ? void 0 : _d.find( (chord) => equalUpToN(chord.fractionalBeat, durationWithAddedNote) ); if (chordToAdd) abcToAdd += ` "^${chordToAdd.name}"`; } } this.abc += abcToAdd; } backspace() { var _a, _b, _c; let measureIdx = -1; let lastItem; while (!(lastItem = (_a = this.measures.at(measureIdx)) == null ? void 0 : _a.notes.at(-1))) { if (Math.abs(measureIdx) >= this.measures.length) return; measureIdx--; } this.abc = this.abc.slice(0, lastItem.startChar); if (this.chordTemplate) { const currentMeasure = this.measures.at(-1); if (!currentMeasure) return; const durationWithRemovedNote = currentMeasure.duration - lastItem.duration * (lastItem.isTriplet ? 2 / 3 : 1); const chordToAdd = (_c = (_b = this.chordTemplate) == null ? void 0 : _b.at(this.measures.length - 1)) == null ? void 0 : _c.find( (chord) => equalUpToN(chord.fractionalBeat, durationWithRemovedNote) ); if (chordToAdd) this.abc += ` "^${chordToAdd.name}"`; } } newLine() { var _a, _b; if (this.abc.at(-1) === "\n") return; const lastBarlineIndex = this.abc.lastIndexOf("|"); if (lastBarlineIndex) this.abc = this.abc.slice(0, lastBarlineIndex + 1); this.abc = this.abc + "\n"; if (this.chordTemplate) { const chordToAdd = (_b = (_a = this.chordTemplate) == null ? void 0 : _a.at(this.measures.length - 1)) == null ? void 0 : _b.find((chord) => equalUpToN(chord.fractionalBeat, 0)); if (chordToAdd) this.abc += `"^${chordToAdd.name}"`; } } shouldBeamNextNote(nextRhythm) { const lastMeasure = this.measures.at(-1); const lastNote = lastMeasure == null ? void 0 : lastMeasure.notes.at(-1); if (!lastNote || !lastMeasure || lastNote.endTriplet) return false; const currentDuration = lastMeasure.notes.reduce( (acc, curr) => acc + curr.duration * (curr.isTriplet ? 2 / 3 : 1), 0 ); const currentBeat = currentDuration * 4; return (nextRhythm === Rhythm.Eighth && ![0, 2].includes(currentBeat) || nextRhythm === Rhythm.Sixteenth && ![0, 1, 2, 3].includes(currentBeat)) && [0.125, 0.0625].concat([0.125, 0.0625].map((v) => v * 1.5)).includes(lastNote.duration); } get currentDuration() { const lastMeasure = this.measures.at(-1); if (!lastMeasure) return -1; return lastMeasure.notes.reduce( (acc, curr) => acc + curr.duration * (curr.isTriplet ? 2 / 3 : 1), 0 ); } get isEndOfTriplet() { var _a; let measureIdx = -1; let lastNote; while (!(lastNote = (_a = this.measures.at(measureIdx)) == null ? void 0 : _a.notes.at(-1))) { if (Math.abs(measureIdx) >= this.measures.length) return; measureIdx--; } return !!lastNote && !!lastNote.endTriplet; } } exports2.Accidental = Accidental; exports2.EditorState = EditorState; exports2.Icon = Icon; exports2.Rhythm = Rhythm; exports2.editorCommandReducer = editorCommandReducer; exports2.getIcon = getIcon; exports2.setupKeyboardListener = setupKeyboardListener; exports2.setupMIDIListener = setupMIDIListener; exports2.setupStaffMouseListeners = setupStaffMouseListeners; Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" }); });