Barres de spectre

Un balayage généré routé vers son propre bus et mesuré par AudioEffectSpectrumAnalyzer. Le repère est la fréquence source, les barres sont ce que lit l'analyseur.

L'éditeur ci-dessus fait tourner ce projet. Changez une ligne et il se recharge.

Classes Godot utilisées

AudioEffectSpectrumAnalyzerAudioServerColorRect

Le code

scripts/main.gd
extends Node

const MIX_RATE: float = 22050.0
const BANDS: int = 28
const LOW_HZ: float = 60.0
const HIGH_HZ: float = 9000.0
const SWEEP_LOW: float = 90.0
const SWEEP_HIGH: float = 1600.0
const SWEEP_SECONDS: float = 5.0

var player: AudioStreamPlayer
var playback: AudioStreamGeneratorPlayback
var analyzer: AudioEffectSpectrumAnalyzerInstance
var fills: Array[ColorRect] = []
var levels: PackedFloat32Array = PackedFloat32Array()
var marker: ColorRect
var readout: Label
var start_button: Button
var sweep: float = 0.0
var phase: float = 0.0
var wall: Rect2


func _ready() -> void:
	_add_background()

	var view: Vector2 = get_viewport().get_visible_rect().size
	var size: Vector2 = Vector2(view.x * 0.78, view.y * 0.46)
	wall = Rect2(Vector2((view.x - size.x) * 0.5, view.y * 0.24), size)

	_add_label('Spectrum bars', Vector2(wall.position.x, view.y * 0.07), 26, Color('#ffffff'))
	_add_label('a saw sweep on its own bus, measured by AudioEffectSpectrumAnalyzer', Vector2(wall.position.x, view.y * 0.13), 15, Color('#999999'))

	levels.resize(BANDS)
	_add_wall()
	_add_axis()
	_add_marker()
	_add_player()

	readout = _add_label('', Vector2(wall.position.x, wall.end.y + 40.0), 18, Color('#f3d48d'))

	start_button = Button.new()
	start_button.text = 'Start sound'
	start_button.position = Vector2(wall.position.x, wall.end.y + 78.0)
	start_button.pressed.connect(_toggle_sound)
	add_child(start_button)

	print('spectrum ready, the yellow marker is the source, press Start sound to fill the bars')


func _process(delta: float) -> void:
	sweep = fmod(sweep + delta / SWEEP_SECONDS, 1.0)

	var hz: float = _source_hz()
	marker.position.x = wall.position.x + _band_at(hz) * wall.size.x - 13.0
	readout.text = 'source %d Hz   %s' % [int(hz), 'playing' if playback != null else 'silent, bars read zero']

	_read_bands(delta)

	if playback != null:
		_fill_buffer(hz)


func _read_bands(delta: float) -> void:
	for i in BANDS:
		var low: float = _band_hz(i)
		var high: float = _band_hz(i + 1)
		var magnitude: float = analyzer.get_magnitude_for_frequency_range(low, high, AudioEffectSpectrumAnalyzerInstance.MAGNITUDE_MAX).length()
		var target: float = clampf((70.0 + linear_to_db(magnitude)) / 70.0, 0.0, 1.0)

		levels[i] = maxf(target, levels[i] - delta * 1.1)

		var fill: ColorRect = fills[i]
		var height: float = maxf(levels[i] * wall.size.y, 3.0)
		fill.size.y = height
		fill.position.y = wall.end.y - height


func _fill_buffer(hz: float) -> void:
	for i in playback.get_frames_available():
		phase = fmod(phase + hz / MIX_RATE, 1.0)
		var sample: float = (phase * 2.0 - 1.0) * 0.16
		playback.push_frame(Vector2(sample, sample))


func _source_hz() -> float:
	var ping: float = 1.0 - absf(1.0 - sweep * 2.0)

	return SWEEP_LOW * pow(SWEEP_HIGH / SWEEP_LOW, ping)


func _band_hz(index: int) -> float:
	return LOW_HZ * pow(HIGH_HZ / LOW_HZ, float(index) / float(BANDS))


func _band_at(hz: float) -> float:
	return clampf(log(hz / LOW_HZ) / log(HIGH_HZ / LOW_HZ), 0.0, 1.0)


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

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


func _add_wall() -> void:
	var slot: float = wall.size.x / float(BANDS)
	var width: float = slot - 6.0

	for i in BANDS:
		var left: float = wall.position.x + slot * i

		var track: ColorRect = ColorRect.new()
		track.color = Color('#2a2f3d')
		track.position = Vector2(left, wall.position.y)
		track.size = Vector2(width, wall.size.y)
		add_child(track)

		var fill: ColorRect = ColorRect.new()
		fill.color = _band_color(float(i) / float(BANDS - 1))
		fill.position = Vector2(left, wall.end.y - 3.0)
		fill.size = Vector2(width, 3.0)
		add_child(fill)
		fills.append(fill)


func _band_color(at: float) -> Color:
	if at < 0.5:
		return Color('#8da5f3').lerp(Color('#8eef97'), at * 2.0)

	return Color('#8eef97').lerp(Color('#f3d48d'), at * 2.0 - 1.0)


func _add_axis() -> void:
	for hz in [60.0, 200.0, 800.0, 3000.0, 9000.0]:
		var text: String = '%d Hz' % int(hz) if hz < 1000.0 else '%d kHz' % int(hz / 1000.0)
		_add_label(text, Vector2(wall.position.x + _band_at(hz) * wall.size.x - 22.0, wall.end.y + 8.0), 13, Color('#999999'))


func _add_marker() -> void:
	marker = ColorRect.new()
	marker.color = Color('#f3d48d', 0.12)
	marker.size = Vector2(26.0, wall.size.y + 26.0)
	marker.position = Vector2(wall.position.x, wall.position.y - 13.0)
	marker.mouse_filter = Control.MOUSE_FILTER_IGNORE
	add_child(marker)

	var line: ColorRect = ColorRect.new()
	line.color = Color('#f3d48d')
	line.size = Vector2(2.0, marker.size.y)
	line.position = Vector2(12.0, 0.0)
	marker.add_child(line)


func _add_player() -> void:
	var bus: int = _find_bus('Spectrum')

	# a rerun in the same process finds the bus already built, and a second effect would leave a stale instance
	if AudioServer.get_bus_effect_count(bus) == 0:
		AudioServer.add_bus_effect(bus, AudioEffectSpectrumAnalyzer.new())

	analyzer = AudioServer.get_bus_effect_instance(bus, 0)

	var generator: AudioStreamGenerator = AudioStreamGenerator.new()
	generator.mix_rate = MIX_RATE
	generator.buffer_length = 0.1

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


func _find_bus(bus: 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, 'Master')

	return index


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

Plus d'exemples de Audio