总线效果
生成的拨弦声从 Voice 经 FX 送到 Master,混响和失真可以随时开关,每条总线的峰值电平都显示在屏幕上。
上方的编辑器正在运行这个项目。改一行代码,它就会重新加载。
用到的 Godot 类
AudioServerAudioEffectReverbAudioEffectDistortion
代码
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