Live-coded audio synthesis as a Godot 4.x AudioStream resource. Ships as a GDExtension: compile your Akkado source at runtime, drive it with parameters, and visualize the output straight from GDScript.
The addon is a GDExtension with prebuilt binaries for Linux, Windows, and macOS (debug + release). Godot 4.1 or later required; developed against 4.5.
# Download the latest release
curl -LO https://github.com/mlaass/godot-nkido-addon/releases/latest/download/nkido.zip
unzip nkido.zip -d your-project/addons/Use this if you want to build for an unsupported platform or track the bleeding edge.
Requires CMake 3.22+ and a C++20 toolchain. The build expects godot-cpp and nkido as sibling directories next to the addon.
git clone https://github.com/mlaass/godot-nkido-addon.git
git clone -b godot-4.5-stable https://github.com/godotengine/godot-cpp.git
git clone https://github.com/mlaass/nkido.git
cd godot-nkido-addon
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)Paths can be overridden with -DGODOT_CPP_PATH=... and -DNKIDO_PATH=....
After install, open Project → Reload the project. The NKIDO editor plugin adds a bottom panel for compiling and previewing patches.
Attach an AudioStreamPlayer with its stream set to a new NkidoAudioStream. From GDScript, assign a NkidoAkkadoSource and
call compile():
extends Node
@onready var player: AudioStreamPlayer = $Player
func _ready() -> void:
var stream: NkidoAudioStream = player.stream
var akkado := NkidoAkkadoSource.new()
akkado.source_code = """
cutoff = param("cutoff", 1200, 100, 8000)
osc("saw", 220) * 0.3
|> lp(@, cutoff, 0.4)
|> out(@)
"""
stream.akkado_source = akkado
stream.compilation_finished.connect(_on_compiled)
stream.compile()
func _on_compiled(success: bool, errors: Array) -> void:
if not success:
for e in errors:
push_error("Line %d: %s" % [e.line, e.message])
return
player.play()Load from a .akk file by dragging a NkidoAkkadoSource resource
into the inspector; .akk files are loaded as first-class Godot resources.
All methods and signals below are on NkidoAudioStream, which extends AudioStream.
| Name | Type | Default | Description |
|---|---|---|---|
akkado_source | NkidoAkkadoSource | null | Resource holding the Akkado source code (inline source_code string or loaded from a .akk file). |
sample_pack | Resource | null | Optional bundle of samples referenced by the patch. |
bpm | float | 120.0 | Tempo used by pattern builtins. |
crossfade_blocks | int | 3 | Hot-swap crossfade length, in audio blocks (1 to 10). |
| Method | Returns | Description |
|---|---|---|
compile() | bool | Compile the assigned akkado_source. Hot-swaps the running program if already playing. |
is_compiled() | bool | Whether a valid program is loaded. |
get_diagnostics() | Array | List of {line, column, message} dicts from the last compile. |
| Method | Description |
|---|---|
set_param(name: String, value: float, slew_ms := 20.0) | Smoothly set a param() declared in the patch. |
get_param(name: String) -> float | Read the current value. |
trigger_button(name: String) | Pulse a button() (auto-releases after two audio blocks). |
get_param_decls() -> Array | All params the current program exposes: {name, type, default, min, max, options}. |
| Method | Description |
|---|---|
load_sample(name, path) -> bool | Register a WAV, OGG, FLAC, or MP3 file by logical name, reachable from pattern strings like pat("name"). |
load_soundfont(name, path) -> bool | Register an SF2 soundfont. |
clear_samples() / clear_soundfonts() | Drop all currently loaded assets. |
get_loaded_samples() / get_loaded_soundfonts() | Inspect what's currently bound. |
get_required_samples() -> Array | Sample names the last compile referenced (but may not be loaded yet). |
get_waveform_data() returns a PackedFloat32Array of 1024
interleaved L/R frames from the last rendered block, enough to drive a scope widget
without reaching into the audio thread.
| Signal | Payload |
|---|---|
compilation_finished | (success: bool, errors: Array) |
params_changed | (params: Array). Emitted when get_param_decls() would change (hot-swap across different param sets). |
A minimal patch with a cutoff slider and a trigger button:
extends Node
@onready var player: AudioStreamPlayer = $Player
@onready var cutoff: HSlider = $UI/Cutoff
@onready var hit_btn: Button = $UI/Hit
var stream: NkidoAudioStream
func _ready() -> void:
stream = player.stream
stream.compilation_finished.connect(_on_compiled)
cutoff.value_changed.connect(func(v: float) -> void:
stream.set_param("cutoff", v))
hit_btn.pressed.connect(func() -> void:
stream.trigger_button("hit"))
stream.compile()
func _on_compiled(success: bool, _errors: Array) -> void:
if success: player.play()The GDExtension didn't load. Check that the binary for your platform exists under addons/nkido/bin/. The naming convention is libnkido.<platform>.template_<mode>.<arch>.<ext>. A debug Linux build is libnkido.linux.template_debug.x86_64.so. Godot prints the missing path to
the console; double-check it against nkido.gdextension's library table.
The editor plugin's bottom panel and your runtime code may both be compiling. Disable the plugin in Project Settings → Plugins while you debug runtime calls.
Raise crossfade_blocks. Default is 3 blocks (~8 ms at 48 kHz / 128-sample
blocks). Values up to 10 are fine; above that the swap lag becomes audible. If clicks
persist after raising it, the node's semantic ID is changing on every edit. See Hot-swap explained for what keeps state.
NKIDO renders at the audio engine's mix rate, which Godot defaults to 44.1 kHz. If a
patch sounds pitched, set Project Settings → Audio → Driver → Mix Rate to
48000 and reload.
The build expects godot-cpp and nkido as sibling directories
next to godot-nkido-addon. Clone both, or override the paths with -DGODOT_CPP_PATH=... and -DNKIDO_PATH=... when running cmake.