A 528 Hz tone is one of those frequencies people keep asking about. You do not need a
special app, a studio, or a hardware oscillator to hear it. You can generate it in a
plain browser tab with a few lines of JavaScript, and you can even export it as a WAV
file without ever uploading anything to a server.
I built a small online tool around exactly this idea, and in this post I will walk
through the minimal Web Audio API code that makes it work. The point is to give you a
tiny, copy-paste-able example you can run yourself — and to show you the same three
ideas behind a real generator: create an oscillator, route it through a gain node,
and render it offline when you want a file.
The shortest possible tone
Open your browser console and paste this:
const ctx = new AudioContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(528, ctx.currentTime);
gain.gain.setValueAtTime(0.3, ctx.currentTime);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start();
That is the whole thing. osc.frequency.setValueAtTime(528, ...) sets the pitch to
528 Hz. The gain node controls how loud it is. Connect oscillator → gain → speakers,
start it, and you are listening to 528 Hz.
You can change the waveform by swapping 'sine' for 'square', 'triangle', or
'sawtooth'. A sine wave is the smoothest, and the one most people use for a steady
meditative or focus background.
Toggle the frequency without restarting the sound
A fixed 528 Hz is fine, but the interesting part is switching between frequencies while
the tone keeps playing. Instead of building a new node each time, keep the oscillator
around and use exponentialRampToValueAtTime to glide:
const startTone = (freq) => {
const ctx = new AudioContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, ctx.currentTime);
gain.gain.setValueAtTime(0.3, ctx.currentTime);
osc.connect(gain).connect(ctx.destination);
osc.start();
return { osc, gain, ctx };
};
const { osc, gain, ctx } = startTone(528);
const shiftTo = (freq) => {
osc.frequency.exponentialRampToValueAtTime(freq, ctx.currentTime + 0.02);
};
shiftTo(432);
The + 0.02 is a 20 ms glide, which avoids the audible click you would get from an
instant jump. Users who want to sweep between notes will notice this immediately.
Exporting a WAV file, fully in the browser
This is the part most tutorials skip, and the reason a generator that can only play is
half a product. The trick is OfflineAudioContext. You render the tone into a buffer
offline — no live playback, no upload — and then encode that buffer as a WAV.
A compact example:
const renderWav = async (freq, seconds = 5) => {
const sampleRate = 44100;
const length = Math.floor(sampleRate * seconds);
const ctx = new OfflineAudioContext(2, length, sampleRate);
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, 0);
gain.gain.setValueAtTime(0.3, 0);
// fade in/out a few ms to avoid a click at the file edges
gain.gain.setValueAtTime(0, 0);
gain.gain.linearRampToValueAtTime(0.3, 0.01);
gain.gain.setValueAtTime(0.3, seconds - 0.01);
gain.gain.linearRampToValueAtTime(0, seconds);
osc.connect(gain).connect(ctx.destination);
osc.start(0);
osc.stop(seconds);
const buffer = await ctx.startRendering();
return encodeWav(buffer);
};
The WAV encoder is the only really tedious part — you write the 44-byte RIFF header and
then interleave the channels as 16-bit PCM samples. It is a few dozen lines, but once
you have it, you can hand the user a real .wav file downloaded from the browser. No
backend, no middleman, works on phones.
What a production generator adds
The example above gets you the sound. A genuinely useful tool adds the small things:
- Clamping the frequency to the audible range (1 Hz to 20 kHz) so a bad input does not produce silence or error.
- Auto-stop so the tone does not run forever by accident (ours stops after 60 s).
-
A volume slider wired to the gain node, and a clean
stop()that ramps gain to zero instead of cutting the sound off at full volume. - Four waveforms, because a 528 Hz sine is different from a 528 Hz sawtooth, and people want both.
If you would rather not write the encoder yourself, the practical version of that
whole flow is running live at onlinegeneratortone.com.
You pick a frequency, choose a waveform, press Play, and hit Download WAV to grab a file.
You can jump straight to the 528 Hz generator
if that is the tone you came for.
The Web Audio API is one of the most underrated parts of the platform. A few lines give
you a real instrument in the browser — and the offline rendering path means you can
ship a downloadable file without a single network call. Try the snippet, change the
frequency, and see what a 528 Hz sound actually does when it is sitting behind whatever
you are doing.
Top comments (1)
It's fascinating how you've utilized the Web Audio API to generate and export a 528 Hz tone directly in the browser. The implementation of
exponentialRampToValueAtTimefor smooth frequency transitions is a thoughtful touch that really enhances user experience. One idea for improvement could be adding a user interface to allow users to easily change frequencies and export settings, making it more accessible for non-developers. If you're looking for additional support in implementing features like that, I’d be happy to explore a paid collaboration. What challenges have you faced in optimizing the WAV export process?