| <!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; |
| |
| 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; |
| |
| |
| var midiBuffer = new ABCJS.synth.CreateSynth(); |
| midiBuffer.init({ |
| |
| visualObj: visualObj, |
| |
| |
| |
| options: { |
| |
| |
| |
| |
| |
| } |
| }).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> |
|
|