Generador de tonos
Seno, cuadrada y sierra escritas muestra a muestra en un AudioStreamGenerator. El Line2D dibuja la misma onda con la que se está llenando el buffer.
El editor de arriba está ejecutando este proyecto. Cambia una línea y se recarga.
Clases de Godot usadas
AudioStreamGeneratorAudioStreamPlayerLine2D
El 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