paulrosen / abcjs

javascript for rendering abc music notation

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Sound and cursor position is not Sync

pandya293 opened this issue · comments

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
` <title>abcjs: Synth Demo</title>

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

`

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?