节拍器(HTML5版)

跑步的步频是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>

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值