// File imports
import atVars from "../AlphaTab/variables";
import * as sketchBehaviors from "./sketchBehaviors";
/**
* P5 sketch wrapper for performance highlighting
* @module sketchPerformance
* @category P5
* @author Daniel Griessler <dgriessler20@gmail.com>
* @author Dan Levy <danlevy124@gmail.com>
*/
/**
* Wrapper for local p5 setup and draw functions
* @function
* @param {sketch} p Sketch object that will include all of the functions that will be called by p5
*/
const p5PerformanceSketch = (p) => {
p.type = sketchBehaviors.PERFORMANCE_HIGHLIGHTING;
// document elements retrieved from the document
let barCursor;
let alphaTabSurface;
// provided by reference which is updated in other functions
let drawer;
// reference to p5's canvas when created. It should overlay the AlphaTab canvas
let canvas;
const STATE_DEFAULT = 1;
const STATE_HIGHLIGHT = 2;
let state = STATE_DEFAULT;
let latestDrawnMeasure = -1;
let latestBase = 0;
let musicSections = [];
let currentMusicSection = null;
/**
* This function is called twice. Once, upon initialization p5 calls it which we use to tell p5 to stop looping
* Then, AlphaTab will call setup when its done being rendered. Then, the canvas can be setup for drawing since
* the canvas overlays the AlphaTab container
* @param {Drawer} drawerGiven - p5 will not provide this but atVars provides a reference to the Drawer being used
*/
p.setup = function (drawerGiven) {
if (drawerGiven === undefined) {
p.noLoop();
return;
}
drawer = drawerGiven;
// Retrieved by attaching unique IDs to elements generated by AlphaTab. Required editing AlphaTab.js directly
barCursor = document.getElementById("bC");
alphaTabSurface = document.getElementById("aTS");
// creates a canvas that overlaps the alphaTabSurface. Position is absolute for the canvas by default
canvas = p.createCanvas(
alphaTabSurface.clientWidth,
alphaTabSurface.clientHeight
);
const x = 0;
const y = 0;
canvas.position(x, y);
canvas.parent("sketch-holder");
p.loop();
};
const getPositionsObj = function (obj) {
return {
left: parseInt(obj.left.substring(0, obj.left.length - 2), 10),
top: parseInt(obj.top.substring(0, obj.top.length - 2), 10),
width: parseInt(obj.width.substring(0, obj.width.length - 2), 10),
height: parseInt(
obj.height.substring(0, obj.height.length - 2),
10
),
};
};
const setDrawColor = (measureNumber) => {
let measureIndex = measureNumber - 1;
if (
atVars.texLoaded &&
measureIndex < atVars.texLoaded.performanceProgress.length &&
measureIndex >= 0
) {
let score = atVars.texLoaded.performanceProgress[measureIndex];
if (score > 90) {
p.fill(0, 255, 0);
} else if (score > 80) {
p.fill(255, 255, 0);
} else {
p.fill(255, 0, 0);
}
} else {
p.fill(255, 0, 0);
}
};
/**
* Draws the canvas on the screen. Requires that the canvas is not undefined ie setup has run
* TODO: Handle sheet music scale
*/
p.draw = function () {
if (!atVars.getsFeedback || !drawer) {
return;
}
// TODO: Fix the first measure highlighting
if (
atVars &&
atVars.sketchBehavior === sketchBehaviors.PERFORMANCE_HIGHLIGHTING
) {
let firstBarPos = atVars.texLoaded.firstBarMeasurePosition;
let compareBarPos = null;
try {
let cursorBarStyle = document.getElementsByClassName(
"at-cursor-bar"
)[0].style;
compareBarPos = getPositionsObj(cursorBarStyle);
} catch (error) {}
if (compareBarPos === null || firstBarPos === null) {
return;
}
if (state === STATE_DEFAULT) {
state = STATE_HIGHLIGHT;
p.clear();
}
const measurePositions = document
.getElementById("aTS")
.getElementsByClassName("measureSeparator");
p.noStroke();
if (
!isNaN(compareBarPos.left) &&
!isNaN(compareBarPos.top) &&
!isNaN(compareBarPos.width) &&
!isNaN(compareBarPos.height) &&
(firstBarPos.left !== compareBarPos.left ||
firstBarPos.top !== compareBarPos.top ||
firstBarPos.width !== compareBarPos.width ||
firstBarPos.height !== compareBarPos.height)
) {
p.fill("#F8F8F8");
// draws clearing rectangle with total height of alpha tab from previous X position to the end
p.rect(
0,
measurePositions[0].y.baseVal.value,
measurePositions[0].x.baseVal.value,
drawer.distanceBetweenLines * 4
);
atVars.texLoaded.firstBarMeasurePosition = {
left: compareBarPos.left,
top: compareBarPos.top,
width: compareBarPos.width,
height: compareBarPos.height,
};
setDrawColor(1);
let firstBarPos = atVars.texLoaded.firstBarMeasurePosition;
let pos1X = firstBarPos.left;
let pos1Y = firstBarPos.top + drawer.distanceBetweenLines;
let pos2X = measurePositions[0].x.baseVal.value;
p.rect(
pos1X,
pos1Y,
pos2X - pos1X,
drawer.distanceBetweenLines * 4
);
}
if (latestDrawnMeasure === -1) {
setDrawColor(1);
// draws highlight on first measure
let firstBarPos = atVars.texLoaded.firstBarMeasurePosition;
let pos1X = firstBarPos.left;
let pos1Y = firstBarPos.top + drawer.distanceBetweenLines;
let pos2X = measurePositions[0].x.baseVal.value;
p.rect(
pos1X,
pos1Y,
pos2X - pos1X,
drawer.distanceBetweenLines * 4
);
if (!isNaN(pos1X)) {
latestDrawnMeasure++;
currentMusicSection = {
startMeasure: 1,
endMeasure: 1,
base: 0,
};
} else {
atVars.api.timePosition = 0;
atVars.texLoaded.firstBarMeasurePosition = getPositionsObj(
barCursor.style
);
}
} else {
// draws height of later measures only drawing new measures
while (latestDrawnMeasure < measurePositions.length - 1) {
let pos1X =
measurePositions[latestDrawnMeasure].x.baseVal.value +
latestBase;
let pos1Y =
measurePositions[latestDrawnMeasure].y.baseVal.value;
if (
latestDrawnMeasure === 0 ||
pos1Y ===
measurePositions[latestDrawnMeasure - 1].y.baseVal
.value
) {
latestDrawnMeasure++;
if (
!measurePositions[
latestDrawnMeasure - 1
].parentNode.isSameNode(
measurePositions[latestDrawnMeasure].parentNode
)
) {
currentMusicSection.endMeasure = latestDrawnMeasure;
const newSection = JSON.parse(
JSON.stringify(currentMusicSection)
);
musicSections.push(newSection);
latestBase =
latestBase +
measurePositions[latestDrawnMeasure - 1].x
.baseVal.value +
1;
currentMusicSection.startMeasure = latestDrawnMeasure;
currentMusicSection.base = latestBase;
}
let dist = Math.abs(
measurePositions[latestDrawnMeasure].x.baseVal
.value +
latestBase -
pos1X
);
setDrawColor(latestDrawnMeasure + 1);
p.rect(
pos1X,
pos1Y,
dist,
drawer.distanceBetweenLines * 4
);
} else {
break;
}
}
}
} else if (state === STATE_HIGHLIGHT) {
p.clear();
latestDrawnMeasure = -1;
latestBase = 0;
musicSections.length = 0;
currentMusicSection = null;
state = STATE_DEFAULT;
}
};
p.clear = function () {
latestDrawnMeasure = -1;
latestBase = 0;
musicSections.length = 0;
currentMusicSection = null;
};
};
export default p5PerformanceSketch;
Source