Subtractive Dungeons, Additive Terrain
There are two classical ways to make a sound from nothing. Subtractive synthesis starts with something harmonically overloaded, a sawtooth or a burst of noise, and removes what isn't wanted with filters. Additive synthesis starts with silence and sums sine waves until the desired shape exists.
I've written a modular synthesizer with eighteen modules, and separately a pile of procedural generators across four game engines. Going back through the generators after writing about the synth, the same two shapes are in all of them, and in one case the correspondence isn't a comparison at all. It's the same arithmetic with the same name.
Subtractive: Make Too Much, Then Filter
The dungeon generator in my second engine is subtractive in the strict sense. It places more rooms than it needs, connects every pair of rooms that could possibly be connected, and then removes things.
The door pass generates a connection between every pair of rooms whose authored door regions overlap. The result is a maximally connected dungeon, which is unplayable, and is the level design equivalent of a sawtooth wave. A sawtooth is unpleasant on its own and useful because it contains every harmonic, so a filter has material to work with. The all-doors dungeon contains every possible route for the same reason.
Then the filter:
if(rand() % 100 < 10){
itd->active = false;
...
if(pathfindAll()){
rooms[itd->roomId].doors.erase(itd2);
itd = it->doors.erase(itd);
}
else{
itd->active = true;
itd2->active = true;
}
}Consider ten percent of doors, remove each tentatively, keep the removal if the dungeon is still fully connected and revert it otherwise.
The erosion in the terrain experiments is subtractive in exactly the same way. A heightmap is generated at full material, water is run over it, and the terrain is lowered only where the water was moving. Channels appear because material is taken away, not because channels were placed.
Both have the property that defines subtractive synthesis: the output is bounded by what the source contained. A filter can only remove harmonics the oscillator generated, and no filter setting turns a sine into a brass sound because there's nothing in a sine to bring forward. The door cull can only produce topologies that were present in the maximally connected graph, and the erosion can only carve valleys where the diamond-square pass put slopes. Overproduction isn't waste, it's the harmonic content the later stage works on.
The Filter Needs Something It Can't Remove
The interesting difference is that a resonant filter left unchecked will self-oscillate, and a door cull left unchecked will disconnect the dungeon. Both need a constraint that stops the removal going too far.
In the generator that constraint is explicit and exact. pathfindAll() runs after every tentative removal, and connectivity is the thing that cannot be filtered out. The dungeon is allowed to lose any door whose loss doesn't cost reachability, and that single invariant is what makes an aggressive cull safe.
That's a better arrangement than a synthesizer usually gets, because a filter has no way to ask whether the note is still recognizable. It's the advantage of generating content rather than signal: the output can be tested against a definition of correct before the removal is committed, and every intermediate state stays valid.
Additive: Octaves, and the Word Is Not a Metaphor
The terrain generator itself is additive, and here the correspondence stops being an analogy.
Diamond-square starts with four corners and repeatedly adds displacement at the midpoints, halving the displacement range at each level of subdivision:
float heightrange = initialheight;
for(int i=0;i<seg->depth;i++){
heightrange /= 2.0f;
}Each level doubles the spatial frequency of the detail and halves its amplitude.
Additive synthesis builds a waveform by summing partials at multiples of the fundamental, with amplitude falling as frequency rises. A sawtooth is every harmonic at 1/n. Pink noise is amplitude falling as 1/f.
These are the same construction. Terrain people and audio people both call the layers octaves, both mean a doubling of frequency, and both halve the amplitude across one. When a shader samples noise four times at doubling frequencies and halving weights, the code is identical to summing four partials, and the only difference is whether the sum is interpreted as a height or a voltage.
The vignette in one of my testbeds is a two-partial additive synth that happens to output brightness:
float fVignetteAlpha = 0.75f + ((float)m_perlin.noise(m_fPerlinTimer, 1.0f, 1.0f)
+ (float)m_perlin.noise(m_fPerlinTimer / 5.0f, 1.0f, 1.0f)) * 0.25f;Two samples of one noise field at rates five apart, summed, scaled, and offset. A fast term for flicker and a slow term for drift. 0.75 is the DC offset, 0.25 is the output gain, and the two noise calls are the partials. Written as audio it's a two-oscillator patch. Written as graphics it's a flickering light.
The particle system is additive too, in the plainer sense: forces accumulate onto a velocity, each contributing independently, and the visible result is the sum. Nothing is filtered out, and complexity comes from stacking terms.
The Oscillator Decides What Can Come Out
The rhythm game I wrote in twelve hours picks notes like this:
pitch = Random.Range(0, spawnerArray.Length);No scale, no constraint, no correction. It's listenable because the five available sounds are e, g, b, g2, and b2, an E minor triad plus two octaves, so every note is consonant with every other by construction.
In synthesis terms that's oscillator selection, and it's the decision subtractive synthesis makes first and least reversibly. Choosing a square wave rather than a sawtooth removes the even harmonics before any filter is touched. Choosing five notes from one chord removes every dissonant interval before any selection logic runs.
It's the opposite strategy to the dungeon generator, and both are right for their situation. The dungeon overproduces because it can test the result and delete what fails, so a rich source is an advantage. The rhythm game can't test the result, because there's no cheap definition of a bad melody to check against, so it constrains the source until no bad output exists to produce. Generate and reject needs a test. Constrain the alphabet doesn't.
The tell for which one a problem needs is whether correctness is easy to measure. Connectivity is trivial to measure and hard to construct, so the dungeon builds too much and filters. Musical consonance is awkward to measure and easy to guarantee up front, so the rhythm game picks its five notes and stops worrying.
Envelopes, Modulation, and Sample and Hold
Once the two frames are in place, the rest of the synthesizer's parts turn up in the generators too, and some of them are already wearing their audio names.
The GUI motion system gives every component a delay, a duration, a target, and a curve, on each of three channels. That's an envelope generator with a delay stage, and one of its curves is called AttackEaseOut, borrowing the word for the initial rise straight from audio envelopes. The naming wasn't a joke. The system does what an envelope does: produce a scalar over time from a small set of parameters, and let something else decide what that scalar drives.
The character animation in the fruit game is low frequency modulation with a depth control. Head wobble, hat wobble and a speed wiggle each keep their own phase accumulator and advance it by delta * frequency, which is exactly how an LFO is implemented, and the hat's motion is the head's multiplied by 0.9. In a synth that multiplier is called modulation depth. Here it makes a hat look like it's resting on a head rather than painted onto one.
The particle system's two stored seeds are a sample and hold. A sample and hold module reads a noise source at a trigger and holds that value until the next trigger, which is how a synth gets a random but stable pitch per note rather than a value that hisses. The particle samples randomness once at spawn and holds it for its lifetime, so its variation is stable and reproducible instead of jittering per frame. The starfield does the same thing with position rather than time as the trigger: sample the generator once per chunk, hold for as long as the chunk is in view, and get the same stars back on return.
The particle ring buffer is voice allocation. A fixed pool, oldest reused when the pool is exhausted, which is voice stealing under another name, and it's the same problem a polyphonic synth solves when a seventeenth key goes down on a sixteen voice instrument.
And the glitch shader is a noise gate. Sample noise, discard everything below a threshold, rescale what's left so it starts from zero:
if (intensity < threshold) intensity = 0.0;
else intensity = intensity - threshold;That's a gate with the threshold subtracted rather than a hard cut, which is what stops the effect snapping on at full strength. A gate that removes its threshold from the signal instead of just muting below it is a standard trick for exactly the same reason.
Where the Comparison Stops
Two of my generators have no clean synthesis counterpart, and the reason is instructive.
The dungeon's outer loop builds a complete dungeon, measures its depth and room count, discards it and starts again if it falls short, up to a hundred times. Nothing in a synthesizer does that, because audio is realtime and there is no opportunity to hear a note, decide it was wrong, and play a different one before it reaches the speaker. Rejection sampling is available to a generator that runs ahead of consumption, and not to one that runs against a deadline. It's the same reason the strictest rules in real-time audio are about never doing work of unpredictable duration.
The autotiler is the other one. It hashes a tile's eight neighbors and looks up a replacement, so its output depends on spatial context rather than on a signal path. The closest audio idea is a waveshaper, a lookup that maps input to output, but a waveshaper reads one sample and the autotiler reads a neighborhood. Context-sensitive substitution is a grammar, and grammars are what synthesis doesn't have.
What Transfers
Decide early whether a generator will overproduce and filter or constrain and accumulate, because it settles almost everything else. Overproduction needs a test that's cheap to run and safe to fail, and it buys richness the constrained approach can't reach. Constraint needs no test at all and can't produce anything interesting that wasn't anticipated in the alphabet.
Halve the amplitude when doubling the frequency, whatever the domain. Terrain, noise, animation detail, and audio all end up at the same 1/f relationship, and the reason is the same in each: it's the distribution that reads as natural rather than as either uniform or arbitrary.
Look for a stored seed wherever a value should be random but stable. Sample and hold is one of the oldest modules on a synthesizer and an idea procedural generation reuses constantly, and it's the same operation both times.
And take the vocabulary seriously. When two fields have independently arrived at the word octave for the same halving, the borrowing usually runs deeper than the term. The rest of the shared vocabulary then reads as a map of which techniques crossed over with it.