NKIDO for Godot

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.

v0.1 Status: MVP. Hot-swap, params, samples, and waveform viz work end-to-end

Install

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.

From a release zip

# 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/

From source

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.

Quickstart

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.

API reference

All methods and signals below are on NkidoAudioStream, which extends AudioStream.

Properties

NameTypeDefaultDescription
akkado_sourceNkidoAkkadoSourcenullResource holding the Akkado source code (inline source_code string or loaded from a .akk file).
sample_packResourcenullOptional bundle of samples referenced by the patch.
bpmfloat120.0Tempo used by pattern builtins.
crossfade_blocksint3Hot-swap crossfade length, in audio blocks (1 to 10).

Compilation & hot-swap

MethodReturnsDescription
compile()boolCompile the assigned akkado_source. Hot-swaps the running program if already playing.
is_compiled()boolWhether a valid program is loaded.
get_diagnostics()ArrayList of {line, column, message} dicts from the last compile.

Parameters (runtime)

MethodDescription
set_param(name: String, value: float, slew_ms := 20.0)Smoothly set a param() declared in the patch.
get_param(name: String) -> floatRead the current value.
trigger_button(name: String)Pulse a button() (auto-releases after two audio blocks).
get_param_decls() -> ArrayAll params the current program exposes: {name, type, default, min, max, options}.

Samples & soundfonts

MethodDescription
load_sample(name, path) -> boolRegister a WAV, OGG, FLAC, or MP3 file by logical name, reachable from pattern strings like pat("name").
load_soundfont(name, path) -> boolRegister 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() -> ArraySample names the last compile referenced (but may not be loaded yet).

Visualization

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.

Signals

SignalPayload
compilation_finished(success: bool, errors: Array)
params_changed(params: Array). Emitted when get_param_decls() would change (hot-swap across different param sets).

Driving it from UI

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()

Troubleshooting

"No class named NkidoAudioStream"

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.

Compile reports the same error twice

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.

Audio clicks every time I edit

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.

Sample rate mismatch

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.

Building from source fails finding godot-cpp

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.