Pitch and tempo

피치와 템포

Pitch (how high a sound is) and tempo (how fast it plays) are independent axes — changing one doesn't have to change the other.

Also known as: Frequency and BPMSpeed vs tone
···
html
<div class="wrap">
  <div class="panel">
    <div class="cap">pitch</div>
    <div class="staff">
      <div class="line"></div><div class="line"></div><div class="line"></div>
      <div class="note" id="note"></div>
    </div>
  </div>
  <div class="panel">
    <div class="cap">tempo</div>
    <div class="metro"><div class="needle" id="needle"></div></div>
    <div class="bpm" id="bpm">96 BPM</div>
    <button class="play" id="play" type="button" aria-label="play sound"><span class="tri"></span></button>
  </div>
</div>
css
.wrap{position:relative;width:94%;height:88%;display:flex;gap:5%}
.panel{position:relative;flex:1;border:1px solid var(--line);border-radius:12px;background:var(--surface);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;overflow:hidden}
.cap{position:absolute;top:8px;left:10px;font:700 10px/1 monospace;color:var(--muted)}
.staff{position:relative;width:70%;height:60%}
.line{position:absolute;left:0;right:0;height:1px;background:var(--line)}
.line:nth-child(1){top:20%}.line:nth-child(2){top:50%}.line:nth-child(3){top:80%}
.note{position:absolute;left:50%;width:12px;height:12px;border-radius:50%;background:var(--accent);transform:translate(-50%,-50%);top:50%;animation:notemove 2.4s ease-in-out infinite}
@keyframes notemove{0%,100%{top:80%}50%{top:12%}}
.metro{position:relative;width:56%;aspect-ratio:1;border-radius:50%;border:1px solid var(--line);display:grid;place-items:center}
.needle{width:2px;height:38%;background:var(--accent-2);transform-origin:bottom center;position:absolute;bottom:50%;animation:swing 1.2s ease-in-out infinite}
@keyframes swing{0%,100%{transform:rotate(-28deg)}50%{transform:rotate(28deg)}}
.bpm{font:600 10px/1 monospace;color:var(--muted)}
.play{position:absolute;bottom:6px;right:6px;width:20px;height:20px;border-radius:50%;border:1px solid var(--line);background:var(--surface);display:grid;place-items:center;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,.12)}
.play .tri{width:0;height:0;border-style:solid;border-width:5px 0 5px 7px;border-color:transparent transparent transparent var(--fg);margin-left:1px}
.play:active{transform:scale(.92)}
@media (min-width:460px){
  .cap{font-size:14px;top:14px;left:18px}
  .staff{width:80%;height:66%}
  .line{border-top:1.5px solid var(--line);height:0;background:none}
  .note{width:20px;height:20px}
  .metro{width:64%}
  .needle{width:3px}
  .bpm{font-size:15px}
  .play{width:34px;height:34px;bottom:14px;right:14px}
  .play .tri{border-width:7px 0 7px 10px;margin-left:2px}
}
js
const bpmEl = document.getElementById('bpm');
document.getElementById('play').addEventListener('click', () => {
  const ctx = new (window.AudioContext || window.webkitAudioContext)();
  const now = ctx.currentTime;
  [392, 587.33].forEach((f, i) => {
    const t = now + i * 0.16;
    const osc = ctx.createOscillator();
    const gain = ctx.createGain();
    osc.type = 'sine';
    osc.frequency.value = f;
    gain.gain.setValueAtTime(0, t);
    gain.gain.linearRampToValueAtTime(0.13, t + 0.015);
    gain.gain.exponentialRampToValueAtTime(0.0001, t + 0.17);
    osc.connect(gain).connect(ctx.destination);
    osc.start(t);
    osc.stop(t + 0.18);
  });
  const gaps = [0.42, 0.28, 0.16];
  let tt = now + 0.4;
  gaps.forEach((gap) => {
    tt += gap;
    const t = tt;
    const buf = ctx.createBuffer(1, Math.floor(ctx.sampleRate * 0.012), ctx.sampleRate);
    const d = buf.getChannelData(0);
    for (let j = 0; j < d.length; j++) d[j] = (Math.random() * 2 - 1) * (1 - j / d.length);
    const src = ctx.createBufferSource();
    src.buffer = buf;
    const gain = ctx.createGain();
    gain.gain.value = 0.13;
    src.connect(gain).connect(ctx.destination);
    src.start(t);
    setTimeout(() => { bpmEl.textContent = Math.round(60 / gap) + ' BPM'; }, (t - now) * 1000);
  });
  setTimeout(() => ctx.close(), 1300);
});

In the analog era, changing tape or record playback speed moved pitch and tempo together (speed it up and the voice gets higher and faster). Digital audio's time-stretching and pitch-shifting algorithms separate the two, letting you double the speed while keeping pitch fixed, or shift the pitch while keeping speed fixed. The Web Audio API's OscillatorNode.frequency controls pitch; playbackRate moves both together unless you process them separately.

In UI design, pitch mostly signals positive/negative — a success tone rises (an ascending interval), an error tone falls (descending). Tempo mostly signals urgency — a low-battery warning that repeats faster and faster. When designing feedback with both axes, assign each one a single meaning and don't mix them, so users can learn the pattern.

Used for speed playback in calls (1.5x podcasts), accelerating danger-warning tempo in games, and pitch contrast between success/failure notification tones.

When to use

Carry positive/negative through pitch (rise/fall) and urgency through tempo (speed) — keep them separate, not mixed into one sound.