A designer needs a sound for a wall of corrupted data that the player walks past in a sci-fi corridor. The sound has to be continuous, subtly changing over the twenty seconds it takes to walk past, and it has to feel like the same wall the whole time. A looping ambience fails because the loop point is audible at this length. A synthesized drone fails because it has no internal detail. A recorded wall of static fails because it never changes and the ear detects the sameness within ten seconds.
Granular synthesis solves this category of problem. It takes a short source sample — half a second of static is enough — and plays hundreds of tiny overlapping fragments of it per second. Each fragment is called a grain, and because each grain is scheduled independently, the output never exactly repeats. The source material provides the character; the granular engine provides the variation. The result is a texture that sounds continuous but is never identical across any two seconds.
The technique has been used in game audio for two decades, but it remains underused outside of ambient and sci-fi sound design because the parameter space is unfamiliar to most designers. This article covers what the parameters actually do, a working implementation, and the specific sound categories where granular is the right tool rather than an interesting technique in search of a problem.
What is a grain, and why does it work?
A grain is a short windowed fragment of a sound source, typically between 1 and 100 milliseconds long. The grain has three properties: its duration, its source position, and its window shape. The duration controls the perceived smoothness — very short grains sound clicky and metallic, longer grains sound smooth but lose the character of the source. The source position controls which part of the underlying sample is being played. The window shape controls how the grain fades in and out at its boundaries.
The window is not optional. A grain that starts and ends abruptly at full amplitude produces a click, and a thousand clicks per second is not a texture, it is a problem. The window function shapes the grain so that its amplitude is zero at both ends and peaks in the middle, which removes the boundary discontinuity. The two window functions used in nearly every granular implementation are the Hann window and the Gaussian window.
The Hann window is the default for most applications. Its shape is a smooth curve that peaks at the center and tapers symmetrically to zero at both ends. For overlapping grains, the Hann window has a property that makes it particularly useful: when two Hann windows are overlapped by 50 percent, their sum is a constant. This means that with the correct overlap, the amplitude of the output stays flat no matter how many grains are running at once.
The Gaussian window is narrower and produces a softer texture. It is used when the granular engine needs to preserve the pitch of the source more faithfully, because the Gaussian window has less spectral leakage. For texture generation — which is what most game audio needs from granular — the Hann window is usually the right choice.
What do grain size, density, and position control?
The three parameters that determine the character of a granular texture are grain size, density, and source position. Each has a distinct perceptual effect, and the ranges below cover the useful space for game sound effects.
| Parameter | Typical range | Perceptual effect | Game audio use case |
|---|---|---|---|
| Grain size | 10 to 50 ms | Texture density and clarity | Short = metallic, long = smooth |
| Density | 20 to 200 grains/sec | Thickness and warmth | Low = sparse, high = continuous |
| Position scatter | 0 to 100% of sample | Coherence of the source | 0% = frozen, 100% = cloud |
| Position drift | 0 to 50% per second | Movement within the sample | Slow = evolving, fast = sweeping |
| Pitch scatter | 0 to 12 semitones | Detune and instability | 0 = clean, 12 = chaotic |
Grain size is the parameter that most affects the perceived character. A grain size below 10 milliseconds produces a metallic, buzzy quality that reads as "digital" or "synthetic." Between 10 and 30 milliseconds, the grain is long enough to capture the tone of the source but short enough that the overlap creates a texture rather than a sustained tone. Above 50 milliseconds, the grains become individually distinguishable and the output sounds like a stutter rather than a texture.
Density is the number of grains launched per second. At low densities — 20 to 40 grains per second — the grains do not overlap enough to fill the gaps, and the texture has a characteristic pulsing quality. At 100 grains per second with 50 percent overlap, the texture is continuous and smooth. Above 200 grains per second, the texture becomes a solid wall of sound and loses any sense of individual grains, which is sometimes exactly what is wanted.
Source position determines which part of the underlying sample each grain is drawn from. If the source is a single second of sound and the position is fixed at 50 percent, every grain plays the same 30 milliseconds of the source and the output is a slightly unstable version of that fragment. If the position is scattered randomly across the full sample, each grain comes from a different moment and the output is a cloud of fragments that collectively represent the source without preserving any of its structure.
What does the grain scheduling look like in code?
The implementation is simpler than the concept suggests. The
scheduler runs at a fixed interval, launches a grain, and the
grain plays itself out. In the Web Audio API, this can be done
with an AudioBufferSourceNode per grain, scheduled
at a precise time on the audio clock. The code below implements
a minimal granular engine that plays a source buffer as a
continuous texture.
function createGranularEngine(ctx, sourceBuffer, options = {}) {
const grainSize = options.grainSize || 0.03; // 30 ms
const density = options.density || 100; // grains per second
const scatter = options.scatter || 0.8; // 0 to 1
const pitchScatter = options.pitchScatter || 0;
const interval = 1 / density;
let nextGrainTime = ctx.currentTime;
function scheduleGrain(startTime) {
// Pick a random position in the source
const maxPos = sourceBuffer.duration - grainSize;
const pos = Math.random() * maxPos * scatter
+ (1 - scatter) * maxPos * 0.5;
// Create the source node and a gain for the Hann window
const grain = ctx.createBufferSource();
const gain = ctx.createGain();
grain.buffer = sourceBuffer;
// Pitch scatter: random playbackRate
grain.playbackRate.value =
1 + (Math.random() - 0.5) * (pitchScatter / 12);
// Hann window envelope
const attack = grainSize * 0.5;
const release = grainSize * 0.5;
gain.gain.setValueAtTime(0, startTime);
gain.gain.linearRampToValueAtTime(1, startTime + attack);
gain.gain.linearRampToValueAtTime(0, startTime + grainSize);
grain.connect(gain);
gain.connect(ctx.destination);
grain.start(startTime, pos, grainSize);
grain.stop(startTime + grainSize);
}
return {
start() {
nextGrainTime = ctx.currentTime;
const scheduler = setInterval(() => {
const now = ctx.currentTime;
while (nextGrainTime < now + 0.1) {
scheduleGrain(nextGrainTime);
nextGrainTime += interval;
}
}, 20);
return scheduler;
},
stop(scheduler) {
clearInterval(scheduler);
}
};
}
The linearRampToValueAtTime pair at the top and
bottom of the grain envelope approximates the Hann window. A true
Hann window uses a cosine curve rather than a linear ramp, but
for most texture-generation purposes the linear approximation is
inaudible, and it is much cheaper to schedule. The scheduler
uses a 20-millisecond lookahead window, which is the same
approach used for the
coin sound scheduling in the Web Audio tutorial.
The scheduler pattern is worth understanding. The
setInterval call runs every 20 milliseconds on the
main thread, and each invocation schedules any grains that need
to start in the next 100 milliseconds. Because the grains are
scheduled on the audio clock rather than fired directly, the
main thread's jitter does not affect the timing of the output.
This is the same decoupling that makes scheduled sound effects
work reliably in browser games.
Which game sound categories suit granular?
Granular synthesis is not a general-purpose tool. It excels in specific categories and is wasted in others. The pattern is that granular is best when the sound needs to be continuous, evolving slowly, and derived from a source that has interesting internal character.
Corrupted or unstable textures. Static, glitch, radio interference, corrupted data. The source is a burst of noise or a distorted tone, and the granular engine turns it into a texture that sounds like the same material is constantly reforming. The horror sound design approach uses this heavily, because instability is exactly the quality that makes a sound feel threatening.
Sustained energy fields. Force fields, energy barriers, magic auras, sci-fi shields. These need to be continuous, have a clear identity, and produce variation without changing their character. Granular provides all three, using a short synthesized or recorded source as the raw material.
Slow-evolving ambience. Wind through an abandoned corridor, distant mechanical groans, the drone of a derelict spaceship. These are the sounds where a looping recording becomes noticeable after a minute of exposure, and where granular eliminates the loop entirely.
Morphing transitions. A normal sound effect that transforms into something else — a hit that decays into a metallic tail, a voice that dissolves into static, an engine that degrades into noise. The granular engine can cross-fade between two source samples by gradually shifting the grain position from one source to the other.
Creature vocalizations that need variation. A monster roar that plays identically every time becomes a loop the player learns. A granular engine driving a short recorded roar produces a version that varies every playback, because the grains never land in exactly the same pattern twice.
The categories where granular is the wrong tool are equally specific. Short punchy impacts — a hit, a coin, a UI click — do not benefit, because the grain engine has no time to establish texture. Musical sounds with a clear pitch — a note, a chord, a melody — also do not benefit unless the grain size is large enough to preserve the pitch, at which point it stops being granular and starts being sample playback. The dividing line is whether the sound is defined by its shape or by its texture. Granular works on texture. Shape needs a different tool.
How do you build a morphing engine between two sources?
The morphing application is one of the more useful extensions of the basic engine, and it is worth showing the specific implementation because the design is not obvious. The goal is a single continuous texture that starts as one source sample and gradually becomes another.
function scheduleMorphGrain(startTime, sources, morphAmount) {
// sources: array of { buffer, weight }
// morphAmount: 0 = first source, 1 = last source
// Choose which source to draw this grain from
const roll = Math.random();
let cumulative = 0;
let chosen = sources[0].buffer;
for (let i = 0; i < sources.length; i++) {
cumulative += sources[i].weight;
if (roll < cumulative) {
chosen = sources[i].buffer;
break;
}
}
// Blend the position within the chosen source
// based on the morph amount
const grainSize = 0.03;
const maxPos = chosen.duration - grainSize;
const pos = Math.random() * maxPos * (1 - morphAmount);
// ... (the rest is the same as the single-source scheduler)
}
The morphing works by weighting the source selection. At
morphAmount = 0, the first source has weight 1.0
and every grain is drawn from it. As morphAmount
increases, the second source's weight increases and the
probability that a given grain comes from the second source
rises. At morphAmount = 1, every grain comes from
the second source.
The advantage of this approach over a simple cross-fade is that the transition is granular. There is no point at which both sources are playing at half volume — each grain is entirely from one source or the other, and the mixture changes over hundreds of grains. The ear perceives a slow transformation rather than a fade, and the individual identities of both sources are preserved through the transition.
When does granular become the wrong tool?
The technique has a specific failure mode that is worth understanding before committing to it for a sound. Granular synthesis is expensive per voice, because each grain requires its own audio source node and its own envelope. A granular texture running at 100 grains per second with a 30-millisecond grain size uses approximately three overlapping grains at any instant, which is manageable. But a granular engine running at 500 grains per second with 100-millisecond grains uses fifty simultaneous source nodes, and on mobile hardware that is enough to consume a measurable portion of the audio budget.
The mitigation is to bake the granular output to a fixed loop when the sound does not need to vary at runtime. A twenty-second granular texture rendered once and shipped as a compressed audio file costs nothing at runtime, and for many ambient and sci-fi applications the variation between playbacks is not important enough to justify the ongoing CPU cost. Granular is most valuable at runtime when the sound needs to respond to gameplay state — when the intensity of a shield effect tracks the amount of damage the shield is taking, for example — and least valuable when the sound is a static texture that happens to have been generated granularly.
The other failure mode is over-parameterization. Because the granular engine has five or six continuous parameters, it is tempting to modulate all of them at once. The result is a texture that never settles into anything recognizable, because the ear has no stable reference to latch onto. The granular textures that work best in games are usually the ones where two parameters are modulated and the rest are fixed. The movement comes from those two, and the remaining parameters provide the stable identity that makes the sound recognizable across its lifetime.
If you are considering granular for a sound in your project, the quickest way to know whether it fits is to ask what part of the sound the player is supposed to be paying attention to. If the answer is the texture — the quality of the material itself — granular is the right tool. If the answer is the shape — when it starts, when it peaks, when it ends — then a sound with a clear envelope, generated with the same approach used for game sound effects generally, will communicate more clearly. The two approaches produce different kinds of information, and the right choice depends on which kind the gameplay needs.
Generate a Source Sample for Granular Processing
Open the SfxMaker generator and create a short characteristic sample — a burst of noise, a tonal blip, a metallic ring — to use as the source for a granular engine. The best granular sources have internal variation within a single short file, because the engine will pull grains from many different positions and the differences between them are what make the texture interesting.
Open SfxMaker Generator →