abcjs / examples /full-synth.html
KEXEL's picture
Upload 337 files
af6912c verified
<!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">
<link rel="icon" href="favicon.ico" type="image/x-icon"/>
<link rel="stylesheet" href="examples-styles.css"/>
<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 = [
"T: Cooley's\n" +
"M: 4/4\n" +
"Q: 1/4=120\n" +
"L: 1/8\n" +
"R: reel\n" +
"K: Emin\n" +
"|:{E}D2|EB{c}BA B2 EB|~B2 AB dBAG|FDAD BDAD|FDAD dAFD|\n" +
"EBBA B2 EB|B2 AB defg|afe^c dBAF|DEFD E2:|\n" +
"|:gf|eB B2 efge|eB B2 gedB|A2 FA DAFA|A2 FA defg|\n" +
"eB B2 eBgB|eB B2 defg|afe^c dBAF|DEFD E2:|",
"X:1\n" +
"T:Bill Bailey\n" +
"M:4/4\n" +
"L:1/4\n" +
"Q:1/4=210\n" +
"K:C\n" +
"\"C\"GA2c|e3/2^d/2eg|GA2c|e4|GA2c|e2g2|\"G7\"(gB3-|B4)|\n" +
"GB2d|fefg|GB2d|f4|GB2d|g2\"G+\"a2|\"C\"(ae3-|e4)|\n" +
"GA2c|e3/2^d/2eg|GA2c|e3G|GGce|g2_b2|\"F\"a2-a2-|a3c|\n" +
"cc2c|\"F#dim7\"d2c2|\"C\"gg2a|\"A7\"e3e|\"D7\"ed^cd|\"G7\"f2e2|\"C\"c4-|czz2|]",
"X:1\n" +
"T:All Notes On Piano\n" +
"M:4/4\n" +
"Q:120\n" +
"L:1/4\n" +
"K:C clef=bass\n" +
"A,,,,^A,,,,B,,,,C,,,|^C,,,D,,,^D,,,E,,,|F,,,^F,,,G,,,^G,,,|A,,,^A,,,B,,,C,,|\n" +
"^C,,D,,^D,,E,,|F,,^F,,G,,^G,,|A,,^A,,B,,C,|^C,D,^D,E,|\n" +
"K:C clef=treble\n" +
"F,^F,G,^G,|A,^A,B,C|^CD^DE|F^FG^G|\n" +
"A^ABc|^cd^de|f^fg^g|a^abc'|\n" +
"^c'd'^d'e'|f'^f'g'^g'|a'^a'b'c''|^c''d''^d''e''|\n" +
"f''^f''g''^g''|a''^a''b''c'''|^c'''4|]"
];
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>
</head>
<body onload="load()">
<header>
<img src="https://paulrosen.github.io/abcjs/img/abcjs_comp_extended_08.svg" alt="abcjs logo">
<h1>Full Synth</h1>
</header>
<div class="container">
<main>
<p>This is a complete demo of what you can do with matching audio with the visual music.</p>
<p>Different pieces of music are provided to demonstrate changing tunes. To toggle between the pieces, click "Next Tune".</p>
<p>You are in control of the look of the cursor. Two different techniques are demonstrated: highlighting the note being played and putting a cursor on the page. The class CursorControl must be supplied by your program.</p>
<p>As the piece is playing, there are callbacks when the note changes. The info returned in the callback is printed to the page as it is received.</p>
<p>The visual control for playing music can look different. In this example, the abcjs-audio.css file has been loaded. You can supply your own css.</p>
<button class="next">Next Tune</button>
<button class="start">Start/Pause</button>
<button class="warp">Rand. Warp</button>
<button class="seek">Seek to Middle</button>
<button class="download" style="display: none;">Download</button>
<div class="seek-controls disabled">
<label>Seek: <input type="number" min="0" id="seek-amount"></label>
<select aria-label="seek units" id="seek-units">
<option value="percent">Percent</option>
<option value="seconds">Seconds</option>
<option value="beats">Beats</option>
</select>
<button class="seek2">Seek</button>
<span id="unit-explanation" class="click-explanation">First start playing to load audio before seeking.</span>
</div>
<div class="midi">MIDI</div>
<div id="paper"></div>
<div id="audio"></div>
<p class="beat"></p>
<p class="click-explanation" style="display:none;">Click on a note to play that note.</p>
<pre class="clicked-info"></pre>
<pre class="feedback"></pre>
</main>
</div>
</body>
</html>