Philippa Markovics / Dec 06 2018
Animated Sonakinatography in Javascript
Animated Sonakinatography in Javascript
Sonakinatography
Sonakinatography
This article provides a very basic animated reproduction of Channa Horowitz’ Sonakinatography. For a detailed write-up see David’s article Sound, Motion, Notation.
Implementation
Implementation
Language:HTML
<style> #sk-demo { width: 260px; padding-left: 20px; position: relative; margin: 0 auto; } #sk-scroll { height: 240px; width: 240px; overflow: auto; padding-bottom: 240px; background: white; box-sizing: border-box; position: relative; border: 1px solid #ddd; } #sk-play { font-size: 40px; position: absolute; bottom: -180px; left: 50%; cursor: pointer; transition: all 0.2s ease; transform: translate(-50%, -50%); } #sk-play:hover { transform: translate(-50%, -50%) scale(1.1); } #sk-wrapper { position: relative; padding: 0 40px 0 60px; } .beat-label, .instrument-label, #sk-beats-label, #sk-replay { font-size: 9px; font-family: Menlo, monospace; color: #666; line-height: 20px; } .beat-label { position: absolute; text-align: right; width: 30px; height: 20px; left: 0; padding-right: 10px; margin-bottom: 4px; border-right: 1px solid #ddd; } #sk-legend { align-items: center; width: 240px; } #sk-instrument-labels { list-style: none; padding: 0 0 0 60px; margin: 0 0 0 0; display: flex; align-items: center; border: 1px solid #ddd; border-top: none; } .instrument-label { margin: 0; height: 20px; line-heigiht: 20px; width: 20px; text-align: center; } #sk-legend-label { width: 100%; } #sk-beats-label { transform-origin: center center; transform: rotate(-90deg) translateY(-110%); position: absolute; top: 50%; margin-top: -20px; } #sk-replay { height: 30px; line-height: 32px; font-size: 13px; text-align: center; border-bottom: 1px solid #ddd; width: 100%; cursor: pointer; transition: all 0.125s ease; } #sk-replay:hover { font-size: 15px; } </style> <div id="sk-container"> <div id="sk-demo"> <div id="sk-scroll"> <div id="sk-replay">🔁 Replay</div> <div id="sk-wrapper"> <canvas id="sk" width="160"></canvas> <div id="sk-play">▶️</div> </div> </div> <div id="sk-legend"> <ul id="sk-instrument-labels"> <li class="instrument-label">1</li> <li class="instrument-label">2</li> <li class="instrument-label">3</li> <li class="instrument-label">4</li> <li class="instrument-label">5</li> <li class="instrument-label">6</li> <li class="instrument-label">7</li> <li class="instrument-label">8</li> </ul> <div id="sk-legend-label" class="instrument-label"> Instrument </div> </div> <div id="sk-beats-label">Beats</div> </div> </div> <script> const colors = [ "rgb(0, 204, 0)", // yellow green "rgb(0, 102, 0)", // green "rgb(0, 0, 255)", // blue "rgb(127, 0, 255)", // blue violet "rgb(153, 0, 153)", // red violet "rgb(204, 0, 0)", // red "rgb(255, 128, 0)", // orange "rgb(255, 255, 0)" // yellow ]; var gridObjects = [0,1,2,3,4,5,6,7].map(function(n) { return { countdown: n + 1, color: n, instrument: n } }); const squareSize = 20; const lineWidth = 3; const maxBeats = 200; const visibleBeats = 12; var canvas = document.getElementById("sk"); var ctx = canvas.getContext("2d"); var beat = 0; var scrollEl = document.getElementById("sk-scroll"); var wrapperEl = document.getElementById("sk-wrapper"); canvas.setAttribute("height", squareSize * maxBeats); while (beat < maxBeats) { beat += 1; const beatLabel = document.createElement("div"); beatLabel.className = "beat-label"; beatLabel.innerHTML = beat; beatLabel.style.bottom = (beat - 1) * squareSize + "px"; wrapperEl.appendChild(beatLabel); gridObjects.map(function(instrument) { instrument.countdown -= 1; var x = squareSize * instrument.instrument; var y = maxBeats * squareSize - beat * squareSize; ctx.fillStyle = colors[instrument.color]; if (instrument.countdown === 0) { ctx.fillRect(x, y, squareSize, squareSize); if (instrument.color === 0) { instrument.color = 7; } else { instrument.color -= 1; } instrument.countdown = instrument.color + 1; } else { x = x + squareSize / 2 - 2; ctx.fillRect(x, y, lineWidth, squareSize); } }); } const easeInOutQuad = function (t, b, c, d) { t /= d / 2; if (t < 1) return c / 2 * t * t + b; t--; return - c / 2 * (t * (t - 2) - 1) + b; }; function scrollTo(el, to, duration) { var start = el.scrollTop, change = to - start, currentTime = 0, increment = 20; var animateScroll = function() { currentTime += increment; var val = easeInOutQuad(currentTime, start, change, duration); el.scrollTop = val; if (currentTime < duration) { setTimeout(animateScroll, increment); } }; animateScroll(); } function animate() { scrollEl.scrollTop = scrollEl.scrollHeight; scrollTo(scrollEl, 0, 10000); } scrollEl.scrollTop = scrollEl.scrollHeight; document.getElementById("sk-play").addEventListener("click", animate); document.getElementById("sk-replay").addEventListener("click", animate); </script>