paulrosen / abcjs

javascript for rendering abc music notation
Other
1.89k stars 280 forks source link

Sound and cursor position is not Sync #950

Open pandya293 opened 11 months ago

pandya293 commented 11 months ago

Hello @paulrosen ,

can you please helps me ? I am attaching full-synth.txt Please save file into .html and run in Browser. Note: using abcjs-basic.js file of version 6.2.2 library

Till 15 bars, sound and cursor is properly synchronise, after 15 bars finished, rhythm sound is come before the cursor reach particular symbol

Please help me to resolve this issue.

also attaching source code ` abcjs: Synth Demo

<link rel="stylesheet" type="text/css" href="../abcjs-audio.css">
<style>
    main {
        max-width: 770px;
        margin: 0 auto;
    }
    .feedback {
        height: 600px;
        font-family: Arial, "sans-serif";
    }
    .highlight {
        fill: #0a9ecc;
    }
    .abcjs-cursor {
        stroke: red;
    }
    .click-explanation {
        color: red;
        font-style: italic;
    }
    .beat {
        font-weight: bold;
    }
    .label {
        color: #888888;
}
    .midi {
        margin-top: 20px;
        margin-left: 5px;
    }
    .seek-controls {
        margin-top: 5px;
    }
    .seek-controls.disabled {
        background-color: #cccccc;
        opacity: 0.5;
    }
</style>

<script src="../dist/abcjs-basic.js" type="text/javascript"></script>
<script type="text/javascript">

    function CursorControl() {
        var self = this;

        self.onReady = function() {
            var downloadLink = document.querySelector(".download");
            downloadLink.addEventListener("click", download);
            downloadLink.setAttribute("style", "");
            var clickEl = document.querySelector(".click-explanation")
            clickEl.setAttribute("style", "");
        };
        self.onStart = function() {
            var svg = document.querySelector("#paper svg");
            var cursor = document.createElementNS("http://www.w3.org/2000/svg", "line");
            cursor.setAttribute("class", "abcjs-cursor");
            cursor.setAttributeNS(null, 'x1', 0);
            cursor.setAttributeNS(null, 'y1', 0);
            cursor.setAttributeNS(null, 'x2', 0);
            cursor.setAttributeNS(null, 'y2', 0);
            svg.appendChild(cursor);

        };
        self.beatSubdivisions = 2;
        self.onBeat = function(beatNumber, totalBeats, totalTime) {
            if (!self.beatDiv)
                self.beatDiv = document.querySelector(".beat");
            self.beatDiv.innerText = "Beat: " + beatNumber + " Total: " + totalBeats + " Total time: " + totalTime;
        };
        self.onEvent = function(ev) {
            if (ev.measureStart && ev.left === null)
                return; // this was the second part of a tie across a measure line. Just ignore it.

            var lastSelection = document.querySelectorAll("#paper svg .highlight");
            for (var k = 0; k < lastSelection.length; k++)
                lastSelection[k].classList.remove("highlight");

            var el = document.querySelector(".feedback").innerHTML = "<div class='label'>Current Note:</div>" + JSON.stringify(ev, null, 4);
            for (var i = 0; i < ev.elements.length; i++ ) {
                var note = ev.elements[i];
                for (var j = 0; j < note.length; j++) {
                    note[j].classList.add("highlight");
                }
            }

            var cursor = document.querySelector("#paper svg .abcjs-cursor");
            if (cursor) {
                cursor.setAttribute("x1", ev.left - 2);
                cursor.setAttribute("x2", ev.left - 2);
                cursor.setAttribute("y1", ev.top);
                cursor.setAttribute("y2", ev.top + ev.height);
            }
        };
        self.onFinished = function() {
            var els = document.querySelectorAll("svg .highlight");
            for (var i = 0; i < els.length; i++ ) {
                els[i].classList.remove("highlight");
            }
            var cursor = document.querySelector("#paper svg .abcjs-cursor");
            if (cursor) {
                cursor.setAttribute("x1", 0);
                cursor.setAttribute("x2", 0);
                cursor.setAttribute("y1", 0);
                cursor.setAttribute("y2", 0);
            }
        };
    }

    var cursorControl = new CursorControl();

    var abc = [
    "M: 4/4\n" +
  "L: 1/8\n" +
  "Q:1/4=60\n" +
  "K:clef=perc stafflines = 1\n" +
  "%%barnumbers 1\n" +
  "%%stretchlast 0.6\n" +
  "%%partsbox 1\n" +
  "V:1\n" +
  "A2 z2 (3A4yA2 A2|\n" +
  "(3z2yA4 z2 A2 z2|\n" +
  "(3A2yA4 A2 z2 A2|\n" +
  "z2 A2 (3z2A2A2 z2|\n" +
  "A2 z2 (3A2A2A2 A2|\n" +
  "z2 A2 z2 (3z2z2A2|\n" +
  "A2 z2 (3z2A2A2 A2|\n" +
  "z2 A2 z2 (3z2yA4|\n" +
  "A2 (3A4yA2 z2 A2|\n" +
  "z2 (3A2A2A2 A2 z2|\n" +
  "A2 z2 A2 (3A2yA4|\n" +
  "z2 A2 z2 (3z2z2A2|\n" +
  "A2 (3A4yA2 z2 A2|\n" +
  "z2 (3z2A2A2 A2 z2|\n" +
  "A2 (3A2A2A2 z2 A2|\n" +
  "z2 A2 z2 (3z2yA4|\n" +
  "(3A2yA4 A2 z2 A2|\n" +
  "z2 A2 z2 (3z2z2A2|\n" +
  "A2 (3A4yA2 z2 A2|\n" +
  "z2 A2 z2 (3A2yA4|\n" +
  "A2 z2 (3A2A2A2 A2|\n" +
  "z2 A2 z2 (3z2yA4|\n" +
  "(3z2A2A2 A2 z2 A2|\n" +
  "z2 A2 z2 (3z2z2A2|\n" +
  "A2 (3A2A2A2 z2 A2|\n" +
  "(3z2A2A2 z2 A2 z2|\n" +
  "(3z2yA4 A2 z2 A2|\n" +
  "z2 (3A2yA4 A2 z2|\n" +
  "A2 z2 (3A4yA2 A2|\n" +
  "(3z2z2A2 z2 A2 z2|\n" +
  "(3A2yA4 A2 z2 A2|\n" +
  "(3z2A2A2 z2 A2 z2|\n" +
  "A2 (3A4yA2 z2 A2|\n" +
  "(3z2yA4 z2 A2 z2|\n" +
  "A2 (3A2A2A2 z2 A2|\n" +
  "z2 (3z2z2A2 A2 z2|\n" +
  "A2 z2 A2 (3A2yA4|\n" +
  "z2 A2 z2 (3A2A2A2|\n" +
  "A2 z2 (3z2yA4 A2|\n" +
  "z2 A2 (3A4yA2 z2|\n" +
  "A2 z2 (3z2A2A2 A2|\n" +
  "z2 A2 (3z2z2A2 z2|\n" +
  "A2 (3A2A2A2 z2 A2|\n" +
  "z2 A2 z2 (3A4yA2|\n" +
  "(3z2A2A2 A2 z2 A2|\n" +
  "(3A2yA4 z2 A2 z2|\n"
    ];

    var tuneNames = [ "Cooleys", "Bill Bailey", "All Notes On Piano" ];

    var currentTune = 0;

    var synthControl;

    function clickListener(abcElem, tuneNumber, classes, analysis, drag, mouseEvent) {
        var output = "currentTrackMilliseconds: " + abcElem.currentTrackMilliseconds + "<br>" +
            "currentTrackWholeNotes: " + abcElem.currentTrackWholeNotes + "<br>" +
            "midiPitches: " + JSON.stringify(abcElem.midiPitches, null, 4) + "<br>" +
            "gracenotes: " + JSON.stringify(abcElem.gracenotes, null, 4) + "<br>" +
            "midiGraceNotePitches: " + JSON.stringify(abcElem.midiGraceNotePitches, null, 4) + "<br>";
        document.querySelector(".clicked-info").innerHTML = "<div class='label'>Clicked info:</div>" +output;

        var lastClicked = abcElem.midiPitches;
        if (!lastClicked)
            return;

        ABCJS.synth.playEvent(lastClicked, abcElem.midiGraceNotePitches, synthControl.visualObj.millisecondsPerMeasure()).then(function (response) {
            console.log("note played");
        }).catch(function (error) {
            console.log("error playing note", error);
        });
    }

    var abcOptions = {
        add_classes: true,
        clickListener: self.clickListener,
        responsive: "resize"
    };

    function load() {
        document.querySelector(".next").addEventListener("click", next);
        document.querySelector(".start").addEventListener("click", start);
        document.querySelector(".warp").addEventListener("click", warp);
        document.querySelector(".seek").addEventListener("click", seek);
        document.querySelector(".seek2").addEventListener("click", seek2);
        document.querySelector("#seek-units").addEventListener("change", seekExplanation);

        if (ABCJS.synth.supportsAudio()) {
            synthControl = new ABCJS.synth.SynthController();
            synthControl.load("#audio", cursorControl, {displayLoop: true, displayRestart: true, displayPlay: true, displayProgress: true, displayWarp: true});
        } else {
            document.querySelector("#audio").innerHTML = "<div class='audio-error'>Audio is not supported in this browser.</div>";
        }
        setTune(false);
    }

    function download() {
        if (synthControl)
            synthControl.download(tuneNames[currentTune] + ".wav");
    }

    function start() {
        if (synthControl)
            synthControl.play();
    }

    function seek() {
        synthControl.seek(0.50)
    }

    function seekExplanation() {
        var explanation = document.getElementById("unit-explanation");
        if (!synthControl.visualObj.noteTimings) {
            explanation.innerText = "First start playing to load audio before seeking.";
            return;
        }
        var units = this.value;
        var max = 1;
        switch (units) {
            case "seconds":
                max = synthControl.visualObj.getTotalTime();
                break;
            case "beats":
                max = synthControl.visualObj.getTotalBeats();
                break;
        }
        explanation.innerText = "Enter a number between 0 and {0}.".replace("{0}", max);
    }

    function seek2() {
        var amount = document.getElementById("seek-amount").value;
        var units = document.getElementById("seek-units").value;
        synthControl.seek(amount, units)
    }

    function warp() {
        var el = document.querySelector(".warp");
        el.setAttribute("disabled", true)
        var amount = Math.random()
        console.log("warp", amount)
        synthControl.setWarp(amount*100).then(function () {
            el.removeAttribute("disabled")
        })
    }

    function setTune(userAction) {
        var seekControls = document.querySelector(".seek-controls");
        seekControls.classList.add("disabled");
        // synthControl.disable(true);
        var visualObj = ABCJS.renderAbc("paper", abc[currentTune], abcOptions)[0];
        var midi = ABCJS.synth.getMidiFile(abc[currentTune]);
        var midiButton = document.querySelector(".midi");
        midiButton.innerHTML = midi;

        // TODO-PER: This will allow the callback function to have access to timing info - this should be incorporated into the render at some point.
        var midiBuffer = new ABCJS.synth.CreateSynth();
        midiBuffer.init({
            //audioContext: new AudioContext(),
            visualObj: visualObj,
            // sequence: [],
            // millisecondsPerMeasure: 1000,
            // debugCallback: function(message) { console.log(message) },
            options: {
                // soundFontUrl: "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/" ,
                // sequenceCallback: function(noteMapTracks, callbackContext) { return noteMapTracks; },
                // callbackContext: this,
                // onEnded: function(callbackContext),
                // pan: [ -0.5, 0.5 ]
            }
        }).then(function (response) {
            console.log(response);
            if (synthControl) {
                synthControl.setTune(visualObj, userAction).then(function (response) {
                    console.log("Audio successfully loaded.")
                    seekControls.classList.remove("disabled");
                    seekExplanation();
                }).catch(function (error) {
                    console.warn("Audio problem:", error);
                });
            }
        }).catch(function (error) {
            console.warn("Audio problem:", error);
        });
    }

    function next() {
        currentTune++;
        if (currentTune >= abc.length)
            currentTune = 0;
        setTune(true);
    }

</script>

`

paulrosen commented 11 months ago

It looks like there might be a bug but I'm not sure where. Can you make the smallest reproduceable example in https://editor.drawthedots.com?

I suspect it is either related to the "y" spacers (but that seems to work for me in other pieces) or something to do with the triplets. Or perhaps when you have a "y" spacer inside a triplet?

pandya293 commented 11 months ago

Hello paulrosen/Abcjs,

I am using below abc string in your given editor. But that also have have issues, cursor position why music sound is coming both are not synchronised.

Here is abc string M: 4/4 L: 1/8 Q:1/4=60 K:clef=perc stafflines = 1 %%barnumbers 1 %%stretchlast 0.6 %%partsbox 1 V:1 z2 A2 (3z2A2A2 z2| A2 z2 (3A2A2A2 A2| z2 A2 z2 (3z2z2A2| A2 z2 (3z2A2A2 A2| z2 A2 z2 (3z2yA4| A2 (3A4yA2 z2 A2| z2 (3A2A2A2 A2 z2| z2 A2 z2 (3z2z2A2| A2 (3A4yA2 z2 A2| z2 (3z2A2A2 A2 z2| A2 (3A2A2A2 z2 A2| z2 A2 z2 (3z2yA4| (3A2yA4 A2 z2 A2| z2 A2 z2 (3z2z2A2| A2 (3A4yA2 z2 A2| z2 A2 z2 (3A2yA4| A2 z2 (3A2A2A2 A2| (3z2A2A2 A2 z2 A2| z2 A2 z2 (3z2z2A2| A2 (3A2A2A2 z2 A2| (3z2A2A2 z2 A2 z2| (3z2yA4 A2 z2 A2| z2 (3A2yA4 A2 z2| A2 z2 (3A4yA2 A2| (3z2z2A2 z2 A2 z2| (3A2yA4 A2 z2 A2| (3z2A2A2 z2 A2 z2| A2 (3A4yA2 z2 A2| (3z2yA4 z2 A2 z2| A2 (3A2A2A2 z2 A2| z2 (3z2z2A2 A2 z2| A2 z2 A2 (3A2yA4| z2 A2 z2 (3A2A2A2| A2 z2 (3z2yA4 A2| z2 A2 (3A4yA2 z2| A2 z2 (3z2A2A2 A2| z2 A2 (3z2z2A2 z2| A2 (3A2A2A2 z2 A2| z2 A2 z2 (3A4yA2| (3z2A2A2 A2 z2 A2| (3A2yA4 z2 A2 z2|

On Mon, 2 Oct 2023, 7:04 am Paul Rosen, @.***> wrote:

It looks like there might be a bug but I'm not sure where. Can you make the smallest reproduceable example in https://editor.drawthedots.com?

I suspect it is either related to the "y" spacers (but that seems to work for me in other pieces) or something to do with the triplets. Or perhaps when you have a "y" spacer inside a triplet?

— Reply to this email directly, view it on GitHub https://github.com/paulrosen/abcjs/issues/950#issuecomment-1742290728, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABQVBZLJG76KOJG45GYTUDX5IK3NANCNFSM6AAAAAA5K4HS6E . You are receiving this because you authored the thread.Message ID: @.***>