Skip welcome & menu and move to editor
Welcome to JS Bin
Load cached copy from
 
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Load a sound sample using XhR2, decode it and play it using Web Audio</title>
</head>
<body>
  <h1>Load a sound sample using XhR2, decode it and play it using Web Audio</H1>
  <p>The button below will be enabled when the sound is loaded. The download of the sound starts as soon as the page is being loaded.</p>
  <button id="playButton" disabled=true>Play sound</button>
  Try clicking rapidly on the button.
  <div class="wrapper">
    <canvas id="myCanvas" width=600 height=100></canvas>
    <canvas id="myCanvasOverlay" width=600 height=100></canvas>
  </div>
</body>
</html>
 
var ctx;
var soundURL = 
 'https://mainline.i3s.unice.fr/mooc/shoot2.mp3';
var decodedSound;
var canvas, canvasOverlay;
var ctxCanvasOverlay;
var waveformDrawer;
var mousePos = {x:0, y:0}
var xStart = 100;
var xEnd = 200;
var leftTrimBar = {
  x: xStart,
  color:"white"
}
var rightTrimBar = {
  x: xEnd,
  color:"white"
}
window.onload = async function init() {
  // To make it work even on browsers like Safari, that still
  // do not recognize the non prefixed version of AudioContext
var audioContext = window.AudioContext || window.webkitAudioContext;
 ctx = new audioContext();
 canvas = document.querySelector("#myCanvas");
 canvasOverlay = document.querySelector("#myCanvasOverlay");
 ctxCanvasOverlay = canvasOverlay.getContext("2d");
  
  ctxCanvasOverlay.fillStyle = "white"
  
  canvasOverlay.onmousemove = (evt) => {
    let rect = canvas.getBoundingClientRect();
   
    mousePos.x = (evt.clientX - rect.left);
    mousePos.y = (evt.clientY - rect.top);
    
    highLightTrimBarsWhenClose();
    
    if(mousePos.x <= 0) {
        leftTrimBar.x = 0;
        //leftTrimBar.dragged = false;
        //leftTrimBar.selected = false;
        //leftTrimBar.color = "white";
      }
    if(mousePos.x >= canvas.width) {
        rightTrimBar.x = 0;
        //leftTrimBar.dragged = false;
        //leftTrimBar.selected = false;
        //leftTrimBar.color = "white";
      }
    
    if(leftTrimBar.dragged) {
        if(leftTrimBar.x < rightTrimBar.x)
          leftTrimBar.x = mousePos.x;
        else {
          if(mousePos.x < rightTrimBar.x)
            leftTrimBar.x = mousePos.x;
        }
      
    }
    if(rightTrimBar.dragged) {
      if(rightTrimBar.x > leftTrimBar.x)
        rightTrimBar.x = mousePos.x;
      else {
        if(mousePos.x > rightTrimBar.x)
            rightTrimBar.x = mousePos.x;
      }
    }
    
    //ctxCanvasOverlay.fillStyle = "red"
    //ctxCanvasOverlay.fillRect(mousePos.x, mousePos.y, 5, 5);
  }
  
  canvasOverlay.onmousedown = (evt) => {
    if(leftTrimBar.selected)
      leftTrimBar.dragged = true;
    
    if(rightTrimBar.selected)
      rightTrimBar.dragged = true;
  }
  
  canvasOverlay.onmouseup = (evt) => {
    if(leftTrimBar.dragged) {
      leftTrimBar.dragged = false;
      leftTrimBar.selected = false;
      if(leftTrimBar.x > rightTrimBar.x)
        leftTrimBar.x = rightTrimBar.x;
    }
    
    if(rightTrimBar.dragged) {
      rightTrimBar.dragged = false;
      rightTrimBar.selected = false;
      
      if(rightTrimBar.x < leftTrimBar.x)
        rightTrimBar.x = leftTrimBar.x;
    }
  }
  
  loadSound(soundURL);
  
  waveformDrawer = new WaveformDrawer();
  
  playButton.onclick = function(evt) {
      playSound(decodedSound);
  };
  
  requestAnimationFrame(animate);
};
async function loadSound(url) {
   response = await fetch(url);
   sound =    await response.arrayBuffer();
  
    console.log("Sound loaded");
    
    // Let's decode it. This is also asynchronous
    ctx.decodeAudioData(sound, (buffer) => {
      console.log("Sound decoded");
      decodedSound = buffer;
      
      waveformDrawer.init(decodedSound, canvas, '#83E83E');
      waveformDrawer.drawWave(0, canvas.height);
      //drawTrimArrows(xStart, xEnd);
      // we enable the button
      playButton.disabled = false;
    }, (e) => {
      console.log("error");
    });
  };
  
function animate() {
  // clear overlay canvas;
  ctxCanvasOverlay.clearRect(0, 0, canvasOverlay.width, canvasOverlay.height);
  //waveformDrawer.drawWave(0, canvas.height);
  // draw trim bars and triangles
  drawTrimArrows(xStart, xEnd);
  
  // redraw
  requestAnimationFrame(animate);
}
function drawTrimArrows(xStart, xEnd) {
    let ctx = ctxCanvasOverlay;
    ctx.save();
  
  
  // two vertical lines
  ctx.lineWidth=2;
  
  ctx.strokeStyle=leftTrimBar.color;  
  ctx.beginPath();
  // start
  ctx.moveTo(leftTrimBar.x, 0);
  ctx.lineTo(leftTrimBar.x, canvas.height);
    ctx.stroke();
  // end
  ctx.beginPath();
  ctx.strokeStyle=rightTrimBar.color;  
  ctx.moveTo(rightTrimBar.x, 0);
  ctx.lineTo(rightTrimBar.x, canvas.height);
  ctx.stroke();
  
  // triangle start
  ctx.fillStyle=leftTrimBar.color; 
  ctx.beginPath();
  ctx.moveTo(leftTrimBar.x, -0);
  ctx.lineTo(leftTrimBar.x+10, 8);
  ctx.lineTo(leftTrimBar.x, 16);
  ctx.fill();
  
  // tiangle end
  ctx.beginPath();
  ctx.fillStyle=rightTrimBar.color; 
  ctx.moveTo(rightTrimBar.x, -0);
  ctx.lineTo(rightTrimBar.x-10, 8);
  ctx.lineTo(rightTrimBar.x, 16);
  ctx.fill();
  
  // We draw grey transparent rectangles before leftTrimBar and after rightTrimBar
  ctx.fillStyle = "rgba(128, 128, 128, 0.7)"
  ctx.fillRect(0, 0, leftTrimBar.x, canvas.height);
  ctx.fillRect(rightTrimBar.x, 0, canvas.width, canvas.height);
  
  ctx.restore();
}
function moveTrimBars() {
  // compute distance between mousePos and trim pos
  let d = distance(mousePos.x, mousePos.y, xStart+5, 4);
  if(d < 20) {
    leftTrimBar.x = mousePos.x;
    drawTrimArrows(leftTrimBar.x, 200)
  }
  
}
function highLightTrimBarsWhenClose() {
  // compute distance between mousePos and trim pos
  let d = distance(mousePos.x, mousePos.y, leftTrimBar.x+5, 4);
  if((d < 10) && (!rightTrimBar.selected)){
    leftTrimBar.color = "red";
    leftTrimBar.selected = true;
  } else {
    leftTrimBar.color = "white";
    leftTrimBar.selected = false;
  }
  
  d = distance(mousePos.x, mousePos.y, rightTrimBar.x-5, 4);
  if((d < 10)&& (!leftTrimBar.selected)) {
    rightTrimBar.color = "red";
    rightTrimBar.selected = true;
  } else {
    rightTrimBar.color = "white";
    rightTrimBar.selected = false;
  }
  
}
function distance(x1, y1, x2, y2){
    let y = x2 - x1;
    let x = y2 - y1;
    
    return Math.sqrt(x * x + y * y);
}
function playSound(buffer){
    var bufferSource = ctx.createBufferSource();
    bufferSource.buffer = buffer;
    bufferSource.connect(ctx.destination);
  
    let bufferDuration = bufferSource.buffer.duration;
    // pixelsToSeconds
  
    let start = pixelToSeconds(leftTrimBar.x, bufferDuration);
    let trimmedDuration = pixelToSeconds(rightTrimBar.x - leftTrimBar.x, bufferDuration);
    bufferSource.start(0, start, trimmedDuration);
}
function pixelToSeconds(x, bufferDuration) {
  // canvas.width -> bufferDuration
  // x -> result
  let result = x * bufferDuration / canvas.width;
  return result;
}
function WaveformDrawer() {
    this.decodedAudioBuffer;
    this.peaks;
    this.canvas;
    this.displayWidth;
    this.displayHeight;
    this.sampleStep =  10;
    this.color = 'black';
    //test
    this.init = function(decodedAudioBuffer, canvas, color) {
        this.decodedAudioBuffer = decodedAudioBuffer;
        this.canvas = canvas;
        this.displayWidth = canvas.width;
        this.displayHeight = canvas.height;
        this.color = color;
        //this.sampleStep = sampleStep;
        // Initialize the peaks array from the decoded audio buffer and canvas size
        this.getPeaks();
    }
    this.max = function max(values) {
        var max = -Infinity;
        for (var i = 0, len = values.length; i < len; i++) {
            var val = values[i];
            if (val > max) { max = val; }
        }
        return max;
    }
    // Fist parameter : where to start vertically in the canvas (useful when we draw several
    // waveforms in a single canvas)
    // Second parameter = height of the sample
    this.drawWave = function(startY, height) {
        var ctx = this.canvas.getContext('2d');
        ctx.save();
        ctx.translate(0, startY);
        ctx.fillStyle = this.color;
        ctx.strokeStyle = this.color;
        var width = this.displayWidth;
        var coef = height / (2 * this.max(this.peaks));
        var halfH = height / 2;
        ctx.beginPath();
        ctx.moveTo(0, halfH);
        ctx.lineTo(width, halfH);
        console.log("drawing from 0, " + halfH + " to " + width + ", " + halfH);
        ctx.stroke();
        ctx.beginPath();
        ctx.moveTo(0, halfH);
       
        for (var i = 0; i < width; i++) {
            var h = Math.round(this.peaks[i] * coef);
            ctx.lineTo(i, halfH + h);
        }
        ctx.lineTo(width, halfH);
        ctx.moveTo(0, halfH);
        for (var i = 0; i < width; i++) {
            var h = Math.round(this.peaks[i] * coef);
            ctx.lineTo(i, halfH - h);
        }
        ctx.lineTo(width, halfH);
        ctx.fill();
        
        ctx.restore();
        }
    // Builds an array of peaks for drawing
    // Need the decoded buffer
    // Note that we go first through all the sample data and then
    // compute the value for a given column in the canvas, not the reverse
    // A sampleStep value is used in order not to look each indivudal sample
    // value as they are about 15 millions of samples in a 3mn song !
    this.getPeaks = function() {
        var buffer = this.decodedAudioBuffer;
        var sampleSize = Math.ceil(buffer.length / this.displayWidth);
        console.log("sample size = " + buffer.length);
        this.sampleStep = this.sampleStep || ~~(sampleSize / 10);
        var channels = buffer.numberOfChannels;
        // The result is an array of size equal to the displayWidth
        this.peaks = new Float32Array(this.displayWidth);
        // For each channel
        for (var c = 0; c < channels; c++) {
            var chan = buffer.getChannelData(c);
            for (var i = 0; i < this.displayWidth; i++) {
                var start = ~~(i * sampleSize);
                var end = start + sampleSize;
                var peak = 0;
                for (var j = start; j < end; j += this.sampleStep) {
                    var value = chan[j];
                    if (value > peak) {
                        peak = value;
                    } else if (-value > peak) {
                        peak = -value;
                    }
                }
                if (c > 1) {
                    this.peaks[i] += peak / channels;
                } else {
                    this.peaks[i] = peak / channels;
                }
            }
        }
    }
}
Output

You can jump to the latest bin by adding /latest to your URL

Dismiss x
public
Bin info
micbuffapro
0viewers