mirror of
https://github.com/Relintai/gdfxr.git
synced 2025-04-20 01:43:13 +02:00
parent
3d59d2fcad
commit
7981128335
@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- Paste from JSXFR.
|
||||||
|
|
||||||
## [1.2.0] - 2022-10-13
|
## [1.2.0] - 2022-10-13
|
||||||
### Added
|
### Added
|
||||||
|
38
addons/gdfxr/Base58.gd
Normal file
38
addons/gdfxr/Base58.gd
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
extends Object
|
||||||
|
|
||||||
|
const BASE_58_ALPHABET := "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||||
|
|
||||||
|
|
||||||
|
static func b58decode(v: String) -> StreamPeerBuffer:
|
||||||
|
# Base 58 is a number expressed in the base-58 numeral system.
|
||||||
|
# When encoding data, big-endian is used and leading zeros are encoded as leading `1`s.
|
||||||
|
|
||||||
|
var original_length := v.length()
|
||||||
|
v = v.lstrip(BASE_58_ALPHABET[0])
|
||||||
|
var zeros := original_length - v.length()
|
||||||
|
|
||||||
|
var buffer := PackedByteArray()
|
||||||
|
buffer.resize(v.length()) # Won't be as long as base 58 string since the buffer is 256-based.
|
||||||
|
buffer.fill(0)
|
||||||
|
|
||||||
|
var length := 0
|
||||||
|
for c in v:
|
||||||
|
var carry := BASE_58_ALPHABET.find(c)
|
||||||
|
if carry == -1:
|
||||||
|
return null
|
||||||
|
var i := 0
|
||||||
|
while carry != 0 or i < length:
|
||||||
|
var pos := buffer.size() - 1 - i
|
||||||
|
carry += 58 * buffer[pos]
|
||||||
|
buffer[pos] = carry % 256
|
||||||
|
carry /= 256
|
||||||
|
i += 1
|
||||||
|
length = i
|
||||||
|
|
||||||
|
var result := StreamPeerBuffer.new()
|
||||||
|
for _i in zeros:
|
||||||
|
result.put_8(0)
|
||||||
|
result.put_data(buffer.slice(buffer.size() - length))
|
||||||
|
result.seek(0)
|
||||||
|
|
||||||
|
return result
|
@ -18,6 +18,8 @@ enum Category {
|
|||||||
BLIP_SELECT,
|
BLIP_SELECT,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Base58 := preload("res://addons/gdfxr/Base58.gd")
|
||||||
|
|
||||||
var wave_type: int = WaveType.SQUARE_WAVE
|
var wave_type: int = WaveType.SQUARE_WAVE
|
||||||
|
|
||||||
var p_env_attack := 0.0 # Attack Time
|
var p_env_attack := 0.0 # Attack Time
|
||||||
@ -463,3 +465,45 @@ func is_equal(other: RefCounted) -> bool: # SFXRConfig
|
|||||||
|
|
||||||
and sound_vol == other.sound_vol
|
and sound_vol == other.sound_vol
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Load base58 string copied from jsfxr
|
||||||
|
# See https://github.com/chr15m/jsfxr/blob/a708164e6ce200008d88202e1aaf2b9171a17ec2/sfxr.js#L132-L175
|
||||||
|
func load_from_base58(v: String) -> int: # Error
|
||||||
|
var buffer := Base58.b58decode(v)
|
||||||
|
if not buffer:
|
||||||
|
return ERR_INVALID_DATA
|
||||||
|
if buffer.get_size() != 89:
|
||||||
|
return ERR_INVALID_DATA
|
||||||
|
|
||||||
|
var params_order = [
|
||||||
|
"p_env_attack",
|
||||||
|
"p_env_sustain",
|
||||||
|
"p_env_punch",
|
||||||
|
"p_env_decay",
|
||||||
|
"p_base_freq",
|
||||||
|
"p_freq_limit",
|
||||||
|
"p_freq_ramp",
|
||||||
|
"p_freq_dramp",
|
||||||
|
"p_vib_strength",
|
||||||
|
"p_vib_speed",
|
||||||
|
"p_arp_mod",
|
||||||
|
"p_arp_speed",
|
||||||
|
"p_duty",
|
||||||
|
"p_duty_ramp",
|
||||||
|
"p_repeat_speed",
|
||||||
|
"p_pha_offset",
|
||||||
|
"p_pha_ramp",
|
||||||
|
"p_lpf_freq",
|
||||||
|
"p_lpf_ramp",
|
||||||
|
"p_lpf_resonance",
|
||||||
|
"p_hpf_freq",
|
||||||
|
"p_hpf_ramp",
|
||||||
|
]
|
||||||
|
|
||||||
|
wave_type = buffer.get_8()
|
||||||
|
|
||||||
|
for param in params_order:
|
||||||
|
set(param, buffer.get_float())
|
||||||
|
|
||||||
|
return OK
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
@tool
|
@tool
|
||||||
extends Container
|
extends Container
|
||||||
|
|
||||||
enum ExtraOption { SAVE_AS, COPY, PASTE, RECENT }
|
enum ExtraOption { SAVE_AS, COPY, PASTE, PASTE_JSFXR, RECENT }
|
||||||
|
|
||||||
const SFXRConfig := preload("../SFXRConfig.gd")
|
const SFXRConfig := preload("../SFXRConfig.gd")
|
||||||
const SFXRGenerator := preload("../SFXRGenerator.gd")
|
const SFXRGenerator := preload("../SFXRGenerator.gd")
|
||||||
|
const Base58 := preload("../Base58.gd")
|
||||||
const NUM_RECENTS := 4
|
const NUM_RECENTS := 4
|
||||||
|
|
||||||
class RecentEntry:
|
class RecentEntry:
|
||||||
@ -47,6 +48,7 @@ func _ready():
|
|||||||
popup.add_separator()
|
popup.add_separator()
|
||||||
popup.add_icon_item(get_theme_icon("ActionCopy", "EditorIcons"), translator.tr("Copy"), ExtraOption.COPY)
|
popup.add_icon_item(get_theme_icon("ActionCopy", "EditorIcons"), translator.tr("Copy"), ExtraOption.COPY)
|
||||||
popup.add_icon_item(get_theme_icon("ActionPaste", "EditorIcons"), translator.tr("Paste"), ExtraOption.PASTE)
|
popup.add_icon_item(get_theme_icon("ActionPaste", "EditorIcons"), translator.tr("Paste"), ExtraOption.PASTE)
|
||||||
|
popup.add_item(translator.tr("Paste from jsfxr"), ExtraOption.PASTE_JSFXR)
|
||||||
popup.add_separator(translator.tr("Recently Generated"))
|
popup.add_separator(translator.tr("Recently Generated"))
|
||||||
popup.id_pressed.connect(_on_Extra_id_pressed)
|
popup.id_pressed.connect(_on_Extra_id_pressed)
|
||||||
|
|
||||||
@ -129,7 +131,7 @@ func _popup_message(content: String) -> void:
|
|||||||
var dialog := AcceptDialog.new()
|
var dialog := AcceptDialog.new()
|
||||||
add_child(dialog)
|
add_child(dialog)
|
||||||
dialog.dialog_text = content
|
dialog.dialog_text = content
|
||||||
dialog.window_title = translator.tr("SFXR Editor")
|
dialog.title = translator.tr("SFXR Editor")
|
||||||
dialog.popup_centered()
|
dialog.popup_centered()
|
||||||
dialog.visibility_changed.connect(dialog.queue_free)
|
dialog.visibility_changed.connect(dialog.queue_free)
|
||||||
|
|
||||||
@ -297,6 +299,7 @@ func _on_Load_pressed():
|
|||||||
func _on_Extra_about_to_show():
|
func _on_Extra_about_to_show():
|
||||||
var popup := extra_button.get_popup()
|
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), _config_clipboard == null)
|
||||||
|
popup.set_item_disabled(popup.get_item_index(ExtraOption.PASTE_JSFXR), not DisplayServer.clipboard_has())
|
||||||
|
|
||||||
# Rebuild recents menu everytime :)
|
# Rebuild recents menu everytime :)
|
||||||
var first_recent_index := popup.get_item_index(ExtraOption.RECENT)
|
var first_recent_index := popup.get_item_index(ExtraOption.RECENT)
|
||||||
@ -326,6 +329,13 @@ func _on_Extra_id_pressed(id: int) -> void:
|
|||||||
ExtraOption.PASTE:
|
ExtraOption.PASTE:
|
||||||
_restore_from_config(_config_clipboard)
|
_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.tr("Clipboard does not contain code copied from jsfxr."))
|
||||||
|
|
||||||
_:
|
_:
|
||||||
var i := id - ExtraOption.RECENT as int
|
var i := id - ExtraOption.RECENT as int
|
||||||
if i < 0 or _config_recents.size() <= i:
|
if i < 0 or _config_recents.size() <= i:
|
||||||
|
@ -8,14 +8,14 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: gdfxr 1.0\n"
|
"Project-Id-Version: gdfxr 1.0\n"
|
||||||
"Report-Msgid-Bugs-To: timothyqiu32@gmail.com\n"
|
"Report-Msgid-Bugs-To: timothyqiu32@gmail.com\n"
|
||||||
"POT-Creation-Date: 2022-09-20 14:01+0800\n"
|
"POT-Creation-Date: 2022-12-04 13:45+0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: Babel 2.10.3\n"
|
"Generated-By: Babel 2.11.0\n"
|
||||||
|
|
||||||
#: addons/gdfxr/editor/Editor.gd
|
#: addons/gdfxr/editor/Editor.gd
|
||||||
msgid "Save As..."
|
msgid "Save As..."
|
||||||
@ -29,6 +29,10 @@ msgstr ""
|
|||||||
msgid "Paste"
|
msgid "Paste"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: addons/gdfxr/editor/Editor.gd
|
||||||
|
msgid "Paste from jsfxr"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: addons/gdfxr/editor/Editor.gd
|
#: addons/gdfxr/editor/Editor.gd
|
||||||
msgid "Recently Generated"
|
msgid "Recently Generated"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -109,6 +113,10 @@ msgstr ""
|
|||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: addons/gdfxr/editor/Editor.gd
|
||||||
|
msgid "Clipboard does not contain code copied from jsfxr."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: addons/gdfxr/editor/Editor.tscn
|
#: addons/gdfxr/editor/Editor.tscn
|
||||||
msgid "New"
|
msgid "New"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -245,7 +253,3 @@ msgstr ""
|
|||||||
msgid "Waveform"
|
msgid "Waveform"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: addons/gdfxr/editor/ParamSlider.tscn
|
|
||||||
msgid "Hold Ctrl to snap to 0.01 increments."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: gdfxr 1.0\n"
|
"Project-Id-Version: gdfxr 1.0\n"
|
||||||
"Report-Msgid-Bugs-To: timothyqiu32@gmail.com\n"
|
"Report-Msgid-Bugs-To: timothyqiu32@gmail.com\n"
|
||||||
"POT-Creation-Date: 2022-09-20 14:01+0800\n"
|
"POT-Creation-Date: 2022-12-04 13:45+0800\n"
|
||||||
"PO-Revision-Date: 2022-09-20 14:01+0800\n"
|
"PO-Revision-Date: 2022-12-04 13:45+0800\n"
|
||||||
"Last-Translator: Haoyu Qiu <timothyqiu32@gmail.com>\n"
|
"Last-Translator: Haoyu Qiu <timothyqiu32@gmail.com>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"Language: zh_CN\n"
|
"Language: zh_CN\n"
|
||||||
@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
"Generated-By: Babel 2.9.1\n"
|
"Generated-By: Babel 2.9.1\n"
|
||||||
"X-Generator: Poedit 3.1\n"
|
"X-Generator: Poedit 3.2.1\n"
|
||||||
|
|
||||||
#: addons/gdfxr/editor/Editor.gd
|
#: addons/gdfxr/editor/Editor.gd
|
||||||
msgid "Save As..."
|
msgid "Save As..."
|
||||||
@ -31,6 +31,10 @@ msgstr "复制"
|
|||||||
msgid "Paste"
|
msgid "Paste"
|
||||||
msgstr "粘贴"
|
msgstr "粘贴"
|
||||||
|
|
||||||
|
#: addons/gdfxr/editor/Editor.gd
|
||||||
|
msgid "Paste from jsfxr"
|
||||||
|
msgstr "从 jsfxr 粘贴"
|
||||||
|
|
||||||
#: addons/gdfxr/editor/Editor.gd
|
#: addons/gdfxr/editor/Editor.gd
|
||||||
msgid "Recently Generated"
|
msgid "Recently Generated"
|
||||||
msgstr "最近生成"
|
msgstr "最近生成"
|
||||||
@ -117,6 +121,10 @@ msgstr ""
|
|||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "无"
|
msgstr "无"
|
||||||
|
|
||||||
|
#: addons/gdfxr/editor/Editor.gd
|
||||||
|
msgid "Clipboard does not contain code copied from jsfxr."
|
||||||
|
msgstr "剪贴板中没有从 jsfxr 复制的代码。"
|
||||||
|
|
||||||
#: addons/gdfxr/editor/Editor.tscn
|
#: addons/gdfxr/editor/Editor.tscn
|
||||||
msgid "New"
|
msgid "New"
|
||||||
msgstr "新建"
|
msgstr "新建"
|
||||||
@ -253,6 +261,5 @@ msgstr "高通变频"
|
|||||||
msgid "Waveform"
|
msgid "Waveform"
|
||||||
msgstr "波形"
|
msgstr "波形"
|
||||||
|
|
||||||
#: addons/gdfxr/editor/ParamSlider.tscn
|
#~ msgid "Hold Ctrl to snap to 0.01 increments."
|
||||||
msgid "Hold Ctrl to snap to 0.01 increments."
|
#~ msgstr "按住 Ctrl 吸附到 0.01 增量。"
|
||||||
msgstr "按住 Ctrl 吸附到 0.01 增量。"
|
|
||||||
|
@ -1,7 +1,38 @@
|
|||||||
extends CenterContainer
|
extends Container
|
||||||
|
|
||||||
func _ready() -> void:
|
# These two classes are for runtime generation.
|
||||||
var audio := load("res://example/example.sfxr") as AudioStreamSample
|
const SFXRConfig = preload("res://addons/gdfxr/SFXRConfig.gd")
|
||||||
print(audio)
|
const SFXRGenerator = preload("res://addons/gdfxr/SFXRGenerator.gd")
|
||||||
audio.save_to_wav("/home/timothy/Desktop/foo.wav")
|
|
||||||
$AudioStreamPlayer.stream = audio
|
@onready var audio_player: AudioStreamPlayer = $AudioPlayer
|
||||||
|
@onready var adhoc_audio_player: AudioStreamPlayer = $AdhocAudioPlayer
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Play_pressed() -> void:
|
||||||
|
audio_player.play()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_PlayFile_pressed() -> void:
|
||||||
|
adhoc_audio_player.stream = preload("res://example/example.sfxr")
|
||||||
|
adhoc_audio_player.play()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Generate_pressed() -> void:
|
||||||
|
var config := SFXRConfig.new()
|
||||||
|
|
||||||
|
# Fill the fields manually
|
||||||
|
# config.p_base_freq = 0.5
|
||||||
|
|
||||||
|
# Load from .sfxr file
|
||||||
|
# config.load("res://example/example.sfxr")
|
||||||
|
|
||||||
|
# Load from jsfxr base58 string
|
||||||
|
config.load_from_base58("34T6PkmKkNTf3aUynCpV3oetaq6ecj9Grh9W7tiTbccVYK8FxNKBbfBFXJCLzk8QTy4d7fbiCfY2gXDaiengXbENjdLWt5jZBtcz8QmSCXjHCSuooDCWp4SrT")
|
||||||
|
|
||||||
|
# generate_audio_stream() might freeze a bit when generating long sounds.
|
||||||
|
# It's recommended to pre-generate the sound effects in editor.
|
||||||
|
# If you do want to generate the sound effects on the fly, you might want
|
||||||
|
# to generate and cache the sound effects at the start of your game.
|
||||||
|
var generator := SFXRGenerator.new()
|
||||||
|
adhoc_audio_player.stream = generator.generate_audio_stream(config)
|
||||||
|
adhoc_audio_player.play()
|
||||||
|
@ -1,23 +1,59 @@
|
|||||||
[gd_scene load_steps=2 format=3 uid="uid://bv31mn2hs6wom"]
|
[gd_scene load_steps=3 format=3 uid="uid://bv31mn2hs6wom"]
|
||||||
|
|
||||||
[ext_resource type="AudioStream" uid="uid://byf7u7a25fuf4" path="res://example/example.sfxr" id="1"]
|
[ext_resource type="Script" path="res://example/Example.gd" id="1"]
|
||||||
|
[ext_resource type="AudioStream" uid="uid://byf7u7a25fuf4" path="res://example/example.sfxr" id="2"]
|
||||||
|
|
||||||
[node name="Example" type="CenterContainer"]
|
[node name="Example" type="GridContainer"]
|
||||||
anchors_preset = 15
|
anchors_preset = 8
|
||||||
anchor_right = 1.0
|
anchor_left = 0.5
|
||||||
anchor_bottom = 1.0
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
|
theme_override_constants/h_separation = 32
|
||||||
|
theme_override_constants/v_separation = 32
|
||||||
|
columns = 2
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
[node name="Button" type="Button" parent="."]
|
[node name="AudioPlayer" type="AudioStreamPlayer" parent="."]
|
||||||
|
stream = ExtResource("2")
|
||||||
|
|
||||||
|
[node name="AdhocAudioPlayer" type="AudioStreamPlayer" parent="."]
|
||||||
|
|
||||||
|
[node name="Play" type="Button" parent="."]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
offset_left = 555.0
|
size_flags_vertical = 4
|
||||||
offset_top = 308.0
|
|
||||||
offset_right = 596.0
|
|
||||||
offset_bottom = 339.0
|
|
||||||
text = "Play"
|
text = "Play"
|
||||||
|
|
||||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
|
[node name="Label" type="Label" parent="."]
|
||||||
stream = ExtResource("1")
|
custom_minimum_size = Vector2(500, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "A .sfxr file can be used as regular audio files like .wav, .ogg, and .mp3."
|
||||||
|
autowrap_mode = 3
|
||||||
|
|
||||||
[connection signal="pressed" from="Button" to="AudioStreamPlayer" method="play"]
|
[node name="PlayFile" type="Button" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
text = "Load .sfxr File"
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="."]
|
||||||
|
custom_minimum_size = Vector2(500, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "A .sfxr file is a AudioStreamSample resource that can be loaded with load() or preload()."
|
||||||
|
autowrap_mode = 3
|
||||||
|
|
||||||
|
[node name="Generate" type="Button" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
text = "Runtime Generation"
|
||||||
|
|
||||||
|
[node name="Label3" type="Label" parent="."]
|
||||||
|
custom_minimum_size = Vector2(500, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "You can generate the sound effect at runtime. However, due to performance constraints with GDScript, your game might freeze when generating long sounds."
|
||||||
|
autowrap_mode = 3
|
||||||
|
|
||||||
|
[connection signal="pressed" from="Play" to="." method="_on_Play_pressed"]
|
||||||
|
[connection signal="pressed" from="PlayFile" to="." method="_on_PlayFile_pressed"]
|
||||||
|
[connection signal="pressed" from="Generate" to="." method="_on_Generate_pressed"]
|
||||||
|
Loading…
Reference in New Issue
Block a user