Oh MyUtils

메트로놈 - 탭 템포 BPM 감지기 & 클릭 트랙 온라인

음악에 맞춰 탭하여 BPM을 감지하고, 조절 가능한 템포, 박자표, 서브디비전, 시각적 비트 표시기로 정밀한 클릭 트랙을 재생하세요 — Web Audio API를 사용한 100% 클라이언트 사이드.

귀를 보호하세요. 낮은 볼륨에서 시작하세요.

탭하여 시작...

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(복합 2박자, 지그에서 흔함), 5/4(불규칙, Take Five에서와 같이), 7/8(불규칙, 발칸 음악에서 흔함). 각 박자표에는 리듬 구조를 느낄 수 있도록 적절한 강세 패턴이 있습니다.

세분화 간의 차이점은 무엇인가요?

세분화는 각 메인 박자를 더 작은 단위로 나누어 더 세밀한 리듬 연습을 할 수 있게 합니다. '없음'은 4분 음표 박자만 재생합니다. '8분 음표'는 각 박자 사이에 클릭을 추가합니다(박자당 2개). '셋잇단음표'는 각 박자를 3등분합니다. '16분 음표'는 각 박자를 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);

관련 도구