Audio posicional

Una fuente en movimiento con max_distance y attenuation, junto a una barra que muestra la misma caída que AudioStreamPlayer2D aplica al tono generado.

El editor de arriba está ejecutando este proyecto. Cambia una línea y se recarga.

Clases de Godot usadas

AudioStreamPlayer2DAudioListener2DLine2D

El código

scripts/main.gd
extends Node

const MIX_RATE: float = 22050.0
const TONE_HZ: float = 330.0
const ATTENUATION: float = 1.6
const BAR: float = 260.0

var source: Node2D
var speaker: AudioStreamPlayer2D
var link: Line2D
var volume_bar: ColorRect
var pan_marker: ColorRect
var readout: Label
var start_button: Button
var center: Vector2
var range_radius: float
var elapsed: float = 0.0


func _ready() -> void:
	_add_background()

	var view: Vector2 = get_viewport().get_visible_rect().size
	center = Vector2(view.x * 0.5, view.y * 0.52)
	range_radius = minf(view.x, view.y) * 0.33

	_add_label('Positional audio', Vector2(48.0, view.y * 0.07), 26, Color('#ffffff'))
	_add_label('the circle is max_distance, and the bar is the same falloff AudioStreamPlayer2D applies', Vector2(48.0, view.y * 0.13), 15, Color('#999999'))

	_add_field()
	_add_listener()
	_add_source()
	_add_meters(Vector2(48.0, view.y * 0.78))

	start_button = Button.new()
	start_button.text = 'Start sound'
	start_button.position = Vector2(48.0, view.y * 0.9)
	start_button.pressed.connect(_toggle_sound)
	add_child(start_button)

	print('the source is already orbiting, press Start sound to hear it fade with distance')


func _process(delta: float) -> void:
	elapsed += delta

	source.position = center + Vector2(cos(elapsed * 0.7 + 0.5) * range_radius * 1.15, sin(elapsed * 1.1 + 1.9) * range_radius * 0.8)
	source.scale = Vector2.ONE * (1.0 + sin(elapsed * 7.0) * 0.12)

	var distance: float = source.position.distance_to(center)
	var volume: float = pow(1.0 - clampf(distance / range_radius, 0.0, 1.0), ATTENUATION)
	var pan: float = clampf((source.position.x - center.x) / range_radius, -1.0, 1.0)

	link.points = PackedVector2Array([center, source.position])
	link.default_color = Color('#2a2f3d').lerp(Color('#8eef97'), volume)

	volume_bar.size.x = maxf(volume * BAR, 2.0)
	pan_marker.position.x = 48.0 + (pan * 0.5 + 0.5) * BAR - 3.0
	readout.text = 'distance %d px   volume %.2f   pan %+.2f' % [int(distance), volume, pan]


func _toggle_sound() -> void:
	if speaker.playing:
		speaker.stop()
		start_button.text = 'Start sound'
		return

	speaker.play()
	start_button.text = 'Stop sound'


func _add_field() -> void:
	for at in [1.0, 0.5]:
		var circle: Line2D = _make_circle(range_radius * at, Color('#8da5f3', 0.45 - at * 0.2))
		circle.position = center
		add_child(circle)

	link = Line2D.new()
	link.width = 3.0
	add_child(link)


func _add_listener() -> void:
	var ear: Node2D = Node2D.new()
	ear.position = center
	ear.add_child(_make_disc(16.0, Color('#f3d48d')))
	add_child(ear)

	var listener: AudioListener2D = AudioListener2D.new()
	ear.add_child(listener)
	listener.make_current()

	_add_label('AudioListener2D', center + Vector2(-58.0, 24.0), 14, Color('#999999'))


func _add_source() -> void:
	source = Node2D.new()
	source.add_child(_make_disc(18.0, Color('#8da5f3')))
	add_child(source)

	speaker = AudioStreamPlayer2D.new()
	speaker.stream = _make_tone()
	speaker.max_distance = range_radius
	speaker.attenuation = ATTENUATION
	speaker.panning_strength = 1.4
	speaker.volume_db = -8.0
	source.add_child(speaker)


func _add_meters(at: Vector2) -> void:
	readout = _add_label('', at - Vector2(0.0, 30.0), 18, Color('#f3d48d'))

	var track: ColorRect = ColorRect.new()
	track.color = Color('#2a2f3d')
	track.position = at
	track.size = Vector2(BAR, 16.0)
	add_child(track)

	volume_bar = ColorRect.new()
	volume_bar.color = Color('#8eef97')
	volume_bar.position = at
	volume_bar.size = Vector2(2.0, 16.0)
	add_child(volume_bar)

	var pan_track: ColorRect = ColorRect.new()
	pan_track.color = Color('#2a2f3d')
	pan_track.position = at + Vector2(0.0, 28.0)
	pan_track.size = Vector2(BAR, 10.0)
	add_child(pan_track)

	pan_marker = ColorRect.new()
	pan_marker.color = Color('#8da5f3')
	pan_marker.position = at + Vector2(BAR * 0.5 - 3.0, 24.0)
	pan_marker.size = Vector2(6.0, 18.0)
	add_child(pan_marker)


func _make_tone() -> AudioStreamWAV:
	var period: int = int(round(MIX_RATE / TONE_HZ))
	var frames: int = period * 40
	var data: PackedByteArray = PackedByteArray()
	data.resize(frames * 2)

	for i in frames:
		var phase: float = float(i % period) / float(period)
		var sample: float = sin(phase * TAU) * 0.7 + (phase * 2.0 - 1.0) * 0.2
		data.encode_s16(i * 2, int(sample * 24000.0))

	var wav: AudioStreamWAV = AudioStreamWAV.new()
	wav.format = AudioStreamWAV.FORMAT_16_BITS
	wav.mix_rate = int(MIX_RATE)
	wav.stereo = false
	wav.data = data
	wav.loop_mode = AudioStreamWAV.LOOP_FORWARD
	wav.loop_begin = 0
	wav.loop_end = frames

	return wav


func _make_disc(radius: float, color: Color) -> Polygon2D:
	var points: PackedVector2Array = PackedVector2Array()

	for i in 30:
		points.append(Vector2(cos(TAU * i / 30.0), sin(TAU * i / 30.0)) * radius)

	var disc: Polygon2D = Polygon2D.new()
	disc.polygon = points
	disc.color = color

	return disc


func _make_circle(radius: float, color: Color) -> Line2D:
	var circle: Line2D = Line2D.new()
	circle.closed = true
	circle.width = 2.0
	circle.default_color = color

	for i in 48:
		circle.add_point(Vector2(cos(TAU * i / 48.0), sin(TAU * i / 48.0)) * radius)

	return circle


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

Más ejemplos de Audio