Gerador de tons

Senoide, quadrada e dente de serra escritas amostra por amostra num AudioStreamGenerator. O Line2D desenha a mesma onda com que o buffer está sendo preenchido.

O editor acima está rodando este projeto. Mude uma linha e ele recarrega.

Classes do Godot usadas

AudioStreamGeneratorAudioStreamPlayerLine2D

O código

scripts/main.gd
extends Node

const MIX_RATE: float = 22050.0
const POINTS: int = 260
const SHAPES: Array[String] = ['sine', 'square', 'saw']

var player: AudioStreamPlayer
var playback: AudioStreamGeneratorPlayback
var scope: Line2D
var readout: Label
var start_button: Button
var hz: float = 220.0
var shape: int = 0
var phase: float = 0.0
var scroll: float = 0.0
var box: Rect2


func _ready() -> void:
	_add_background()

	var view: Vector2 = get_viewport().get_visible_rect().size
	var size: Vector2 = Vector2(view.x * 0.74, view.y * 0.44)
	box = Rect2(Vector2((view.x - size.x) * 0.5, view.y * 0.24), size)

	_add_label('Tone generator', Vector2(box.position.x, view.y * 0.08), 26, Color('#ffffff'))
	_add_label('sine, square and saw written sample by sample into an AudioStreamGenerator', Vector2(box.position.x, view.y * 0.14), 15, Color('#999999'))

	_add_scope()
	_add_player()

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

	_add_controls()
	_refresh()

	print('tone generator ready, press Start sound to hear the wave on screen')


func _process(delta: float) -> void:
	scroll = fmod(scroll + delta * 0.4, 1.0)
	_draw_scope()

	if playback != null:
		_fill_buffer()


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


func _wave(at: float) -> float:
	match shape:
		1:
			return 1.0 if at < 0.5 else -1.0
		2:
			return at * 2.0 - 1.0

	return sin(at * TAU)


func _draw_scope() -> void:
	var cycles: float = hz / 110.0
	var points: PackedVector2Array = PackedVector2Array()

	for i in POINTS:
		var at: float = float(i) / float(POINTS - 1)
		var sample: float = _wave(fmod(scroll + at * cycles, 1.0))
		points.append(Vector2(box.position.x + at * box.size.x, box.get_center().y - sample * box.size.y * 0.4))

	scope.points = points


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

	player.play()
	# get_stream_playback() only answers after play()
	playback = player.get_stream_playback()
	start_button.text = 'Stop sound'


func _set_hz(value: float) -> void:
	hz = value
	_refresh()


func _set_shape(index: int) -> void:
	shape = index
	_refresh()


func _refresh() -> void:
	readout.text = '%d Hz   %s wave' % [int(hz), SHAPES[shape]]


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
	add_child(player)


func _add_scope() -> void:
	var panel: ColorRect = ColorRect.new()
	panel.color = Color('#2a2f3d')
	panel.position = box.position
	panel.size = box.size
	add_child(panel)

	var axis: ColorRect = ColorRect.new()
	axis.color = Color('#8da5f3', 0.25)
	axis.position = Vector2(box.position.x, box.get_center().y - 1.0)
	axis.size = Vector2(box.size.x, 2.0)
	add_child(axis)

	scope = Line2D.new()
	scope.width = 3.0
	scope.default_color = Color('#8da5f3')
	scope.joint_mode = Line2D.LINE_JOINT_ROUND
	add_child(scope)


func _add_controls() -> void:
	var row: HBoxContainer = HBoxContainer.new()
	row.add_theme_constant_override('separation', 18)
	row.position = Vector2(box.position.x, box.end.y + 58.0)
	add_child(row)

	var slider: HSlider = HSlider.new()
	slider.min_value = 110.0
	slider.max_value = 880.0
	slider.value = hz
	slider.custom_minimum_size = Vector2(280, 28)
	slider.value_changed.connect(_set_hz)
	row.add_child(slider)

	var group: ButtonGroup = ButtonGroup.new()

	for i in SHAPES.size():
		var button: Button = Button.new()
		button.text = SHAPES[i]
		button.toggle_mode = true
		button.button_group = group
		button.button_pressed = i == shape
		button.add_theme_color_override('font_pressed_color', Color('#8da5f3'))
		button.add_theme_color_override('font_hover_pressed_color', Color('#8da5f3'))
		button.pressed.connect(_set_shape.bind(i))
		row.add_child(button)

	# the browser keeps audio silent until a real click, so the tone waits for this button
	start_button = Button.new()
	start_button.text = 'Start sound'
	start_button.pressed.connect(_toggle_sound)
	row.add_child(start_button)


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

Mais exemplos de Áudio