跑步的步频是180,走路的步频是130,跑步或走路时听的音乐节拍也要与步频一致,像我这样的严重强迫症患者,多一拍少一拍都不行,因此要找到相应节奏的音乐很不容易。
网上找了很多的节拍器,都是设定频率后开始打拍,问题是我不可能知道一首音乐的精确节拍,一点一点试的话太麻烦!所以还是自己动手吧!
下面这个节拍器中有一个“连续点击测速”的按钮,可以听着音乐按节拍点击按钮,会测出节拍的具体频率,再通过加减精调,实在是方便 (^_^)
2018-03-27:【连续点击测速】这个功能加以改进,取多次点击后的平均速度值,这样结果更准确。
把下面的代码保存为 html 格式。代码中使用了 Web Audio API,动态生成音效,所以无需单独的音频文件。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>节拍器</title>
<style>
*{padding: 0;margin: 0;}
header{background-image: linear-gradient(to bottom,#4387fd,#4683ea);padding: 20px;font-size: 30px;color: #fff;}
footer{padding: 20px;text-align: center;color: #ccc;}
footer>a{color: #999;}
body>section{text-align: center;padding: 20px;width: 300px;margin: auto;}
body>section>section{margin: 30px auto;}
body>section>section>div{display: flex;justify-content:space-between;align-items: center;}
button {padding: 5px 10px;}
#btnTest {padding: 15px 30px;font-size: 20px;}
#btnStop {display: none;}
#speed{font-size: 50px;padding:0 20px;width: 100px;}
#msg{height: 20px;padding-top: 5px; font-size: 9px;color: gray;}
</style>
<script>
document.addEventListener("DOMContentLoaded", function () {
let speed = 60, //节拍速度
arrClick = [], //测速时每次点击的速度组成的数组,节拍速度取其平均值
clickTime = 0, //测速点击计时器,超过 3 秒清空数组
s = Date.now(), //记录每一次点击的时间,下一次点击时与此时间的间隔,来计算速度
time = 0, //play 过程 timeout 变量
isPlay = false, //是否正在播放
speedMsg = document.getElementById("speed"), //页面正中间显示速度值的元素
rangeValue = document.getElementById("rangeValue"), //滑块元素
showSpeed = () => rangeValue.value = speedMsg.innerText = speed; //更新显示速度值的函数
/** 测速按钮点击 */
document.getElementById("btnTest").addEventListener("click", function () {
let lastSpeed = Math.floor(60000 / (Date.now() - s));
if (lastSpeed - arrClick[arrClick.length - 1] > 30) { arrClick = []; } //如果点击时间和上次差别较大,则清零重测
arrClick.push(lastSpeed);
if (arrClick.length > 31) arrClick.shift(); //最大容量保持在30个(除去第 1 个不用)
//如果数量多于1个则计算速度(第 1 个时间间隔太久,不准确,弃之)
if (arrClick.length > 1) {
//取第2个到最后的平均值
speed = Math.ceil((arrClick.reduce((sum, n) => sum + n) - arrClick[0]) / (arrClick.length - 1));
if (arrClick.length > 5) document.getElementById("msg").innerText = "多点几次更准确...";
}
showSpeed();
s = Date.now();
document.getElementById("btnStop").click(); //测速时停止播放
//两次点击间隔大于 3 秒就重置
window.clearTimeout(clickTime);
clickTime = window.setTimeout(function () {
arrClick = [];
document.getElementById("msg").innerText = "";
}, 3000);
});
/** 播放按钮点击 */
document.getElementById("btnPlay").addEventListener("click", function () {
isPlay = true;
play();
this.style.display = "none";
document.getElementById("btnStop").style.display = "inline-block";
});
/** 停止按钮点击 */
document.getElementById("btnStop").addEventListener("click", function () {
window.clearTimeout(time);
isPlay = false;
this.style.display = "none";
document.getElementById("btnPlay").style.display = "inline-block";
});
/** 减号按钮点击 */
document.getElementById("btnSub").addEventListener("click", function () {
speed--;
showSpeed();
});
/** 加号按钮点击 */
document.getElementById("btnAdd").addEventListener("click", function () {
speed++;
showSpeed();
});
/** 滑动条更改 */
rangeValue.addEventListener("change", function () {
speed = this.value * 1;
showSpeed();
});
/** 播放 */
let play = () => {
window.clearTimeout(time);
playsound();
if (isPlay) {
time = window.setTimeout(play, Math.floor(60000 / speed));
};
}
let audioCtx = new AudioContext();
/** 发声 */
let playsound = () => {
let oscillator = audioCtx.createOscillator();
let gainNode = audioCtx.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillator.type = 'sine';
oscillator.frequency.setValueAtTime(220, audioCtx.currentTime);
oscillator.frequency.linearRampToValueAtTime(50, audioCtx.currentTime + 0.1);
gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
gainNode.gain.linearRampToValueAtTime(1, audioCtx.currentTime + 0.01);
gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.5);
oscillator.start(audioCtx.currentTime);
oscillator.stop(audioCtx.currentTime + 0.5);
}
});
</script>
</head>
<body>
<header>节拍器</header>
<section>
<button id="btnTest">连续点击测速</button>
<div id="msg"></div>
<section>
<div>
<button id="btnSub"><svg style="width: 16px; height: 16px;vertical-align: middle;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M66.462276 431.621065l891.075447 0 0 127.296492L66.462276 558.917558 66.462276 431.621065z"></path></svg></button>
<div id="speed">60</div>
<button id="btnAdd"><svg style="width: 16px; height: 16px;vertical-align: middle;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M1024 448H576V0H448v448H0v128h448v448h128V576h448z"></path></svg></button>
</div>
<div><input type="range" style="width:100%;" min="20" max="300" id="rangeValue" value="60"></div>
</section>
<button id="btnPlay">
<svg style="width: 32px; height: 32px;vertical-align: middle;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M196.394461 103.235223 196.394461 920.764777 827.604516 535.079648Z"></path></svg>
</button>
<button id="btnStop">
<svg style="width: 32px; height: 32px;vertical-align: middle;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M256 256l512 0 0 512-512 0z"></path></svg>
</button>
</section>
<footer>2018-02-05 by <a href="mailto://zk1218@gmail.com">zhangkai</a></footer>
</body>
</html>