Spectrum Analyzer
Hi everybody! I have recorded another video tutorial about shaders in Godot. This time, we will demonstrate how to create a spectrum analyzer that responds to music played by the Godot audio system. This is a nice effect known from various music applications and can be useful for enhancing your game. Let’s do it.
- Create a Node2D scene.
- Add a child node ColorRect and assign a new ShaderMaterial to it. Add a new shader to the material and fill the provided shader code.
- Add a child node AudioStreamPlayer and assign an audio file to its Stream property in the Inspector. Set Autoplay to On.
- Open Audio tab at the bottom of the Godot editor, and add SpectrumAnalyzer effect to Master Bus.
- Assign a new script to the root node and add this code:
extends Node2D
const VU_COUNT = 30
const FREQ_MAX = 11050.0
const MIN_DB = 60
const ANIMATION_SPEED = 0.1
const HEIGHT_SCALE = 8.0
@onready var color_rect = $ColorRect
var spectrum
var min_values = []
var max_values = []
func _ready():
spectrum = AudioServer.get_bus_effect_instance(0, 0)
min_values.resize(VU_COUNT)
min_values.fill(0.0)
max_values.resize(VU_COUNT)
max_values.fill(0.0)
func _process(delta):
var prev_hz = 0
var data = []
for i in range(1, VU_COUNT + 1):
var hz = i * FREQ_MAX / VU_COUNT
var f = spectrum.get_magnitude_for_frequency_range(prev_hz, hz)
var energy = clamp((MIN_DB + linear_to_db(f.length())) / MIN_DB, 0.0, 1.0)
data.append(energy * HEIGHT_SCALE)
prev_hz = hz
for i in range(VU_COUNT):
if data[i] > max_values[i]:
max_values[i] = data[i]
else:
max_values[i] = lerp(max_values[i], data[i], ANIMATION_SPEED)
if data[i] <= 0.0:
min_values[i] = lerp(min_values[i], 0.0, ANIMATION_SPEED)
var fft = []
for i in range(VU_COUNT):
fft.append(lerp(min_values[i], max_values[i], ANIMATION_SPEED))
color_rect.get_material().set_shader_parameter("freq_data", fft)
Shader code
shader_type canvas_item;
const int VU_COUNT = 30;
const float segment_count = 40.0;
uniform float[VU_COUNT] freq_data;
void fragment() {
vec2 uv = UV;
uv.y = 1.0 - uv.y;
float vu_count = float(VU_COUNT);
vec2 pos = vec2(floor(uv.x * vu_count) / vu_count, floor(uv.y * segment_count) / segment_count);
float fft = freq_data[int(uv.x * vu_count)];
vec3 color = mix(vec3(0.0, 2.0, 1.0), vec3(2.0, 0.0, 0.0), sqrt(uv.y));
float mask = pos.y < fft ? 1.0 : 0.1;
vec2 dist = fract((uv - pos) * vec2(vu_count, segment_count)) - 0.5;
float led = smoothstep(0.5, 0.35, abs(dist.x)) * smoothstep(0.5, 0.35, abs(dist.y));
COLOR = vec4(led * color * mask, 1.0);
}