mirror of
https://github.com/Relintai/gdfxr.git
synced 2024-11-22 13:07:47 +01:00
Initial Commit
This commit is contained in:
commit
bf351cff87
20
.gitattributes
vendored
Normal file
20
.gitattributes
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
||||
|
||||
# Ignore some files when exporting to a ZIP.
|
||||
/.gitattributes export-ignore
|
||||
/.gitignore export-ignore
|
||||
/LICENSE export-ignore
|
||||
/README.md export-ignore
|
||||
/README-zh_CN.md export-ignore
|
||||
/project.godot export-ignore
|
||||
/icon.png export-ignore
|
||||
/icon.png.import export-ignore
|
||||
/icon.svg export-ignore
|
||||
/icon.svg.import export-ignore
|
||||
/screenshots export-ignore
|
||||
/babel.cfg export-ignore
|
||||
/gdfxr.pot export-ignore
|
||||
/Makefile export-ignore
|
||||
/example export-ignore
|
||||
/addons/gdfxr/editor/translations/gdfxr.pot export-ignore
|
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
|
||||
# Godot-specific ignores
|
||||
.import/
|
||||
export.cfg
|
||||
export_presets.cfg
|
||||
|
||||
# Imported translations (automatically generated from CSV files)
|
||||
*.translation
|
||||
|
||||
# Mono-specific ignores
|
||||
.mono/
|
||||
data_*/
|
||||
mono_crash.*.json
|
7
LICENSE
Normal file
7
LICENSE
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright 2022 Haoyu Qiu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
15
Makefile
Normal file
15
Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
pot:
|
||||
@pybabel extract \
|
||||
--project gdfxr \
|
||||
--version 1.0 \
|
||||
--msgid-bugs-address 'timothyqiu32@gmail.com' \
|
||||
--copyright-holder 'Haoyu Qiu' \
|
||||
--add-location file \
|
||||
-F babel.cfg \
|
||||
-k tr \
|
||||
-k text \
|
||||
-k label \
|
||||
-k hint_tooltip \
|
||||
-k options \
|
||||
-o addons/gdfxr/editor/translations/gdfxr.pot \
|
||||
addons/gdfxr/editor
|
32
README-zh_CN.md
Normal file
32
README-zh_CN.md
Normal file
@ -0,0 +1,32 @@
|
||||
# gdfxr
|
||||
|
||||
<img src="icon.png?raw=true" align="right" />
|
||||
|
||||
[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
|
||||
[![English README](https://img.shields.io/badge/README-English-red)](README.md)
|
||||
|
||||
以 Godot 插件形式移植的 [sfxr](https://www.drpetter.se/project_sfxr.html "DrPetter's homepage - sfxr"),这是一个非常流行的复古游戏音效生成器。
|
||||
|
||||
你可以在 Godot 中把 sfxr 音效文件当作普通的音频文件使用,也可以像在原始的 sfxr 中一样对音效进行编辑。
|
||||
|
||||
## 安装
|
||||
|
||||
这是一个普通的 Godot 插件。安装时,先下载 ZIP 包,解压后将 `addons/` 文件夹移动到你的项目文件夹中,然后在项目设置中启用本插件。
|
||||
|
||||
## 用法
|
||||
|
||||
启用插件后,你会看到出现了一个名叫“gdfxr”的底部面板。这就是音效编辑器。
|
||||
|
||||
<p align="center">
|
||||
<img src="screenshots/editor-zh_CN.png?raw=true" />
|
||||
</p>
|
||||
|
||||
左侧的按钮是 7 种不同类型的音效生成器。还有一个根据当前音效略微演化声音的选项,以及一个完全随机生成音效的选项。大多数时候用这些按钮就可以了。
|
||||
|
||||
使用生成器按钮随机生成音效后,可以使用右侧的控件对音效的参数进行微调。
|
||||
|
||||
生成的音效可以保存成 `.sfxr` 文件,之后可以再次打开编辑。这些文件只包含生成器的参数,所以只有大概 100 字节。但可以直接把它们当普通的 `AudioStream` 用。
|
||||
|
||||
如果你希望使用原始的 sfxr 保存出的文件,请确保使用 `.sfxr` 扩展名保存。你也可以在原始的 sfxr 中加载并编辑 `.sfxr` 文件。
|
||||
|
||||
**注意:** 由于 GDScript 的性能限制,生成较长的音效时编辑器可能会有短暂的停滞。只有编辑器会受此影响。在游戏中使用 `.sfxr` 文件是不会在运行时生成任何东西的。
|
44
README.md
Normal file
44
README.md
Normal file
@ -0,0 +1,44 @@
|
||||
# gdfxr
|
||||
|
||||
<img src="icon.png?raw=true" align="right" />
|
||||
|
||||
[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
|
||||
[![中文 README](https://img.shields.io/badge/README-%E4%B8%AD%E6%96%87-red)](README-zh_CN.md)
|
||||
|
||||
A Godot plugin that ports [sfxr](https://www.drpetter.se/project_sfxr.html "DrPetter's homepage - sfxr"),
|
||||
the popular program of choice to make retro sound effects for games.
|
||||
|
||||
You can use sfxr sound files like regular audio files in Godot and edit sound files like in the
|
||||
original sfxr.
|
||||
|
||||
## Installation
|
||||
|
||||
This is a regular plugin for Godot. To install, download the ZIP archive, extract it, and move the
|
||||
`addons/` folder it contains into your project folder. Then, enable the plugin in project settings.
|
||||
|
||||
## Usage
|
||||
|
||||
After enabling the plugin, you'll see a bottom panel named "gdfxr" appear.
|
||||
This is the sound editor.
|
||||
|
||||
<p align="center">
|
||||
<img src="screenshots/editor.png?raw=true" />
|
||||
</p>
|
||||
|
||||
Buttons on the left are sound generators of 7 different categories. There are also an option to
|
||||
mutate the current sound slightly, and an option to generate a completely random sound.
|
||||
These are the buttons you'll be working with most of the time.
|
||||
|
||||
After a random sound is generated with the generator buttons, you can fine-tune the sound with
|
||||
the controls on the right.
|
||||
|
||||
The generated sound can be saved and edited later as an `.sfxr` file.
|
||||
These files only contain the generator parameters, so they are only about 100 bytes.
|
||||
But they can be used directly as regular `AudioStream`s.
|
||||
|
||||
If you want to reuse an existing sound from the original sfxr, make sure to save it with an
|
||||
`.sfxr` extension. You can also load & edit the `.sfxr` file with the original sfxr.
|
||||
|
||||
**Note:** Due to performance constraints with GDScript, the editor may freeze a bit when generating
|
||||
long sounds. This only happens in-editor.
|
||||
Using `.sfxr` files in-game won't generate anything at runtime.
|
467
addons/gdfxr/SFXRConfig.gd
Normal file
467
addons/gdfxr/SFXRConfig.gd
Normal file
@ -0,0 +1,467 @@
|
||||
# GDScript port of the original SFXR
|
||||
# https://www.drpetter.se/project_sfxr.html
|
||||
|
||||
enum WaveType {
|
||||
SQUARE_WAVE,
|
||||
SAWTOOTH,
|
||||
SINE_WAVE,
|
||||
NOISE,
|
||||
}
|
||||
|
||||
enum Category {
|
||||
PICKUP_COIN,
|
||||
LASER_SHOOT,
|
||||
EXPLOSION,
|
||||
POWERUP,
|
||||
HIT_HURT,
|
||||
JUMP,
|
||||
BLIP_SELECT,
|
||||
}
|
||||
|
||||
var wave_type: int = WaveType.SQUARE_WAVE
|
||||
|
||||
var p_env_attack := 0.0 # Attack Time
|
||||
var p_env_sustain := 0.3 # Sustain Time
|
||||
var p_env_punch := 0.0 # Sustain Punch
|
||||
var p_env_decay := 0.4 # Decay Time
|
||||
|
||||
var p_base_freq := 0.3 # Start Frequency
|
||||
var p_freq_limit := 0.0 # Min Frequency
|
||||
var p_freq_ramp := 0.0 # Slide
|
||||
var p_freq_dramp := 0.0 # Delta Slide
|
||||
var p_vib_strength := 0.0 # Vibrato Depth
|
||||
var p_vib_speed := 0.0 # Vibrato Speed
|
||||
|
||||
var p_duty := 0.0 # Square Duty
|
||||
var p_duty_ramp := 0.0 # Duty Sweep
|
||||
|
||||
var p_arp_mod := 0.0 # Change Amount
|
||||
var p_arp_speed := 0.0 # Change Speed
|
||||
|
||||
var p_repeat_speed := 0.0 # Repeat Speed
|
||||
|
||||
var p_pha_offset := 0.0 # Phaser Offset
|
||||
var p_pha_ramp := 0.0 # Phaser Weep
|
||||
|
||||
var p_lpf_freq := 1.0 # Lp Filter Cutoff
|
||||
var p_lpf_ramp := 0.0 # Lp Filter Cutoff Sweep
|
||||
var p_lpf_resonance := 0.0 # Lp Filter Resonance
|
||||
var p_hpf_freq := 0.0 # Hp Filter Cutoff
|
||||
var p_hpf_ramp := 0.0 # Hp Filter Cutoff Sweep
|
||||
|
||||
var sound_vol := 0.5
|
||||
|
||||
|
||||
func load(path: String) -> int: # Error
|
||||
var f := File.new()
|
||||
var err := f.open(path, File.READ)
|
||||
if err != OK:
|
||||
return err
|
||||
|
||||
var version := f.get_32()
|
||||
if not [100, 101, 102].has(version):
|
||||
return ERR_FILE_UNRECOGNIZED
|
||||
|
||||
wave_type = f.get_32()
|
||||
sound_vol = 0.5
|
||||
if version == 102:
|
||||
sound_vol = f.get_float()
|
||||
|
||||
p_base_freq = f.get_float()
|
||||
p_freq_limit = f.get_float()
|
||||
p_freq_ramp = f.get_float()
|
||||
if version >= 101:
|
||||
p_freq_dramp = f.get_float()
|
||||
p_duty = f.get_float()
|
||||
p_duty_ramp = f.get_float()
|
||||
|
||||
p_vib_strength = f.get_float()
|
||||
p_vib_speed = f.get_float()
|
||||
f.get_float() # p_vib_delay
|
||||
|
||||
p_env_attack = f.get_float()
|
||||
p_env_sustain = f.get_float()
|
||||
p_env_decay = f.get_float()
|
||||
p_env_punch = f.get_float()
|
||||
|
||||
f.get_8() # filter_on
|
||||
p_lpf_resonance = f.get_float()
|
||||
p_lpf_freq = f.get_float()
|
||||
p_lpf_ramp = f.get_float()
|
||||
p_hpf_freq = f.get_float()
|
||||
p_hpf_ramp = f.get_float()
|
||||
|
||||
p_pha_offset = f.get_float()
|
||||
p_pha_ramp = f.get_float()
|
||||
|
||||
p_repeat_speed = f.get_float()
|
||||
|
||||
if version >= 101:
|
||||
p_arp_speed = f.get_float()
|
||||
p_arp_mod = f.get_float()
|
||||
|
||||
return OK
|
||||
|
||||
|
||||
func save(path: String) -> int: # Error
|
||||
var f := File.new()
|
||||
var err := f.open(path, File.WRITE)
|
||||
if err != OK:
|
||||
return err
|
||||
|
||||
f.store_32(102)
|
||||
f.store_32(wave_type)
|
||||
f.store_float(sound_vol)
|
||||
|
||||
f.store_float(p_base_freq)
|
||||
f.store_float(p_freq_limit)
|
||||
f.store_float(p_freq_ramp)
|
||||
f.store_float(p_freq_dramp)
|
||||
f.store_float(p_duty)
|
||||
f.store_float(p_duty_ramp)
|
||||
|
||||
f.store_float(p_vib_strength)
|
||||
f.store_float(p_vib_speed)
|
||||
f.store_float(0) # p_vib_delay
|
||||
|
||||
f.store_float(p_env_attack)
|
||||
f.store_float(p_env_sustain)
|
||||
f.store_float(p_env_decay)
|
||||
f.store_float(p_env_punch)
|
||||
|
||||
f.store_8(true) # filter_on
|
||||
f.store_float(p_lpf_resonance)
|
||||
f.store_float(p_lpf_freq)
|
||||
f.store_float(p_lpf_ramp)
|
||||
f.store_float(p_hpf_freq)
|
||||
f.store_float(p_hpf_ramp)
|
||||
|
||||
f.store_float(p_pha_offset)
|
||||
f.store_float(p_pha_ramp)
|
||||
|
||||
f.store_float(p_repeat_speed)
|
||||
|
||||
f.store_float(p_arp_speed)
|
||||
f.store_float(p_arp_mod)
|
||||
|
||||
return OK
|
||||
|
||||
|
||||
func randomize_in_category(category: int) -> void:
|
||||
reset()
|
||||
|
||||
match category:
|
||||
Category.PICKUP_COIN:
|
||||
p_base_freq = rand_range(0.4, 0.9)
|
||||
p_env_attack = 0.0
|
||||
p_env_sustain = rand_range(0.0, 0.1)
|
||||
p_env_decay = rand_range(0.1, 0.5)
|
||||
p_env_punch = rand_range(0.3, 0.6)
|
||||
if randi() % 2:
|
||||
p_arp_speed = rand_range(0.5, 0.7)
|
||||
p_arp_mod = rand_range(0.2, 0.6)
|
||||
|
||||
Category.LASER_SHOOT:
|
||||
wave_type = randi() % 3
|
||||
if wave_type == 2 and randi() % 2:
|
||||
wave_type = randi() % 2
|
||||
p_base_freq = rand_range(0.5, 1.0)
|
||||
p_freq_limit = max(0.2, p_base_freq - rand_range(0.2, 0.8))
|
||||
p_freq_ramp = rand_range(-0.35, -0.15)
|
||||
if randi() % 3 == 0:
|
||||
p_base_freq = rand_range(0.3, 0.9)
|
||||
p_freq_limit = rand_range(0, 0.1)
|
||||
p_freq_ramp = rand_range(-0.65, -0.35)
|
||||
if randi() % 2:
|
||||
p_duty = rand_range(0, 0.5)
|
||||
p_duty_ramp = rand_range(0, 0.2)
|
||||
else:
|
||||
p_duty = rand_range(0.4, 0.9)
|
||||
p_duty_ramp = rand_range(-0.7, 0)
|
||||
p_env_attack = 0.0
|
||||
p_env_sustain = rand_range(0.1, 0.3)
|
||||
p_env_decay = rand_range(0.0, 0.4)
|
||||
if randi() % 2:
|
||||
p_env_punch = rand_range(0, 0.3)
|
||||
if randi() % 3 == 0:
|
||||
p_pha_offset = rand_range(0, 0.2)
|
||||
p_pha_ramp = rand_range(0, 0.2)
|
||||
if randi() % 2:
|
||||
p_hpf_freq = rand_range(0, 0.3)
|
||||
|
||||
Category.EXPLOSION:
|
||||
wave_type = WaveType.NOISE
|
||||
if randi() % 2:
|
||||
p_base_freq = rand_range(0.1, 0.4)
|
||||
p_freq_ramp = rand_range(-0.1, 0.3)
|
||||
else:
|
||||
p_base_freq = rand_range(0.2, 0.9)
|
||||
p_freq_ramp = rand_range(-0.4, -0.2)
|
||||
p_base_freq *= p_base_freq
|
||||
if randi() % 5 == 0:
|
||||
p_freq_ramp = 0.0
|
||||
if randi() % 3 == 0:
|
||||
p_repeat_speed = rand_range(0.3, 0.8)
|
||||
p_env_attack = 0.0
|
||||
p_env_sustain = rand_range(0.1, 0.4)
|
||||
p_env_decay = rand_range(0, 0.5)
|
||||
if randi() % 2:
|
||||
p_pha_offset = rand_range(-0.3, 0.6)
|
||||
p_pha_ramp = rand_range(-0.3, 0)
|
||||
p_env_punch = rand_range(0.2, 0.8)
|
||||
if randi() % 2:
|
||||
p_vib_strength = rand_range(0, 0.7)
|
||||
p_vib_speed = rand_range(0, 0.6)
|
||||
if randi() % 3:
|
||||
p_arp_speed = rand_range(0.6, 0.9)
|
||||
p_arp_mod = rand_range(-0.8, 0.8)
|
||||
|
||||
Category.POWERUP:
|
||||
if randi() % 2:
|
||||
wave_type = WaveType.SAWTOOTH
|
||||
else:
|
||||
p_duty = rand_range(0, 0.6)
|
||||
if randi() % 2:
|
||||
p_base_freq = rand_range(0.2, 0.5)
|
||||
p_freq_ramp = rand_range(0.1, 0.5)
|
||||
p_repeat_speed = rand_range(0.4, 0.8)
|
||||
else:
|
||||
p_base_freq = rand_range(0.2, 0.5)
|
||||
p_freq_ramp = rand_range(0.05, 0.25)
|
||||
if randi() % 2:
|
||||
p_vib_strength = rand_range(0, 0.7)
|
||||
p_vib_speed = rand_range(0, 0.6)
|
||||
p_env_attack = 0.0
|
||||
p_env_sustain = rand_range(0, 0.4)
|
||||
p_env_decay = rand_range(0.1, 0.5)
|
||||
|
||||
Category.HIT_HURT:
|
||||
wave_type = randi() % 3
|
||||
match wave_type:
|
||||
WaveType.SINE_WAVE:
|
||||
wave_type = WaveType.NOISE
|
||||
WaveType.SQUARE_WAVE:
|
||||
p_duty = rand_range(0, 0.6)
|
||||
p_base_freq = rand_range(0.2, 0.8)
|
||||
p_freq_ramp = rand_range(-0.7, -0.3)
|
||||
p_env_attack = 0.0
|
||||
p_env_sustain = rand_range(0, 0.1)
|
||||
p_env_decay = rand_range(0.1, 0.3)
|
||||
if randi() % 2:
|
||||
p_hpf_freq = rand_range(0, 0.3)
|
||||
|
||||
Category.JUMP:
|
||||
wave_type = WaveType.SQUARE_WAVE
|
||||
p_duty = rand_range(0, 0.6)
|
||||
p_base_freq = rand_range(0.3, 0.6)
|
||||
p_freq_ramp = rand_range(0.1, 0.3)
|
||||
p_env_attack = 0.0
|
||||
p_env_sustain = rand_range(0.1, 0.4)
|
||||
p_env_decay = rand_range(0.1, 0.3)
|
||||
if randi() % 2:
|
||||
p_hpf_freq = rand_range(0, 0.3)
|
||||
if randi() % 2:
|
||||
p_lpf_freq = rand_range(0.4, 1.0)
|
||||
|
||||
Category.BLIP_SELECT:
|
||||
wave_type = randi() % 2
|
||||
if wave_type == WaveType.SQUARE_WAVE:
|
||||
p_duty = rand_range(0, 0.6)
|
||||
p_base_freq = rand_range(0.2, 0.6)
|
||||
p_env_attack = 0.0
|
||||
p_env_sustain = rand_range(0.1, 0.2)
|
||||
p_env_decay = rand_range(0, 0.2)
|
||||
p_hpf_freq = 0.1
|
||||
|
||||
|
||||
func randomize() -> void:
|
||||
p_base_freq = pow(rand_range(-1.0, +1.0), 2.0)
|
||||
if randi() % 2:
|
||||
p_base_freq = pow(rand_range(-1.0, +1.0), 3.0) + 0.5
|
||||
p_freq_limit = 0.0
|
||||
p_freq_ramp = pow(rand_range(-1.0, +1.0), 5.0)
|
||||
if p_base_freq > 0.7 and p_freq_ramp > 0.2:
|
||||
p_freq_ramp = -p_freq_ramp
|
||||
if p_base_freq < 0.2 and p_freq_ramp < -0.05:
|
||||
p_freq_ramp = -p_freq_ramp
|
||||
p_freq_dramp = pow(rand_range(-1.0, +1.0), 3.0)
|
||||
p_duty = rand_range(-1.0, +1.0)
|
||||
p_duty_ramp = pow(rand_range(-1.0, +1.0), 3.0)
|
||||
p_vib_strength = pow(rand_range(-1.0, +1.0), 3.0)
|
||||
p_vib_speed = rand_range(-1.0, +1.0)
|
||||
# p_vib_delay = rand_range(-1.0, +1.0)
|
||||
p_env_attack = pow(rand_range(-1.0, +1.0), 3.0)
|
||||
p_env_sustain = pow(rand_range(-1.0, +1.0), 2.0)
|
||||
p_env_decay = rand_range(-1.0, +1.0)
|
||||
p_env_punch = pow(rand_range(0, 0.8), 2.0)
|
||||
if p_env_attack + p_env_sustain + p_env_decay < 0.2:
|
||||
p_env_sustain += rand_range(0.2, 0.5)
|
||||
p_env_decay += rand_range(0.2, 0.5)
|
||||
p_lpf_resonance = rand_range(-1.0, +1.0)
|
||||
p_lpf_freq = 1.0 - pow(randf(), 3.0)
|
||||
p_lpf_ramp = pow(rand_range(-1.0, +1.0), 3.0)
|
||||
if p_lpf_freq < 0.1 and p_lpf_ramp < -0.05:
|
||||
p_lpf_ramp = -p_lpf_ramp
|
||||
p_hpf_freq = pow(randf(), 5.0)
|
||||
p_hpf_ramp = pow(rand_range(-1.0, +1.0), 5.0)
|
||||
p_pha_offset = pow(rand_range(-1.0, +1.0), 3.0)
|
||||
p_pha_ramp = pow(rand_range(-1.0, +1.0), 3.0)
|
||||
p_repeat_speed = rand_range(-1.0, +1.0)
|
||||
p_arp_speed = rand_range(-1.0, +1.0)
|
||||
p_arp_mod = rand_range(-1.0, +1.0)
|
||||
|
||||
|
||||
func mutate() -> void:
|
||||
if randi() % 2:
|
||||
p_base_freq += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_freq_limit += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_freq_ramp += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_freq_dramp += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_duty += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_duty_ramp += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_vib_strength += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_vib_speed += rand_range(-0.05, +0.05)
|
||||
# if randi() % 2:
|
||||
# p_vib_delay += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_env_attack += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_env_sustain += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_env_decay += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_env_punch += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_lpf_resonance += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_lpf_freq += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_lpf_ramp += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_hpf_freq += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_hpf_ramp += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_pha_offset += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_pha_ramp += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_repeat_speed += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_arp_speed += rand_range(-0.05, +0.05)
|
||||
if randi() % 2:
|
||||
p_arp_mod += rand_range(-0.05, +0.05)
|
||||
|
||||
|
||||
func reset():
|
||||
wave_type = WaveType.SQUARE_WAVE
|
||||
|
||||
p_base_freq = 0.3
|
||||
p_freq_limit = 0.0
|
||||
p_freq_ramp = 0.0
|
||||
p_freq_dramp = 0.0
|
||||
p_duty = 0.0
|
||||
p_duty_ramp = 0.0
|
||||
|
||||
p_vib_strength = 0.0
|
||||
p_vib_speed = 0.0
|
||||
# p_vib_delay = 0.0
|
||||
|
||||
p_env_attack = 0.0
|
||||
p_env_sustain = 0.3
|
||||
p_env_decay = 0.4
|
||||
p_env_punch = 0.0
|
||||
|
||||
# filter_on = false
|
||||
p_lpf_resonance = 0.0
|
||||
p_lpf_freq = 1.0
|
||||
p_lpf_ramp = 0.0
|
||||
p_hpf_freq = 0.0
|
||||
p_hpf_ramp = 0.0
|
||||
|
||||
p_pha_offset = 0.0
|
||||
p_pha_ramp = 0.0
|
||||
|
||||
p_repeat_speed = 0.0
|
||||
|
||||
p_arp_speed = 0.0
|
||||
p_arp_mod = 0.0
|
||||
|
||||
|
||||
func copy_from(other: Reference) -> void: # SFXRConfig
|
||||
wave_type = other.wave_type
|
||||
|
||||
p_env_attack = other.p_env_attack
|
||||
p_env_sustain = other.p_env_sustain
|
||||
p_env_punch = other.p_env_punch
|
||||
p_env_decay = other.p_env_decay
|
||||
|
||||
p_base_freq = other.p_base_freq
|
||||
p_freq_limit = other.p_freq_limit
|
||||
p_freq_ramp = other.p_freq_ramp
|
||||
p_freq_dramp = other.p_freq_dramp
|
||||
p_vib_strength = other.p_vib_strength
|
||||
p_vib_speed = other.p_vib_speed
|
||||
|
||||
p_duty = other.p_duty
|
||||
p_duty_ramp = other.p_duty_ramp
|
||||
|
||||
p_arp_mod = other.p_arp_mod
|
||||
p_arp_speed = other.p_arp_speed
|
||||
|
||||
p_repeat_speed = other.p_repeat_speed
|
||||
|
||||
p_pha_offset = other.p_pha_offset
|
||||
p_pha_ramp = other.p_pha_ramp
|
||||
|
||||
p_lpf_freq = other.p_lpf_freq
|
||||
p_lpf_ramp = other.p_lpf_ramp
|
||||
p_lpf_resonance = other.p_lpf_resonance
|
||||
p_hpf_freq = other.p_hpf_freq
|
||||
p_hpf_ramp = other.p_hpf_ramp
|
||||
|
||||
sound_vol = other.sound_vol
|
||||
|
||||
|
||||
func is_equal(other: Reference) -> bool: # SFXRConfig
|
||||
return (
|
||||
wave_type == other.wave_type
|
||||
|
||||
and p_env_attack == other.p_env_attack
|
||||
and p_env_sustain == other.p_env_sustain
|
||||
and p_env_punch == other.p_env_punch
|
||||
and p_env_decay == other.p_env_decay
|
||||
|
||||
and p_base_freq == other.p_base_freq
|
||||
and p_freq_limit == other.p_freq_limit
|
||||
and p_freq_ramp == other.p_freq_ramp
|
||||
and p_freq_dramp == other.p_freq_dramp
|
||||
and p_vib_strength == other.p_vib_strength
|
||||
and p_vib_speed == other.p_vib_speed
|
||||
|
||||
and p_duty == other.p_duty
|
||||
and p_duty_ramp == other.p_duty_ramp
|
||||
|
||||
and p_arp_mod == other.p_arp_mod
|
||||
and p_arp_speed == other.p_arp_speed
|
||||
|
||||
and p_repeat_speed == other.p_repeat_speed
|
||||
|
||||
and p_pha_offset == other.p_pha_offset
|
||||
and p_pha_ramp == other.p_pha_ramp
|
||||
|
||||
and p_lpf_freq == other.p_lpf_freq
|
||||
and p_lpf_ramp == other.p_lpf_ramp
|
||||
and p_lpf_resonance == other.p_lpf_resonance
|
||||
and p_hpf_freq == other.p_hpf_freq
|
||||
and p_hpf_ramp == other.p_hpf_ramp
|
||||
|
||||
and sound_vol == other.sound_vol
|
||||
)
|
249
addons/gdfxr/SFXRGenerator.gd
Normal file
249
addons/gdfxr/SFXRGenerator.gd
Normal file
@ -0,0 +1,249 @@
|
||||
# GDScript port of the original SFXR
|
||||
# https://www.drpetter.se/project_sfxr.html
|
||||
|
||||
const SFXRConfig := preload("SFXRConfig.gd")
|
||||
|
||||
const master_vol := 0.05
|
||||
|
||||
var _config: SFXRConfig
|
||||
|
||||
var rep_time: int
|
||||
var rep_limit: int
|
||||
var arp_time: int
|
||||
var arp_limit: int
|
||||
var arp_mod: float
|
||||
var period: int
|
||||
var fperiod: float
|
||||
var fmaxperiod: float
|
||||
var fslide: float
|
||||
var fdslide: float
|
||||
var vib_amp: float
|
||||
var vib_phase: float
|
||||
var vib_speed: float
|
||||
var square_duty: float
|
||||
var square_slide: float
|
||||
var env_vol: float
|
||||
var env_length := PoolIntArray([0, 0, 0])
|
||||
var phase: int
|
||||
var fphase: float
|
||||
var fdphase: float
|
||||
var iphase: int
|
||||
var flthp: float
|
||||
var flthp_d: float
|
||||
var noise_buffer := PoolRealArray([])
|
||||
var phaser_buffer := PoolRealArray([])
|
||||
var ipp: int
|
||||
var fltp: float
|
||||
var fltdp: float
|
||||
var fltw: float
|
||||
var fltw_d: float
|
||||
var fltdmp: float
|
||||
var fltphp: float
|
||||
|
||||
|
||||
func generate_audio_stream(config: SFXRConfig) -> AudioStreamSample:
|
||||
var stream := AudioStreamSample.new()
|
||||
stream.format = AudioStreamSample.FORMAT_8_BITS
|
||||
stream.mix_rate = 44100
|
||||
|
||||
_config = config
|
||||
stream.data = _generate_samples()
|
||||
_config = null
|
||||
|
||||
return stream
|
||||
|
||||
|
||||
func generate_samples(config: SFXRConfig) -> PoolByteArray:
|
||||
_config = config
|
||||
var data := _generate_samples()
|
||||
_config = null
|
||||
return data
|
||||
|
||||
|
||||
func _generate_samples() -> PoolByteArray:
|
||||
_reset_sample(true)
|
||||
|
||||
var playing_sample := true
|
||||
var env_stage := 0
|
||||
var env_time := 0
|
||||
var output := PoolByteArray([])
|
||||
|
||||
# SynthSample
|
||||
while playing_sample:
|
||||
rep_time += 1
|
||||
if rep_limit != 0 and rep_time >= rep_limit:
|
||||
rep_time = 0
|
||||
_reset_sample(false)
|
||||
|
||||
# frequency envelopes/arpeggios
|
||||
arp_time += 1
|
||||
if arp_limit != 0 and arp_time >= arp_limit:
|
||||
arp_limit = 0
|
||||
fperiod *= arp_mod
|
||||
fslide += fdslide
|
||||
fperiod *= fslide
|
||||
if fperiod > fmaxperiod:
|
||||
fperiod = fmaxperiod
|
||||
if _config.p_freq_limit > 0:
|
||||
playing_sample = false
|
||||
var rfperiod := fperiod
|
||||
if vib_amp > 0.0:
|
||||
vib_phase += vib_speed
|
||||
rfperiod = fperiod * (1.0 + sin(vib_phase) * vib_amp)
|
||||
period = int(max(8, rfperiod))
|
||||
square_duty = clamp(square_duty + square_slide, 0.0, 0.5)
|
||||
|
||||
# volume envelope
|
||||
env_time += 1 # Note: this skips 0 of env_stage 0. Seems problematic.
|
||||
if env_time > env_length[env_stage]:
|
||||
env_time = 0
|
||||
|
||||
while true:
|
||||
env_stage += 1
|
||||
if env_stage == 3:
|
||||
playing_sample = false
|
||||
break
|
||||
if env_length[env_stage] != 0:
|
||||
break
|
||||
|
||||
match env_stage:
|
||||
0:
|
||||
env_vol = float(env_time) / env_length[0]
|
||||
1:
|
||||
env_vol = 1.0 + pow(1.0 - float(env_time) / env_length[1], 1.0) * 2.0 * _config.p_env_punch
|
||||
2:
|
||||
env_vol = 1.0 - float(env_time) / env_length[2]
|
||||
|
||||
# phaser step
|
||||
fphase += fdphase
|
||||
iphase = int(min(abs(fphase), 1023))
|
||||
|
||||
if flthp_d != 0.0:
|
||||
flthp = clamp(flthp * flthp_d, 0.00001, 0.1)
|
||||
|
||||
var ssample := 0.0
|
||||
for si in 8: # 8x supersampling
|
||||
var sample := 0.0
|
||||
phase += 1
|
||||
if phase >= period:
|
||||
phase %= period
|
||||
if _config.wave_type == SFXRConfig.WaveType.NOISE:
|
||||
for j in 32:
|
||||
noise_buffer[j] = rand_range(-1.0, +1.0)
|
||||
|
||||
# base waveform
|
||||
var fp := float(phase) / period
|
||||
match _config.wave_type:
|
||||
SFXRConfig.WaveType.SQUARE_WAVE:
|
||||
sample = 0.5 if fp < square_duty else -0.5
|
||||
SFXRConfig.WaveType.SAWTOOTH:
|
||||
sample = 1.0 - fp * 2
|
||||
SFXRConfig.WaveType.SINE_WAVE:
|
||||
sample = sin(fp * 2 * PI)
|
||||
SFXRConfig.WaveType.NOISE:
|
||||
sample = noise_buffer[phase * 32 / period]
|
||||
|
||||
# lp filter
|
||||
var pp := fltp
|
||||
fltw = clamp(fltw * fltw_d, 0.0, 0.1)
|
||||
if _config.p_lpf_freq == 1.0:
|
||||
fltp = sample
|
||||
fltdp = 0.0
|
||||
else:
|
||||
fltdp += (sample - fltp) * fltw
|
||||
fltdp -= fltdp * fltdmp
|
||||
fltp += fltdp
|
||||
|
||||
# hp filter
|
||||
fltphp += fltp - pp
|
||||
fltphp -= fltphp * flthp
|
||||
sample = fltphp
|
||||
|
||||
# phaser
|
||||
phaser_buffer[ipp & 1023] = sample
|
||||
sample += phaser_buffer[(ipp - iphase + 1024) & 1023]
|
||||
ipp = (ipp + 1) & 1023
|
||||
|
||||
# final accumulation and envelope application
|
||||
ssample += sample * env_vol
|
||||
|
||||
ssample = ssample / 8 * master_vol
|
||||
|
||||
ssample *= 2.0 * _config.sound_vol
|
||||
|
||||
ssample *= 4.0 # arbitrary gain to get reasonable output volume...
|
||||
ssample = clamp(ssample, -1.0, +1.0)
|
||||
|
||||
var filesample := int((1 + ssample) / 2 * 255)
|
||||
|
||||
# This is a hack, AudioStreamSample wants a int8_t directly interpreted as uint8_t
|
||||
filesample += 128
|
||||
if filesample > 255:
|
||||
filesample -= 255
|
||||
|
||||
output.push_back(filesample)
|
||||
return output
|
||||
|
||||
|
||||
func _reset_sample(restart: bool) -> void:
|
||||
fperiod = 100.0 / (_config.p_base_freq * _config.p_base_freq + 0.001)
|
||||
period = int(fperiod)
|
||||
fmaxperiod = 100.0 / (_config.p_freq_limit * _config.p_freq_limit + 0.001)
|
||||
fslide = 1.0 - pow(_config.p_freq_ramp, 3.0) * 0.01
|
||||
fdslide = -pow(_config.p_freq_dramp, 3.0) * 0.000001
|
||||
square_duty = 0.5 - _config.p_duty * 0.5
|
||||
square_slide = -_config.p_duty_ramp * 0.00005
|
||||
if _config.p_arp_mod >= 0.0:
|
||||
arp_mod = 1.0 - pow(_config.p_arp_mod, 2.0) * 0.9
|
||||
else:
|
||||
arp_mod = 1.0 + pow(_config.p_arp_mod, 2.0) * 10.0
|
||||
arp_time = 0
|
||||
arp_limit = int(pow(1.0 - _config.p_arp_speed, 2.0) * 20000 + 32)
|
||||
if _config.p_arp_speed == 1.0:
|
||||
arp_limit = 0
|
||||
|
||||
if restart:
|
||||
phase = 0
|
||||
|
||||
# Reset filter.
|
||||
fltp = 0.0
|
||||
fltdp = 0.0
|
||||
fltw = pow(_config.p_lpf_freq, 3.0) * 0.1
|
||||
fltw_d = 1.0 + _config.p_lpf_ramp * 0.0001
|
||||
fltdmp = min(5.0 / (1.0 + pow(_config.p_lpf_resonance, 2.0) * 20.0) * (0.01 + fltw), 0.8)
|
||||
fltphp = 0.0
|
||||
flthp = pow(_config.p_hpf_freq, 2.0) * 0.1
|
||||
flthp_d = 1.0 + _config.p_hpf_ramp * 0.0003
|
||||
|
||||
# Reset vibrato
|
||||
vib_phase = 0.0
|
||||
vib_speed = pow(_config.p_vib_speed, 2.0) * 0.01
|
||||
vib_amp = _config.p_vib_strength * 0.5
|
||||
|
||||
# Reset envelope
|
||||
env_vol = 0.0
|
||||
env_length[0] = int(_config.p_env_attack * _config.p_env_attack * 100000.0)
|
||||
env_length[1] = int(_config.p_env_sustain * _config.p_env_sustain * 100000.0)
|
||||
env_length[2] = int(_config.p_env_decay * _config.p_env_decay * 100000.0)
|
||||
|
||||
fphase = pow(_config.p_pha_offset, 2.0) * 1020.0
|
||||
if _config.p_pha_offset < 0.0:
|
||||
fphase = -fphase
|
||||
fdphase = pow(_config.p_pha_ramp, 2.0) * 1.0
|
||||
if _config.p_pha_ramp < 0.0:
|
||||
fdphase = -fdphase
|
||||
iphase = int(abs(fphase))
|
||||
ipp = 0
|
||||
|
||||
phaser_buffer.resize(1024)
|
||||
for i in phaser_buffer.size():
|
||||
phaser_buffer[i] = 0.0
|
||||
|
||||
noise_buffer.resize(32)
|
||||
for i in noise_buffer.size():
|
||||
noise_buffer[i] = rand_range(-1.0, +1.0)
|
||||
|
||||
rep_time = 0
|
||||
rep_limit = int(pow(1.0 - _config.p_repeat_speed, 2.0) * 20000 + 32)
|
||||
if _config.p_repeat_speed == 0.0:
|
||||
rep_limit = 0
|
235
addons/gdfxr/editor/Editor.gd
Normal file
235
addons/gdfxr/editor/Editor.gd
Normal file
@ -0,0 +1,235 @@
|
||||
tool
|
||||
extends Container
|
||||
|
||||
const SFXRConfig := preload("../SFXRConfig.gd")
|
||||
const SFXRGenerator := preload("../SFXRGenerator.gd")
|
||||
|
||||
var plugin: EditorPlugin
|
||||
|
||||
var _config := SFXRConfig.new()
|
||||
var _config_defaults := SFXRConfig.new()
|
||||
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
|
||||
|
||||
onready var audio_player := $AudioStreamPlayer as AudioStreamPlayer
|
||||
onready var filename_label := find_node("Filename") as Label
|
||||
onready var save_button := find_node("Save") as Button
|
||||
onready var restore_button := find_node("Restore") as Button
|
||||
onready var version_button := find_node("VersionButton")
|
||||
onready var translator := $PluginTranslator
|
||||
|
||||
|
||||
func _ready():
|
||||
if not plugin:
|
||||
return # Running in the edited scene instead of from Plugin
|
||||
|
||||
for child in get_children():
|
||||
_hook_plugin(child)
|
||||
|
||||
var params := find_node("Params") as Container
|
||||
for category in params.get_children():
|
||||
for control in category.get_children():
|
||||
_param_map[control.parameter] = control
|
||||
control.connect("param_changed", self, "_on_param_changed")
|
||||
control.connect("param_reset", self, "_on_param_reset")
|
||||
|
||||
_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_node("ScrollContainer").add_stylebox_override("bg", get_stylebox("bg", "Tree"))
|
||||
|
||||
|
||||
func edit(path: String) -> void:
|
||||
if _modified:
|
||||
_popup_confirm(
|
||||
translator.tr("There are unsaved changes.\nOpen '%s' anyway?") % path,
|
||||
"_set_editing_file", [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 _popup_confirm(content: String, callback: String, binds := []) -> void:
|
||||
var dialog := ConfirmationDialog.new()
|
||||
add_child(dialog)
|
||||
dialog.dialog_text = content
|
||||
dialog.window_title = translator.tr("SFXR Editor")
|
||||
dialog.connect("confirmed", self, callback, binds)
|
||||
dialog.connect("popup_hide", dialog, "queue_free")
|
||||
dialog.popup_centered()
|
||||
|
||||
|
||||
func _popup_message(content: String) -> void:
|
||||
var dialog := AcceptDialog.new()
|
||||
add_child(dialog)
|
||||
dialog.dialog_text = content
|
||||
dialog.window_title = translator.tr("SFXR Editor")
|
||||
dialog.connect("popup_hide", dialog, "queue_free")
|
||||
dialog.popup_centered()
|
||||
|
||||
|
||||
func _popup_file_dialog(mode: int, callback: String) -> void:
|
||||
var dialog := EditorFileDialog.new()
|
||||
add_child(dialog)
|
||||
dialog.access = EditorFileDialog.ACCESS_RESOURCES
|
||||
dialog.mode = mode
|
||||
dialog.add_filter("*.sfxr; %s" % translator.tr("SFXR Audio"))
|
||||
dialog.connect("popup_hide", dialog, "queue_free")
|
||||
dialog.connect("file_selected", self, callback)
|
||||
dialog.popup_centered_ratio()
|
||||
|
||||
|
||||
func _reset_defaults() -> void: # SFXRConfig
|
||||
_config_defaults.copy_from(_config)
|
||||
_set_modified(false)
|
||||
_sync_ui()
|
||||
|
||||
|
||||
func _set_editing_file(path: String) -> int: # Error
|
||||
if path.empty():
|
||||
_config.reset()
|
||||
audio_player.stream = null
|
||||
else:
|
||||
var err := _config.load(path)
|
||||
if err != OK:
|
||||
_popup_message(translator.tr("'%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.empty()
|
||||
var base = _path if has_file else translator.tr("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 _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
|
||||
|
||||
|
||||
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()
|
||||
else:
|
||||
_config.randomize_in_category(category)
|
||||
|
||||
_set_modified(true)
|
||||
_sync_ui()
|
||||
_on_Play_pressed(true)
|
||||
|
||||
|
||||
func _on_Mutate_pressed():
|
||||
_config.mutate()
|
||||
|
||||
_set_modified(true)
|
||||
_sync_ui()
|
||||
_on_Play_pressed(true)
|
||||
|
||||
|
||||
func _on_Restore_pressed():
|
||||
_set_editing_file(_path)
|
||||
|
||||
|
||||
func _on_New_pressed():
|
||||
if _modified:
|
||||
_popup_confirm(
|
||||
translator.tr("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.empty():
|
||||
_popup_file_dialog(EditorFileDialog.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.tr("There are unsaved changes.\nLoad anyway?"),
|
||||
"_popup_file_dialog", [EditorFileDialog.MODE_OPEN_FILE, "_set_editing_file"]
|
||||
)
|
||||
else:
|
||||
_popup_file_dialog(EditorFileDialog.MODE_OPEN_FILE, "_set_editing_file")
|
||||
|
380
addons/gdfxr/editor/Editor.tscn
Normal file
380
addons/gdfxr/editor/Editor.tscn
Normal file
@ -0,0 +1,380 @@
|
||||
[gd_scene load_steps=7 format=2]
|
||||
|
||||
[ext_resource path="res://addons/gdfxr/editor/EditorIconButton.gd" type="Script" id=1]
|
||||
[ext_resource path="res://addons/gdfxr/editor/Editor.gd" type="Script" id=2]
|
||||
[ext_resource path="res://addons/gdfxr/editor/ParamSlider.tscn" type="PackedScene" id=3]
|
||||
[ext_resource path="res://addons/gdfxr/editor/ParamOption.tscn" type="PackedScene" id=4]
|
||||
[ext_resource path="res://addons/gdfxr/editor/PluginTranslator.tscn" type="PackedScene" id=5]
|
||||
[ext_resource path="res://addons/gdfxr/editor/VersionButton.tscn" type="PackedScene" id=6]
|
||||
|
||||
[node name="Editor" type="VBoxContainer"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 2 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
|
||||
|
||||
[node name="Toolbar" type="HBoxContainer" parent="."]
|
||||
margin_right = 1024.0
|
||||
margin_bottom = 22.0
|
||||
|
||||
[node name="New" type="ToolButton" parent="Toolbar"]
|
||||
margin_right = 12.0
|
||||
margin_bottom = 22.0
|
||||
hint_tooltip = "New"
|
||||
script = ExtResource( 1 )
|
||||
icon_name = "New"
|
||||
|
||||
[node name="Load" type="ToolButton" parent="Toolbar"]
|
||||
margin_left = 16.0
|
||||
margin_right = 28.0
|
||||
margin_bottom = 22.0
|
||||
hint_tooltip = "Load"
|
||||
script = ExtResource( 1 )
|
||||
icon_name = "Load"
|
||||
|
||||
[node name="Save" type="ToolButton" parent="Toolbar"]
|
||||
margin_left = 32.0
|
||||
margin_right = 44.0
|
||||
margin_bottom = 22.0
|
||||
hint_tooltip = "Save"
|
||||
script = ExtResource( 1 )
|
||||
icon_name = "Save"
|
||||
|
||||
[node name="VSeparator" type="VSeparator" parent="Toolbar"]
|
||||
margin_left = 48.0
|
||||
margin_right = 52.0
|
||||
margin_bottom = 22.0
|
||||
|
||||
[node name="Play" type="Button" parent="Toolbar"]
|
||||
margin_left = 56.0
|
||||
margin_right = 136.0
|
||||
margin_bottom = 22.0
|
||||
rect_min_size = Vector2( 80, 0 )
|
||||
size_flags_horizontal = 0
|
||||
text = "Play"
|
||||
script = ExtResource( 1 )
|
||||
icon_name = "Play"
|
||||
|
||||
[node name="Restore" type="Button" parent="Toolbar"]
|
||||
margin_left = 140.0
|
||||
margin_right = 220.0
|
||||
margin_bottom = 22.0
|
||||
rect_min_size = Vector2( 80, 0 )
|
||||
hint_tooltip = "Restore"
|
||||
disabled = true
|
||||
text = "Restore"
|
||||
script = ExtResource( 1 )
|
||||
icon_name = "Reload"
|
||||
|
||||
[node name="VSeparator2" type="VSeparator" parent="Toolbar"]
|
||||
margin_left = 224.0
|
||||
margin_right = 228.0
|
||||
margin_bottom = 22.0
|
||||
|
||||
[node name="Filename" type="Label" parent="Toolbar"]
|
||||
margin_left = 232.0
|
||||
margin_top = 4.0
|
||||
margin_right = 1020.0
|
||||
margin_bottom = 18.0
|
||||
size_flags_horizontal = 3
|
||||
text = "Unsaved sound"
|
||||
clip_text = true
|
||||
|
||||
[node name="VersionButton" parent="Toolbar" instance=ExtResource( 6 )]
|
||||
margin_top = 4.0
|
||||
margin_bottom = 18.0
|
||||
website = "https://github.com/timothyqiu/gdfxr"
|
||||
|
||||
[node name="HSeparator" type="HSeparator" parent="."]
|
||||
margin_top = 26.0
|
||||
margin_right = 1024.0
|
||||
margin_bottom = 30.0
|
||||
|
||||
[node name="Editor" type="HBoxContainer" parent="."]
|
||||
margin_top = 34.0
|
||||
margin_right = 1024.0
|
||||
margin_bottom = 600.0
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Generators" type="VBoxContainer" parent="Editor"]
|
||||
margin_right = 128.0
|
||||
margin_bottom = 566.0
|
||||
rect_min_size = Vector2( 128, 0 )
|
||||
|
||||
[node name="Button" type="Button" parent="Editor/Generators"]
|
||||
margin_right = 128.0
|
||||
margin_bottom = 20.0
|
||||
text = "Pickup/Coin"
|
||||
|
||||
[node name="Button2" type="Button" parent="Editor/Generators"]
|
||||
margin_top = 24.0
|
||||
margin_right = 128.0
|
||||
margin_bottom = 44.0
|
||||
text = "Laser/Shoot"
|
||||
|
||||
[node name="Button3" type="Button" parent="Editor/Generators"]
|
||||
margin_top = 48.0
|
||||
margin_right = 128.0
|
||||
margin_bottom = 68.0
|
||||
text = "Explosion"
|
||||
|
||||
[node name="Button4" type="Button" parent="Editor/Generators"]
|
||||
margin_top = 72.0
|
||||
margin_right = 128.0
|
||||
margin_bottom = 92.0
|
||||
text = "Powerup"
|
||||
|
||||
[node name="Button5" type="Button" parent="Editor/Generators"]
|
||||
margin_top = 96.0
|
||||
margin_right = 128.0
|
||||
margin_bottom = 116.0
|
||||
text = "Hit/Hurt"
|
||||
|
||||
[node name="Button6" type="Button" parent="Editor/Generators"]
|
||||
margin_top = 120.0
|
||||
margin_right = 128.0
|
||||
margin_bottom = 140.0
|
||||
text = "Jump"
|
||||
|
||||
[node name="Button7" type="Button" parent="Editor/Generators"]
|
||||
margin_top = 144.0
|
||||
margin_right = 128.0
|
||||
margin_bottom = 164.0
|
||||
text = "Blip/Select"
|
||||
|
||||
[node name="HSeparator" type="HSeparator" parent="Editor/Generators"]
|
||||
margin_top = 168.0
|
||||
margin_right = 128.0
|
||||
margin_bottom = 172.0
|
||||
|
||||
[node name="Button8" type="Button" parent="Editor/Generators"]
|
||||
margin_top = 176.0
|
||||
margin_right = 128.0
|
||||
margin_bottom = 196.0
|
||||
size_flags_horizontal = 3
|
||||
text = "Mutate"
|
||||
|
||||
[node name="Button9" type="Button" parent="Editor/Generators"]
|
||||
margin_top = 200.0
|
||||
margin_right = 128.0
|
||||
margin_bottom = 220.0
|
||||
size_flags_horizontal = 3
|
||||
text = "Randomize"
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="Editor"]
|
||||
margin_left = 132.0
|
||||
margin_right = 1024.0
|
||||
margin_bottom = 566.0
|
||||
size_flags_horizontal = 3
|
||||
scroll_vertical_enabled = false
|
||||
|
||||
[node name="Params" type="HBoxContainer" parent="Editor/ScrollContainer"]
|
||||
margin_top = 188.0
|
||||
margin_right = 895.0
|
||||
margin_bottom = 366.0
|
||||
size_flags_horizontal = 6
|
||||
size_flags_vertical = 6
|
||||
|
||||
[node name="Envolope" type="VBoxContainer" parent="Editor/ScrollContainer/Params"]
|
||||
margin_right = 211.0
|
||||
margin_bottom = 178.0
|
||||
|
||||
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource( 3 )]
|
||||
margin_right = 211.0
|
||||
margin_bottom = 22.0
|
||||
label = "Attack Time"
|
||||
parameter = "p_env_attack"
|
||||
|
||||
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource( 3 )]
|
||||
margin_top = 26.0
|
||||
margin_right = 211.0
|
||||
margin_bottom = 48.0
|
||||
label = "Sustain Time"
|
||||
parameter = "p_env_sustain"
|
||||
|
||||
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource( 3 )]
|
||||
margin_top = 52.0
|
||||
margin_right = 211.0
|
||||
margin_bottom = 74.0
|
||||
label = "Sustain Punch"
|
||||
parameter = "p_env_punch"
|
||||
|
||||
[node name="ParamSlider4" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource( 3 )]
|
||||
margin_top = 78.0
|
||||
margin_right = 211.0
|
||||
margin_bottom = 100.0
|
||||
label = "Decay Time"
|
||||
parameter = "p_env_decay"
|
||||
|
||||
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource( 3 )]
|
||||
margin_top = 104.0
|
||||
margin_right = 211.0
|
||||
margin_bottom = 126.0
|
||||
label = "Change Amount"
|
||||
parameter = "p_arp_mod"
|
||||
|
||||
[node name="ParamSlider6" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource( 3 )]
|
||||
margin_top = 130.0
|
||||
margin_right = 211.0
|
||||
margin_bottom = 152.0
|
||||
label = "Change Speed"
|
||||
parameter = "p_arp_speed"
|
||||
|
||||
[node name="ParamSlider7" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource( 3 )]
|
||||
margin_top = 156.0
|
||||
margin_right = 211.0
|
||||
margin_bottom = 178.0
|
||||
label = "Volume"
|
||||
parameter = "sound_vol"
|
||||
|
||||
[node name="Frequency" type="VBoxContainer" parent="Editor/ScrollContainer/Params"]
|
||||
margin_left = 215.0
|
||||
margin_right = 425.0
|
||||
margin_bottom = 178.0
|
||||
|
||||
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource( 3 )]
|
||||
margin_right = 210.0
|
||||
margin_bottom = 22.0
|
||||
label = "Start Frequency"
|
||||
parameter = "p_base_freq"
|
||||
|
||||
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource( 3 )]
|
||||
margin_top = 26.0
|
||||
margin_right = 210.0
|
||||
margin_bottom = 48.0
|
||||
label = "Min Frequency"
|
||||
parameter = "p_freq_limit"
|
||||
|
||||
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource( 3 )]
|
||||
margin_top = 52.0
|
||||
margin_right = 210.0
|
||||
margin_bottom = 74.0
|
||||
label = "Slide"
|
||||
parameter = "p_freq_ramp"
|
||||
|
||||
[node name="ParamSlider4" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource( 3 )]
|
||||
margin_top = 78.0
|
||||
margin_right = 210.0
|
||||
margin_bottom = 100.0
|
||||
label = "Delta Slide"
|
||||
parameter = "p_freq_dramp"
|
||||
|
||||
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource( 3 )]
|
||||
margin_top = 104.0
|
||||
margin_right = 210.0
|
||||
margin_bottom = 126.0
|
||||
label = "Vibrato Depth"
|
||||
parameter = "p_vib_strength"
|
||||
|
||||
[node name="ParamSlider6" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource( 3 )]
|
||||
margin_top = 130.0
|
||||
margin_right = 210.0
|
||||
margin_bottom = 152.0
|
||||
label = "Vibrato Speed"
|
||||
parameter = "p_vib_speed"
|
||||
|
||||
[node name="ParamSlider7" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource( 3 )]
|
||||
margin_top = 156.0
|
||||
margin_right = 210.0
|
||||
margin_bottom = 178.0
|
||||
label = "Repeat Speed"
|
||||
parameter = "p_repeat_speed"
|
||||
|
||||
[node name="Waveform" type="VBoxContainer" parent="Editor/ScrollContainer/Params"]
|
||||
margin_left = 429.0
|
||||
margin_right = 649.0
|
||||
margin_bottom = 178.0
|
||||
|
||||
[node name="WaveformOption" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource( 4 )]
|
||||
margin_right = 220.0
|
||||
margin_bottom = 22.0
|
||||
options = [ "Square", "Sawtooth", "Sine", "Noise" ]
|
||||
parameter = "wave_type"
|
||||
|
||||
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource( 3 )]
|
||||
margin_top = 26.0
|
||||
margin_right = 220.0
|
||||
margin_bottom = 48.0
|
||||
label = "Square Duty"
|
||||
parameter = "p_duty"
|
||||
|
||||
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource( 3 )]
|
||||
margin_top = 52.0
|
||||
margin_right = 220.0
|
||||
margin_bottom = 74.0
|
||||
label = "Duty Sweep"
|
||||
parameter = "p_duty_ramp"
|
||||
|
||||
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource( 3 )]
|
||||
margin_top = 78.0
|
||||
margin_right = 220.0
|
||||
margin_bottom = 100.0
|
||||
label = "Phaser Offset"
|
||||
parameter = "p_pha_offset"
|
||||
|
||||
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource( 3 )]
|
||||
margin_top = 104.0
|
||||
margin_right = 220.0
|
||||
margin_bottom = 126.0
|
||||
label = "Phaser Sweep"
|
||||
parameter = "p_pha_ramp"
|
||||
|
||||
[node name="Filter" type="VBoxContainer" parent="Editor/ScrollContainer/Params"]
|
||||
margin_left = 653.0
|
||||
margin_right = 895.0
|
||||
margin_bottom = 178.0
|
||||
|
||||
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource( 3 )]
|
||||
margin_right = 242.0
|
||||
margin_bottom = 22.0
|
||||
label = "Low-pass Cutoff"
|
||||
parameter = "p_lpf_freq"
|
||||
|
||||
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource( 3 )]
|
||||
margin_top = 26.0
|
||||
margin_right = 242.0
|
||||
margin_bottom = 48.0
|
||||
label = "Low-pass Sweep"
|
||||
parameter = "p_lpf_ramp"
|
||||
|
||||
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource( 3 )]
|
||||
margin_top = 52.0
|
||||
margin_right = 242.0
|
||||
margin_bottom = 74.0
|
||||
label = "Low-pass Resonance"
|
||||
parameter = "p_lpf_resonance"
|
||||
|
||||
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource( 3 )]
|
||||
margin_top = 78.0
|
||||
margin_right = 242.0
|
||||
margin_bottom = 100.0
|
||||
label = "High-pass Cutoff"
|
||||
parameter = "p_hpf_freq"
|
||||
|
||||
[node name="ParamSlider4" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource( 3 )]
|
||||
margin_top = 104.0
|
||||
margin_right = 242.0
|
||||
margin_bottom = 126.0
|
||||
label = "High-pass Sweep"
|
||||
parameter = "p_hpf_ramp"
|
||||
|
||||
[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="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 ]]
|
16
addons/gdfxr/editor/EditorIconButton.gd
Normal file
16
addons/gdfxr/editor/EditorIconButton.gd
Normal 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_icon(icon_name, "EditorIcons")
|
43
addons/gdfxr/editor/ParamOption.gd
Normal file
43
addons/gdfxr/editor/ParamOption.gd
Normal file
@ -0,0 +1,43 @@
|
||||
tool
|
||||
extends HBoxContainer
|
||||
|
||||
signal param_changed(name, value)
|
||||
signal param_reset(name)
|
||||
|
||||
export var options: Array setget set_options
|
||||
export var parameter: String # Could be PoolStringArray, 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):
|
||||
emit_signal("param_changed", parameter, index)
|
||||
|
||||
|
||||
func _on_Reset_pressed():
|
||||
emit_signal("param_reset", parameter)
|
44
addons/gdfxr/editor/ParamOption.tscn
Normal file
44
addons/gdfxr/editor/ParamOption.tscn
Normal file
@ -0,0 +1,44 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://addons/gdfxr/editor/ParamOption.gd" type="Script" id=1]
|
||||
[ext_resource path="res://addons/gdfxr/editor/EditorIconButton.gd" type="Script" id=2]
|
||||
|
||||
[node name="WaveformOption" type="HBoxContainer"]
|
||||
margin_right = 253.0
|
||||
margin_bottom = 40.0
|
||||
rect_pivot_offset = Vector2( 41, -65 )
|
||||
size_flags_horizontal = 3
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
margin_top = 13.0
|
||||
margin_right = 133.0
|
||||
margin_bottom = 27.0
|
||||
rect_min_size = Vector2( 100, 0 )
|
||||
size_flags_horizontal = 3
|
||||
text = "Waveform"
|
||||
align = 2
|
||||
|
||||
[node name="OptionButton" type="OptionButton" parent="."]
|
||||
margin_left = 137.0
|
||||
margin_top = 10.0
|
||||
margin_right = 237.0
|
||||
margin_bottom = 30.0
|
||||
rect_min_size = Vector2( 100, 0 )
|
||||
size_flags_vertical = 4
|
||||
clip_text = true
|
||||
|
||||
[node name="Reset" type="ToolButton" parent="."]
|
||||
margin_left = 241.0
|
||||
margin_top = 9.0
|
||||
margin_right = 253.0
|
||||
margin_bottom = 31.0
|
||||
size_flags_vertical = 4
|
||||
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"]
|
33
addons/gdfxr/editor/ParamSlider.gd
Normal file
33
addons/gdfxr/editor/ParamSlider.gd
Normal file
@ -0,0 +1,33 @@
|
||||
tool
|
||||
extends HBoxContainer
|
||||
|
||||
signal param_changed(name, value)
|
||||
signal param_reset(name)
|
||||
|
||||
export var label: String setget set_label
|
||||
export var parameter: String
|
||||
|
||||
|
||||
func set_label(v: String) -> void:
|
||||
label = v
|
||||
$Label.text = v
|
||||
|
||||
|
||||
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):
|
||||
emit_signal("param_changed", parameter, value)
|
||||
|
||||
|
||||
func _on_Reset_pressed():
|
||||
emit_signal("param_reset", parameter)
|
43
addons/gdfxr/editor/ParamSlider.tscn
Normal file
43
addons/gdfxr/editor/ParamSlider.tscn
Normal file
@ -0,0 +1,43 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://addons/gdfxr/editor/ParamSlider.gd" type="Script" id=1]
|
||||
[ext_resource path="res://addons/gdfxr/editor/EditorIconButton.gd" type="Script" id=2]
|
||||
|
||||
[node name="ParamSlider" type="HBoxContainer"]
|
||||
margin_right = 253.0
|
||||
margin_bottom = 40.0
|
||||
size_flags_horizontal = 3
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
margin_top = 13.0
|
||||
margin_right = 143.0
|
||||
margin_bottom = 27.0
|
||||
rect_min_size = Vector2( 100, 0 )
|
||||
size_flags_horizontal = 3
|
||||
align = 2
|
||||
|
||||
[node name="HSlider" type="HSlider" parent="."]
|
||||
margin_left = 147.0
|
||||
margin_top = 12.0
|
||||
margin_right = 237.0
|
||||
margin_bottom = 28.0
|
||||
rect_min_size = Vector2( 90, 0 )
|
||||
size_flags_vertical = 4
|
||||
max_value = 1.0
|
||||
step = 0.0
|
||||
|
||||
[node name="Reset" type="ToolButton" parent="."]
|
||||
margin_left = 241.0
|
||||
margin_top = 9.0
|
||||
margin_right = 253.0
|
||||
margin_bottom = 31.0
|
||||
size_flags_vertical = 4
|
||||
script = ExtResource( 2 )
|
||||
icon_name = "ReloadSmall"
|
||||
|
||||
[connection signal="value_changed" from="HSlider" to="." method="_on_HSlider_value_changed"]
|
||||
[connection signal="pressed" from="Reset" to="." method="_on_Reset_pressed"]
|
53
addons/gdfxr/editor/PluginTranslator.gd
Normal file
53
addons/gdfxr/editor/PluginTranslator.gd
Normal file
@ -0,0 +1,53 @@
|
||||
tool
|
||||
extends Node
|
||||
|
||||
var plugin: EditorPlugin setget 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().plus_file("translations/%s.po" % locale)
|
||||
if ResourceLoader.exists(path):
|
||||
_translation = ResourceLoader.load(path)
|
||||
|
||||
if _translation:
|
||||
_translate_node(get_parent())
|
||||
|
||||
|
||||
func tr(message: String) -> String:
|
||||
if _translation:
|
||||
var translated := _translation.get_message(message)
|
||||
if not translated.empty():
|
||||
return translated
|
||||
return message
|
||||
|
||||
|
||||
func _translate_node(node: Node):
|
||||
if node is Control:
|
||||
node.hint_tooltip = tr(node.hint_tooltip)
|
||||
|
||||
if node is HBoxContainer and node.has_method("set_options"):
|
||||
var options = []
|
||||
for item in node.options:
|
||||
options.append(tr(item))
|
||||
node.options = options
|
||||
|
||||
if node is Button and not node is OptionButton:
|
||||
node.text = tr(node.text)
|
||||
|
||||
if node is Label:
|
||||
node.text = tr(node.text)
|
||||
|
||||
for child in node.get_children():
|
||||
_translate_node(child)
|
6
addons/gdfxr/editor/PluginTranslator.tscn
Normal file
6
addons/gdfxr/editor/PluginTranslator.tscn
Normal file
@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/gdfxr/editor/PluginTranslator.gd" type="Script" id=1]
|
||||
|
||||
[node name="PluginTranslator" type="Node"]
|
||||
script = ExtResource( 1 )
|
25
addons/gdfxr/editor/VersionButton.gd
Normal file
25
addons/gdfxr/editor/VersionButton.gd
Normal file
@ -0,0 +1,25 @@
|
||||
tool
|
||||
extends LinkButton
|
||||
|
||||
export var website: String
|
||||
|
||||
var plugin: EditorPlugin setget set_plugin
|
||||
|
||||
|
||||
func set_plugin(v: EditorPlugin) -> void:
|
||||
plugin = v
|
||||
|
||||
var script := get_script() as Script
|
||||
var path := script.resource_path.get_base_dir().plus_file("../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)
|
19
addons/gdfxr/editor/VersionButton.tscn
Normal file
19
addons/gdfxr/editor/VersionButton.tscn
Normal 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 )
|
||||
margin_left = 1024.0
|
||||
margin_top = 5.0
|
||||
margin_right = 1024.0
|
||||
margin_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"]
|
223
addons/gdfxr/editor/translations/gdfxr.pot
Normal file
223
addons/gdfxr/editor/translations/gdfxr.pot
Normal file
@ -0,0 +1,223 @@
|
||||
# Translations template for gdfxr.
|
||||
# Copyright (C) 2022 Haoyu Qiu
|
||||
# This file is distributed under the same license as the gdfxr project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gdfxr 1.0\n"
|
||||
"Report-Msgid-Bugs-To: timothyqiu32@gmail.com\n"
|
||||
"POT-Creation-Date: 2022-02-25 17:37+0800\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.9.1\n"
|
||||
|
||||
#: addons/gdfxr/editor/Editor.gd
|
||||
#, python-format
|
||||
msgid ""
|
||||
"There are unsaved changes.\n"
|
||||
"Open '%s' anyway?"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.gd addons/gdfxr/editor/Editor.gd
|
||||
msgid "SFXR Editor"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.gd
|
||||
msgid "SFXR Audio"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.gd
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid SFXR file."
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.gd addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Unsaved sound"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.gd
|
||||
msgid ""
|
||||
"There are unsaved changes.\n"
|
||||
"Create a new one anyway?"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.gd
|
||||
msgid ""
|
||||
"There are unsaved changes.\n"
|
||||
"Load anyway?"
|
||||
msgstr ""
|
||||
|
||||
#: 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 "Play"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Pickup/Coin"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Laser/Shoot"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Explosion"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Powerup"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Hit/Hurt"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Jump"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Blip/Select"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Mutate"
|
||||
msgstr ""
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Randomize"
|
||||
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 ""
|
||||
|
230
addons/gdfxr/editor/translations/zh_CN.po
Normal file
230
addons/gdfxr/editor/translations/zh_CN.po
Normal file
@ -0,0 +1,230 @@
|
||||
# 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-02-25 17:37+0800\n"
|
||||
"PO-Revision-Date: 2022-02-25 17:38+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.0.1\n"
|
||||
|
||||
#: 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
|
||||
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.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 "Play"
|
||||
msgstr "播放"
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Restore"
|
||||
msgstr "恢复"
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Pickup/Coin"
|
||||
msgstr "拾取/金币"
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Laser/Shoot"
|
||||
msgstr "激光/射击"
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Explosion"
|
||||
msgstr "爆炸"
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Powerup"
|
||||
msgstr "升级"
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Hit/Hurt"
|
||||
msgstr "击中/受伤"
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Jump"
|
||||
msgstr "跳跃"
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Blip/Select"
|
||||
msgstr "短滴/选择"
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Mutate"
|
||||
msgstr "演化"
|
||||
|
||||
#: addons/gdfxr/editor/Editor.tscn
|
||||
msgid "Randomize"
|
||||
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 "波形"
|
62
addons/gdfxr/import_plugin.gd
Normal file
62
addons/gdfxr/import_plugin.gd
Normal file
@ -0,0 +1,62 @@
|
||||
tool
|
||||
extends EditorImportPlugin
|
||||
|
||||
const SFXRConfig = preload("SFXRConfig.gd")
|
||||
const SFXRGenerator = preload("SFXRGenerator.gd")
|
||||
|
||||
|
||||
func get_importer_name():
|
||||
return "com.timothyqiu.gdfxr.importer"
|
||||
|
||||
|
||||
func get_visible_name():
|
||||
return "SFXR Audio"
|
||||
|
||||
|
||||
func get_recognized_extensions():
|
||||
return ["sfxr"]
|
||||
|
||||
|
||||
func get_save_extension():
|
||||
return "sample"
|
||||
|
||||
|
||||
func get_resource_type():
|
||||
return "AudioStreamSample"
|
||||
|
||||
|
||||
func get_preset_count():
|
||||
return 1
|
||||
|
||||
|
||||
func get_preset_name(preset):
|
||||
return "Default"
|
||||
|
||||
|
||||
func get_import_options(preset):
|
||||
return [
|
||||
{
|
||||
name="loop",
|
||||
default_value=false,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
func get_option_visibility(option, options):
|
||||
return true
|
||||
|
||||
|
||||
func import(source_file, save_path, options, platform_variants, gen_files):
|
||||
var config := SFXRConfig.new()
|
||||
var err := config.load(source_file)
|
||||
if err != OK:
|
||||
printerr("Failed to open %s: %d" % [source_file, err])
|
||||
return err
|
||||
|
||||
var stream := SFXRGenerator.new().generate_audio_stream(config)
|
||||
if options.loop:
|
||||
stream.loop_mode = AudioStreamSample.LOOP_FORWARD
|
||||
stream.loop_end = stream.data.size()
|
||||
|
||||
var filename = save_path + "." + get_save_extension()
|
||||
return ResourceSaver.save(filename, stream)
|
7
addons/gdfxr/plugin.cfg
Normal file
7
addons/gdfxr/plugin.cfg
Normal file
@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="gdfxr"
|
||||
description="A Godot plugin that ports sfxr, the popular program of choice to make retro sound effects for games."
|
||||
author="Haoyu Qiu"
|
||||
version="1.0"
|
||||
script="plugin.gd"
|
39
addons/gdfxr/plugin.gd
Normal file
39
addons/gdfxr/plugin.gd
Normal file
@ -0,0 +1,39 @@
|
||||
tool
|
||||
extends EditorPlugin
|
||||
|
||||
|
||||
var import_plugin: EditorImportPlugin
|
||||
var sfxr_editor: Control
|
||||
|
||||
|
||||
func _enter_tree():
|
||||
import_plugin = preload("import_plugin.gd").new()
|
||||
add_import_plugin(import_plugin)
|
||||
|
||||
sfxr_editor = preload("editor/Editor.tscn").instance()
|
||||
sfxr_editor.plugin = self
|
||||
add_control_to_bottom_panel(sfxr_editor, "gdfxr")
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
remove_control_from_bottom_panel(sfxr_editor)
|
||||
sfxr_editor.queue_free()
|
||||
sfxr_editor = null
|
||||
|
||||
remove_import_plugin(import_plugin)
|
||||
import_plugin = null
|
||||
|
||||
|
||||
func handles(object: Object) -> bool:
|
||||
return object is AudioStreamSample and object.resource_path.ends_with(".sfxr")
|
||||
|
||||
|
||||
func edit(object: Object):
|
||||
sfxr_editor.edit(object.resource_path) # Should already passed `handles()` checks
|
||||
|
||||
|
||||
func make_visible(visible: bool):
|
||||
if visible:
|
||||
make_bottom_panel_item_visible(sfxr_editor)
|
||||
elif sfxr_editor.is_visible_in_tree():
|
||||
hide_bottom_panel()
|
5
babel.cfg
Normal file
5
babel.cfg
Normal file
@ -0,0 +1,5 @@
|
||||
[python: **.gd]
|
||||
encoding = utf-8
|
||||
|
||||
[godot_scene: **.tscn]
|
||||
encoding = utf-8
|
23
example/Example.tscn
Normal file
23
example/Example.tscn
Normal file
@ -0,0 +1,23 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://example/example.sfxr" type="AudioStream" id=1]
|
||||
|
||||
[node name="Example" type="CenterContainer"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Button" type="Button" parent="."]
|
||||
margin_left = 448.0
|
||||
margin_top = 284.0
|
||||
margin_right = 576.0
|
||||
margin_bottom = 316.0
|
||||
rect_min_size = Vector2( 128, 32 )
|
||||
text = "Play"
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
|
||||
stream = ExtResource( 1 )
|
||||
|
||||
[connection signal="pressed" from="Button" to="AudioStreamPlayer" method="play"]
|
BIN
example/example.sfxr
Normal file
BIN
example/example.sfxr
Normal file
Binary file not shown.
14
example/example.sfxr.import
Normal file
14
example/example.sfxr.import
Normal file
@ -0,0 +1,14 @@
|
||||
[remap]
|
||||
|
||||
importer="com.timothyqiu.gdfxr.importer"
|
||||
type="AudioStreamSample"
|
||||
path="res://.import/example.sfxr-e3731bcdcd8403a2391e92667a5d1076.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://example/example.sfxr"
|
||||
dest_files=[ "res://.import/example.sfxr-e3731bcdcd8403a2391e92667a5d1076.sample" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
35
icon.png.import
Normal file
35
icon.png.import
Normal file
@ -0,0 +1,35 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.png"
|
||||
dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
114
icon.svg
Normal file
114
icon.svg
Normal file
@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 33.866666 33.866668"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
sodipodi:docname="icon.svg"
|
||||
inkscape:export-filename="icon.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
width="128px"
|
||||
inkscape:zoom="11.313708"
|
||||
inkscape:cx="65.628348"
|
||||
inkscape:cy="89.228037"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1029"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-intersection-paths="false"
|
||||
inkscape:object-paths="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid841"
|
||||
spacingx="0.79375001"
|
||||
spacingy="0.79375001"
|
||||
empspacing="8"
|
||||
originy="0"
|
||||
originx="8.4666667" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="图层 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:#c0b090;fill-opacity:1;stroke:#504030;stroke-width:1.04997;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
|
||||
id="rect926"
|
||||
width="22.762537"
|
||||
height="29.377117"
|
||||
x="5.5520649"
|
||||
y="2.2447748"
|
||||
ry="3.1518908" />
|
||||
<path
|
||||
d="m 23.851198,15.382701 c 0,0 -0.02298,-0.140998 -0.03642,-0.139725 l -2.558476,0.246853 c -0.203578,0.01966 -0.361688,0.18201 -0.375972,0.386012 l -0.07029,1.007424 -1.97935,0.14121 -0.134634,-0.913024 c -0.02998,-0.203082 -0.207326,-0.356172 -0.4126,-0.356172 H 15.58314 c -0.205204,0 -0.382548,0.15309 -0.412529,0.356172 l -0.134705,0.913024 -1.979351,-0.14121 -0.07029,-1.007424 c -0.01428,-0.204072 -0.172394,-0.366425 -0.375971,-0.386083 l -2.559749,-0.246782 c -0.01322,-0.0013 -0.02291,0.139866 -0.03613,0.139866 l -0.0035,0.55367 2.167866,0.349596 0.07099,1.016404 c 0.01436,0.20577 0.180385,0.372154 0.386296,0.386932 l 2.725991,0.194456 c 0.01032,7.08e-4 0.02051,0.0011 0.03069,0.0011 0.204851,0 0.381912,-0.153161 0.411894,-0.356244 l 0.138523,-0.93947 h 1.980269 l 0.138524,0.93947 c 0.02991,0.203012 0.207184,0.356173 0.412317,0.356173 0.01004,0 0.02008,-3.53e-4 0.02991,-0.0011 l 2.726344,-0.194456 c 0.205841,-0.01478 0.371942,-0.181162 0.386296,-0.386932 l 0.07092,-1.016404 2.166948,-0.351152 z"
|
||||
fill="#ffffff"
|
||||
stroke-width="0.0226495"
|
||||
id="path2595" />
|
||||
<path
|
||||
d="m 15.341263,4.7816971 c -0.03857,-0.00172 -0.07712,0.00276 -0.114804,0.011032 -0.746673,0.1659962 -1.497569,0.3994768 -2.211559,0.7588595 -0.165298,0.083205 -0.267714,0.2543551 -0.26294,0.439347 0.01289,0.4975921 0.05631,0.9816702 0.111101,1.47023 C 12.67731,7.5723468 12.49175,7.6735304 12.287021,7.8379063 12.141274,7.9498287 11.97873,8.091865 11.819732,8.2233864 11.449352,7.9907848 11.075128,7.7592865 10.678771,7.5598152 10.488729,7.4641304 10.258299,7.5055691 10.113505,7.6614814 9.5745775,8.2414856 9.0622819,8.8760635 8.6392415,9.5960354 c -0.089745,0.1528613 -0.087542,0.3428175 0.00572,0.4935586 0.2797803,0.452568 0.5777633,0.875557 0.8915014,1.285721 v 5.137608 c 0,1.417432 0.9432311,2.498735 2.2924031,3.143758 1.349137,0.645127 3.134622,0.939835 5.098135,0.946713 h 0.0128 c 1.963513,-0.0069 3.748964,-0.301551 5.09779,-0.946713 1.348827,-0.645161 2.291713,-1.726498 2.291713,-3.143758 l 6.73e-4,-5.004191 c 0.318668,-0.467444 0.583055,-0.91946 0.891829,-1.418724 0.09313,-0.1506552 0.09533,-0.340439 0.0057,-0.4932142 C 24.804575,8.8767702 24.291814,8.2419166 23.752921,7.6618951 23.608182,7.5061379 23.377956,7.4646992 23.188,7.5602117 22.791798,7.7596485 22.417436,7.9915433 22.047039,8.2241276 21.888061,8.09252 21.725992,7.9504665 21.57975,7.8383027 c -5.86e-4,0 -0.0011,0 -0.0017,-0.00172 C 21.373701,7.672655 21.188761,7.5716438 21.0037,7.460859 21.05836,6.9723509 21.10185,6.4881522 21.1148,5.990629 21.1196,5.8056199 21.01717,5.6344697 20.851861,5.5512648 20.137836,5.1919338 19.387009,4.9584359 18.639957,4.7924225 c -0.201471,-0.044731 -0.408888,0.044869 -0.514433,0.2222074 -0.24141,0.405734 -0.448465,0.8369105 -0.648764,1.2628295 -0.178442,-0.016203 -0.359538,-0.041025 -0.532274,-0.043094 -0.0019,-1.72e-5 -0.0038,-1.72e-5 -0.0057,0 h -0.01044 c -0.0019,-1.72e-5 -0.0038,-1.72e-5 -0.0057,0 -0.173081,0.00207 -0.354418,0.026821 -0.532946,0.043094 C 16.197437,5.8684983 15.997225,5.456214 15.766502,5.064787 15.758869,5.0474462 15.750213,5.030588 15.740579,5.0142814 15.656457,4.8730209 15.50565,4.7849033 15.341292,4.7809732 Z"
|
||||
fill="#ffffff"
|
||||
id="path2597"
|
||||
style="stroke-width:0.172374" />
|
||||
<path
|
||||
d="m 15.330059,5.2471071 c -0.720127,0.1600838 -1.43248,0.3829634 -2.100378,0.7191273 0.01528,0.5897433 0.0534,1.1547854 0.130689,1.7287393 -0.259371,0.1661858 -0.531946,0.3088081 -0.774218,0.503315 -0.24615,0.1893529 -0.49754,0.3705353 -0.720437,0.5920014 -0.44526,-0.2945183 -0.91653,-0.5712648 -1.402039,-0.8155878 -0.5233276,0.563215 -1.0126631,1.1710921 -1.4123813,1.8512973 0.300741,0.4864744 0.6146859,0.9425064 0.9536763,1.3752694 h 0.0095 v 4.174899 c 0.0076,6.9e-5 0.01526,0 0.02283,0.0017 l 2.559409,0.246788 c 0.134068,0.01293 0.239135,0.120834 0.248478,0.255182 l 0.07893,1.129826 2.232588,0.159274 0.153813,-1.042743 c 0.01994,-0.13521 0.135903,-0.235411 0.272662,-0.235411 h 2.700239 c 0.136684,0 0.252649,0.100201 0.272592,0.235411 l 0.153813,1.042743 2.232761,-0.159274 0.07887,-1.129826 c 0.0094,-0.134348 0.114403,-0.242185 0.248477,-0.255182 l 2.558376,-0.246788 c 0.0076,0 0.01513,-0.0017 0.02276,-0.0017 v -0.33313 h 0.001 v -3.841356 h 0.0095 c 0.339032,-0.432745 0.65277,-0.888777 0.953649,-1.3752687 C 24.415638,9.1461908 23.926027,8.5382965 23.402699,7.975116 22.917346,8.219439 22.445902,8.4962028 22.000626,8.7907038 21.777815,8.5692377 21.526856,8.3880725 21.280361,8.1987196 21.038175,8.0041783 20.765255,7.8615732 20.506384,7.6954047 20.583459,7.1214335 20.621563,6.5563742 20.636907,5.9666653 19.968975,5.6305187 19.256708,5.4076218 18.536185,5.247538 18.248527,5.7309955 17.985467,6.2545127 17.756365,6.7663258 17.484686,6.7209397 17.211749,6.704116 16.93845,6.7008581 h -0.0053 -0.0052 c -0.273799,0.00328 -0.546494,0.020082 -0.818242,0.065468 C 15.880758,6.2545127 15.617853,5.7309955 15.329764,5.247538 Z M 10.010251,15.928437 c 9.95e-4,0.24734 0.0042,0.518329 0.0042,0.572282 0,2.430647 3.083427,3.598826 6.914268,3.612271 h 0.0094 c 3.830841,-0.01345 6.913234,-1.181693 6.913234,-3.612271 0,-0.05495 0.0033,-0.324821 0.0044,-0.572282 l -2.300504,0.22188 -0.0793,1.135859 c -0.0095,0.136882 -0.118442,0.245823 -0.255338,0.255648 l -2.726268,0.194541 h -0.01987 c -0.135481,0 -0.252373,-0.09941 -0.272385,-0.235463 L 18.045803,16.44063 h -2.224487 l -0.156334,1.060272 c -0.021,0.142674 -0.14858,0.245272 -0.292261,0.234722 L 12.646453,17.5411 c -0.136896,-0.0098 -0.245788,-0.118748 -0.255337,-0.255648 l -0.07926,-1.135876 z"
|
||||
fill="#478cbf"
|
||||
id="path2599"
|
||||
style="stroke-width:0.172374;fill:#988070;fill-opacity:1" />
|
||||
<g
|
||||
fill="#ffffff"
|
||||
stroke-width="0.32031"
|
||||
id="g2607"
|
||||
transform="matrix(0.17237405,0,0,0.17237405,8.3146077,-172.19474)">
|
||||
<path
|
||||
d="m 0,0 c 0,-12.052 -9.765,-21.815 -21.813,-21.815 -12.042,0 -21.81,9.763 -21.81,21.815 0,12.044 9.768,21.802 21.81,21.802 C -9.765,21.802 0,12.044 0,0"
|
||||
transform="matrix(0.41022,0,0,-0.41022,37.9,1073.8)"
|
||||
id="path2601" />
|
||||
<path
|
||||
d="m 0,0 c -3.878,0 -7.021,2.858 -7.021,6.381 v 20.081 c 0,3.52 3.143,6.381 7.021,6.381 3.878,0 7.028,-2.861 7.028,-6.381 V 6.381 C 7.028,2.858 3.878,0 0,0"
|
||||
transform="matrix(0.41022,0,0,-0.41022,50,1083.6)"
|
||||
id="path2603" />
|
||||
<path
|
||||
d="m 0,0 c 0,-12.052 9.765,-21.815 21.815,-21.815 12.041,0 21.808,9.763 21.808,21.815 0,12.044 -9.767,21.802 -21.808,21.802 C 9.765,21.802 0,12.044 0,0"
|
||||
transform="matrix(0.41022,0,0,-0.41022,62.101,1073.8)"
|
||||
id="path2605" />
|
||||
</g>
|
||||
<path
|
||||
d="m 13.452561,11.969695 c -0.565404,0 -1.023816,0.458532 -1.023816,1.023798 0,0.565284 0.458412,1.023402 1.023816,1.023402 0.565697,0 1.02385,-0.458118 1.02385,-1.023402 0,-0.565266 -0.458153,-1.023798 -1.02385,-1.023798 z m 6.960981,0 c -0.565266,0 -1.023247,0.458532 -1.023247,1.023798 0,0.565284 0.457981,1.023402 1.023247,1.023402 0.565835,0 1.023833,-0.458118 1.023833,-1.023402 0,-0.565266 -0.458015,-1.023798 -1.023833,-1.023798 z"
|
||||
fill="#414042"
|
||||
id="path2609"
|
||||
style="stroke-width:0.172374;fill:#504030;fill-opacity:1" />
|
||||
<path
|
||||
id="path956"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 9.2604172,23.547917 v 0.529167 H 8.7312505 v 0.529166 h 0.5291667 v 0.529167 H 11.377084 V 26.19375 H 9.2604172 V 25.664584 H 8.7312505 v 1.058333 h 0.5291667 v 0.529167 h 2.6458338 v -0.529167 h 0.529166 v -1.5875 H 11.906251 V 24.60625 H 9.7895839 v -0.529166 h 2.6458331 v -0.529167 z m 3.7041668,0 v 3.704167 h 1.058333 v -2.116667 h 0.529167 V 24.60625 h -0.529167 v -0.529166 h 2.116667 v -0.529167 z m 3.175,0.529167 v 0.529166 h 0.529167 v -0.529166 z m 1.058333,-0.529167 v 1.058333 h 0.529167 v 0.529167 h 0.529167 v 0.529167 h -0.529167 v 0.529166 h -0.529167 v 1.058334 h 1.058334 v -0.529167 h 0.529166 V 26.19375 h 0.529167 v 0.529167 h 0.529167 v 0.529167 h 1.058333 V 26.19375 h -0.529167 v -0.529166 h -0.529166 v -0.529167 h 0.529166 V 24.60625 h 0.529167 v -1.058333 h -1.058333 v 0.529167 h -0.529167 v 0.529166 h -0.529167 v -0.529166 h -0.529166 v -0.529167 z m 4.233334,0 v 3.704167 h 1.058333 v -2.116667 h 1.5875 v 2.116667 h 1.058334 V 25.135417 H 24.606251 V 24.60625 h 0.529167 v -0.529166 h -0.529167 v -0.529167 z m 1.058333,0.529167 h 1.5875 v 0.529166 h -1.5875 z"
|
||||
inkscape:export-filename="icon.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 10 KiB |
3
icon.svg.import
Normal file
3
icon.svg.import
Normal file
@ -0,0 +1,3 @@
|
||||
[remap]
|
||||
|
||||
importer="keep"
|
19
project.godot
Normal file
19
project.godot
Normal file
@ -0,0 +1,19 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=4
|
||||
|
||||
[application]
|
||||
|
||||
config/name="gdfxr"
|
||||
run/main_scene="res://example/Example.tscn"
|
||||
config/icon="res://icon.png"
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PoolStringArray( "res://addons/gdfxr/plugin.cfg" )
|
0
screenshots/.gdignore
Normal file
0
screenshots/.gdignore
Normal file
BIN
screenshots/editor-zh_CN.png
Normal file
BIN
screenshots/editor-zh_CN.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
screenshots/editor.png
Normal file
BIN
screenshots/editor.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Loading…
Reference in New Issue
Block a user