Advertisement
UI & Interface Sounds

Procedural Blip Sound Generation in the Defold Engine

What procedural audio actually means in Defold, why the engine has no real-time synthesis API, the runtime variation tricks that work with the native sound module, and the specific case where a Native Extension is the right answer.

A Defold developer searches for how to generate a blip sound at runtime, the way the Web Audio API does with an oscillator. They find the Defold sound documentation, read through the sound.play() API, and discover that there is no oscillator, no envelope, and no synthesis of any kind. The engine plays audio files. It does not generate them.

That is the actual situation in Defold, and it is worth stating plainly before exploring the workarounds. Defold does not expose a real-time audio synthesis API, and no amount of Lua scripting can create one, because the engine's audio pipeline is built around decoding pre-recorded .ogg and .wav files. The oscillators, envelope generators, and noise sources that make procedural audio possible in Godot, Unity, and the browser do not exist in Defold's native runtime.

This article is about what a Defold developer can realistically do instead. That covers three techniques that work with the native engine, one technique that requires a Native Extension, and the specific situations where each one is the right answer. The Web Audio blip tutorial and the Godot procedural audio guide cover the same problem in engines that do support real-time synthesis; this article is specifically about Defold.

Why does Defold not have a real-time audio synthesis API?

The reason is architectural. Defold's audio system is built on top of a playback backend that decodes compressed audio files into a stream and sends the samples to the platform's audio output. The engine provides no callback to the game code that would allow the developer to generate samples on the audio thread, and no node graph that connects oscillators to output the way the Web Audio API does.

This is a deliberate design choice, not an oversight. The Defold team has historically prioritized a small runtime, fast startup, and consistent behavior across platforms over a feature-rich audio API. The tradeoff is that the engine does not support the kinds of audio work that require direct sample generation, and developers who need that capability have to provide it themselves.

The practical consequence is that "procedural blip generation" in Defold means something different from what it means in Godot or the browser. It does not mean synthesizing the waveform at runtime. It means producing the perceived variation that procedural generation would provide, using the tools that are actually available: pre-rendered audio assets, runtime playback parameters, and — if necessary — a compiled extension.

What can you do with only the native sound module?

The sound module in Defold exposes a small set of parameters that can be adjusted at runtime. They are not synthesis parameters, but they do allow a pre-rendered blip to be varied enough that a set of indistinguishable playbacks becomes a set of clearly distinct playbacks. Those parameters are:

Parameter Range Effect on a blip
sound.set_gain() 0.0 to 1.0 Volume of the playback
sound.set_pan() -1.0 to 1.0 Left/right stereo position
sound.set_speed() 0.5 to 2.0 (typical) Playback rate — affects both pitch and duration

The set_speed parameter is the closest thing Defold has to pitch control, but it changes both pitch and duration together, the way a tape machine running at the wrong speed does. For a blip, this is actually an advantage rather than a limitation, because the effect is exactly the kind of variation that makes a set of blips feel alive. A blip that is played at speed 1.0 sounds neutral, at 1.05 sounds slightly brighter, at 0.95 sounds slightly darker and — critically — slightly longer.

The variation ranges that produce a useful result without making the blip sound like a different sound are tighter than they might seem. For a blip in the 50 to 100 millisecond range, the speed variation should stay between 0.94 and 1.06. Beyond that range, the pitch shift becomes noticeable as a distinct musical interval rather than as a natural variation. The variation system guide covers the perceptual thresholds in more detail; the Defold-specific value is that the same thresholds apply even when the variation is implemented through speed rather than through separate samples.

How do you build a blip set with runtime speed variation?

The working pattern has two parts: a small pool of pre-rendered blip files and a Lua function that plays one of them with a random speed and gain. Three blip files with four speed values each give twelve perceived variations, which is more than enough for most UI use cases.

The three blips should share a family resemblance but differ in pitch. A neutral blip at 1000 Hz, a confirmation blip at 1400 Hz, and an alert blip at 1800 Hz cover the three common UI roles. The blip sound design guide covers the parameter ranges for each of these; the Defold-specific part is what happens to them at runtime.

-- blip.lua
local M = {}

-- Table of available blips. The keys are the roles the game
-- calls, the values are the sound component names in the .collection
local blip_map = {
    neutral = "blip_neutral",
    confirm = "blip_confirm",
    alert   = "blip_alert"
}

-- Per-role speed ranges. Narrower for tonal blips, wider for
-- ones that already have some noise content.
local speed_ranges = {
    neutral = { min = 0.96, max = 1.04 },
    confirm = { min = 0.94, max = 1.06 },
    alert   = { min = 0.97, max = 1.03 }
}

function M.play(role)
    local component = blip_map[role]
    if not component then return end

    local range = speed_ranges[role]
    local speed = range.min + math.random() * (range.max - range.min)
    local gain = 0.7 + math.random() * 0.15

    sound.set_gain(component, gain)
    sound.set_speed(component, speed)
    sound.play(component)
end

return M

The sound.set_speed and sound.set_gain calls apply to the next playback, not to a sound that is already playing. This means the parameters can be set immediately before the sound.play call, and they do not interfere with any other blip that is currently playing through a different component.

The gain range of 0.7 to 0.85 is deliberately narrow. A blip that varies from 0.5 to 1.0 gain would produce a variation that reads as inconsistent rather than as natural, because the loudness difference is more noticeable than the pitch difference. The narrow range gives the impression of variation without the impression of inconsistency.

The three blip files themselves can be generated with a browser-based tool and exported as .wav. Defold will import them directly, and the resulting game build packages the audio files the same way it would any other asset. The file size reduction guide covers the compression choices; for a short blip, the difference between .wav and .ogg is negligible at the file size level, but the decode cost of .ogg is higher and can matter for a game that plays many blips per second.

What are the limits of the speed variation approach?

The speed variation approach produces convincing variation for a blip that plays occasionally, such as a UI confirmation that fires a few times per minute. It breaks down in three specific cases.

High playback rates. A blip that plays ten times per second — a keyboard click, a rapid-fire game action — begins to expose the limits of speed variation because the ear has more samples to compare. The same three files at the same speed ranges sound like three files at those speed ranges, and the range becomes predictable.

Continuous parameter tracking. If the blip needs to respond to a continuous game value — for example, a blip whose pitch rises with a combo counter — the speed parameter can be set to any value in the range, but the blip's duration changes with it. A combo blip that reaches speed 1.3 plays noticeably faster than the same blip at speed 1.0, which may not be what the design intends.

Cross-platform consistency. The set_speed parameter is implemented differently on different platforms at the audio driver level, and the pitch shift produced by a given speed value is not guaranteed to be identical across all targets. A game that ships on desktop and mobile may see a slightly different pitch range on each, which is usually invisible in a UI blip but becomes noticeable if the blip is part of a musical system.

In each of these cases, the speed variation approach has reached its practical limit, and the developer has to choose between accepting the limitation or moving to a heavier solution. The heavier solution in Defold is a Native Extension, and it is worth being honest about what that involves.

When does a Native Extension make sense for blip generation?

A Defold Native Extension is a compiled library — written in C, C++, or a language that compiles to a compatible binary — that exposes new functions to the Lua runtime. It can access the platform's audio APIs directly and generate audio samples without going through the sound module. In principle, a Native Extension can do anything the platform allows, including full real-time synthesis.

The practical constraints are significant. A Native Extension for audio synthesis has to:

  • Run on every target platform the game ships on. A Defold game that targets Windows, macOS, Linux, iOS, Android, and HTML5 needs the extension to be built for all six. That is six build pipelines, six dependency trees, and six sets of platform-specific audio API calls.
  • Handle the audio thread correctly. Synthesis happens on a real-time thread with hard deadlines. Blocking operations, garbage collection, and dynamic allocation are all forbidden in that thread, which means the extension has to pre-allocate everything and use lock-free communication with the game thread.
  • Manage its own node graph. Without the Defold sound module, the extension has to implement its own mixing, gain, envelope, and output routing. The Web Audio API provides these for free; a Native Extension does not.
  • Be maintained indefinitely. Every Defold version update can require an extension rebuild. Every new platform target requires a new build configuration. The maintenance cost of a Native Extension is not a one-time investment; it is an ongoing commitment.

For a project that needs a handful of blips, the cost of a Native Extension is orders of magnitude larger than the cost of pre-rendering a few more variations in a browser tool and shipping them as static assets. The break-even point is somewhere around the case where the game needs hundreds of distinct sounds that vary continuously with gameplay state, at which point the asset-based approach becomes impractical and the extension becomes justified.

There is a middle option worth mentioning: a Defold Native Extension does not have to replace the sound module. It can be a narrower tool that only handles the specific case that needs real-time generation — a single procedural blip generator, for example — while the rest of the game's audio goes through the standard sound.play() path. This keeps the extension small, which reduces the platform support burden, while still providing the capability that the native module cannot.

What is the pragmatic recommendation for a Defold blip set?

For the vast majority of Defold projects that need blip sounds, the answer is the speed variation approach. It uses only the native engine, it produces convincing variation for the use cases where blips are actually needed, and it has no platform support burden.

The specific recipe that works:

  1. Generate three blip variants in a browser tool. A neutral blip at 1000 Hz, a confirmation blip at 1400 Hz, and an alert blip at 1800 Hz. Each should be around 60 milliseconds long with a fast attack and no sustain. The blip design guide covers the specific parameter ranges.
  2. Export as WAV. Defold imports both WAV and OGG. For a blip under 100 milliseconds, WAV is the better choice because the decode cost is lower and the file size difference is negligible.
  3. Import into the project. Each blip becomes a Sound component in the collection.
  4. Write the Lua wrapper. The blip.lua module above is a working starting point. Adjust the speed ranges for each role based on listening tests.
  5. Test in the actual game. Play the blip in the context it is used, with the music and other sounds present. A blip that sounds correct in isolation can be masked by music, and one that sounds too bright in isolation can be perfect in the mix.

The same approach applies to other short sound effects that a Defold game needs. A click, a footstep, a hit — each can be a small pool of pre-rendered variations with runtime speed and gain adjustment. The engineering pattern is identical, and the range of variation that works for each category is what changes.

Defold is not the engine to choose if a project's audio depends on real-time synthesis. Godot, Unity, Unreal, and the browser all provide that capability, and the engine comparison covers the tradeoffs between them. But Defold is a legitimate choice for a project whose audio needs are served by pre-rendered assets with runtime variation, and the speed-based pattern described above is sufficient for the vast majority of UI sounds, gameplay feedback, and stylistic effects that such a project requires.

The distinguishing feature of a Defold audio system is not what it can generate. It is what it can vary. The engine provides three runtime parameters, and a developer who understands the perceptual thresholds behind those parameters can produce a set of blips that sounds alive with nothing more than a handful of pre-rendered files and a few lines of Lua. The variation system guide covers the thresholds in more depth, and the same principles apply here as in any other engine.

Generate the Blip Files for Your Defold Project

Open the SfxMaker blip generator and create the neutral, confirmation, and alert blips that form the base of the runtime variation system. Export each as a WAV file, then import them into your Defold collection as Sound components.

Open Blip Sound Generator →
Advertisement