楽器の練習用アプリ

音程を維持しつつ再生速度を変えつつ楽器を持つ間を設ける

http://cyokodog.github.io/material/web_audio/timestretch/demo/demo05.html

```html
<!DOCTYPE html>
<html>
<head>
<style>
.rate,
.count{
  width: 300px;
}
</style>
<title></title>
<meta charset="utf-8">
</head>
<body>
<h1>楽器のおとも</h1>

<div>
  <h3>音声ファイル</h3>
  <input type="file"/>
</div>
<div>
  <h3>再生速度</h3>
  <input class="rate" type="range" min=".5" max="1.5" step=".1" value="1">
  <span class="dispRate"></span>倍速
</div>
<div>
  <h3>再生開始までの秒数</h3>
  <input class="count" type="range" min="1" max="10" step="1" value="5">
  <span class="dispCount"></span>秒
</div>
<div>
  <button class="start">再生</button>
  <button class="stop">停止</button>
</div>
<script>

var player = {
  src: '',
  audio: '',
  count: 1,
  currentCount: 1,
  rate: 1,
  cancelDelay: '',
  watchCb: [],
  setCount: function( count ){
    this.execWatchCb('count', this.count = count);
    this.setCurrentCount( count );
  },
  setRate: function( rate ){
    this.execWatchCb('rate', this.rate = rate);
    if( this.audio ){
      this.audio.playbackRate = this.rate;
    }
  },
  setSrc: function( src ){
    this.execWatchCb('src', this.src = src);
  },
  setCurrentCount: function( count ){
    this.execWatchCb('currentCount', this.currentCount = count);
  },
  play: function(){
    if( !this.src ) return false;
    this.stop();
    this.audio = new Audio( this.src );
    var count = player.count * 1000;
    this.cancelDelay = delay(
      function(){
        if( player.audio ){
          player.audio.playbackRate = player.rate;
          player.audio.play();
        }
      },
      count,
      function( elapsedTime ){
        player.setCurrentCount( Math.floor((count - elapsedTime)/1000) );
      }
    );
  },
  stop: function(){
    if( this.cancelDelay ){
      this.cancelDelay();
    }
    if( this.audio ){
      this.audio.pause();
    }
  },
  watch: function( cb ){
    this.watchCb.push( cb );
  },
  execWatchCb: function( name, value){
    var o = this;
    setTimeout(function(){
      o.watchCb.forEach(function( cb ){
        cb(name, value);
      });
    },0)
  }
};


var el = {
  fileSelector: document.querySelector('[type="file"]'),
  start: document.querySelector('.start'),
  stop: document.querySelector('.stop'),
  rate: document.querySelector('.rate'),
  dispRate: document.querySelector('.dispRate'),
  count: document.querySelector('.count'),
  dispCount: document.querySelector('.dispCount')
};

el.fileSelector.addEventListener('change', function(event) {
    var file = event.target.files[0];
    if (!(file instanceof File)) {
        window.alert('Please upload file.');
    } else if (file.type.indexOf('audio') === -1) {
        window.alert('Please upload audio file.');
    } else {
      player.setSrc( window.URL.createObjectURL(file) );
    }
}, false);


player.watch(function(name, value){
  if( name === 'currentCount'){
    el.count.value = value;
    el.dispCount.textContent = value;
  }
  else if( name === 'rate'){
    el.rate.value = value;
    el.dispRate.textContent = value;
  }
});

player.setCount(5);
player.setRate(.8);

el.start.addEventListener('click', function(){
  player.play();
});

el.stop.addEventListener('click', function(){
  player.stop();
});

el.rate.addEventListener('change', function(){
  player.setRate( this.value );
});

el.count.addEventListener('change', function(){
  player.setCount( this.value );
});

function delay(cb, delayTime, everyTimeCb){
  var cancel = false;
  delayTime = delayTime || 0;
  (function(){
    var baseTime;
    var raf = window.requestAnimationFrame;
    raf(function(now){
      baseTime = baseTime || now;
      var elapsedTime = now - baseTime;
      if(elapsedTime >= delayTime){
        cb(now);
      }
      else{
        everyTimeCb( elapsedTime );
        cancel || raf(arguments.callee);
      }
    });
  })();
  return function(){
    cancel = true;
  };
};

</script>
</body>
</html>

```