Focus Sound

White, brown, and pink noise, generated live in the browser. No audio files, no streaming, no ads between tracks. Use it right here, or copy the code below into your own project.

How This Generator Actually Works

There's no audio file behind any of these three buttons, nothing gets downloaded or streamed. Each noise color is math, generated sample by sample in your browser with the Web Audio API, which is why the page loads instantly and keeps working offline once it's open. For the reasoning on which color to actually reach for and when, see Sound as a Focus Tool.

Synthesized, Not Streamed

A 5-second buffer of raw audio samples gets built once, up front, then looped continuously by the browser's audio graph. That's long enough that the loop point is inaudible for random noise, there's no melody or rhythm for your ear to catch repeating, and short enough that generating it doesn't cause any noticeable delay before playback starts.

How Each Color Is Generated

White noise is the simplest, just Math.random() for every sample, equal energy at every frequency. Pink noise runs that same random signal through a small cascade of filters, a widely-used approximation credited to Paul Kellet, that rolls off high frequencies gradually. Brown noise pushes further with a leaky integrator, each sample nudged toward the last one and decayed slightly, which is what produces its deep, rounded low end.

Nothing Leaves Your Browser

Because the audio is generated locally rather than fetched from a server, there's no request to log, no file to cache, and nothing running in the background once you close the tab. The volume slider and Stop button just control the same local audio graph, there's no account or session tied to any of it.

Copy the Code

Plain HTML, CSS, and JavaScript, no dependencies, no build step, no audio files to host. Comments walk through what each block is doing, so it's easy to adapt rather than just paste and forget.

<!-- Focus Sound Generator -- copy this whole block into any HTML page. -->
<!-- No build step, no dependencies, no audio files -- pure Web Audio API. -->

<div id="focus-sound">
  <div class="fs-buttons">
    <button id="fsWhite" type="button">White</button>
    <button id="fsBrown" type="button">Brown</button>
    <button id="fsPink" type="button">Pink</button>
    <button id="fsStop" type="button">Stop</button>
  </div>
  <div class="fs-volume">
    <label for="fsVolume">Volume</label>
    <input type="range" id="fsVolume" min="0" max="100" value="50">
  </div>
</div>

<style>
  /* Minimal styling -- restyle freely to match your own site. */
  #focus-sound { font-family: sans-serif; max-width: 320px; text-align: center; }
  .fs-buttons { display: flex; gap: 8px; justify-content: center; margin-bottom: 12px; }
  .fs-buttons button { padding: 8px 14px; cursor: pointer; }
  .fs-buttons button.active { font-weight: bold; }
  .fs-volume { display: flex; align-items: center; gap: 8px; justify-content: center; }
</style>

<script>
(function () {
  var buttons = {
    white: document.getElementById('fsWhite'),
    brown: document.getElementById('fsBrown'),
    pink: document.getElementById('fsPink')
  };
  var stopBtn = document.getElementById('fsStop');
  var volumeInput = document.getElementById('fsVolume');

  var audioCtx = null;
  var source = null;
  var gainNode = null;

  // Builds a few seconds of noise up front, then loops it -- looping is
  // inaudible for random noise, and this avoids needing a ScriptProcessor
  // or AudioWorklet just to make continuous sound.
  function buildNoiseBuffer(type) {
    var duration = 5; // seconds
    var bufferSize = audioCtx.sampleRate * duration;
    var buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
    var data = buffer.getChannelData(0);

    if (type === 'white') {
      // Pure random noise, equal energy at every frequency.
      for (var i = 0; i < bufferSize; i++) {
        data[i] = Math.random() * 2 - 1;
      }
    } else if (type === 'pink') {
      // Paul Kellet's refined pink noise approximation -- rolls off
      // high frequencies for a softer, less hissy sound than white.
      var b0 = 0, b1 = 0, b2 = 0, b3 = 0, b4 = 0, b5 = 0, b6 = 0;
      for (var j = 0; j < bufferSize; j++) {
        var white = Math.random() * 2 - 1;
        b0 = 0.99886 * b0 + white * 0.0555179;
        b1 = 0.99332 * b1 + white * 0.0750759;
        b2 = 0.96900 * b2 + white * 0.1538520;
        b3 = 0.86650 * b3 + white * 0.3104856;
        b4 = 0.55000 * b4 + white * 0.5329522;
        b5 = -0.7616 * b5 - white * 0.0168980;
        var pink = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
        b6 = white * 0.115926;
        data[j] = pink * 0.11;
      }
    } else if (type === 'brown') {
      // A leaky integrator over white noise -- rolls off even more
      // aggressively, closer to distant thunder than a hiss.
      var lastOut = 0;
      for (var k = 0; k < bufferSize; k++) {
        var w = Math.random() * 2 - 1;
        var out = (lastOut + (0.02 * w)) / 1.02;
        lastOut = out;
        data[k] = out * 3.5;
      }
    }

    return buffer;
  }

  function stop() {
    if (source) {
      source.stop();
      source.disconnect();
      source = null;
    }
    Object.keys(buttons).forEach(function (key) {
      buttons[key].classList.remove('active');
    });
  }

  function play(type) {
    stop();
    audioCtx = audioCtx || new (window.AudioContext || window.webkitAudioContext)();
    var buffer = buildNoiseBuffer(type);

    source = audioCtx.createBufferSource();
    source.buffer = buffer;
    source.loop = true;

    gainNode = audioCtx.createGain();
    gainNode.gain.value = volumeInput.value / 100;

    source.connect(gainNode);
    gainNode.connect(audioCtx.destination);
    source.start(0);

    buttons[type].classList.add('active');
  }

  buttons.white.addEventListener('click', function () { play('white'); });
  buttons.brown.addEventListener('click', function () { play('brown'); });
  buttons.pink.addEventListener('click', function () { play('pink'); });
  stopBtn.addEventListener('click', stop);

  // Live volume changes while something is already playing.
  volumeInput.addEventListener('input', function () {
    if (gainNode) {
      gainNode.gain.value = volumeInput.value / 100;
    }
  });
})();
</script>