Edited at

楽器の練習用アプリ

More than 3 years have passed since last update.

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

http://cyokodog.github.io/material/web_audio/timestretch/demo/demo05.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>