Sonogram

by Licson Lee

HTML

<canvas id="screen"></canvas>
<a class="start-btn" href="#">&#x25b6; Play</a>

CSS

body {
  margin: 0;
  padding: 0;
  outline: none;
  background: #000;
}

canvas#screen {
  position: absolute;
  top: 0;
  left: 0;
}

.start-btn {
  display: block;
  position: absolute;
  left: calc(50vw - 45px);
  top: calc(50vh - 15px);
  width: 90px;
  height: 30px;
  background: #eee;
  color: #000;
  font-size: 22px;
  font-family: Consolas, Courier, sans-serif;
  text-align: center;
}

JavaScript

class Sonogram {
  constructor(canvasSel) {
    this.canvasEl = document.querySelector(canvasSel);
    this.tmpCanvas = document.createElement('canvas');

    this.gctx = this.canvasEl.getContext('2d');
    this.tctx = this.tmpCanvas.getContext('2d');
    this.actx = new AudioContext();

    this.anodes = {
      src: null,
      analyser: null
    };

    this.buf = null;
    this.freqBins = new Uint8Array(256);
    this.loaded = false;
    this.loadprogress = 0;

    this.setupAudioRouting();

    this.canvasEl.width = window.innerWidth;
    this.canvasEl.height = window.innerHeight;
    this.tmpCanvas.width = window.innerWidth;
    this.tmpCanvas.height = window.innerHeight;

    window.addEventListener('resize', () => {
      this.canvasEl.width = window.innerWidth;
      this.canvasEl.height = window.innerHeight;
      this.tmpCanvas.width = window.innerWidth;
      this.tmpCanvas.height = window.innerHeight;
    }, false);

    this.render();
  }

  load(url) {
    this.loaded = false;

    let xhr = new XMLHttpRequest();
    xhr.responseType = 'arraybuffer';

    xhr.onprogress = (e) => {
      if (e.lengthComputable) {
        this.loadprogress = (e.loaded / e.total);
      }
    }

    xhr.onload = () => {
      this.actx.decodeAudioData(xhr.response, (buffer) => {
        this.buf = buffer;
        this.loaded = true;

        if (this.anodes.src) {
          this.anodes.src.buffer = this.buf;
          this.anodes.src.start(0);
        }
      });
    }

    xhr.open('GET', url);
    xhr.send(null);
  }

  setupAudioRouting() {
    let srcNode = this.actx.createBufferSource();
    let analyserNode = this.actx.createAnalyser();

    analyserNode.fftSize = 2048;
    analyserNode.smoothingTimeConstant = 0.3;
    analyserNode.maxDecibels = 0;
    analyserNode.minDecibels = -65;

    srcNode.loop = true;

    srcNode.connect(analyserNode);
    analyserNode.connect(this.actx.destination);

    this.anodes.src = srcNode;
    this.anodes.analyser =...