Source

vendors/P5/sketchPerformance.js

// 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;