Until this week, the loudest sound in a normal game of flags.games was the correct-answer chime. At −22.1 it was 7 louder than winning a whole challenge, and as loud as finishing the daily.
Nobody decided that. The site had grown 35 sound effects, each added on its own and tuned by ear in isolation. So we measured all of them, then rebuilt every one on a single small engine: one musical key, one shared room, and one rule for how every note starts and stops.
Every example below plays in your browser, so turn your sound on. The Before buttons play the old sounds, re-created from the old code.
Before and after
Both sides follow your Settings volume. Sound effects must be on.
Correct answer
Heard every round. It was the loudest everyday sound on the site.
Wrong answer
A bell with sour extra tones, 11 dB under the correct chime.
Countdown into a round
Clock ticks, then a buzzy chord on every round.
Lobby start countdown
Two beeps, both off the note. Both countdowns now share one sound.
Weekly spin jackpot
The best prize on the site played C and C♯ in the same phrase.
Measuring 35 sounds
Ears adapt. After 10 minutes of auditioning, a harsh sound starts to feel normal and a quiet one starts to feel fine. So before changing anything, we built a way to measure.
A script loads each sound's code into headless Chromium and renders it with an at the default volume of 75%. Offline rendering runs faster than real time and produces the same samples on every run. Then the script measures each file:
- Loudness as momentary LUFS. It tracks how loud a sound feels. Peak level only catches the tallest instant.
- Brightness as the , plus how strong the content above 7,000 is.
- Pitch: the strongest frequencies, named as notes, with how many each sits from the nearest piano key.
- Clicks: the largest sudden jump in the waveform.
34 of the 35 sounds rendered. The 35th was an MP3 on a CDN our test machine couldn't reach. The result was a table with one row per sound, and reading it top to bottom was humbling.
What the measurements found
Loudness followed nothing
Game sounds need an order. A tap you hear constantly should be the quietest thing on the site, and a win you hear once a day should be the loudest. In between, loudness should rise with how rare and how important the moment is.
The old palette had no order at all:
- A correct answer, heard every round, measured −22.1 LUFS. Winning a whole challenge measured −29.3.
- A wrong answer measured −33.6, more than 11 dB under a correct one. Misses barely registered.
- Switching the theme measured −21.7, louder than finishing the daily.
- The multiplayer "last player standing" sting was the loudest sound on the site at −18.5.
- The last-five-seconds tick, the one sound meant to raise your pulse, was among the quietest at −43.1.
Loudness, before and after
Momentary LUFS at 75% volume. Closer to zero (further right) is louder. Shaded bands are the 5 tiers.
Opponent answered −39.8 → −39.0
Stepper −38.9 → −39.5
Globe ping −27.3 → −39.1
Click −35.8 → −37.3
Selection −31.1 → −37.1
Player left −35.1 → −36.7
Toggle on −29.3 → −33.9
Last-second tick −43.1 → −33.9
Theme switch −21.7 → −33.5
Milestone −27.3 → −32.9
Start button −32.9 → −32.0
Round start −25.0 → −29.0
Wrong answer −33.6 → −28.5
Correct answer −22.1 → −27.0
Game lost −26.5 → −25.5
Challenge won −29.3 → −25.0
Last player standing −18.5 → −25.0
Spin jackpot −32.5 → −22.8
Daily finished −22.4 → −22.1
Game won new → −22.0
Show the numbers
| Cue | Before | After |
|---|---|---|
| Opponent answered | −39.8 | −39.0 |
| Stepper | −38.9 | −39.5 |
| Globe ping | −27.3 | −39.1 |
| Click | −35.8 | −37.3 |
| Selection | −31.1 | −37.1 |
| Player left | −35.1 | −36.7 |
| Toggle on | −29.3 | −33.9 |
| Last-second tick | −43.1 | −33.9 |
| Theme switch | −21.7 | −33.5 |
| Milestone | −27.3 | −32.9 |
| Start button | −32.9 | −32.0 |
| Round start | −25.0 | −29.0 |
| Wrong answer | −33.6 | −28.5 |
| Correct answer | −22.1 | −27.0 |
| Game lost | −26.5 | −25.5 |
| Challenge won | −29.3 | −25.0 |
| Last player standing | −18.5 | −25.0 |
| Spin jackpot | −32.5 | −22.8 |
| Daily finished | −22.4 | −22.1 |
| Game won | n/a | −22.0 |
Each sound now sits in one of 5 tiers, and the tier depends on how often the sound fires:
| Tier | LUFS | Sounds |
|---|---|---|
| Micro | −40 to −37 | Taps, stepper, opponents answering, globe pings |
| Interface | −36 to −32 | Toggles, theme switch, joins and leaves, milestones, countdown ticks |
| Feedback | −29 to −27 | Correct, wrong, time up, round start |
| Outcome | −27 to −25 | Challenge won or lost, game lost, last player standing |
| Finale | −23 to −22 | Game won, daily finished, weekly spin jackpot |
Correct and wrong now sit 1.5 dB apart. A miss is as clear as a hit, and it still doesn't sound like a buzzer.
Round numbers are out of tune
Most of the sounds were written as note names (C5, E5, G5), and most of those were in . The plain Web Audio sounds were written in hertz, and the hertz were round numbers:
- Lobby countdown: 600 Hz for 3, 2, 1 and 800 Hz for go. 600 Hz is a D5 that's 37 cents sharp. 800 Hz is a G5 that's 35 cents sharp.
- Stepper: 440 Hz going up, a perfect A4, and 380 Hz going down, an F♯4 that's 46 cents sharp. That's nearly halfway to G.
- Player kicked: 300 Hz, a D4 that's 37 cents sharp.
- Player joined: A4 then C♯5, a major third from A major. That C♯ clashes with the C natural in almost every other sound.
- Weekly spin jackpot: C4, F4, A4, C♯5, E5. The best prize on the site played C and C♯ in the same phrase.
A cent is a hundredth of a . At 35 cents and up, a note is far enough off that it sounds sour next to anything in tune.
That matters because game sounds overlap all the time. The last countdown tick rings into the round's opening chord. An opponent's answer chime lands on top of yours. When every sound draws from the same few notes, an overlap makes a chord. When they don't, it makes a clash.
The sneakiest case was the wrong-answer bell. On paper it was in key: B4, G4, E4, an E minor arpeggio. It was an bell, which means a second oscillator wobbled the note's frequency at 1.5 times the note. FM adds at the note plus and minus multiples of that wobble, and with a 1.5 ratio one of them lands at 2.5 times the note. For E4 that's 824 Hz, a slightly flat G♯5. For B4 it's 1,235 Hz, a slightly flat D♯6.
So the sad E minor sound also played a G♯ over its own G. That's where its clangy, sour edge came from.
FM has a second trap. At a 1:1 ratio, one of those partials lands at 0 Hz, which is a . In our test, a 440 Hz FM tone at modulation index 1 averaged −0.23 where it should average 0. The new bell is additive: plain partials, each at exactly the ratio we choose.
Square waves on the busiest sound
The round-start sound plays every round, which makes it one of the most-heard sounds in the game. It was 4 notes, C5 E5 G5 C6, 60 ms apart.
A square wave stacks every odd harmonic, and they fade slowly: the 9th harmonic is still a ninth as strong as the note. So most of its energy sits far above the pitch you hear. Its spectral centroid was 4,220 Hz, 3 octaves above the C5 it started on, and its content above 7 peaked only 5.8 dB below the whole sound's peak.
The new round start is a soft strum of C5, G5 and C6 with a low felt thump under it. Its centroid is 820 Hz, and its content above 7 kHz sits 47.7 dB below the peak. The game-over sound had the same square-wave problem at 3,391 Hz and now sits at 536 Hz.
Clicks at both ends of a note
A click is a sudden jump in the waveform. A speaker can't follow a jump, so it plays a short burst of every frequency at once. The helper behind the stepper, the lobby countdown, joins, kicks and the replay beep made 2 clicks per note:
const scaledVolume = 0.1 * globalVolume;
gainNode.gain.setValueAtTime(scaledVolume, ctx.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
Math.max(0.001, scaledVolume * 0.1),
ctx.currentTime + duration
);
oscillator.start(ctx.currentTime);
oscillator.stop(ctx.currentTime + duration);The gain is at full level on the very first sample, so the note starts with a jump. Then
it only decays to 10% of that level before stop() cuts the oscillator
mid-cycle. That's the second jump.
The countdown tick had a gentler version: a 1 ms , short enough to splash a click across the spectrum. The milestone chime started with a hard onset straight into a big , so the reverb kept its click ringing.
7 reverbs and 2 audio contexts
Reverb tells your ear what kind of space a sound is in. The old palette used 7 different reverbs with different lengths and tones, and some sounds had none. Played one after another, they came from different rooms: a milestone chime rang in a big wet space, and the click right after it was bone dry.
Tone.js also ran its own next to the one the small sounds used. Each context has its own clock and its own output, so the 2 halves of the palette couldn't be scheduled against each other, and the ad-break mute only reached some of them.
Sounds that arrived late
The daily results screen reveals 5 columns, one per round, with a ping for each. On a cold start, the pings lagged the animation:
const synth = await this.ensureDailySlotTickSynth();
const Tone = await this.getTone();
const now = Tone.now();
for (let k = 1; k <= roundCount; k++) {
const revealAt = now + (initialDelayMs + k * stepMs) / 1000;
synth.triggerAttackRelease(roots[k - 1], "32n", revealAt);
}Columns start revealing
0 msThe animation starts on mount, on its own timer.
Sound waits for Tone.js
thenThe ping code awaits the Tone.js import and its synth before scheduling anything.
Tone.now() becomes zero
+ load timeAll 5 pings are scheduled from the moment Tone finished loading, so each lands late by the whole load.
Same ping for every column
alwaysThe schedule only knew how many rounds there were. Solved and missed sounded alike.
The fix is to time the pings from when the reveal started, measured with performance.now(), and to subtract however long audio took to wake:
await playCue((bus, at, volume) => {
const elapsedMs = performance.now() - schedule.startedAtMs;
schedule.solved.forEach((solved, index) => {
const offsetMs =
schedule.initialDelayMs + (index + 1) * schedule.stepMs - elapsedMs;
if (offsetMs >= -40) {
const slotAt = at + Math.max(0, offsetMs) / 1000;
dailySlotCue(bus, slotAt, volume, index, solved);
}
});
});Reveal starts
0 msThe results component records performance.now() as the stagger begins.
Audio wakes
first cueNo library to load. The shared context resumes.
Each ping lands on the audio clock
thenIts time is the reveal start plus its delay, minus the time audio took to wake.
Solved climbs, missed taps
alwaysSolved rounds step up a bright ladder; missed rounds tap a muted one an octave lower. Columns already shown are skipped.
The other timing fixes:
- The first answer waited for a download. Correct and wrong lived in the Tone.js bundle, so the first one in a session waited
for it. The old code even logged the wait as
import_wait_ms. - Weekly spin ticks drifted. Every peg tick was its own
setTimeout, and timers slip when the main thread is busy, which it is while a wheel spins. Tick times now come from the wheel's own easing curve and are all scheduled on the audio clock before the wheel moves. - Running out of time was silent. The last 5 seconds ticked, then nothing. A new time-up sound lands the countdown.
- Survival mode doubled up. On your last heart, the wrong-answer sound and the game-over sound started on the same frame. Game over now waits 650 ms.
- Phrases used timers. Some multi-note sounds spaced their notes with timers. Every note of every sound is now scheduled on the audio clock.
- An unused import loaded all of Tone. The daily results component imported the old audio module without calling it, which was enough to pull Tone.js into that page.
How the new sounds work
The new engine is about 1,600 lines of plain Web Audio in 5 files: a score with every sound, the instruments, the shared room, a player, and the note names.
One key
Every pitch is a natural note (C, D, E, F, G, A or B), so everything is in C major. Most sounds use only 5 of those: the C major , C D E G A. The pentatonic has no half steps, so any of its notes sound fine together. Many wind chimes are tuned to it for the same reason.
Sad sounds use the same 7 notes and lean on the minor chords inside the key, A minor and E minor. A wrong answer sounds like a gentle letdown and still sits in the same key as everything around it.
The note type only accepts naturals, so a sharp can't sneak in through a typo or a round number:
type Letter = "C" | "D" | "E" | "F" | "G" | "A" | "B";
type Octave = 2 | 3 | 4 | 5 | 6 | 7;
export type Note = `${Letter}${Octave}`;
export function hz(note: Note): number {
const letter = note.charAt(0) as Letter;
const octave = Number(note.slice(1));
return 440 * 2 ** ((SEMITONES_FROM_A[letter] + (octave - 4) * 12) / 12);
}hz("C#5") is a compile error. There's one deliberate bend in the whole palette: the second note of
a wrong answer drifts 35 cents flat over 350 ms as it fades, like a voice saying "aww".
It starts in tune and only sags on the way out.
A small band of instruments
Every sound is built from 6 voices. Reusing a few instruments everywhere is a big part of why the sounds feel related.
- Glass: a soft celesta or music box. A sine fundamental, a quieter octave and twelfth that fade faster, and a faint metallic tine for the first few milliseconds. Longer notes add a slow chorus pair, 3.5 cents either side, spread across the stereo field. Correct answers, chimes and wins.
- Wood: a soft marimba tap. A sine body with a quick downward mallet bend and a 2 ms burst of filtered noise for contact, which makes it feel tactile. Clicks, ticks and steppers.
- Felt: a low, round thump whose pitch falls into the note. An octave partial keeps it audible on phone speakers that can't play the fundamental. Reveals, kicks, and the floor under big chords.
- Pad: 2 triangle oscillators per note, detuned 7 cents either way, through a slowly opening low-pass. Finales and long streaks.
- Air: filtered noise with a moving filter, for whooshes and breaths.
- Flick: a few milliseconds of band-limited noise, like a card edge. The avatar shuffle.
Here's the correct-answer sound in full, minus the 2 bigger streak layers:
export function successCue(bus: SfxBus, at: number, volume: number, streak: number) {
const cue = open(bus, "success", volume, 0.18);
glass(cue, { at, frequency: hz("E5"), peak: 0.75, decay: 0.12 });
glass(cue, { at: at + 0.055, frequency: hz("G5"), peak: 0.8, decay: 0.14 });
glass(cue, { at: at + 0.11, frequency: hz("C6"), peak: 0.95, decay: 0.27 });
if (streak >= 3) {
glass(cue, { at: at + 0.165, frequency: hz("E6"), peak: 0.42, decay: 0.3 });
}
// 6+ adds G6, 10+ adds a warm C-G pad underneath
}One room
Every sound sends a little signal into one shared reverb, so the whole palette sounds like it's in the same room. The room is generated in code: 2.4 seconds of stereo noise that decays by 60 dB in 1.55 s, after an 11 ms . A filter on that noise closes from 6.5 kHz to 1.1 kHz over the tail, so echoes get warmer as they fade and never hiss. A 200 Hz on the return keeps low thumps from turning to mud.
A soft clipper sits after the room. It's linear up to about −4.4 and only rounds off peaks when several sounds pile up at once.
Every note starts and ends at zero
/** e^-9 ≈ -78 dB: an envelope this many time constants into its decay is silent. */
const SILENT_AFTER_TAUS = 9;
gain.gain.setValueAtTime(0, at);
gain.gain.linearRampToValueAtTime(peak, at + attack);
gain.gain.setTargetAtTime(0, at + attack, decay);
oscillator.start(at);
oscillator.stop(at + attack + decay * SILENT_AFTER_TAUS);Every partial of every voice goes through this . It starts at exactly 0, ramps to its peak in a few milliseconds, then decays exponentially. The oscillator stops only after 9 , when the level is e−9 of the peak, about −78 dB. At that level there's nothing left to cut, so there's nothing to click.
Levels set by measurement
Each sound has one level in a table, in decibels. To set them, a calibration script rendered every sound, measured its momentary loudness and moved its level toward the middle of its tier. After a few passes every sound landed within about 0.4 dB of target.
Streaks are the one place we kept the level flat on purpose. A streak of 3 adds an E6 on top of the correct chime, 6 adds a G6, and 10 adds a warm pad underneath. Measured loudness barely moves (−27.0 to −26.3), so a long run sounds richer without getting tiring.
Sounds that know what's happening
A sound that repeats identically starts to grate. Real instruments never play the same note exactly the same way twice, so the new sounds vary in small ways. The click shifts a few cents and a fraction of a decibel each time, and several sounds react to what's going on in the game:
Sounds in context
Each button plays a short sequence the way the game would.
Answer streak
Correct at streaks of 1, 3, 6 and 10. More sparkle, about the same level.
Opponents answering
Each answer in a round climbs one note, like rain starting.
Stepper drag
The detent's pitch follows the value, so a drag up and back plays a scale.
Daily results
Solved, missed, solved, solved, missed. Solved climbs, missed taps.
Globe activity
Wind chimes: a random pentatonic note, never the same one twice in a row.
Reveals
Quick repeats climb a step each, then start over after a pause.
Last 5 seconds
Ticks lean in as time runs out, then time up lands the countdown.
Dropping Tone.js
We were loading about 270 KB of Tone.js, plus a second audio context, to play short stings. The new engine is 21 KB, 7 KB gzipped, and the production build no longer contains Tone at all.
One context means one clock and one output. Recorded voice clips now decode onto the same bus, so the Settings volume and the ad-break mute reach every sound. The mute is a short fade, so ringing tails don't click off.
The recorder lied
One detour is worth writing down. Our first measurements recorded sounds in real time with a , and the recordings had clicks in them, even in sounds that couldn't click.
The clicks sat on 2,048-sample boundaries, the recorder's buffer size. Under load the capture dropped buffers, and the splices around each gap looked just like clicks in the sound. Offline rendering computes every sample without a real-time deadline, and the phantom clicks went away. Every number in this post comes from offline renders.
Tests that listen for us
A fake Web Audio implementation records every oscillator, every gain automation, and every start and stop time. The tests play every sound into it:
it.each(CUES)("%s starts from silence and is silent before its voices stop", async (_, play) => {
for (const source of await sourcesFrom(play)) {
const envelope = envelopeFor(source);
expect(source.startTime).toBeGreaterThanOrEqual(fakeContext().currentTime);
expect(envelope.valueAt(source.startTime)).toBeLessThanOrEqual(1e-9);
expect(envelope.valueAt(source.stopTime)).toBeLessThanOrEqual(envelope.peak * 1e-3);
}
});This one checks that every voice starts from silence and is at least 60 dB below its peak when it stops. A second test checks that every oscillator plays a natural note, or a known partial of one, within 12 cents.
To prove the tests can fail, we broke the engine on purpose, stopping voices after 3 time constants where it should wait 9, and 33 of the 71 sound tests failed.
Every change in one table
Show all 28 changes
| Sound | Before | After |
|---|---|---|
| Click | 2 notes, a little boopy | One soft wooden tap on A5 that varies slightly |
| Stepper | 440 Hz and an out-of-tune 380 Hz, clicking | Wooden detent that plays a scale |
| Countdown ticks | Nearly inaudible, 1 ms attack | G5 tick that leans in each second |
| Time up | Silent | New: the tick stops and a soft fall lands it |
| Lobby countdown | 600 and 800 Hz beeps, 35 to 37 cents sharp | Same ticks and start chord as rounds |
| Round start | Square-wave arpeggio | Soft C chord strum over a low thump |
| Toggles | Waited for Tone.js on first use | On lifts a fifth, off settles a fifth |
| Theme switch | As loud as the daily finale | Falling dusk into dark, rising sunrise into light |
| Milestone | Hard onset into a big reverb | 2 soft chime notes a fifth apart |
| Globe activity | The milestone chime, every time | One quiet wind-chime note |
| Opponent answered | A5 and E6, identical every time | Climbs one note per answer |
| Player joined | A major third, out of key | Lifts a fifth |
| Player kicked | 300 Hz, 37 cents sharp | Low muted knock |
| Avatar shuffle | Harsh white-noise clicks | Card flicks that slow and land on a chime |
| Reveals | The same thump every time | Quick repeats climb a step |
| Replay results | Clicky beep, cut off | Soft open fifth |
| Correct | Loudest everyday sound | E5 G5 C6 roll; streaks add sparkle |
| Wrong | Sour FM bell, 11 dB under correct | Falling minor third that sags flat |
| Challenge won | Quieter than one correct answer | Pickup into a bell chord over a pad |
| Challenge lost | Opened on a high alert note | Gentle A minor fall |
| Last player standing | Loudest sound on the site | Fast cascade that lands softly |
| Daily columns | Same ping for solved and missed, late | Solved climbs, missed taps, on time |
| Daily finished | Already in C | Slower, warmer chord with a pad swell |
| Game won | An MP3 from a CDN | Synthesized fanfare in the same palette |
| Game lost | Square-wave womp womp | A kind phrase that comes to rest |
| Survival game over | Fired with the wrong-answer sound | Waits 650 ms |
| Weekly spin | Timer ticks that drifted | Music-box ticks locked to the wheel |
| Spin prizes | Clashing chords, similar levels | Consonant phrases that grow by tier |
Rules for every new sound
These now live in the repo next to the code, and every new sound has to pass them:
- Stay in the key. Use the
Notetype. Reach for the pentatonic first (C D E G A). For sad moments, lean on A minor or E minor. - Build from the existing voices. Glass, wood, felt, pad, air and flick. A new instrument needs a reason, and it goes through the same room.
- Start at zero, end in silence. Use the shared envelope. Never jump to a peak, and never stop a voice before 9 time constants.
- Pick the tier by how often it fires. Constant sounds go in micro or interface; once-a-game sounds go in outcome or finale. Then calibrate the level offline.
- Schedule on the audio clock. Space notes with
at + offset, never timers. When a sound follows an animation, take its times from the animation's constants and its start time. - One focal moment at a time. If 2 big sounds can fire together, like a wrong answer and game over, delay one.
- Vary what repeats. A few cents and a fraction of a decibel of variation, or a ladder that climbs with quick repeats.
- Let pitch carry meaning. Up is good, down is bad, higher is lighter. Pairs like on and off, join and leave, or solved and missed should mirror each other.
- Measure, then listen. Render offline, check loudness and brightness, then play it next to its neighbors.
Every sound in the game, plus full sequences and all 4 weekly spin tiers, is on the audio demo page. If one of them still bothers you, tell us. Now we can measure it.
Glossary
Every highlighted word in this post, in the order it first appears. Hover or tap one in the text to read it in place.
- LUFS
- Loudness Units relative to Full Scale: how loud a sound feels to people, measured with ITU-R BS.1770, the standard streaming services use. One LU is one dB. Always negative here. Closer to 0 is louder, so −22 is louder than −30. Neither is better on its own: each sound should sit at the right level for its moment.
- Decibel (dB)
- A step on a logarithmic loudness scale, always relative to something. Adding about 6 dB doubles a signal's level; about 10 dB sounds roughly twice as loud. Negative values mean quieter than the reference, so −30 dB is quieter than −20 dB.
- OfflineAudioContext
- The same engine rendering into memory instead of to speakers: faster than real time, and the same result every run.
- Momentary loudness
- LUFS measured over a sliding 400 ms window. It suits short sounds; we report the loudest window of each one.
- Spectral centroid
- The average frequency of a sound, weighted by how much energy each frequency has. A rough brightness meter. Higher is brighter. Short game sounds start to feel harsh once it climbs into the thousands of hertz.
- Hertz (Hz)
- Vibrations per second, which sets a sound's pitch. 440 Hz is the A above middle C; 1 kHz is 1,000 Hz. Higher is higher-pitched. Doubling the hertz raises a note by one octave.
- Cent
- A hundredth of a semitone, used to measure how far a note is from being in tune. A few cents off is hard to hear. By 35 cents a note sounds sour next to one that is in tune.
- Tone.js
- A popular JavaScript library for making music in the browser, built on Web Audio.
- Key (C major)
- The set of notes a piece uses and the one that feels like home. C major is the seven white keys, C D E F G A B, with C as home. A minor uses the same notes with A as home.
- Semitone
- The smallest step on a piano: from one key to the very next, black keys included. Twelve make an octave.
- FM synthesis
- Frequency modulation: one oscillator wobbles another's pitch very fast, which adds new partials. It makes bells easily, but the partials only land in key at certain ratios.
- Harmonic (partial)
- The quieter tones stacked above the note you hear, at multiples of its frequency. Their mix is why a flute and a violin playing the same note sound different.
- DC offset
- A waveform that sits off center instead of swinging evenly around zero. It wastes headroom and can thump when the sound starts or stops.
- Sine wave
- A pure tone with no harmonics at all: soft, round and a little plain.
- Square wave
- A tone that flips between two levels. It carries every odd harmonic, each only slowly quieter than the last, so it sounds bright and buzzy, like retro game music.
- Attack
- How long a note takes to reach full volume after it starts. Around a millisecond or less, the start itself is heard as a click.
- Reverb
- The tail of echoes a room adds to a sound. Its length and tone tell your ear what kind of space the sound is in.
- AudioContext
- The browser's Web Audio engine. It has its own clock, separate from timers, and sounds can be scheduled on it to the sample.
- Pentatonic scale
- A five-note scale. In C it is C, D, E, G and A. It leaves out the two notes that clash most, so any of its notes sound fine together.
- Pre-delay
- The short gap before a reverb's first echo. A few milliseconds keeps the start of each note clear.
- High-pass and low-pass filters
- A high-pass filter keeps the frequencies above a cutoff and removes the lows. A low-pass does the opposite.
- Soft clipper
- A safety stage that rounds off peaks gently instead of chopping them flat, so loud overlapping sounds don't crackle.
- dBFS
- Decibels relative to the loudest sample the system can output, which is 0 dBFS. Everything is at or below 0. −4.4 dBFS is a little under the ceiling.
- Envelope
- How a note's volume changes over time: how fast it rises (the attack) and how it fades (the decay).
- Time constant
- For a fading sound, the time it takes to fall to about 37% of where it was. Each further time constant takes it down by the same share again. After 9 time constants a sound is about 78 dB down: silent.
- ScriptProcessorNode
- An old Web Audio node that hands raw audio to JavaScript in real time. When the page is busy it can miss buffers.