Initial Commit

This commit is contained in:
Haoyu Qiu 2022-02-22 20:54:25 +08:00
commit bf351cff87
36 changed files with 2520 additions and 0 deletions

20
.gitattributes vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
)

View 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

View 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")

View 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 ]]

View File

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

View 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)

View 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"]

View 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)

View 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"]

View 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)

View 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 )

View 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)

View File

@ -0,0 +1,19 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/gdfxr/editor/VersionButton.gd" type="Script" id=1]
[node name="VersionButton" type="LinkButton"]
self_modulate = Color( 1, 1, 1, 0.65 )
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"]

View 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 ""

View 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 "波形"

View 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
View 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
View 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
View File

@ -0,0 +1,5 @@
[python: **.gd]
encoding = utf-8
[godot_scene: **.tscn]
encoding = utf-8

23
example/Example.tscn Normal file
View 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

Binary file not shown.

View 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

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

35
icon.png.import Normal file
View 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
View 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
View File

@ -0,0 +1,3 @@
[remap]
importer="keep"

19
project.godot Normal file
View 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
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
screenshots/editor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB