Use static typing in all demos (#1063)

This leads to code that is easier to understand and runs
faster thanks to GDScript's typed instructions.

The untyped declaration warning is now enabled on all projects
where type hints were added. All projects currently run without
any untyped declration warnings.

Dodge the Creeps and Squash the Creeps demos intentionally don't
use type hints to match the documentation, where type hints haven't
been adopted yet (given its beginner focus).
This commit is contained in:
Hugo Locurcio
2024-06-01 12:12:18 +02:00
committed by GitHub
parent 8e9c180278
commit bac1e69164
498 changed files with 5218 additions and 4776 deletions

View File

@@ -1,36 +1,29 @@
extends Panel
const BPM = 116
const BARS = 4
var playing = false
const COMPENSATE_FRAMES = 2
const COMPENSATE_HZ = 60.0
enum SyncSource {
SYSTEM_CLOCK,
SOUND_CLOCK,
}
var sync_source = SyncSource.SYSTEM_CLOCK
const BPM = 116
const BARS = 4
const COMPENSATE_FRAMES = 2
const COMPENSATE_HZ = 60.0
var playing := false
var sync_source := SyncSource.SYSTEM_CLOCK
# Used by system clock.
var time_begin
var time_delay
var time_begin: float
var time_delay: float
func strsec(secs):
var s = str(secs)
if (secs < 10):
s = "0" + s
return s
func _process(_delta):
func _process(_delta: float) -> void:
if not playing or not $Player.playing:
return
var time = 0.0
var time := 0.0
if sync_source == SyncSource.SYSTEM_CLOCK:
# Obtain from ticks.
time = (Time.get_ticks_usec() - time_begin) / 1000000.0
@@ -39,14 +32,14 @@ func _process(_delta):
elif sync_source == SyncSource.SOUND_CLOCK:
time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix() - AudioServer.get_output_latency() + (1 / COMPENSATE_HZ) * COMPENSATE_FRAMES
var beat = int(time * BPM / 60.0)
var seconds = int(time)
var seconds_total = int($Player.stream.get_length())
var beat := int(time * BPM / 60.0)
var seconds := int(time)
var seconds_total := int($Player.stream.get_length())
@warning_ignore("integer_division")
$Label.text = str("BEAT: ", beat % BARS + 1, "/", BARS, " TIME: ", seconds / 60, ":", strsec(seconds % 60), " / ", seconds_total / 60, ":", strsec(seconds_total % 60))
$Label.text = str("BEAT: ", beat % BARS + 1, "/", BARS, " TIME: ", seconds / 60, ":", str(seconds % 60).pad_zeros(2), " / ", seconds_total / 60, ":", str(seconds_total % 60).pad_zeros(2))
func _on_PlaySystem_pressed():
func _on_PlaySystem_pressed() -> void:
sync_source = SyncSource.SYSTEM_CLOCK
time_begin = Time.get_ticks_usec()
time_delay = AudioServer.get_time_to_next_mix() + AudioServer.get_output_latency()
@@ -54,7 +47,7 @@ func _on_PlaySystem_pressed():
$Player.play()
func _on_PlaySound_pressed():
func _on_PlaySound_pressed() -> void:
sync_source = SyncSource.SOUND_CLOCK
playing = true
$Player.play()

View File

@@ -19,32 +19,62 @@ size_flags_vertical = 4
script = ExtResource("7")
[node name="Label" type="Label" parent="."]
layout_mode = 0
offset_left = 106.895
offset_top = 427.158
offset_right = 914.895
offset_bottom = 488.158
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -404.0
offset_top = 55.0
offset_right = 404.0
offset_bottom = 121.0
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0.654902, 1, 0.67451, 1)
theme_override_colors/font_shadow_color = Color(0, 0, 0, 1)
theme_override_constants/shadow_offset_x = 4
theme_override_constants/shadow_offset_y = 4
theme_override_fonts/font = ExtResource("2_wyi3x")
theme_override_font_sizes/font_size = 48
text = "Press one of the buttons."
horizontal_alignment = 1
vertical_alignment = 1
[node name="Player" type="AudioStreamPlayer" parent="."]
stream = ExtResource("3")
volume_db = -6.0
[node name="PlaySystem" type="TextureButton" parent="."]
layout_mode = 0
offset_left = 214.737
offset_top = 187.368
offset_right = 342.737
offset_bottom = 315.368
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -288.0
offset_top = -136.0
offset_right = -160.0
offset_bottom = -7.99997
grow_horizontal = 2
grow_vertical = 2
texture_normal = ExtResource("5")
texture_pressed = ExtResource("5")
texture_hover = ExtResource("1")
[node name="PlaySound" type="TextureButton" parent="."]
layout_mode = 0
offset_left = 622.105
offset_top = 183.158
offset_right = 750.105
offset_bottom = 311.158
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = 160.0
offset_top = -136.0
offset_right = 288.0
offset_bottom = -8.0
grow_horizontal = 2
grow_vertical = 2
texture_normal = ExtResource("2")
texture_pressed = ExtResource("2")
texture_hover = ExtResource("4")

View File

@@ -17,6 +17,10 @@ run/main_scene="res://bpm_sync.tscn"
config/features=PackedStringArray("4.2")
config/icon="res://icon.webp"
[debug]
gdscript/warnings/untyped_declaration=1
[display]
window/stretch/mode="canvas_items"

View File

@@ -1,22 +1,21 @@
extends Control
@onready var item_list = get_node(^"ItemList")
@onready var item_list: ItemList = $ItemList
func _ready():
func _ready() -> void:
for item in AudioServer.get_output_device_list():
item_list.add_item(item)
var device = AudioServer.get_output_device()
for i in range(item_list.get_item_count()):
var device := AudioServer.get_output_device()
for i in item_list.get_item_count():
if device == item_list.get_item_text(i):
item_list.select(i)
break
func _process(_delta):
var speaker_mode_text = "Stereo"
var speaker_mode = AudioServer.get_speaker_mode()
func _process(_delta: float) -> void:
var speaker_mode_text := "Stereo"
var speaker_mode := AudioServer.get_speaker_mode()
if speaker_mode == AudioServer.SPEAKER_SURROUND_31:
speaker_mode_text = "Surround 3.1"
@@ -29,13 +28,13 @@ func _process(_delta):
$DeviceInfo.text += "Speaker Mode: " + speaker_mode_text
func _on_Button_button_down():
func _on_Button_button_down() -> void:
for item in item_list.get_selected_items():
var device = item_list.get_item_text(item)
var device := item_list.get_item_text(item)
AudioServer.set_output_device(device)
func _on_Play_Audio_button_down():
func _on_Play_Audio_button_down() -> void:
if $AudioStreamPlayer.playing:
$AudioStreamPlayer.stop()
$PlayAudio.text = "Play Audio"

View File

@@ -27,10 +27,11 @@ offset_bottom = 228.0
[node name="DeviceInfo" type="Label" parent="."]
layout_mode = 0
offset_left = 321.0
offset_left = -64.0
offset_top = 248.0
offset_right = 660.0
offset_right = 1046.0
offset_bottom = 284.0
horizontal_alignment = 1
[node name="SetDevice" type="Button" parent="."]
layout_mode = 0

View File

@@ -18,6 +18,10 @@ config/features=PackedStringArray("4.2")
run/low_processor_mode=true
config/icon="res://icon.webp"
[debug]
gdscript/warnings/untyped_declaration=1
[display]
window/stretch/mode="canvas_items"

View File

@@ -1,26 +1,28 @@
extends Node
var sample_hz = 22050.0 # Keep the number of samples to mix low, GDScript is not super fast.
var pulse_hz = 440.0
var phase = 0.0
# Keep the number of samples per second to mix low, as GDScript is not super fast.
var sample_hz := 22050.0
var pulse_hz := 440.0
var phase := 0.0
var playback: AudioStreamPlayback = null # Actual playback stream, assigned in _ready().
# Actual playback stream, assigned in _ready().
var playback: AudioStreamPlayback
func _fill_buffer():
var increment = pulse_hz / sample_hz
func _fill_buffer() -> void:
var increment := pulse_hz / sample_hz
var to_fill = playback.get_frames_available()
var to_fill: int = playback.get_frames_available()
while to_fill > 0:
playback.push_frame(Vector2.ONE * sin(phase * TAU)) # Audio frames are stereo.
phase = fmod(phase + increment, 1.0)
to_fill -= 1
func _process(_delta):
func _process(_delta: float) -> void:
_fill_buffer()
func _ready():
func _ready() -> void:
# Setting mix rate is only possible before play().
$Player.stream.mix_rate = sample_hz
$Player.play()
@@ -30,12 +32,12 @@ func _ready():
_fill_buffer()
func _on_frequency_h_slider_value_changed(value):
func _on_frequency_h_slider_value_changed(value: float) -> void:
%FrequencyLabel.text = "%d Hz" % value
pulse_hz = value
func _on_volume_h_slider_value_changed(value):
func _on_volume_h_slider_value_changed(value: float) -> void:
# Use `linear_to_db()` to get a volume slider that matches perceptual human hearing.
%VolumeLabel.text = "%.2f dB" % linear_to_db(value)
$Player.volume_db = linear_to_db(value)

View File

@@ -20,6 +20,10 @@ config/features=PackedStringArray("4.2")
run/low_processor_mode=true
config/icon="res://icon.webp"
[debug]
gdscript/warnings/untyped_declaration=1
[display]
window/stretch/mode="canvas_items"

View File

@@ -1,19 +1,19 @@
extends Control
var effect # See AudioEffect in docs
var recording # See AudioStreamSample in docs
var effect: AudioEffect
var recording: AudioStreamWAV
var stereo := true
var mix_rate := 44100 # This is the default mix rate on recordings
var format := 1 # This equals to the default format: 16 bits
var mix_rate := 44100 # This is the default mix rate on recordings.
var format := AudioStreamWAV.FORMAT_16_BITS # This is the default format on recordings.
func _ready():
var idx = AudioServer.get_bus_index("Record")
func _ready() -> void:
var idx := AudioServer.get_bus_index("Record")
effect = AudioServer.get_bus_effect(idx, 0)
func _on_RecordButton_pressed():
func _on_record_button_pressed() -> void:
if effect.is_recording_active():
recording = effect.get_recording()
$PlayButton.disabled = false
@@ -32,18 +32,18 @@ func _on_RecordButton_pressed():
$Status.text = "Status: Recording..."
func _on_PlayButton_pressed():
func _on_play_button_pressed() -> void:
print_rich("\n[b]Playing recording:[/b] %s" % recording)
print_rich("[b]Format:[/b] %s" % ("8-bit uncompressed" if recording.format == 0 else "16-bit uncompressed" if recording.format == 1 else "IMA ADPCM compressed"))
print_rich("[b]Mix rate:[/b] %s Hz" % recording.mix_rate)
print_rich("[b]Stereo:[/b] %s" % ("Yes" if recording.stereo else "No"))
var data = recording.get_data()
var data := recording.get_data()
print_rich("[b]Size:[/b] %s bytes" % data.size())
$AudioStreamPlayer.stream = recording
$AudioStreamPlayer.play()
func _on_Play_Music_pressed():
func _on_play_music_pressed() -> void:
if $AudioStreamPlayer2.playing:
$AudioStreamPlayer2.stop()
$PlayMusic.text = "Play Music"
@@ -52,13 +52,13 @@ func _on_Play_Music_pressed():
$PlayMusic.text = "Stop Music"
func _on_SaveButton_pressed():
var save_path = $SaveButton/Filename.text
func _on_save_button_pressed() -> void:
var save_path: String = $SaveButton/Filename.text
recording.save_to_wav(save_path)
$Status.text = "Status: Saved WAV file to: %s\n(%s)" % [save_path, ProjectSettings.globalize_path(save_path)]
func _on_MixRateOptionButton_item_selected(index: int) -> void:
func _on_mix_rate_option_button_item_selected(index: int) -> void:
match index:
0:
mix_rate = 11025
@@ -76,7 +76,7 @@ func _on_MixRateOptionButton_item_selected(index: int) -> void:
recording.set_mix_rate(mix_rate)
func _on_FormatOptionButton_item_selected(index: int) -> void:
func _on_format_option_button_item_selected(index: int) -> void:
match index:
0:
format = AudioStreamWAV.FORMAT_8_BITS
@@ -88,11 +88,12 @@ func _on_FormatOptionButton_item_selected(index: int) -> void:
recording.set_format(format)
func _on_StereoCheckButton_toggled(button_pressed: bool) -> void:
func _on_stereo_check_button_toggled(button_pressed: bool) -> void:
stereo = button_pressed
if recording != null:
recording.set_stereo(stereo)
func _on_open_user_folder_button_pressed():
func _on_open_user_folder_button_pressed() -> void:
OS.shell_open(ProjectSettings.globalize_path("user://"))

View File

@@ -159,11 +159,11 @@ offset_right = 372.0
offset_bottom = 374.0
text = "Open User Folder"
[connection signal="pressed" from="RecordButton" to="." method="_on_RecordButton_pressed"]
[connection signal="pressed" from="PlayButton" to="." method="_on_PlayButton_pressed"]
[connection signal="pressed" from="PlayMusic" to="." method="_on_Play_Music_pressed"]
[connection signal="item_selected" from="FormatOptionButton" to="." method="_on_FormatOptionButton_item_selected"]
[connection signal="item_selected" from="MixRateOptionButton" to="." method="_on_MixRateOptionButton_item_selected"]
[connection signal="toggled" from="StereoCheckButton" to="." method="_on_StereoCheckButton_toggled"]
[connection signal="pressed" from="SaveButton" to="." method="_on_SaveButton_pressed"]
[connection signal="pressed" from="RecordButton" to="." method="_on_record_button_pressed"]
[connection signal="pressed" from="PlayButton" to="." method="_on_play_button_pressed"]
[connection signal="pressed" from="PlayMusic" to="." method="_on_play_music_pressed"]
[connection signal="item_selected" from="FormatOptionButton" to="." method="_on_format_option_button_item_selected"]
[connection signal="item_selected" from="MixRateOptionButton" to="." method="_on_mix_rate_option_button_item_selected"]
[connection signal="toggled" from="StereoCheckButton" to="." method="_on_stereo_check_button_toggled"]
[connection signal="pressed" from="SaveButton" to="." method="_on_save_button_pressed"]
[connection signal="pressed" from="OpenUserFolderButton" to="." method="_on_open_user_folder_button_pressed"]

View File

@@ -24,6 +24,10 @@ config/icon="res://icon.webp"
driver/enable_input=true
enable_audio_input=true
[debug]
gdscript/warnings/untyped_declaration=1
[display]
window/size/viewport_width=640

View File

@@ -9,37 +9,38 @@ extends Control
const START_KEY = 21
const END_KEY = 108
const WhiteKeyScene = preload("res://piano_keys/white_piano_key.tscn")
const BlackKeyScene = preload("res://piano_keys/black_piano_key.tscn")
const WhiteKeyScene := preload("res://piano_keys/white_piano_key.tscn")
const BlackKeyScene := preload("res://piano_keys/black_piano_key.tscn")
var piano_key_dict := Dictionary()
@onready var white_keys = $WhiteKeys
@onready var black_keys = $BlackKeys
@onready var white_keys: HBoxContainer = $WhiteKeys
@onready var black_keys: HBoxContainer = $BlackKeys
func _ready():
# Sanity checks.
if _is_note_index_sharp(_pitch_index_to_note_index(START_KEY)):
printerr("The start key can't be a sharp note (limitation of this piano-generating algorithm). Try 21.")
return
func _ready() -> void:
assert(not _is_note_index_sharp(_pitch_index_to_note_index(START_KEY)), "The start key can't be a sharp note (limitation of this piano-generating algorithm). Try 21.")
for i in range(START_KEY, END_KEY + 1):
piano_key_dict[i] = _create_piano_key(i)
if white_keys.get_child_count() != black_keys.get_child_count():
_add_placeholder_key(black_keys)
OS.open_midi_inputs()
if len(OS.get_connected_midi_inputs()) > 0:
if not OS.get_connected_midi_inputs().is_empty():
print(OS.get_connected_midi_inputs())
func _input(input_event):
if not (input_event is InputEventMIDI):
func _input(input_event: InputEvent) -> void:
if not input_event is InputEventMIDI:
return
var midi_event: InputEventMIDI = input_event
if midi_event.pitch < START_KEY or midi_event.pitch > END_KEY:
# The given pitch isn't on the on-screen keyboard, so return.
return
_print_midi_info(midi_event)
var key: PianoKey = piano_key_dict[midi_event.pitch]
if midi_event.message == MIDI_MESSAGE_NOTE_ON:
@@ -48,16 +49,16 @@ func _input(input_event):
key.deactivate()
func _add_placeholder_key(container):
var placeholder = Control.new()
func _add_placeholder_key(container: HBoxContainer) -> void:
var placeholder := Control.new()
placeholder.size_flags_horizontal = SIZE_EXPAND_FILL
placeholder.mouse_filter = Control.MOUSE_FILTER_IGNORE
placeholder.name = &"Placeholder"
container.add_child(placeholder)
func _create_piano_key(pitch_index):
var note_index = _pitch_index_to_note_index(pitch_index)
func _create_piano_key(pitch_index: int) -> PianoKey:
var note_index := _pitch_index_to_note_index(pitch_index)
var piano_key: PianoKey
if _is_note_index_sharp(note_index):
piano_key = BlackKeyScene.instantiate()
@@ -71,22 +72,22 @@ func _create_piano_key(pitch_index):
return piano_key
func _is_note_index_lacking_sharp(note_index: int):
func _is_note_index_lacking_sharp(note_index: int) -> bool:
# B and E, because no B# or E#
return note_index in [2, 7]
func _is_note_index_sharp(note_index: int):
func _is_note_index_sharp(note_index: int) -> bool:
# A#, C#, D#, F#, and G#
return note_index in [1, 4, 6, 9, 11]
func _pitch_index_to_note_index(pitch: int):
func _pitch_index_to_note_index(pitch: int) -> int:
pitch += 3
return pitch % 12
func _print_midi_info(midi_event: InputEventMIDI):
func _print_midi_info(midi_event: InputEventMIDI) -> void:
print(midi_event)
print("Channel: " + str(midi_event.channel))
print("Message: " + str(midi_event.message))

View File

@@ -7,14 +7,13 @@ var pitch_scale: float
@onready var start_color: Color = key.color
@onready var color_timer: Timer = $ColorTimer
func setup(pitch_index: int):
func setup(pitch_index: int) -> void:
name = "PianoKey" + str(pitch_index)
var exponent := (pitch_index - 69.0) / 12.0
pitch_scale = pow(2, exponent)
func activate():
func activate() -> void:
key.color = (Color.YELLOW + start_color) / 2
var audio := AudioStreamPlayer.new()
add_child(audio)
@@ -26,5 +25,5 @@ func activate():
audio.queue_free()
func deactivate():
func deactivate() -> void:
key.color = start_color

View File

@@ -1,8 +1,8 @@
extends ColorRect
@onready var parent = get_parent()
@onready var parent: PianoKey = get_parent()
# Yes, this script exists just for this one method.
func _gui_input(input_event):
func _gui_input(input_event: InputEvent) -> void:
if input_event is InputEventMouseButton and input_event.pressed:
parent.activate()

View File

@@ -16,6 +16,10 @@ run/main_scene="res://piano.tscn"
config/features=PackedStringArray("4.2")
config/icon="res://icon.webp"
[debug]
gdscript/warnings/untyped_declaration=1
[display]
window/size/viewport_width=1280

View File

@@ -1,6 +1,5 @@
extends Node2D
const VU_COUNT = 16
const FREQ_MAX = 11050.0
@@ -10,14 +9,14 @@ const HEIGHT_SCALE = 8.0
const MIN_DB = 60
const ANIMATION_SPEED = 0.1
var spectrum
var min_values = []
var max_values = []
var spectrum: AudioEffectSpectrumAnalyzerInstance
var min_values: Array[float] = []
var max_values: Array[float] = []
func _draw():
var w = WIDTH / VU_COUNT
for i in range(VU_COUNT):
func _draw() -> void:
@warning_ignore("integer_division")
var w := WIDTH / VU_COUNT
for i in VU_COUNT:
var min_height = min_values[i]
var max_height = max_values[i]
var height = lerp(min_height, max_height, ANIMATION_SPEED)
@@ -48,32 +47,32 @@ func _draw():
)
func _process(_delta):
var data = []
var prev_hz = 0
func _process(_delta: float) -> void:
var data: Array[float] = []
var prev_hz := 0.0
for i in range(1, VU_COUNT + 1):
var hz = i * FREQ_MAX / VU_COUNT
var magnitude = spectrum.get_magnitude_for_frequency_range(prev_hz, hz).length()
var energy = clampf((MIN_DB + linear_to_db(magnitude)) / MIN_DB, 0, 1)
var height = energy * HEIGHT * HEIGHT_SCALE
var hz := i * FREQ_MAX / VU_COUNT
var magnitude := spectrum.get_magnitude_for_frequency_range(prev_hz, hz).length()
var energy := clampf((MIN_DB + linear_to_db(magnitude)) / MIN_DB, 0, 1)
var height := energy * HEIGHT * HEIGHT_SCALE
data.append(height)
prev_hz = hz
for i in range(VU_COUNT):
for i in VU_COUNT:
if data[i] > max_values[i]:
max_values[i] = data[i]
else:
max_values[i] = lerp(max_values[i], data[i], ANIMATION_SPEED)
max_values[i] = lerpf(max_values[i], data[i], ANIMATION_SPEED)
if data[i] <= 0.0:
min_values[i] = lerp(min_values[i], 0.0, ANIMATION_SPEED)
min_values[i] = lerpf(min_values[i], 0.0, ANIMATION_SPEED)
# Sound plays back continuously, so the graph needs to be updated every frame.
queue_redraw()
func _ready():
func _ready() -> void:
spectrum = AudioServer.get_bus_effect_instance(0, 0)
min_values.resize(VU_COUNT)
max_values.resize(VU_COUNT)

View File

@@ -52,6 +52,7 @@ offset_top = 56.0
offset_right = 704.0
offset_bottom = 296.0
columns = 2
select_mode = 1
[node name="Utterance" type="TextEdit" parent="."]
layout_mode = 0
@@ -187,7 +188,6 @@ offset_bottom = 40.0
layout_mode = 0
offset_right = 128.0
offset_bottom = 32.0
theme_override_font_sizes/font_size = 16
text = "Speaking..."
[node name="Log" type="TextEdit" parent="."]
@@ -232,13 +232,12 @@ text = "Demo"
[connection signal="text_changed" from="LineEditFilterLang" to="." method="_on_LineEditFilterName_text_changed"]
[connection signal="text_changed" from="LineEditFilterName" to="." method="_on_LineEditFilterName_text_changed"]
[connection signal="item_activated" from="Tree" to="." method="_on_ItemList_item_activated"]
[connection signal="pressed" from="ButtonSpeak" to="." method="_on_ButtonSpeak_pressed"]
[connection signal="pressed" from="ButtonIntSpeak" to="." method="_on_ButtonIntSpeak_pressed"]
[connection signal="pressed" from="ButtonStop" to="." method="_on_ButtonStop_pressed"]
[connection signal="pressed" from="ButtonPause" to="." method="_on_ButtonPause_pressed"]
[connection signal="pressed" from="ButtonSpeak" to="." method="_on_button_speak_pressed"]
[connection signal="pressed" from="ButtonIntSpeak" to="." method="_on_button_int_speak_pressed"]
[connection signal="pressed" from="ButtonStop" to="." method="_on_button_stop_pressed"]
[connection signal="pressed" from="ButtonPause" to="." method="_on_button_pause_pressed"]
[connection signal="value_changed" from="HSliderRate" to="." method="_on_HSliderRate_value_changed"]
[connection signal="value_changed" from="HSliderPitch" to="." method="_on_HSliderPitch_value_changed"]
[connection signal="value_changed" from="HSliderVolume" to="." method="_on_HSliderVolume_value_changed"]
[connection signal="pressed" from="Log/ButtonClearLog" to="." method="_on_ButtonClearLog_pressed"]
[connection signal="pressed" from="Log/ButtonClearLog" to="." method="_on_button_clear_log_pressed"]
[connection signal="pressed" from="ButtonDemo" to="." method="_on_Button_pressed"]

View File

@@ -21,6 +21,10 @@ config/icon="res://icon.webp"
general/text_to_speech=true
[debug]
gdscript/warnings/untyped_declaration=1
[display]
window/stretch/mode="canvas_items"

View File

@@ -1,127 +1,150 @@
extends Control
var id = 0 #utterance id
var ut_map = {}
var vs
## The utterance ID to use for text to speech.
var id := 0
func _ready():
# get voice data
var ut_map := {}
var vs: Array[Dictionary]
func _ready() -> void:
# Get voice data.
vs = DisplayServer.tts_get_voices()
var root = $Tree.create_item()
var root: TreeItem = $Tree.create_item()
$Tree.set_hide_root(true)
$Tree.set_column_title(0, "Name")
$Tree.set_column_title(1, "Language")
$Tree.set_column_titles_visible(true)
for v in vs:
var child = $Tree.create_item(root)
var child: TreeItem = $Tree.create_item(root)
child.set_text(0, v["name"])
child.set_metadata(0, v["id"])
child.set_text(1, v["language"])
$Log.text += "%d voices available\n" % [vs.size()]
$Log.text += "%d voices available.\n" % [vs.size()]
$Log.text += "=======\n"
# add callbacks
DisplayServer.tts_set_utterance_callback(DisplayServer.TTS_UTTERANCE_STARTED, Callable(self, "_on_utterance_start"))
DisplayServer.tts_set_utterance_callback(DisplayServer.TTS_UTTERANCE_ENDED, Callable(self, "_on_utterance_end"))
DisplayServer.tts_set_utterance_callback(DisplayServer.TTS_UTTERANCE_CANCELED, Callable(self, "_on_utterance_error"))
DisplayServer.tts_set_utterance_callback(DisplayServer.TTS_UTTERANCE_BOUNDARY, Callable(self, "_on_utterance_boundary"))
set_process(true)
# Ensure the first voice added to the list is preselected.
$Tree.get_root().get_child(0).select(0)
func _process(_delta):
# Add callbacks.
DisplayServer.tts_set_utterance_callback(DisplayServer.TTS_UTTERANCE_STARTED, _on_utterance_start)
DisplayServer.tts_set_utterance_callback(DisplayServer.TTS_UTTERANCE_ENDED, _on_utterance_end)
DisplayServer.tts_set_utterance_callback(DisplayServer.TTS_UTTERANCE_CANCELED, _on_utterance_error)
DisplayServer.tts_set_utterance_callback(DisplayServer.TTS_UTTERANCE_BOUNDARY, _on_utterance_boundary)
func _process(_delta: float) -> void:
$ButtonPause.button_pressed = DisplayServer.tts_is_paused()
if DisplayServer.tts_is_speaking():
$ColorRect.color = Color(1, 0, 0)
$ColorRect.color = Color(0.9, 0.3, 0.1)
else:
$ColorRect.color = Color(1, 1, 1)
func _on_utterance_boundary(pos, ut_id):
func _on_utterance_boundary(pos: int, ut_id: int) -> void:
$RichTextLabel.text = "[bgcolor=yellow][color=black]" + ut_map[ut_id].substr(0, pos) + "[/color][/bgcolor]" + ut_map[ut_id].substr(pos, -1)
func _on_utterance_start(ut_id):
$Log.text += "utterance %d started\n" % [ut_id]
func _on_utterance_end(ut_id):
func _on_utterance_start(ut_id: int) -> void:
$Log.text += "Utterance %d started.\n" % [ut_id]
func _on_utterance_end(ut_id: int) -> void:
$RichTextLabel.text = "[bgcolor=yellow][color=black]" + ut_map[ut_id] + "[/color][/bgcolor]"
$Log.text += "utterance %d ended\n" % [ut_id]
$Log.text += "Utterance %d ended.\n" % [ut_id]
ut_map.erase(ut_id)
func _on_utterance_error(ut_id):
func _on_utterance_error(ut_id: int) -> void:
$RichTextLabel.text = ""
$Log.text += "utterance %d canceled/failed\n" % [ut_id]
$Log.text += "Utterance %d canceled/failed.\n" % [ut_id]
ut_map.erase(ut_id)
func _on_ButtonStop_pressed():
func _on_button_stop_pressed() -> void:
DisplayServer.tts_stop()
func _on_ButtonPause_pressed():
func _on_button_pause_pressed() -> void:
if $ButtonPause.pressed:
DisplayServer.tts_pause()
else:
DisplayServer.tts_resume()
func _on_ButtonSpeak_pressed():
func _on_button_speak_pressed() -> void:
if $Tree.get_selected():
$Log.text += "utterance %d queried\n" % [id]
$Log.text += "Utterance %d queried.\n" % [id]
ut_map[id] = $Utterance.text
DisplayServer.tts_speak($Utterance.text, $Tree.get_selected().get_metadata(0), $HSliderVolume.value, $HSliderPitch.value, $HSliderRate.value, id, false)
id += 1
else:
OS.alert("No voice selected.\nSelect a voice in the list, then try using Speak again.")
func _on_ButtonIntSpeak_pressed():
func _on_button_int_speak_pressed() -> void:
if $Tree.get_selected():
$Log.text += "utterance %d interrupt\n" % [id]
$Log.text += "Utterance %d interrupted.\n" % [id]
ut_map[id] = $Utterance.text
DisplayServer.tts_speak($Utterance.text, $Tree.get_selected().get_metadata(0), $HSliderVolume.value, $HSliderPitch.value, $HSliderRate.value, id, true)
id += 1
else:
OS.alert("No voice selected.\nSelect a voice in the list, then try using Interrupt again.")
func _on_ButtonClearLog_pressed():
func _on_button_clear_log_pressed() -> void:
$Log.text = ""
func _on_HSliderRate_value_changed(value):
$HSliderRate/Value.text = "%.2fx" % [value]
func _on_HSliderPitch_value_changed(value):
$HSliderPitch/Value.text = "%.2fx" % [value]
func _on_HSliderRate_value_changed(value: float) -> void:
$HSliderRate/Value.text = "%.2fx" % value
func _on_HSliderVolume_value_changed(value):
$HSliderVolume/Value.text = "%d%%" % [value]
func _on_Button_pressed():
var vc
func _on_HSliderPitch_value_changed(value: float) -> void:
$HSliderPitch/Value.text = "%.2fx" % value
func _on_HSliderVolume_value_changed(value: float) -> void:
$HSliderVolume/Value.text = "%d%%" % value
func _on_Button_pressed() -> void:
var vc: PackedStringArray
#demo - en
vc = DisplayServer.tts_get_voices_for_language("en")
if !vc.is_empty():
if not vc.is_empty():
ut_map[id] = "Beware the Jabberwock, my son!"
ut_map[id + 1] = "The jaws that bite, the claws that catch!"
DisplayServer.tts_speak("Beware the Jabberwock, my son!", vc[0], 50, 1, 1, id)
DisplayServer.tts_speak("The jaws that bite, the claws that catch!", vc[0], 50, 1, 1, id + 1)
DisplayServer.tts_speak("Beware the Jabberwock, my son!", vc[0], $HSliderVolume.value, $HSliderPitch.value, $HSliderRate.value, id)
DisplayServer.tts_speak("The jaws that bite, the claws that catch!", vc[0], $HSliderVolume.value, $HSliderPitch.value, $HSliderRate.value, id + 1)
id += 2
#demo - es
vc = DisplayServer.tts_get_voices_for_language("es")
if !vc.is_empty():
if not vc.is_empty():
ut_map[id] = "¡Cuidado, hijo, con el Fablistanón!"
ut_map[id + 1] = "¡Con sus dientes y garras, muerde, apresa!"
DisplayServer.tts_speak("¡Cuidado, hijo, con el Fablistanón!", vc[0], 50, 1, 1, id)
DisplayServer.tts_speak("¡Con sus dientes y garras, muerde, apresa!", vc[0], 50, 1, 1, id + 1)
DisplayServer.tts_speak("¡Cuidado, hijo, con el Fablistanón!", vc[0], $HSliderVolume.value, $HSliderPitch.value, $HSliderRate.value, id)
DisplayServer.tts_speak("¡Con sus dientes y garras, muerde, apresa!", vc[0], $HSliderVolume.value, $HSliderPitch.value, $HSliderRate.value, id + 1)
id += 2
#demo - ru
vc = DisplayServer.tts_get_voices_for_language("ru")
if !vc.is_empty():
if not vc.is_empty():
ut_map[id] = "О, бойся Бармаглота, сын!"
ut_map[id + 1] = "Он так свирлеп и дик!"
DisplayServer.tts_speak("О, бойся Бармаглота, сын!", vc[0], 50, 1, 1, id)
DisplayServer.tts_speak("Он так свирлеп и дик!", vc[0], 50, 1, 1, id + 1)
DisplayServer.tts_speak("О, бойся Бармаглота, сын!", vc[0], $HSliderVolume.value, $HSliderPitch.value, $HSliderRate.value, id)
DisplayServer.tts_speak("Он так свирлеп и дик!", vc[0], $HSliderVolume.value, $HSliderPitch.value, $HSliderRate.value, id + 1)
id += 2
func _on_LineEditFilterName_text_changed(_new_text):
func _on_LineEditFilterName_text_changed(_new_text: String) -> void:
$Tree.clear()
var root = $Tree.create_item()
var root: TreeItem = $Tree.create_item()
for v in vs:
if ($LineEditFilterName.text.is_empty() || $LineEditFilterName.text.to_lower() in v["name"].to_lower()) && ($LineEditFilterLang.text.is_empty() || $LineEditFilterLang.text.to_lower() in v["language"].to_lower()):
var child = $Tree.create_item(root)
if (
$LineEditFilterName.text.is_empty() or $LineEditFilterName.text.to_lower() in v["name"].to_lower()
) and (
$LineEditFilterLang.text.is_empty() or $LineEditFilterLang.text.to_lower() in v["language"].to_lower()
):
var child: TreeItem = $Tree.create_item(root)
child.set_text(0, v["name"])
child.set_metadata(0, v["id"])
child.set_text(1, v["language"])