Bus effects

A generated pluck routed Voice into FX into Master, with reverb and distortion you switch on and the peak level of every bus on screen.

The editor above is running this project. Change a line and it reloads.

Godot classes used

AudioServerAudioEffectReverbAudioEffectDistortion

The code

scripts/main.gd
extends Node

const MIX_RATE: float = 22050.0
const PANEL: Vector2 = Vector2(210.0, 150.0)
const GAP: float = 120.0
const PLUCK_SECONDS: float = 0.55
const DECAY: float = 0.99975
const NOTES: Array = [261.63, 329.63, 392.00, 523.25]

var player: AudioStreamPlayer
var playback: AudioStreamGeneratorPlayback
var buses: Array[int] = []
var meters: Array[ColorRect] = []
var readouts: Array[Label] = []
var dots: Array[Polygon2D] = []
var wires: Array[Vector2] = []
var effect_labels: Array[Label] = []
var start_button: Button
var phase: float = 0.0
var level: float = 0.0
var note: int = 0
var elapsed: float = 0.0
var flow: float = 0.0


func _ready() -> void:
	_add_background()

	var view: Vector2 = get_viewport().get_visible_rect().size
	var width: float = PANEL.x * 3.0 + GAP * 2.0
	var left: float = (view.x - width) * 0.5
	var top: float = view.y * 0.3

	_add_label('Bus effects', Vector2(left, view.y * 0.09), 26, Color('#ffffff'))
	_add_label('one generated pluck routed Voice into FX into Master, with the peak of every bus below its name', Vector2(left, view.y * 0.15), 15, Color('#999999'))

	_wire_buses()

	for i in 3:
		_add_panel(['Voice', 'FX', 'Master'][i], Vector2(left + i * (PANEL.x + GAP), top))

	for i in 2:
		var from: float = left + i * (PANEL.x + GAP) + PANEL.x
		wires.append(Vector2(from, from + GAP))
		_add_wire(Vector2(from, top + PANEL.y * 0.5))

	_add_effect_labels(Vector2(left + PANEL.x + GAP + 16.0, top + 54.0))
	_add_controls(Vector2(left, top + PANEL.y + 70.0))
	_add_player()

	print('routing is live, press Start sound and toggle the two effects on the FX bus')


func _process(delta: float) -> void:
	flow = fmod(flow + delta * 0.6, 1.0)

	for i in dots.size():
		var at: float = fmod(flow + i * 0.5, 1.0)
		dots[i].position.x = lerpf(wires[i].x, wires[i].y, at)
		dots[i].modulate.a = (1.0 if playback != null else 0.55) * maxf(sin(at * PI), 0.35)

	for i in buses.size():
		var db: float = AudioServer.get_bus_peak_volume_left_db(buses[i], 0)
		var target: float = clampf((db + 60.0) / 60.0, 0.0, 1.0)

		meters[i].size.x = maxf(target * (PANEL.x - 32.0), 3.0)
		readouts[i].text = '%.0f dB' % maxf(db, -60.0)

	if playback == null:
		return

	elapsed += delta

	if elapsed >= PLUCK_SECONDS:
		elapsed -= PLUCK_SECONDS
		note = (note + 1) % NOTES.size()
		level = 0.5

	_fill_buffer()


func _fill_buffer() -> void:
	var hz: float = NOTES[note]

	for i in playback.get_frames_available():
		phase = fmod(phase + hz / MIX_RATE, 1.0)
		level *= DECAY
		var sample: float = (sin(phase * TAU) + sin(phase * TAU * 3.0) * 0.25) * level
		playback.push_frame(Vector2(sample, sample))


func _wire_buses() -> void:
	# a bus can only send to a lower index, so the downstream bus has to be created first
	var fx: int = _find_bus('FX', 'Master')
	var voice: int = _find_bus('Voice', 'FX')

	if AudioServer.get_bus_effect_count(fx) == 0:
		var reverb: AudioEffectReverb = AudioEffectReverb.new()
		reverb.room_size = 0.85
		reverb.wet = 0.6
		AudioServer.add_bus_effect(fx, reverb)

		var distortion: AudioEffectDistortion = AudioEffectDistortion.new()
		distortion.drive = 0.5
		distortion.pre_gain = 8.0
		distortion.post_gain = -14.0
		AudioServer.add_bus_effect(fx, distortion)

	AudioServer.set_bus_effect_enabled(fx, 0, false)
	AudioServer.set_bus_effect_enabled(fx, 1, false)

	buses.append(voice)
	buses.append(fx)
	buses.append(AudioServer.get_bus_index('Master'))


func _find_bus(bus: String, send: String) -> int:
	var index: int = AudioServer.get_bus_index(bus)

	if index != -1:
		return index

	index = AudioServer.get_bus_count()
	AudioServer.add_bus(index)
	AudioServer.set_bus_name(index, bus)
	AudioServer.set_bus_send(index, send)

	return index


func _toggle_effect(on: bool, slot: int) -> void:
	AudioServer.set_bus_effect_enabled(buses[1], slot, on)
	effect_labels[slot].add_theme_color_override('font_color', Color('#8eef97') if on else Color('#999999'))


func _toggle_sound() -> void:
	if playback != null:
		player.stop()
		playback = null
		level = 0.0
		start_button.text = 'Start sound'
		return

	player.play()
	playback = player.get_stream_playback()
	level = 0.5
	start_button.text = 'Stop sound'


func _add_panel(title: String, at: Vector2) -> void:
	var panel: ColorRect = ColorRect.new()
	panel.color = Color('#2a2f3d')
	panel.position = at
	panel.size = PANEL
	add_child(panel)

	_add_label(title, at + Vector2(16.0, 14.0), 20, Color('#ffffff'))

	var track: ColorRect = ColorRect.new()
	track.color = Color('#12141a')
	track.position = at + Vector2(16.0, PANEL.y - 46.0)
	track.size = Vector2(PANEL.x - 32.0, 16.0)
	add_child(track)

	var meter: ColorRect = ColorRect.new()
	meter.color = Color('#8eef97')
	meter.position = track.position
	meter.size = Vector2(3.0, 16.0)
	add_child(meter)
	meters.append(meter)

	readouts.append(_add_label('-60 dB', at + Vector2(16.0, PANEL.y - 26.0), 14, Color('#999999')))


func _add_wire(at: Vector2) -> void:
	var wire: ColorRect = ColorRect.new()
	wire.color = Color('#8da5f3', 0.3)
	wire.position = at - Vector2(0.0, 2.0)
	wire.size = Vector2(GAP, 4.0)
	add_child(wire)

	var dot: Polygon2D = Polygon2D.new()
	dot.color = Color('#8da5f3')
	dot.position = at
	dot.polygon = PackedVector2Array([Vector2(-11, 0), Vector2(0, -11), Vector2(11, 0), Vector2(0, 11)])
	add_child(dot)
	dots.append(dot)


func _add_effect_labels(at: Vector2) -> void:
	for i in 2:
		effect_labels.append(_add_label(['reverb', 'distortion'][i], at + Vector2(0.0, i * 24.0), 16, Color('#999999')))


func _add_controls(at: Vector2) -> void:
	var row: HBoxContainer = HBoxContainer.new()
	row.add_theme_constant_override('separation', 22)
	row.position = at
	add_child(row)

	for i in 2:
		var toggle: CheckButton = CheckButton.new()
		toggle.text = ['Reverb', 'Distortion'][i]
		toggle.toggled.connect(_toggle_effect.bind(i))
		row.add_child(toggle)

	start_button = Button.new()
	start_button.text = 'Start sound'
	start_button.pressed.connect(_toggle_sound)
	row.add_child(start_button)


func _add_player() -> void:
	var generator: AudioStreamGenerator = AudioStreamGenerator.new()
	generator.mix_rate = MIX_RATE
	generator.buffer_length = 0.1

	player = AudioStreamPlayer.new()
	player.stream = generator
	player.bus = 'Voice'
	add_child(player)


func _add_background() -> void:
	var background: ColorRect = ColorRect.new()
	background.color = Color('#12141a')
	add_child(background)
	background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)


func _add_label(text: String, at: Vector2, size: int, color: Color) -> Label:
	var label: Label = Label.new()
	label.text = text
	label.position = at
	label.add_theme_font_size_override('font_size', size)
	label.add_theme_color_override('font_color', color)
	add_child(label)

	return label

More Audio examples