This commit is contained in:
2026-07-01 20:31:41 +02:00
commit 764d4b15f0
104 changed files with 3665 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
@tool
extends Control
signal value_changed(value)
signal value_submitted()
@export var value: float = 0.0 :
set = set_value
@export var min_value: float = 0.0
@export var max_value: float = 1.0
var _line_edit: LineEdit
var _stylebox_normal: StyleBox
var _stylebox_hover: StyleBox
var _stylebox_editing: StyleBox
var _stylebox_value: StyleBox
var _line_edit_just_closed := false
var _mouse_hovering := false
var _is_editing := false
var _is_cancel := false
var _drag_start_position: Vector2
var _drag_cancelled := true
var _drag_dist := 0.0
var _drag_start_factor: float
func _init():
mouse_default_cursor_shape = Control.CURSOR_HSIZE
clip_contents = true
focus_mode = Control.FOCUS_ALL
var style := StyleBoxEmpty.new()
style.content_margin_left = 8
style.content_margin_right = 8
_line_edit = LineEdit.new()
_line_edit.set_as_top_level(true)
_line_edit.visible = false
_line_edit.add_theme_stylebox_override("normal", style)
_line_edit.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
var _ret: int
_ret = _line_edit.focus_exited.connect(self._on_line_edit_focus_exited)
_ret = _line_edit.text_submitted.connect(self._on_line_edit_text_entered)
_ret = _line_edit.visibility_changed.connect(self._on_line_edit_visibility_changed)
_ret = _line_edit.gui_input.connect(self._on_line_edit_gui_input)
add_child(_line_edit)
func _draw() -> void:
var font := get_theme_font("font", "LineEdit")
var font_size := get_theme_font_size("font_size", "LineEdit")
var color := get_theme_color("highlighted_font_color" if _mouse_hovering else "font_color", "Editor")
var number_string := "%.3f" % value
var number_size := font.get_string_size(number_string)
var pos := Vector2(
(size.x - number_size.x) / 2,
(size.y - number_size.y) / 2 + font.get_ascent()
)
var stylebox := _stylebox_editing if _is_editing else _stylebox_hover if _mouse_hovering else _stylebox_normal
if _line_edit.visible:
draw_style_box(stylebox, Rect2(Vector2.ZERO, size))
else:
var value_width := size.x * ((value - min_value) / (max_value - min_value))
draw_style_box(stylebox, Rect2(value_width, 0, size.x - value_width, size.y))
draw_style_box(_stylebox_value, Rect2(0, 0, value_width, size.y))
draw_string(font, pos, number_string, HORIZONTAL_ALIGNMENT_CENTER, -1, font_size, color)
func _get_minimum_size() -> Vector2:
var ms := _stylebox_normal.get_minimum_size() if _stylebox_normal else Vector2.ZERO
ms.y += get_theme_font("font", "LineEdit").get_height()
return ms
func _gui_input(event: InputEvent) -> void:
var mb := event as InputEventMouseButton
if mb and mb.button_index == MOUSE_BUTTON_LEFT:
if mb.pressed:
_drag_prepare(mb)
else:
_drag_done()
if _drag_cancelled:
_show_text_edit()
_drag_cancelled = true
_is_editing = mb.pressed
queue_redraw()
var mm := event as InputEventMouseMotion
if mm and mm.button_mask & MOUSE_BUTTON_MASK_LEFT:
_drag_motion(mm)
_drag_cancelled = false
func _notification(what: int) -> void:
match what:
NOTIFICATION_ENTER_TREE, NOTIFICATION_THEME_CHANGED:
_update_stylebox()
NOTIFICATION_MOUSE_ENTER:
_mouse_hovering = true
queue_redraw()
NOTIFICATION_MOUSE_EXIT:
_mouse_hovering = false
queue_redraw()
NOTIFICATION_FOCUS_ENTER:
if (Input.is_action_pressed("ui_focus_next") or Input.is_action_pressed("ui_focus_prev")) and not _line_edit_just_closed:
_show_text_edit()
_line_edit_just_closed = false
func set_value(v: float) -> void:
if is_equal_approx(v, value):
return
value = v
emit_signal("value_changed", value)
queue_redraw()
func _update_stylebox() -> void:
_stylebox_normal = get_theme_stylebox("normal", "LineEdit")
_stylebox_hover = StyleBoxFlat.new()
_stylebox_hover.bg_color = get_theme_color("highlight_color", "Editor")
_stylebox_editing = StyleBoxFlat.new()
_stylebox_editing.bg_color = get_theme_color("dark_color_2", "Editor")
_stylebox_value = StyleBoxFlat.new()
_stylebox_value.bg_color = get_theme_color("accent_color", "Editor") * Color(1, 1, 1, 0.4)
func _drag_prepare(mouse: InputEventMouse) -> void:
_drag_dist = 0.0
_drag_start_factor = (value - min_value) / (max_value - min_value)
_drag_start_position = mouse.global_position
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _drag_done() -> void:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
if _drag_cancelled:
Input.warp_mouse(_drag_start_position)
else:
Input.warp_mouse(global_position + size * Vector2(
(value - min_value) / (max_value - min_value),
0.5
))
value_submitted.emit()
func _drag_motion(motion: InputEventMouseMotion) -> void:
_drag_dist += motion.relative.x
var factor := _drag_start_factor + _drag_dist / size.x
if factor < 0 or 1 < factor:
factor = clamp(factor, 0, 1)
_drag_dist = (factor - _drag_start_factor) * size.x
var v := factor * (max_value - min_value) + min_value
var snap := motion.is_command_or_control_pressed() or motion.shift_pressed
if snap and not (is_equal_approx(v, min_value) or is_equal_approx(v, max_value)):
if motion.shift_pressed and motion.is_command_or_control_pressed():
v = round(v * 1000.0) * 0.001
elif motion.shift_pressed:
v = round(v * 100.0) * 0.01
else:
v = round(v * 10.0) * 0.1
set_value(clamp(v, min_value, max_value))
queue_redraw()
func _show_text_edit() -> void:
var gr := get_global_rect()
_is_cancel = false
_line_edit.text = str(value)
_line_edit.set_position(gr.position)
_line_edit.set_size(gr.size)
_line_edit.show() # FIXME: no suitable solution for `show_modal` yet
_line_edit.select_all()
_line_edit.grab_focus()
_line_edit.focus_next = find_next_valid_focus().get_path()
_line_edit.focus_previous = find_prev_valid_focus().get_path()
func _on_line_edit_focus_exited():
if _line_edit.get_menu().visible:
return
if not _is_cancel and _line_edit.text.is_valid_float():
set_value(clamp(_line_edit.text.to_float(), min_value, max_value))
if not _line_edit_just_closed:
_line_edit.hide()
queue_redraw()
func _on_line_edit_text_entered(_text: String):
_line_edit.hide()
value_submitted.emit()
func _on_line_edit_visibility_changed():
if not _line_edit.visible:
_line_edit_just_closed = true
func _on_line_edit_gui_input(event: InputEvent):
if event.is_action_pressed("ui_cancel"):
_is_cancel = true
_line_edit.hide()

View File

@@ -0,0 +1 @@
uid://bqeewmmq5kpwx

View File

@@ -0,0 +1,402 @@
@tool
extends Container
enum ExtraOption { SAVE_AS, COPY, PASTE, PASTE_JSFXR, RECENT }
enum DefaultFilename { EMPTY, GUESS_FOR_SAVE }
const SFXRConfig := preload("../SFXRConfig.gd")
const SFXRGenerator := preload("../SFXRGenerator.gd")
const Base58 := preload("../Base58.gd")
const NUM_RECENTS := 4
class RecentEntry:
var title: String
var config := SFXRConfig.new()
var plugin: EditorPlugin:
set(v):
plugin = v
for child in get_children():
_hook_plugin(child)
var _config := SFXRConfig.new()
var _config_defaults := SFXRConfig.new()
var _config_clipboard: SFXRConfig
var _config_recents: Array
var _recents_id := 0
var _generator := SFXRGenerator.new()
var _path: String
var _modified := false
var _param_map := {}
var _syncing_ui := false # a hack since Range set_value emits value_changed
var _category_names := {}
@onready var audio_player := $AudioStreamPlayer as AudioStreamPlayer
@onready var filename_label := find_child("Filename") as Label
@onready var save_button := find_child("Save") as Button
@onready var restore_button := find_child("Restore") as Button
@onready var extra_button := find_child("Extra") as MenuButton
@onready var version_button := find_child("VersionButton")
@onready var translator := $PluginTranslator
func _ready():
if not plugin:
return # Running in the edited scene instead of from Plugin
var popup := extra_button.get_popup()
popup.add_item(translator.t("Save As..."), ExtraOption.SAVE_AS)
popup.add_separator()
popup.add_icon_item(get_theme_icon("ActionCopy", "EditorIcons"), translator.t("Copy"), ExtraOption.COPY)
popup.add_icon_item(get_theme_icon("ActionPaste", "EditorIcons"), translator.t("Paste"), ExtraOption.PASTE)
popup.add_item(translator.t("Paste from jsfxr"), ExtraOption.PASTE_JSFXR)
popup.add_separator(translator.t("Recently Generated"))
popup.id_pressed.connect(_on_Extra_id_pressed)
_category_names = {
SFXRConfig.Category.PICKUP_COIN: translator.t("Pickup/Coin"),
SFXRConfig.Category.LASER_SHOOT: translator.t("Laser/Shoot"),
SFXRConfig.Category.EXPLOSION: translator.t("Explosion"),
SFXRConfig.Category.POWERUP: translator.t("Powerup"),
SFXRConfig.Category.HIT_HURT: translator.t("Hit/Hurt"),
SFXRConfig.Category.JUMP: translator.t("Jump"),
SFXRConfig.Category.BLIP_SELECT: translator.t("Blip/Select"),
}
var params := find_child("Params") as Container
for category in params.get_children():
for control in category.get_children():
_param_map[control.parameter] = control
control.param_changed.connect(_on_param_changed)
control.param_reset.connect(_on_param_reset)
control.param_submitted.connect(_on_param_submitted)
_set_editing_file("")
func _notification(what: int):
if not plugin:
return # Running in the edited scene instead of from Plugin
match what:
NOTIFICATION_ENTER_TREE, NOTIFICATION_THEME_CHANGED:
find_child("ScrollContainer").add_theme_stylebox_override("panel", get_theme_stylebox("panel", "Tree"))
if extra_button:
var popup = extra_button.get_popup()
popup.set_item_icon(popup.get_item_index(ExtraOption.COPY), get_theme_icon("ActionCopy", "EditorIcons"))
popup.set_item_icon(popup.get_item_index(ExtraOption.PASTE), get_theme_icon("ActionPaste", "EditorIcons"))
func edit(object: Variant) -> void:
if not object:
if not _modified:
_set_editing_file("")
# TODO: prompt?
return
var path := object.resource_path as String
if _modified:
_popup_confirm(
translator.t("There are unsaved changes.\nOpen '%s' anyway?") % path,
_set_editing_file.bind(path)
)
else:
_set_editing_file(path)
func _hook_plugin(node: Node) -> void:
if "plugin" in node:
node.plugin = plugin
for child in node.get_children():
_hook_plugin(child)
func _push_recent(title: String) -> void:
var recent: RecentEntry
if _config_recents.size() < NUM_RECENTS:
recent = RecentEntry.new()
else:
recent = _config_recents.pop_back()
_recents_id += 1
recent.title = "#%d %s" % [_recents_id, title]
recent.config.copy_from(_config)
_config_recents.push_front(recent)
func _popup_confirm(content: String, callback: Callable) -> void:
var dialog := ConfirmationDialog.new()
add_child(dialog)
dialog.dialog_text = content
dialog.title = translator.t("SFXR Editor")
dialog.confirmed.connect(callback)
dialog.popup_centered()
dialog.visibility_changed.connect(dialog.queue_free)
func _popup_message(content: String) -> void:
var dialog := AcceptDialog.new()
add_child(dialog)
dialog.dialog_text = content
dialog.title = translator.t("SFXR Editor")
dialog.popup_centered()
dialog.visibility_changed.connect(dialog.queue_free)
func _popup_file_dialog(mode: int, callback: Callable, default_filename := DefaultFilename.EMPTY) -> void:
var dialog := EditorFileDialog.new()
add_child(dialog)
dialog.access = EditorFileDialog.ACCESS_RESOURCES
dialog.file_mode = mode
match default_filename:
DefaultFilename.EMPTY:
pass
DefaultFilename.GUESS_FOR_SAVE:
if _path:
dialog.current_path = _generate_serial_path(_path)
dialog.add_filter("*.sfxr; %s" % translator.t("SFXR Audio"))
dialog.file_selected.connect(callback)
dialog.popup_centered_ratio()
dialog.visibility_changed.connect(dialog.queue_free)
func _reset_defaults() -> void:
_config_defaults.copy_from(_config)
_set_modified(false)
_sync_ui()
func _restore_from_config(config: SFXRConfig) -> void:
_config.copy_from(config)
_sync_ui()
_set_modified(not config.is_equal(_config_defaults))
audio_player.stream = null
func _set_editing_file(path: String) -> int: # Error
if path.is_empty():
_config.reset()
audio_player.stream = null
else:
var err := _config.load(path)
if err != OK:
_popup_message(translator.t("'%s' is not a valid SFXR file.") % path)
return err
audio_player.stream = load(path)
_path = path
_reset_defaults()
return OK
func _set_modified(value: bool) -> void:
_modified = value
var has_file := not _path.is_empty()
var base = _path if has_file else translator.t("Unsaved sound")
if _modified:
base += "(*)"
filename_label.text = base
restore_button.disabled = not _modified
save_button.disabled = has_file and not _modified
func _sync_ui() -> void:
_syncing_ui = true
for name in _param_map:
var control = _param_map[name]
var value = _config.get(name)
control.set_value(value)
control.set_resetable(value != _config_defaults.get(name))
_syncing_ui = false
func _generate_serial_path(path: String) -> String:
var directory := DirAccess.open(path.get_base_dir())
if not directory:
return path
if not directory.file_exists(path.get_file()):
return path
var basename := path.get_basename()
var extension := path.get_extension()
# Extract trailing number.
var num_string: String
for i in range(basename.length() - 1, -1, -1):
var c: String = basename[i]
if "0" <= c and c <= "9":
num_string = c + num_string
else:
break
var number := num_string.to_int() if num_string else 0
var name_string: String = basename.substr(0, basename.length() - num_string.length())
while true:
number += 1
var attemp := "%s%d.%s" % [name_string, number, extension]
if not directory.file_exists(attemp):
return attemp
return path # Unreachable
func _on_param_submitted(_name):
_on_Play_pressed(true)
func _on_param_changed(name, value):
if _syncing_ui:
return
_config.set(name, value)
_param_map[name].set_resetable(value != _config_defaults.get(name))
_set_modified(not _config.is_equal(_config_defaults))
audio_player.stream = null
func _on_param_reset(name):
var value = _config_defaults.get(name)
_config.set(name, value)
_syncing_ui = true
var control = _param_map[name]
control.set_value(value)
control.set_resetable(false)
_syncing_ui = false
_set_modified(not _config.is_equal(_config_defaults))
audio_player.stream = null
_on_Play_pressed(true)
func _on_Play_pressed(force_regenerate := false):
if force_regenerate or audio_player.stream == null:
audio_player.stream = _generator.generate_audio_stream(_config)
audio_player.play()
func _on_Randomize_pressed(category: int):
if category == -1:
_config.randomize()
_push_recent(translator.t("Randomize"))
else:
_config.randomize_in_category(category)
_push_recent(_category_names.get(category, "Unknown"))
_set_modified(true)
_sync_ui()
_on_Play_pressed(true)
func _on_Mutate_pressed():
_config.mutate()
_push_recent(translator.t("Mutate"))
_set_modified(true)
_sync_ui()
_on_Play_pressed(true)
func _on_Restore_pressed():
_set_editing_file(_path)
_on_Play_pressed(true)
func _on_New_pressed():
if _modified:
_popup_confirm(
translator.t("There are unsaved changes.\nCreate a new one anyway?"),
_on_New_confirmed
)
else:
_on_New_confirmed()
func _on_New_confirmed() -> void:
_set_editing_file("")
func _on_Save_pressed():
if _path.is_empty():
_popup_file_dialog(EditorFileDialog.FILE_MODE_SAVE_FILE, _on_SaveAsDialog_confirmed)
else:
_config.save(_path)
plugin.get_editor_interface().get_resource_filesystem().scan_sources()
_reset_defaults()
func _on_SaveAsDialog_confirmed(path: String):
_path = path
_config.save(path)
plugin.get_editor_interface().get_resource_filesystem().scan()
_reset_defaults()
func _on_Load_pressed():
if _modified:
_popup_confirm(
translator.t("There are unsaved changes.\nLoad anyway?"),
_popup_file_dialog.bind(EditorFileDialog.FILE_MODE_OPEN_FILE, _set_editing_file)
)
else:
_popup_file_dialog(EditorFileDialog.FILE_MODE_OPEN_FILE, _set_editing_file)
func _on_Extra_about_to_show():
var popup := extra_button.get_popup()
popup.set_item_disabled(popup.get_item_index(ExtraOption.PASTE), _config_clipboard == null)
popup.set_item_disabled(popup.get_item_index(ExtraOption.PASTE_JSFXR), not DisplayServer.clipboard_has())
# Rebuild recents menu everytime :)
var first_recent_index := popup.get_item_index(ExtraOption.RECENT)
if first_recent_index != -1:
var count := popup.get_item_count()
for i in count - first_recent_index:
popup.remove_item(count - 1 - i)
if _config_recents.is_empty():
popup.add_item(translator.t("None"), ExtraOption.RECENT)
popup.set_item_disabled(popup.get_item_index(ExtraOption.RECENT), true)
else:
for i in _config_recents.size():
popup.add_item(_config_recents[i].title, ExtraOption.RECENT + i)
func _on_Extra_id_pressed(id: int) -> void:
match id:
ExtraOption.SAVE_AS:
_popup_file_dialog(EditorFileDialog.FILE_MODE_SAVE_FILE, _on_SaveAsDialog_confirmed, DefaultFilename.GUESS_FOR_SAVE)
ExtraOption.COPY:
if not _config_clipboard:
_config_clipboard = SFXRConfig.new()
_config_clipboard.copy_from(_config)
ExtraOption.PASTE:
_restore_from_config(_config_clipboard)
ExtraOption.PASTE_JSFXR:
var pasted := SFXRConfig.new()
if pasted.load_from_base58(DisplayServer.clipboard_get()) == OK:
_restore_from_config(pasted)
else:
_popup_message(translator.t("Clipboard does not contain code copied from jsfxr."))
_:
var i := id - ExtraOption.RECENT as int
if i < 0 or _config_recents.size() <= i:
printerr("Bad index %d (%d in total)" % [i, _config_recents.size()])
return
var recent: RecentEntry = _config_recents[i]
_restore_from_config(recent.config)
_on_Play_pressed()

View File

@@ -0,0 +1 @@
uid://b15etbgnf85gd

View File

@@ -0,0 +1,297 @@
[gd_scene load_steps=7 format=3 uid="uid://dv8q33r8aan5b"]
[ext_resource type="Script" path="res://addons/gdfxr/editor/EditorIconButton.gd" id="1"]
[ext_resource type="Script" path="res://addons/gdfxr/editor/Editor.gd" id="2"]
[ext_resource type="PackedScene" uid="uid://p27e3x3kk5e0" path="res://addons/gdfxr/editor/ParamSlider.tscn" id="3"]
[ext_resource type="PackedScene" uid="uid://b8wt6fq8w6mxc" path="res://addons/gdfxr/editor/ParamOption.tscn" id="4"]
[ext_resource type="PackedScene" uid="uid://cewvefxbttrds" path="res://addons/gdfxr/editor/PluginTranslator.tscn" id="5"]
[ext_resource type="PackedScene" path="res://addons/gdfxr/editor/VersionButton.tscn" id="6"]
[node name="Editor" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("2")
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
[node name="Toolbar" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="New" type="Button" parent="Toolbar"]
layout_mode = 2
tooltip_text = "New"
flat = true
script = ExtResource("1")
icon_name = "New"
[node name="Load" type="Button" parent="Toolbar"]
layout_mode = 2
tooltip_text = "Load"
flat = true
script = ExtResource("1")
icon_name = "Load"
[node name="Save" type="Button" parent="Toolbar"]
layout_mode = 2
tooltip_text = "Save"
flat = true
script = ExtResource("1")
icon_name = "Save"
[node name="Extra" type="MenuButton" parent="Toolbar"]
layout_mode = 2
tooltip_text = "Extra Options"
script = ExtResource("1")
icon_name = "GuiTabMenuHl"
[node name="VSeparator" type="VSeparator" parent="Toolbar"]
layout_mode = 2
[node name="Play" type="Button" parent="Toolbar"]
layout_mode = 2
size_flags_horizontal = 0
text = "Play"
script = ExtResource("1")
icon_name = "Play"
[node name="Restore" type="Button" parent="Toolbar"]
layout_mode = 2
tooltip_text = "Restore"
disabled = true
text = "Restore"
script = ExtResource("1")
icon_name = "Reload"
[node name="VSeparator2" type="VSeparator" parent="Toolbar"]
layout_mode = 2
[node name="Filename" type="Label" parent="Toolbar"]
layout_mode = 2
size_flags_horizontal = 3
text = "Unsaved sound"
clip_text = true
[node name="VersionButton" parent="Toolbar" instance=ExtResource("6")]
layout_mode = 2
website = "https://github.com/timothyqiu/gdfxr"
[node name="HSeparator" type="HSeparator" parent="."]
layout_mode = 2
[node name="Editor" type="HBoxContainer" parent="."]
layout_mode = 2
size_flags_vertical = 3
[node name="Generators" type="VBoxContainer" parent="Editor"]
layout_mode = 2
[node name="Button" type="Button" parent="Editor/Generators"]
layout_mode = 2
text = "Pickup/Coin"
[node name="Button2" type="Button" parent="Editor/Generators"]
layout_mode = 2
text = "Laser/Shoot"
[node name="Button3" type="Button" parent="Editor/Generators"]
layout_mode = 2
text = "Explosion"
[node name="Button4" type="Button" parent="Editor/Generators"]
layout_mode = 2
text = "Powerup"
[node name="Button5" type="Button" parent="Editor/Generators"]
layout_mode = 2
text = "Hit/Hurt"
[node name="Button6" type="Button" parent="Editor/Generators"]
layout_mode = 2
text = "Jump"
[node name="Button7" type="Button" parent="Editor/Generators"]
layout_mode = 2
text = "Blip/Select"
[node name="HSeparator" type="HSeparator" parent="Editor/Generators"]
layout_mode = 2
[node name="Button8" type="Button" parent="Editor/Generators"]
layout_mode = 2
size_flags_horizontal = 3
text = "Mutate"
[node name="Button9" type="Button" parent="Editor/Generators"]
layout_mode = 2
size_flags_horizontal = 3
text = "Randomize"
[node name="ScrollContainer" type="ScrollContainer" parent="Editor"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Params" type="HBoxContainer" parent="Editor/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 6
size_flags_vertical = 6
[node name="Envolope" type="VBoxContainer" parent="Editor/ScrollContainer/Params"]
layout_mode = 2
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource("3")]
layout_mode = 2
label = "Attack Time"
parameter = "p_env_attack"
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource("3")]
layout_mode = 2
label = "Sustain Time"
parameter = "p_env_sustain"
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource("3")]
layout_mode = 2
label = "Sustain Punch"
parameter = "p_env_punch"
[node name="ParamSlider4" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource("3")]
layout_mode = 2
label = "Decay Time"
parameter = "p_env_decay"
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource("3")]
layout_mode = 2
label = "Change Amount"
parameter = "p_arp_mod"
bipolar = true
[node name="ParamSlider6" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource("3")]
layout_mode = 2
label = "Change Speed"
parameter = "p_arp_speed"
[node name="ParamSlider7" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource("3")]
layout_mode = 2
label = "Volume"
parameter = "sound_vol"
[node name="Frequency" type="VBoxContainer" parent="Editor/ScrollContainer/Params"]
layout_mode = 2
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource("3")]
layout_mode = 2
label = "Start Frequency"
parameter = "p_base_freq"
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource("3")]
layout_mode = 2
label = "Min Frequency"
parameter = "p_freq_limit"
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource("3")]
layout_mode = 2
label = "Slide"
parameter = "p_freq_ramp"
bipolar = true
[node name="ParamSlider4" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource("3")]
layout_mode = 2
label = "Delta Slide"
parameter = "p_freq_dramp"
bipolar = true
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource("3")]
layout_mode = 2
label = "Vibrato Depth"
parameter = "p_vib_strength"
[node name="ParamSlider6" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource("3")]
layout_mode = 2
label = "Vibrato Speed"
parameter = "p_vib_speed"
[node name="ParamSlider7" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource("3")]
layout_mode = 2
label = "Repeat Speed"
parameter = "p_repeat_speed"
[node name="Waveform" type="VBoxContainer" parent="Editor/ScrollContainer/Params"]
layout_mode = 2
[node name="WaveformOption" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource("4")]
layout_mode = 2
options = ["Square", "Sawtooth", "Sine", "Noise"]
parameter = "wave_type"
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource("3")]
layout_mode = 2
label = "Square Duty"
parameter = "p_duty"
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource("3")]
layout_mode = 2
label = "Duty Sweep"
parameter = "p_duty_ramp"
bipolar = true
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource("3")]
layout_mode = 2
label = "Phaser Offset"
parameter = "p_pha_offset"
bipolar = true
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource("3")]
layout_mode = 2
label = "Phaser Sweep"
parameter = "p_pha_ramp"
bipolar = true
[node name="Filter" type="VBoxContainer" parent="Editor/ScrollContainer/Params"]
layout_mode = 2
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource("3")]
layout_mode = 2
label = "Low-pass Cutoff"
parameter = "p_lpf_freq"
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource("3")]
layout_mode = 2
label = "Low-pass Sweep"
parameter = "p_lpf_ramp"
bipolar = true
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource("3")]
layout_mode = 2
label = "Low-pass Resonance"
parameter = "p_lpf_resonance"
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource("3")]
layout_mode = 2
label = "High-pass Cutoff"
parameter = "p_hpf_freq"
[node name="ParamSlider4" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource("3")]
layout_mode = 2
label = "High-pass Sweep"
parameter = "p_hpf_ramp"
bipolar = true
[node name="PluginTranslator" parent="." instance=ExtResource("5")]
[connection signal="pressed" from="Toolbar/New" to="." method="_on_New_pressed"]
[connection signal="pressed" from="Toolbar/Load" to="." method="_on_Load_pressed"]
[connection signal="pressed" from="Toolbar/Save" to="." method="_on_Save_pressed"]
[connection signal="about_to_popup" from="Toolbar/Extra" to="." method="_on_Extra_about_to_show"]
[connection signal="pressed" from="Toolbar/Play" to="." method="_on_Play_pressed"]
[connection signal="pressed" from="Toolbar/Restore" to="." method="_on_Restore_pressed"]
[connection signal="pressed" from="Editor/Generators/Button" to="." method="_on_Randomize_pressed" binds= [0]]
[connection signal="pressed" from="Editor/Generators/Button2" to="." method="_on_Randomize_pressed" binds= [1]]
[connection signal="pressed" from="Editor/Generators/Button3" to="." method="_on_Randomize_pressed" binds= [2]]
[connection signal="pressed" from="Editor/Generators/Button4" to="." method="_on_Randomize_pressed" binds= [3]]
[connection signal="pressed" from="Editor/Generators/Button5" to="." method="_on_Randomize_pressed" binds= [4]]
[connection signal="pressed" from="Editor/Generators/Button6" to="." method="_on_Randomize_pressed" binds= [5]]
[connection signal="pressed" from="Editor/Generators/Button7" to="." method="_on_Randomize_pressed" binds= [6]]
[connection signal="pressed" from="Editor/Generators/Button8" to="." method="_on_Mutate_pressed"]
[connection signal="pressed" from="Editor/Generators/Button9" to="." method="_on_Randomize_pressed" binds= [-1]]

View File

@@ -0,0 +1,16 @@
@tool
extends Button
@export var icon_name: String
var plugin: EditorPlugin # a hack to know if this is executing as plugin
func _notification(what: int):
if not plugin:
return
match what:
NOTIFICATION_ENTER_TREE, NOTIFICATION_THEME_CHANGED:
if icon_name:
icon = get_theme_icon(icon_name, "EditorIcons")

View File

@@ -0,0 +1 @@
uid://ch7ou8w11mw8r

View File

@@ -0,0 +1,46 @@
@tool
extends HBoxContainer
signal param_changed(name, value)
signal param_submitted(name)
signal param_reset(name)
@export var options: Array :
set = set_options
@export var parameter: String # Could be PackedStringArray, but pybabel won't catch that
@onready var option_button := $OptionButton as OptionButton
func _ready():
set_options(options)
func set_options(v: Array) -> void:
options = v
if is_inside_tree():
option_button.clear()
for item in options:
option_button.add_item(item)
func set_value(v: int) -> void:
option_button.select(v)
func get_value() -> int:
return option_button.selected
func set_resetable(v: bool) -> void:
$Reset.disabled = not v
func _on_OptionButton_item_selected(index: int):
param_changed.emit(parameter, index)
param_submitted.emit(parameter)
func _on_Reset_pressed():
param_reset.emit(parameter)

View File

@@ -0,0 +1 @@
uid://deaxh8vtv7bi

View File

@@ -0,0 +1,35 @@
[gd_scene load_steps=3 format=3 uid="uid://b8wt6fq8w6mxc"]
[ext_resource type="Script" path="res://addons/gdfxr/editor/ParamOption.gd" id="1"]
[ext_resource type="Script" path="res://addons/gdfxr/editor/EditorIconButton.gd" id="2"]
[node name="WaveformOption" type="HBoxContainer"]
offset_right = 253.0
offset_bottom = 40.0
pivot_offset = Vector2(41, -65)
size_flags_horizontal = 3
script = ExtResource("1")
[node name="Label" type="Label" parent="."]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "Waveform"
horizontal_alignment = 2
[node name="OptionButton" type="OptionButton" parent="."]
custom_minimum_size = Vector2(105, 0)
layout_mode = 2
size_flags_vertical = 4
clip_text = true
[node name="Reset" type="Button" parent="."]
layout_mode = 2
size_flags_vertical = 4
focus_mode = 0
flat = true
script = ExtResource("2")
icon_name = "ReloadSmall"
[connection signal="item_selected" from="OptionButton" to="." method="_on_OptionButton_item_selected"]
[connection signal="pressed" from="Reset" to="." method="_on_Reset_pressed"]

View File

@@ -0,0 +1,60 @@
@tool
extends HBoxContainer
signal param_changed(name, value)
signal param_reset(name)
signal param_submitted(name)
@export var label: String :
set = set_label
@export var parameter: String
@export var bipolar := false :
set = set_bipolar
func _ready():
set_label(label)
set_bipolar(bipolar)
func set_label(v: String) -> void:
label = v
if is_inside_tree():
$Label.text = v
func set_bipolar(v: bool) -> void:
bipolar = v
if not is_inside_tree():
return
if bipolar:
$HSlider.min_value = -1.0
else:
$HSlider.min_value = 0.0
func set_value(v: float) -> void:
$HSlider.value = v
func get_value() -> float:
return $HSlider.value
func set_resetable(v: bool) -> void:
$Reset.disabled = not v
func _on_HSlider_value_changed(value: float):
param_changed.emit(parameter, value)
func _on_Reset_pressed():
param_reset.emit(parameter)
func _on_HSlider_value_submitted():
param_submitted.emit(parameter)

View File

@@ -0,0 +1 @@
uid://vkbrq7tdix7f

View File

@@ -0,0 +1,37 @@
[gd_scene load_steps=4 format=3 uid="uid://p27e3x3kk5e0"]
[ext_resource type="Script" path="res://addons/gdfxr/editor/ParamSlider.gd" id="1"]
[ext_resource type="Script" path="res://addons/gdfxr/editor/EditorIconButton.gd" id="2"]
[ext_resource type="Script" path="res://addons/gdfxr/editor/EditSlider.gd" id="3"]
[node name="ParamSlider" type="HBoxContainer"]
offset_right = 253.0
offset_bottom = 40.0
size_flags_horizontal = 3
script = ExtResource("1")
[node name="Label" type="Label" parent="."]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
size_flags_horizontal = 3
horizontal_alignment = 2
[node name="HSlider" type="Control" parent="."]
clip_contents = true
custom_minimum_size = Vector2(105, 0)
layout_mode = 2
focus_mode = 2
mouse_default_cursor_shape = 10
script = ExtResource("3")
[node name="Reset" type="Button" parent="."]
layout_mode = 2
size_flags_vertical = 4
focus_mode = 0
flat = true
script = ExtResource("2")
icon_name = "ReloadSmall"
[connection signal="value_submitted" from="HSlider" to="." method="_on_HSlider_value_submitted"]
[connection signal="value_changed" from="HSlider" to="." method="_on_HSlider_value_changed"]
[connection signal="pressed" from="Reset" to="." method="_on_Reset_pressed"]

View File

@@ -0,0 +1,57 @@
@tool
extends Node
var plugin: EditorPlugin :
set = set_plugin
var _translation: Translation
func set_plugin(v: EditorPlugin) -> void:
if plugin == v:
return
if not v:
plugin = null
_translation = null
return
plugin = v
var locale: String = plugin.get_editor_interface().get_editor_settings().get('interface/editor/editor_language')
var script := get_script() as Script
var path := script.resource_path.get_base_dir().path_join("translations/%s.po" % locale)
if ResourceLoader.exists(path):
_translation = ResourceLoader.load(path)
if _translation:
_translate_node.call_deferred(get_parent())
func t(message: StringName) -> String:
if _translation:
var translated := _translation.get_message(message)
if translated:
return String(translated)
return String(message)
func _translate_node(node: Node):
if node is Control:
node.tooltip_text = t(node.tooltip_text)
if node is HBoxContainer and node.has_method("set_options"):
var options = []
for item in node.options:
options.append(t(item))
node.options = options
if node is Button and not node is OptionButton:
node.text = t(node.text)
if node is Label:
node.text = t(node.text)
if node is Slider:
node.tooltip_text = t(node.tooltip_text)
for child in node.get_children():
_translate_node(child)

View File

@@ -0,0 +1 @@
uid://bifafveiktn3w

View File

@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://cewvefxbttrds"]
[ext_resource type="Script" path="res://addons/gdfxr/editor/PluginTranslator.gd" id="1"]
[node name="PluginTranslator" type="Node"]
script = ExtResource("1")

View File

@@ -0,0 +1,26 @@
@tool
extends LinkButton
@export var website: String
var plugin: EditorPlugin :
set = set_plugin
func set_plugin(v: EditorPlugin) -> void:
plugin = v
var script := get_script() as Script
var path := script.resource_path.get_base_dir().path_join("../plugin.cfg")
var cfg := ConfigFile.new()
var err := cfg.load(path)
text = "%s v%s" % [
cfg.get_value("plugin", "name", "plugin"),
cfg.get_value("plugin", "version", "1.0"),
]
func _on_VersionButton_pressed():
if website:
OS.shell_open(website)

View File

@@ -0,0 +1 @@
uid://cb3ifjg0krnam

View File

@@ -0,0 +1,19 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/gdfxr/editor/VersionButton.gd" type="Script" id=1]
[node name="VersionButton" type="LinkButton"]
self_modulate = Color( 1, 1, 1, 0.65 )
offset_left = 1024.0
offset_top = 5.0
offset_right = 1024.0
offset_bottom = 19.0
focus_mode = 2
size_flags_vertical = 4
underline = 1
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[connection signal="pressed" from="." to="." method="_on_VersionButton_pressed"]

View File

@@ -0,0 +1,265 @@
# Translations template for gdfxr.
# Copyright (C) 2022 Haoyu Qiu
# This file is distributed under the same license as the gdfxr project.
# Haoyu Qiu <timothyqiu32@gmail.com>, 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: gdfxr 1.0\n"
"Report-Msgid-Bugs-To: timothyqiu32@gmail.com\n"
"POT-Creation-Date: 2022-12-04 13:45+0800\n"
"PO-Revision-Date: 2022-12-04 13:45+0800\n"
"Last-Translator: Haoyu Qiu <timothyqiu32@gmail.com>\n"
"Language-Team: \n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"Generated-By: Babel 2.9.1\n"
"X-Generator: Poedit 3.2.1\n"
#: addons/gdfxr/editor/Editor.gd
msgid "Save As..."
msgstr "另存为..."
#: addons/gdfxr/editor/Editor.gd
msgid "Copy"
msgstr "复制"
#: addons/gdfxr/editor/Editor.gd
msgid "Paste"
msgstr "粘贴"
#: addons/gdfxr/editor/Editor.gd
msgid "Paste from jsfxr"
msgstr "从 jsfxr 粘贴"
#: addons/gdfxr/editor/Editor.gd
msgid "Recently Generated"
msgstr "最近生成"
#: addons/gdfxr/editor/Editor.gd addons/gdfxr/editor/Editor.tscn
msgid "Pickup/Coin"
msgstr "拾取/金币"
#: addons/gdfxr/editor/Editor.gd addons/gdfxr/editor/Editor.tscn
msgid "Laser/Shoot"
msgstr "激光/射击"
#: addons/gdfxr/editor/Editor.gd addons/gdfxr/editor/Editor.tscn
msgid "Explosion"
msgstr "爆炸"
#: addons/gdfxr/editor/Editor.gd addons/gdfxr/editor/Editor.tscn
msgid "Powerup"
msgstr "升级"
#: addons/gdfxr/editor/Editor.gd addons/gdfxr/editor/Editor.tscn
msgid "Hit/Hurt"
msgstr "击中/受伤"
#: addons/gdfxr/editor/Editor.gd addons/gdfxr/editor/Editor.tscn
msgid "Jump"
msgstr "跳跃"
#: addons/gdfxr/editor/Editor.gd addons/gdfxr/editor/Editor.tscn
msgid "Blip/Select"
msgstr "短滴/选择"
#: addons/gdfxr/editor/Editor.gd
#, python-format
msgid ""
"There are unsaved changes.\n"
"Open '%s' anyway?"
msgstr ""
"存在未保存的修改。\n"
"仍然要打开“%s”吗"
#: addons/gdfxr/editor/Editor.gd
msgid "SFXR Editor"
msgstr "SFXR 编辑器"
#: addons/gdfxr/editor/Editor.gd
msgid "SFXR Audio"
msgstr "SFXR 音频"
#: addons/gdfxr/editor/Editor.gd
#, python-format
msgid "'%s' is not a valid SFXR file."
msgstr "“%s”不是有效的 SFXR 文件。"
#: addons/gdfxr/editor/Editor.gd addons/gdfxr/editor/Editor.tscn
msgid "Unsaved sound"
msgstr "未保存音效"
#: addons/gdfxr/editor/Editor.gd addons/gdfxr/editor/Editor.tscn
msgid "Randomize"
msgstr "随机"
#: addons/gdfxr/editor/Editor.gd addons/gdfxr/editor/Editor.tscn
msgid "Mutate"
msgstr "演化"
#: addons/gdfxr/editor/Editor.gd
msgid ""
"There are unsaved changes.\n"
"Create a new one anyway?"
msgstr ""
"存在未保存的修改。\n"
"仍然要新建吗?"
#: addons/gdfxr/editor/Editor.gd
msgid ""
"There are unsaved changes.\n"
"Load anyway?"
msgstr ""
"存在未保存的修改。\n"
"仍然要加载吗?"
#: addons/gdfxr/editor/Editor.gd
msgid "None"
msgstr "无"
#: addons/gdfxr/editor/Editor.gd
msgid "Clipboard does not contain code copied from jsfxr."
msgstr "剪贴板中没有从 jsfxr 复制的代码。"
#: addons/gdfxr/editor/Editor.tscn
msgid "New"
msgstr "新建"
#: addons/gdfxr/editor/Editor.tscn
msgid "Load"
msgstr "加载"
#: addons/gdfxr/editor/Editor.tscn
msgid "Save"
msgstr "保存"
#: addons/gdfxr/editor/Editor.tscn
msgid "Extra Options"
msgstr "更多选项"
#: addons/gdfxr/editor/Editor.tscn
msgid "Play"
msgstr "播放"
#: addons/gdfxr/editor/Editor.tscn
msgid "Restore"
msgstr "恢复"
#: addons/gdfxr/editor/Editor.tscn
msgid "Attack Time"
msgstr "起音时间"
#: addons/gdfxr/editor/Editor.tscn
msgid "Sustain Time"
msgstr "延音时间"
#: addons/gdfxr/editor/Editor.tscn
msgid "Sustain Punch"
msgstr "延音冲击"
#: addons/gdfxr/editor/Editor.tscn
msgid "Decay Time"
msgstr "释音时间"
#: addons/gdfxr/editor/Editor.tscn
msgid "Change Amount"
msgstr "改变强度"
#: addons/gdfxr/editor/Editor.tscn
msgid "Change Speed"
msgstr "改变速度"
#: addons/gdfxr/editor/Editor.tscn
msgid "Volume"
msgstr "音量"
#: addons/gdfxr/editor/Editor.tscn
msgid "Start Frequency"
msgstr "起始频率"
#: addons/gdfxr/editor/Editor.tscn
msgid "Min Frequency"
msgstr "最低频率"
#: addons/gdfxr/editor/Editor.tscn
msgid "Slide"
msgstr "滑音"
#: addons/gdfxr/editor/Editor.tscn
msgid "Delta Slide"
msgstr "滑音增量"
#: addons/gdfxr/editor/Editor.tscn
msgid "Vibrato Depth"
msgstr "颤音深度"
#: addons/gdfxr/editor/Editor.tscn
msgid "Vibrato Speed"
msgstr "颤音速度"
#: addons/gdfxr/editor/Editor.tscn
msgid "Repeat Speed"
msgstr "重复速度"
#: addons/gdfxr/editor/Editor.tscn
msgid "Square"
msgstr "方波"
#: addons/gdfxr/editor/Editor.tscn
msgid "Sawtooth"
msgstr "锯齿波"
#: addons/gdfxr/editor/Editor.tscn
msgid "Sine"
msgstr "正弦波"
#: addons/gdfxr/editor/Editor.tscn
msgid "Noise"
msgstr "噪波"
#: addons/gdfxr/editor/Editor.tscn
msgid "Square Duty"
msgstr "方波工作"
#: addons/gdfxr/editor/Editor.tscn
msgid "Duty Sweep"
msgstr "工作变频"
#: addons/gdfxr/editor/Editor.tscn
msgid "Phaser Offset"
msgstr "相位偏移"
#: addons/gdfxr/editor/Editor.tscn
msgid "Phaser Sweep"
msgstr "相位变频"
#: addons/gdfxr/editor/Editor.tscn
msgid "Low-pass Cutoff"
msgstr "低通截频"
#: addons/gdfxr/editor/Editor.tscn
msgid "Low-pass Sweep"
msgstr "低通变频"
#: addons/gdfxr/editor/Editor.tscn
msgid "Low-pass Resonance"
msgstr "低通共振"
#: addons/gdfxr/editor/Editor.tscn
msgid "High-pass Cutoff"
msgstr "高通截频"
#: addons/gdfxr/editor/Editor.tscn
msgid "High-pass Sweep"
msgstr "高通变频"
#: addons/gdfxr/editor/ParamOption.tscn
msgid "Waveform"
msgstr "波形"
#~ msgid "Hold Ctrl to snap to 0.01 increments."
#~ msgstr "按住 Ctrl 吸附到 0.01 增量。"