(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" });
});