节拍器 - 敲击测速BPM检测器与节拍音轨 在线工具
跟随音乐敲击检测BPM,使用可调节速度、拍号、细分和可视化节拍指示器播放精确节拍音轨 — 使用Web Audio API的100%客户端处理。
保护您的听力。请从低音量开始。
点击开始...
常见问题
什么是节拍器,它有什么用途?
节拍器是一种以每分钟节拍数(BPM)为单位的可配置速度产生稳定点击声或节拍的设备或工具。音乐家使用节拍器来培养稳定的节奏感,在特定速度下练习,并在保持节奏准确性的同时逐步提高速度。这个在线节拍器还包含一个点击测速功能,让您可以通过跟着节拍点击来检测任何歌曲的BPM。
如何使用点击测速功能?
随着您正在听的音乐节奏反复点击大的TAP按钮。点击2次或更多次后,工具会计算您点击之间的平均间隔并显示检测到的BPM。为获得最佳精度,请跟着底鼓或主节拍点击4-8拍。稳定性指示器显示您的点击有多一致。获得BPM读数后,点击「Use this BPM」将其直接传输到节拍器进行练习。
如何使用节拍器点击音轨?
切换到节拍器标签页,使用滑块或数字输入设置所需的BPM,选择拍号(例如,4/4表示常见拍号),然后按播放。节拍器会产生可听到的点击声,第1拍有重音强拍。观看可视化节拍指示器查看小节内的当前位置。根据您的练习需要调整音量、细分和点击声音。
我的数据会被发送到服务器吗?
不会。此工具100%在客户端运行,使用浏览器内置的Web Audio API。所有音频合成、速度检测和节拍调度完全在您的设备上进行。任何音频数据、BPM读数或设置都不会被传输到服务器。初次页面加载后,该工具可完全离线工作。
为什么精确计时很重要,这个节拍器是如何实现的?
音乐练习需要几毫秒以内的计时精度。JavaScript内置的setTimeout/setInterval本身不够精确,因为主线程可能被其他任务延迟。这个节拍器使用业界标准的前瞻调度器模式:一个setInterval定时器频繁触发,并使用Web Audio API的高分辨率AudioContext.currentTime时钟提前调度音频事件。这确保了即使在长时间练习中,点击声也能在1-2毫秒内精确,没有漂移。
什么是拍号,支持哪些拍号?
拍号表示每个小节有多少拍以及哪个音符值占一拍。此工具支持:4/4(常见拍号,大多数流行/摇滚使用)、3/4(华尔兹)、2/4(进行曲)、6/8(复合二拍,常见于吉格舞曲)、5/4(不规则拍号,如Take Five)和7/8(不规则拍号,常见于巴尔干音乐)。每种拍号都有适当的重音模式,帮助您感受节奏结构。
细分之间有什么区别?
细分将每个主要节拍分成更小的单位,以进行更精细的节奏练习。「无」仅播放四分音符节拍。「八分音符」在每个节拍之间添加一个点击声(每拍2个)。「三连音」将每个节拍分为3等分。「十六分音符」将每个节拍分为4等分。细分点击声比主要节拍更轻,以保持脉冲层次。
代码示例
// Web Audio API Metronome with Lookahead Scheduler
class Metronome {
constructor(bpm = 120, beatsPerMeasure = 4) {
this.bpm = bpm;
this.beatsPerMeasure = beatsPerMeasure;
this.isPlaying = false;
this.currentBeat = 0;
this.nextNoteTime = 0;
this.timerID = null;
this.audioContext = null;
}
start() {
if (this.isPlaying) return;
this.audioContext = new AudioContext();
this.isPlaying = true;
this.currentBeat = 0;
this.nextNoteTime = this.audioContext.currentTime;
this.timerID = setInterval(() => this._scheduler(), 25);
}
stop() {
this.isPlaying = false;
if (this.timerID) clearInterval(this.timerID);
if (this.audioContext) this.audioContext.close();
}
_scheduler() {
while (this.nextNoteTime < this.audioContext.currentTime + 0.1) {
this._playClick(this.nextNoteTime, this.currentBeat === 0);
this.nextNoteTime += 60.0 / this.bpm;
this.currentBeat = (this.currentBeat + 1) % this.beatsPerMeasure;
}
}
_playClick(time, isAccent) {
const osc = this.audioContext.createOscillator();
const gain = this.audioContext.createGain();
osc.frequency.value = isAccent ? 1000 : 800;
gain.gain.setValueAtTime(isAccent ? 1.0 : 0.7, time);
gain.gain.exponentialRampToValueAtTime(0.001, time + 0.03);
osc.connect(gain);
gain.connect(this.audioContext.destination);
osc.start(time);
osc.stop(time + 0.03);
}
}
// Tap Tempo Detector
function createTapDetector(maxTaps = 8, resetMs = 2000) {
const taps = [];
let resetTimer = null;
return {
tap() {
const now = performance.now();
if (resetTimer) clearTimeout(resetTimer);
resetTimer = setTimeout(() => { taps.length = 0; }, resetMs);
taps.push(now);
if (taps.length > maxTaps) taps.shift();
if (taps.length < 2) return null;
const intervals = [];
for (let i = 1; i < taps.length; i++) {
intervals.push(taps[i] - taps[i - 1]);
}
const avg = intervals.reduce((a, b) => a + b) / intervals.length;
return Math.round(60000 / avg);
},
reset() {
taps.length = 0;
},
};
}
// Usage
const metronome = new Metronome(120, 4);
metronome.start();
setTimeout(() => metronome.stop(), 5000);