How to make dynamic scores in vexflow?

714 views Asked by At

So here's the thing, I've been learning and trying to use veflow for roughly a week. I'm trying to create an app with which you can compose a song (similar to Sibelius, Finale...). Right now, the app creates a f#/4 each time you click, but the problem arises when you try to readjust the screen size, because what I intend to do is that it adapts and puts the maximum amount of bars per row, however the notes that you had already put won't get erased and I'm not sure how to remove them, or if there is a better way to make dynamic scores in vexflow. I will share my code. Thanks for any help :))

(You can check what I mean via, click 15-20 times in screen and then try to readjust the screen to be smaller, notes will be floating in mid air)

1

There are 1 answers

0
Cosme Valera Reales On

I cannot attach a zip with the code, but they are 1 html, and 1 js, you can copy them inside a folder and try it yourself.

<!-- THIS IS THE HTML-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Add notes when click in vexflow</title>
</head>
<body>
    <div id="my-stave"></div>

    <!-- Scripts -->
    <script src="https://unpkg.com/vexflow/releases/vexflow-min.js"></script>
    <script src="index.js"></script>
</body>
</html>

//THIS IS THE JS
const VF = Vex.Flow;
const divStave = document.getElementById("my-stave");
const renderer = new VF.Renderer(divStave, VF.Renderer.Backends.SVG);
const context = renderer.getContext();
var amountOfBarsPerRow;
var rendererWidth;
var rendererHeight;
const bars = [];
const notesOfBars = []; //In each position it has an array of 4 notes
var currentBar;
var currentNotes; //An array of 4 notes

function initialLoad() {
    rendererWidth = 10000;
    rendererHeight = 10000;
    amountOfBarsPerRow = 2;
    renderer.resize(rendererWidth, rendererHeight);
    //Create a first bar full of silences
    createNewBarFullOfSilences(0);
    currentBar = bars[0];
    currentNotes = notesOfBars[0];
}

function divStaveClicked(evt) {
    //- clear context: this removes previous notes and staves
    context.clear();
    // context.rect(10, 40, rendererWidth, rendererHeight, { stroke: 'none', fill: 'white' });
     

    //- we put a note in current bar
    addOneNoteToCurrentNotes();

    //- if last bar has 1 note and 3 silences
    // then we create another bar (with 4 silences)
    if (barHasExactlyOneNote(bars.length - 1)) {
        createNewBarFullOfSilences(bars.length);
        renderer.resize(rendererWidth, rendererHeight);
    } else if (hasCurrentBarFourNotes()) {
        //- we have a new bar
        currentBar = bars[bars.length - 1];
        currentNotes = notesOfBars[bars.length - 1];
    }

    //- recover and draw all bars and its notes
    recoverAndDrawBars();
}

function windowSizeChanged(evt) {
    // I need something to remove notes too, not just stave like in next line
    // context.clear();
    context.rect(10, 40, rendererWidth, rendererHeight, { stroke: 'none', fill: 'white' });

    changeAmountOfBarsPerRowRegardingScreenWidth();

    recoverAndDrawBars();

    recalculatePositionNotes();
}

function createNewBarFullOfSilences(barPos) {
    let widthAndX = calculateWidthAndX();
    let heightAndY = calculateHeightAndY();
    //
    // rendererWidth = calculateRendererWidth(); //TODO
    // rendererHeight = calculateRendererHeight();
    let newBar = createBar(barPos, widthAndX, heightAndY)
        .setEndBarType(Vex.Flow.Barline.type.END)
        .draw();
    bars[barPos] = newBar;

    notesOfBars[barPos] = [
        new VF.StaveNote({ clef: "treble", keys: ["b/4"], duration: "qr" }),
        new VF.StaveNote({ clef: "treble", keys: ["b/4"], duration: "qr" }),
        new VF.StaveNote({ clef: "treble", keys: ["b/4"], duration: "qr" }),
        new VF.StaveNote({ clef: "treble", keys: ["b/4"], duration: "qr" }),
    ];

    let voice = new VF.Voice({ num_beats: 4, beat_value: 4 });
    voice.addTickables(notesOfBars[barPos]);
    new VF.Formatter().joinVoices([voice]).format([voice], 350);
    voice.draw(context, bars[barPos]);
}

function calculateWidthAndX(barParam) {
    let previousBar = getPreviousBar(barParam);
    let barPosition = bars.indexOf(previousBar) + 1; //we need +1 because 1st bar is not in array, so it has -1 pos

    //If it's 0 we are in first bar of row, and 1 means the second bar of row
    let widthAndXPosition = Math.floor(barPosition % amountOfBarsPerRow);

    return previousBar == null || widthAndXPosition == 0
        ? 10
        : previousBar.width + previousBar.x;
}

function calculateHeightAndY(barParam) {
    let previousBar = getPreviousBar(barParam);
    let barPosition = bars.indexOf(previousBar) + 1;

    let heightAndYMultiplier = Math.floor(barPosition / amountOfBarsPerRow);

    return previousBar == null ? 40 : 40 + 100 * heightAndYMultiplier;
}

function getPreviousBar(barParam) {
    let previousBar;
    for (let bar of bars) {
        if (bar === barParam) break;
        previousBar = bar;
    }
    return previousBar;
}

function createBar(barPos, widthAndX, heightAndY) {
    let widthAndXPosition = Math.floor(barPos % amountOfBarsPerRow);

    let bar = new VF.Stave(
        barPos == 0 ? 10 : widthAndX,
        barPos == 0 ? 40 : heightAndY,
        widthAndXPosition == 0 ? 400 : 350
    ).setContext(context);
    if (barPos == 0) bar.addTimeSignature("4/4");
    if (widthAndXPosition == 0) bar.addClef("treble");
    return bar;
}

function addOneNoteToCurrentNotes() {
    let pos = findPositionOfFirstSilenceNote();
    currentNotes[pos] = new VF.StaveNote({
        clef: "treble",
        keys: ["f#/4"],
        duration: "q",
    }).addAccidental(0, new VF.Accidental("#"));
}

function findPositionOfFirstSilenceNote() {
    for (let notePos = 0; notePos < currentNotes.length; notePos++) {
        if (currentNotes[notePos].customTypes[0] === "r") {
            return notePos;
        }
    }
}

function recoverAndDrawBars() {
    for (let barPos = 0; barPos < bars.length; barPos++) {
        let widthAndX = calculateWidthAndX(bars[barPos]);
        let heightAndY = calculateHeightAndY(bars[barPos]);

        let bar = createBar(barPos, widthAndX, heightAndY);
        if (barPos == bars.length - 1) {
            bar.setEndBarType(Vex.Flow.Barline.type.END); // Last bar
        }
        bar.draw();

        let voice = new VF.Voice({ num_beats: 4, beat_value: 4 });
        voice.addTickables(notesOfBars[barPos]);
        new VF.Formatter().joinVoices([voice]).format([voice], 350);
        voice.draw(context, bars[barPos]);
    }
}

function recalculatePositionNotes() {

}

function barHasExactlyOneNote(pos) {
    // 1 note & 3 silences: if note.customTypes[0] == "r": it is a silence
    return (
        notesOfBars[pos][0].customTypes[0] !== "r" &&
        notesOfBars[pos][1].customTypes[0] === "r" &&
        notesOfBars[pos][2].customTypes[0] === "r" &&
        notesOfBars[pos][3].customTypes[0] === "r"
    );
}

function hasCurrentBarFourNotes() {
    currentBarPos = getCurrentBarPos();
    return (
        notesOfBars[currentBarPos][0].customTypes[0] !== "r" &&
        notesOfBars[currentBarPos][1].customTypes[0] !== "r" &&
        notesOfBars[currentBarPos][2].customTypes[0] !== "r" &&
        notesOfBars[currentBarPos][3].customTypes[0] !== "r"
    );
}

function getCurrentBarPos() {
    for (let barPos = 0; barPos < bars.length; barPos++) {
        if (bars[barPos] === currentBar) {
            return barPos;
        }
    }
}

function changeAmountOfBarsPerRowRegardingScreenWidth() {
    let screenWidth = window.innerWidth;

    let screenWidthRemaining = screenWidth - 440; //1 row
    amountOfBarsPerRow = 1;

    while (true) {
        //For each 350 px reamining add 1 row
        screenWidthRemaining -= 350;
        if (screenWidthRemaining < 0) break;
        amountOfBarsPerRow++;
    }
}

//Listeners
divStave.addEventListener("click", divStaveClicked);
window.addEventListener("resize", windowSizeChanged);

initialLoad();