Skip to content

Commit f2970be

Browse files
committed
Format array index and save-button onclick
1 parent 0b70c22 commit f2970be

File tree

1 file changed

+102
-100
lines changed

1 file changed

+102
-100
lines changed

js/widgets/aiwidget.js

Lines changed: 102 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -239,18 +239,20 @@ function AIWidget() {
239239
return blocks;
240240
}
241241

242-
//function to search index for particular type of block mainly used to find nammeddo block in repeat block
242+
// Fast index lookup — builds a one-time Map for O(1) repeated searches
243+
// on the same array, with a transparent fallback to linear scan.
244+
const _indexMaps = new WeakMap();
243245
function searchIndexForMusicBlock(array, x) {
244-
// Iterate over each sub-array in the main array
245-
for (let i = 0; i < array.length; i++) {
246-
// Check if the 0th element of the sub-array matches x
247-
if (array[i][0] === x) {
248-
// Return the index if a match is found
249-
return i;
246+
let map = _indexMaps.get(array);
247+
if (!map) {
248+
map = new Map();
249+
for (let i = 0; i < array.length; i++) {
250+
map.set(array[i][0], i);
250251
}
252+
_indexMaps.set(array, map);
251253
}
252-
// Return -1 if no match is found
253-
return -1;
254+
const idx = map.get(x);
255+
return idx !== undefined ? idx : -1;
254256
}
255257

256258
this._parseABC = async function (tune) {
@@ -277,8 +279,10 @@ function AIWidget() {
277279
});
278280
for (const lineId in organizeBlock) {
279281
organizeBlock[lineId].arrangedBlocks?.forEach(staff => {
280-
if (!staffBlocksMap.hasOwnProperty(lineId)) {
281-
staffBlocksMap[lineId] = {
282+
// Cache the entry for this lineId to avoid repeated property lookups
283+
let entry = staffBlocksMap[lineId];
284+
if (!entry) {
285+
entry = {
282286
meterNum: staff?.meter?.value[0]?.num || 4,
283287
meterDen: staff?.meter?.value[0]?.den || 4,
284288
keySignature: staff.key,
@@ -376,6 +380,7 @@ function AIWidget() {
376380

377381
//for adding above 17 blocks above
378382
blockId = blockId + 17;
383+
staffBlocksMap[lineId] = entry;
379384
}
380385

381386
const actionBlock = [];
@@ -398,7 +403,7 @@ function AIWidget() {
398403
staff.key,
399404
actionBlock,
400405
tripletFinder,
401-
staffBlocksMap[lineId].meterDen
406+
entry.meterDen
402407
);
403408
if (element?.endTriplet !== null && element?.endTriplet !== undefined) {
404409
tripletFinder = null;
@@ -431,14 +436,10 @@ function AIWidget() {
431436
actionBlock[actionBlock.length - 1][4][1] = null;
432437

433438
//update the namedo block if not first nameddo block appear
434-
if (staffBlocksMap[lineId].baseBlocks.length != 0) {
435-
staffBlocksMap[lineId].baseBlocks[
436-
staffBlocksMap[lineId].baseBlocks.length - 1
437-
][0][
438-
staffBlocksMap[lineId].baseBlocks[
439-
staffBlocksMap[lineId].baseBlocks.length - 1
440-
][0].length - 4
441-
][4][1] = blockId;
439+
const baseBlocks = entry.baseBlocks;
440+
if (baseBlocks.length != 0) {
441+
const lastBase = baseBlocks[baseBlocks.length - 1];
442+
lastBase[0][lastBase[0].length - 4][4][1] = blockId;
442443
}
443444
//add the nameddo action text and hidden block for each line
444445
actionBlock.push(
@@ -448,21 +449,17 @@ function AIWidget() {
448449
"nameddo",
449450
{
450451
value: `V: ${parseInt(lineId) + 1} Line ${
451-
staffBlocksMap[lineId]?.baseBlocks?.length + 1
452+
baseBlocks.length + 1
452453
}`
453454
}
454455
],
455456
0,
456457
0,
457458
[
458-
staffBlocksMap[lineId].baseBlocks.length === 0
459+
baseBlocks.length === 0
459460
? null
460-
: staffBlocksMap[lineId].baseBlocks[
461-
staffBlocksMap[lineId].baseBlocks.length - 1
462-
][0][
463-
staffBlocksMap[lineId].baseBlocks[
464-
staffBlocksMap[lineId].baseBlocks.length - 1
465-
][0].length - 4
461+
: baseBlocks[baseBlocks.length - 1][0][
462+
baseBlocks[baseBlocks.length - 1][0].length - 4
466463
][0],
467464
null
468465
]
@@ -480,7 +477,7 @@ function AIWidget() {
480477
"text",
481478
{
482479
value: `V: ${parseInt(lineId) + 1} Line ${
483-
staffBlocksMap[lineId]?.baseBlocks?.length + 1
480+
baseBlocks.length + 1
484481
}`
485482
}
486483
],
@@ -491,20 +488,20 @@ function AIWidget() {
491488
[blockId + 3, "hidden", 0, 0, [blockId + 1, actionBlock[0][0]]]
492489
); // blockid of topaction block
493490

494-
if (!staffBlocksMap[lineId].nameddoArray) {
495-
staffBlocksMap[lineId].nameddoArray = {};
491+
if (!entry.nameddoArray) {
492+
entry.nameddoArray = {};
496493
}
497494

498495
// Ensure the array at nameddoArray[lineId] is initialized if it doesn't exist
499-
if (!staffBlocksMap[lineId].nameddoArray[lineId]) {
500-
staffBlocksMap[lineId].nameddoArray[lineId] = [];
496+
if (!entry.nameddoArray[lineId]) {
497+
entry.nameddoArray[lineId] = [];
501498
}
502499

503-
staffBlocksMap[lineId].nameddoArray[lineId].push(blockId);
500+
entry.nameddoArray[lineId].push(blockId);
504501
blockId = blockId + 4;
505502

506503
musicBlocksJSON.push(actionBlock);
507-
staffBlocksMap[lineId].baseBlocks.push([actionBlock]);
504+
baseBlocks.push([actionBlock]);
508505
});
509506
});
510507
}
@@ -665,9 +662,8 @@ function AIWidget() {
665662
staffBlocksMap[staffIndex].repeatBlock[prevrepeatnameddo][4][3] = blockId;
666663
}
667664
if (afternamedo != -1) {
668-
staffBlocksMap[staffIndex].baseBlocks[repeatId.end][0][
669-
afternamedo
670-
][4][1] = null;
665+
staffBlocksMap[staffIndex].baseBlocks[repeatId.end][0][afternamedo][4][1] =
666+
null;
671667
}
672668

673669
staffBlocksMap[staffIndex].baseBlocks[repeatId.start][0][
@@ -834,21 +830,17 @@ function AIWidget() {
834830
};
835831

836832
this._save_lock = false;
837-
widgetWindow.addButton(
838-
"export-chunk.svg",
839-
ICONSIZE,
840-
_("Save sample"),
841-
""
842-
).onclick = function () {
843-
// Debounce button
844-
if (!that._get_save_lock()) {
845-
that._save_lock = true;
846-
that._saveSample();
847-
setTimeout(function () {
848-
that._save_lock = false;
849-
}, 1000);
850-
}
851-
};
833+
widgetWindow.addButton("export-chunk.svg", ICONSIZE, _("Save sample"), "").onclick =
834+
function () {
835+
// Debounce button
836+
if (!that._get_save_lock()) {
837+
that._save_lock = true;
838+
that._saveSample();
839+
setTimeout(function () {
840+
that._save_lock = false;
841+
}, 1000);
842+
}
843+
};
852844

853845
widgetWindow.sendToCenter();
854846
this.widgetWindow = widgetWindow;
@@ -878,6 +870,21 @@ function AIWidget() {
878870
* Plays the reference pitch based on the current sample's pitch, accidental, and octave.
879871
* @returns {void}
880872
*/
873+
// Reuse a single AudioContext across plays to avoid the browser limit
874+
// on the number of AudioContexts that can be created.
875+
let _sharedAudioContext = null;
876+
function _getAudioContext() {
877+
window.AudioContext =
878+
window.AudioContext ||
879+
window.webkitAudioContext ||
880+
navigator.mozAudioContext ||
881+
navigator.msAudioContext;
882+
if (!_sharedAudioContext || _sharedAudioContext.state === "closed") {
883+
_sharedAudioContext = new window.AudioContext();
884+
}
885+
return _sharedAudioContext;
886+
}
887+
881888
this._playABCSong = function () {
882889
const abc = abcNotationSong;
883890
const stopAudioButton = document.querySelector(".stop-audio");
@@ -887,16 +894,7 @@ function AIWidget() {
887894
})[0];
888895

889896
if (ABCJS.synth.supportsAudio()) {
890-
// An audio context is needed - this can be passed in for two reasons:
891-
// 1) So that you can share this audio context with other elements on your page.
892-
// 2) So that you can create it during a user interaction so that the browser doesn't block the sound.
893-
// Setting this is optional - if you don't set an audioContext, then abcjs will create one.
894-
window.AudioContext =
895-
window.AudioContext ||
896-
window.webkitAudioContext ||
897-
navigator.mozAudioContext ||
898-
navigator.msAudioContext;
899-
const audioContext = new window.AudioContext();
897+
const audioContext = _getAudioContext();
900898
audioContext.resume().then(function () {
901899
// In theory the AC shouldn't start suspended because it is being initialized in a click handler, but iOS seems to anyway.
902900

@@ -1033,9 +1031,10 @@ function AIWidget() {
10331031
this._scale = function () {
10341032
let width, height;
10351033
const canvas = document.getElementsByClassName("samplerCanvas");
1036-
Array.prototype.forEach.call(canvas, ele => {
1037-
this.widgetWindow.getWidgetBody().removeChild(ele);
1038-
});
1034+
const body = this.widgetWindow.getWidgetBody();
1035+
for (let i = canvas.length - 1; i >= 0; i--) {
1036+
body.removeChild(canvas[i]);
1037+
}
10391038
if (!this.widgetWindow.isMaximized()) {
10401039
width = SAMPLEWIDTH;
10411040
height = SAMPLEHEIGHT;
@@ -1056,52 +1055,55 @@ function AIWidget() {
10561055
* @returns {void}
10571056
*/
10581057
this.makeCanvas = function (width, height) {
1058+
// Build the entire widget DOM off-screen in a DocumentFragment,
1059+
// then append once to avoid multiple reflows.
1060+
const fragment = document.createDocumentFragment();
1061+
10591062
// Create a container to center the elements
10601063
const container = document.createElement("div");
1061-
1062-
this.widgetWindow.getWidgetBody().appendChild(container);
1064+
fragment.appendChild(container);
10631065

10641066
// Create a scrollable container for the textarea
10651067
const scrollContainer = document.createElement("div");
1066-
scrollContainer.style.overflowY = "auto"; // Enable vertical scrolling
1067-
scrollContainer.style.height = height + "px"; // Set the height of the scroll container
1068-
scrollContainer.style.border = "1px solid #ccc"; // Optional: Add a border for visibility
1069-
scrollContainer.style.marginBottom = "8px";
1070-
scrollContainer.style.marginLeft = "8px";
1071-
scrollContainer.style.display = "flex"; // Use flexbox for centering
1072-
scrollContainer.style.flexDirection = "column"; // Stack elements vertically
1073-
scrollContainer.style.alignItems = "center"; // Center items horizontally
1068+
scrollContainer.style.cssText =
1069+
"overflow-y:auto;" +
1070+
"height:" +
1071+
height +
1072+
"px;" +
1073+
"border:1px solid #ccc;" +
1074+
"margin-bottom:8px;" +
1075+
"margin-left:8px;" +
1076+
"display:flex;" +
1077+
"flex-direction:column;" +
1078+
"align-items:center";
10741079
container.appendChild(scrollContainer);
10751080

10761081
// Create the textarea element
10771082
const textarea = document.createElement("textarea");
1078-
textarea.style.height = height + "px"; // Keep the height for the scrollable area
1079-
textarea.style.width = width + "px";
1083+
textarea.style.cssText =
1084+
"height:" +
1085+
height +
1086+
"px;" +
1087+
"width:" +
1088+
width +
1089+
"px;" +
1090+
"margin-left:20px;" +
1091+
"font-size:20px;" +
1092+
"padding:10px";
10801093
textarea.className = "samplerTextarea";
1081-
textarea.style.marginLeft = "20px";
1082-
textarea.style.fontSize = "20px";
1083-
textarea.style.padding = "10px";
10841094
scrollContainer.appendChild(textarea); // Append textarea to scroll container
10851095

10861096
// Create hint text elements
10871097
const hintsContainer = document.createElement("div");
1088-
hintsContainer.style.marginBottom = "10px";
1089-
1090-
hintsContainer.style.display = "flex";
1091-
hintsContainer.style.justifyContent = "center";
1092-
hintsContainer.style.marginTop = "8px";
1098+
hintsContainer.style.cssText =
1099+
"margin-bottom:10px;display:flex;justify-content:center;margin-top:8px";
10931100
const hints = ["Dance tune", "Fiddle jig", "Nice melody", "Fun song", "Simple canon"];
10941101
hints.forEach(hintText => {
10951102
const hint = document.createElement("span");
10961103
hint.textContent = hintText;
1097-
hint.style.marginRight = "20px";
1098-
hint.style.cursor = "pointer";
1099-
hint.style.marginRight = "4px";
1100-
hint.style.fontSize = "20px";
1101-
hint.style.color = "blue";
1102-
hint.style.backgroundColor = "rgb(227 162 162 / 80%)"; // Light white background
1103-
hint.style.padding = "10px"; // Add padding for spacing
1104-
hint.style.borderRadius = "5px"; // Optional: Rounded corners
1104+
hint.style.cssText =
1105+
"cursor:pointer;margin-right:4px;font-size:20px;color:blue;" +
1106+
"background-color:rgb(227 162 162 / 80%);padding:10px;border-radius:5px";
11051107

11061108
hint.onclick = function () {
11071109
inputField.value = hintText;
@@ -1117,12 +1119,9 @@ function AIWidget() {
11171119
inputField.type = "text";
11181120
inputField.className = "inputField";
11191121
inputField.placeholder = "Enter text here";
1120-
inputField.style.fontSize = "20px";
1121-
inputField.style.marginRight = "2px";
1122-
inputField.style.marginLeft = "64px";
1123-
inputField.style.padding = "10px";
1124-
inputField.style.marginBottom = "10px";
1125-
inputField.style.width = "60%";
1122+
inputField.style.cssText =
1123+
"font-size:20px;margin-right:2px;margin-left:64px;" +
1124+
"padding:10px;margin-bottom:10px;width:60%";
11261125
container.appendChild(inputField);
11271126

11281127
inputField.addEventListener("click", function () {
@@ -1250,5 +1249,8 @@ function AIWidget() {
12501249
textarea.addEventListener("input", function () {
12511250
abcNotationSong = textarea.value;
12521251
});
1252+
1253+
// Single DOM append — all elements are now in the fragment
1254+
this.widgetWindow.getWidgetBody().appendChild(fragment);
12531255
};
12541256
}

0 commit comments

Comments
 (0)