Compare commits

...

12 Commits
master ... 2.0

Author SHA1 Message Date
Haoyu Qiu
3977e4a553 Update screenshots 2023-02-23 21:58:14 +08:00
Haoyu Qiu
accb9c1ffe Fix localization failure 2023-02-23 21:56:52 +08:00
Haoyu Qiu
eab1983c15 2.0 Release 2023-02-23 21:35:06 +08:00
Haoyu Qiu
d8ae21b130 Add default filename for Save As dialog
Trailing number incremented.

Co-Authored-By:  Russell Matney <russell.matney@gmail.com>
2023-02-16 09:53:14 +08:00
Haoyu Qiu
893b0bdb2b Make toolbar buttons flat 2023-02-16 09:37:17 +08:00
Haoyu Qiu
38911b6290 Press ESC to exit editing slider value 2023-02-16 09:34:34 +08:00
Haoyu Qiu
eb69d75178 Handle null resource for beta15 2023-01-25 20:20:08 +08:00
Haoyu Qiu
140033208e Fix error when importing a new sfxr file 2023-01-22 21:54:27 +08:00
Haoyu Qiu
0fdde9e60a Add import options for bit depth and sample rate 2023-01-04 17:48:53 +08:00
Haoyu Qiu
7981128335 Implement paste from jsfxr
(cherry picked from commit 5b910afb64)
2022-12-04 14:02:37 +08:00
Haoyu Qiu
3d59d2fcad More Godot 4 fixes 2022-11-21 10:02:06 +08:00
Haoyu Qiu
835e8dfd97 Initial port to Godot 4 2022-10-16 11:33:38 +08:00
30 changed files with 821 additions and 640 deletions

View File

@ -6,25 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.2.0] - 2022-10-13
## [2.0.0] - 2023-02-23
### Added
- Implement custom slider.
- First release for Godot 4. See the `master` branch for Godot 3 support.
## [1.1.1] - 2022-07-30
### Fixed
- Slider min value for a bipolar parameter should be -1.
## [1.1.0] - 2022-03-10
### Added
- Extra options menu: Save As, Copy, and Paste.
- Add a copy of the LICENSE in the plugin folder.
- Keep several recently generated sounds in the menu.
## [1.0.0] - 2022-02-25
### Added
- Initial release.
[1.0.0]: https://github.com/timothyqiu/gdfxr/releases/tag/1.0
[1.1.0]: https://github.com/timothyqiu/gdfxr/releases/tag/1.1
[1.1.1]: https://github.com/timothyqiu/gdfxr/releases/tag/1.1.1
[1.2.0]: https://github.com/timothyqiu/gdfxr/releases/tag/1.2
[2.0.0]: https://github.com/timothyqiu/gdfxr/releases/tag/2.0

View File

@ -10,6 +10,8 @@
你可以在 Godot 中把 sfxr 音效文件当作普通的音频文件使用,也可以像在原始的 sfxr 中一样对音效进行编辑。
> 🚧 如果你想在 Godot 3 里使用这个插件,请移步 [master](https://github.com/timothyqiu/gdfxr/tree/master) 分支。
## 安装
这是一个普通的 Godot 插件。安装时,先下载 ZIP 包,解压后将 `addons/` 文件夹移动到你的项目文件夹中,然后在项目设置中启用本插件。
@ -30,6 +32,8 @@
如果你希望使用原始的 sfxr 保存出的文件,请确保使用 `.sfxr` 扩展名保存。你也可以在原始的 sfxr 中加载并编辑 `.sfxr` 文件。
`.sfxr` 文件有循环Loop、位深度Bit Depth、采样率Sample Rate等导入选项。可以在 Godot 编辑器的导入面板中找到。
**注意:** 由于 GDScript 的性能限制,生成较长的音效时编辑器可能会有短暂的停滞。只有编辑器会受此影响。在游戏中使用 `.sfxr` 文件是不会在运行时生成任何东西的。
## 更新日志

View File

@ -12,6 +12,8 @@ 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.
> 🚧 Checkout the [master](https://github.com/timothyqiu/gdfxr/tree/master) branch if you want to use this plugin in Godot 3.
## Installation
This is a regular plugin for Godot. To install, download the ZIP archive, extract it, and move the
@ -40,6 +42,9 @@ 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.
Options for changing Looping, Bit Depth, and Sample Rate are available as import options
of the `.sfxr` file. You can find these options in Godot editor's Import dock.
**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.

38
addons/gdfxr/Base58.gd Normal file
View File

@ -0,0 +1,38 @@
extends Object
const BASE_58_ALPHABET := "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
static func b58decode(v: String) -> StreamPeerBuffer:
# Base 58 is a number expressed in the base-58 numeral system.
# When encoding data, big-endian is used and leading zeros are encoded as leading `1`s.
var original_length := v.length()
v = v.lstrip(BASE_58_ALPHABET[0])
var zeros := original_length - v.length()
var buffer := PackedByteArray()
buffer.resize(v.length()) # Won't be as long as base 58 string since the buffer is 256-based.
buffer.fill(0)
var length := 0
for c in v:
var carry := BASE_58_ALPHABET.find(c)
if carry == -1:
return null
var i := 0
while carry != 0 or i < length:
var pos := buffer.size() - 1 - i
carry += 58 * buffer[pos]
buffer[pos] = carry % 256
carry /= 256
i += 1
length = i
var result := StreamPeerBuffer.new()
for _i in zeros:
result.put_8(0)
result.put_data(buffer.slice(buffer.size() - length))
result.seek(0)
return result

View File

@ -18,6 +18,8 @@ enum Category {
BLIP_SELECT,
}
const Base58 := preload("res://addons/gdfxr/Base58.gd")
var wave_type: int = WaveType.SQUARE_WAVE
var p_env_attack := 0.0 # Attack Time
@ -53,10 +55,9 @@ 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 f := FileAccess.open(path, FileAccess.READ)
if not f:
return FileAccess.get_open_error()
var version := f.get_32()
if not [100, 101, 102].has(version):
@ -104,10 +105,9 @@ func load(path: String) -> int: # Error
func save(path: String) -> int: # Error
var f := File.new()
var err := f.open(path, File.WRITE)
if err != OK:
return err
var f := FileAccess.open(path, FileAccess.WRITE)
if not f:
return FileAccess.get_open_error()
f.store_32(102)
f.store_32(wave_type)
@ -152,88 +152,88 @@ func randomize_in_category(category: int) -> void:
match category:
Category.PICKUP_COIN:
p_base_freq = rand_range(0.4, 0.9)
p_base_freq = randf_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)
p_env_sustain = randf_range(0.0, 0.1)
p_env_decay = randf_range(0.1, 0.5)
p_env_punch = randf_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)
p_arp_speed = randf_range(0.5, 0.7)
p_arp_mod = randf_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)
p_base_freq = randf_range(0.5, 1.0)
p_freq_limit = max(0.2, p_base_freq - randf_range(0.2, 0.8))
p_freq_ramp = randf_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)
p_base_freq = randf_range(0.3, 0.9)
p_freq_limit = randf_range(0, 0.1)
p_freq_ramp = randf_range(-0.65, -0.35)
if randi() % 2:
p_duty = rand_range(0, 0.5)
p_duty_ramp = rand_range(0, 0.2)
p_duty = randf_range(0, 0.5)
p_duty_ramp = randf_range(0, 0.2)
else:
p_duty = rand_range(0.4, 0.9)
p_duty_ramp = rand_range(-0.7, 0)
p_duty = randf_range(0.4, 0.9)
p_duty_ramp = randf_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)
p_env_sustain = randf_range(0.1, 0.3)
p_env_decay = randf_range(0.0, 0.4)
if randi() % 2:
p_env_punch = rand_range(0, 0.3)
p_env_punch = randf_range(0, 0.3)
if randi() % 3 == 0:
p_pha_offset = rand_range(0, 0.2)
p_pha_ramp = rand_range(0, 0.2)
p_pha_offset = randf_range(0, 0.2)
p_pha_ramp = randf_range(0, 0.2)
if randi() % 2:
p_hpf_freq = rand_range(0, 0.3)
p_hpf_freq = randf_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)
p_base_freq = randf_range(0.1, 0.4)
p_freq_ramp = randf_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 = randf_range(0.2, 0.9)
p_freq_ramp = randf_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_repeat_speed = randf_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)
p_env_sustain = randf_range(0.1, 0.4)
p_env_decay = randf_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)
p_pha_offset = randf_range(-0.3, 0.6)
p_pha_ramp = randf_range(-0.3, 0)
p_env_punch = randf_range(0.2, 0.8)
if randi() % 2:
p_vib_strength = rand_range(0, 0.7)
p_vib_speed = rand_range(0, 0.6)
p_vib_strength = randf_range(0, 0.7)
p_vib_speed = randf_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)
p_arp_speed = randf_range(0.6, 0.9)
p_arp_mod = randf_range(-0.8, 0.8)
Category.POWERUP:
if randi() % 2:
wave_type = WaveType.SAWTOOTH
else:
p_duty = rand_range(0, 0.6)
p_duty = randf_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)
p_base_freq = randf_range(0.2, 0.5)
p_freq_ramp = randf_range(0.1, 0.5)
p_repeat_speed = randf_range(0.4, 0.8)
else:
p_base_freq = rand_range(0.2, 0.5)
p_freq_ramp = rand_range(0.05, 0.25)
p_base_freq = randf_range(0.2, 0.5)
p_freq_ramp = randf_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_vib_strength = randf_range(0, 0.7)
p_vib_speed = randf_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)
p_env_sustain = randf_range(0, 0.4)
p_env_decay = randf_range(0.1, 0.5)
Category.HIT_HURT:
wave_type = randi() % 3
@ -241,123 +241,123 @@ func randomize_in_category(category: int) -> void:
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_duty = randf_range(0, 0.6)
p_base_freq = randf_range(0.2, 0.8)
p_freq_ramp = randf_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)
p_env_sustain = randf_range(0, 0.1)
p_env_decay = randf_range(0.1, 0.3)
if randi() % 2:
p_hpf_freq = rand_range(0, 0.3)
p_hpf_freq = randf_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_duty = randf_range(0, 0.6)
p_base_freq = randf_range(0.3, 0.6)
p_freq_ramp = randf_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)
p_env_sustain = randf_range(0.1, 0.4)
p_env_decay = randf_range(0.1, 0.3)
if randi() % 2:
p_hpf_freq = rand_range(0, 0.3)
p_hpf_freq = randf_range(0, 0.3)
if randi() % 2:
p_lpf_freq = rand_range(0.4, 1.0)
p_lpf_freq = randf_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_duty = randf_range(0, 0.6)
p_base_freq = randf_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_env_sustain = randf_range(0.1, 0.2)
p_env_decay = randf_range(0, 0.2)
p_hpf_freq = 0.1
func randomize() -> void:
p_base_freq = pow(rand_range(-1.0, +1.0), 2.0)
p_base_freq = pow(randf_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_base_freq = pow(randf_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)
p_freq_ramp = pow(randf_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)
p_freq_dramp = pow(randf_range(-1.0, +1.0), 3.0)
p_duty = randf_range(-1.0, +1.0)
p_duty_ramp = pow(randf_range(-1.0, +1.0), 3.0)
p_vib_strength = pow(randf_range(-1.0, +1.0), 3.0)
p_vib_speed = randf_range(-1.0, +1.0)
# p_vib_delay = randf_range(-1.0, +1.0)
p_env_attack = pow(randf_range(-1.0, +1.0), 3.0)
p_env_sustain = pow(randf_range(-1.0, +1.0), 2.0)
p_env_decay = randf_range(-1.0, +1.0)
p_env_punch = pow(randf_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_env_sustain += randf_range(0.2, 0.5)
p_env_decay += randf_range(0.2, 0.5)
p_lpf_resonance = randf_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)
p_lpf_ramp = pow(randf_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)
p_hpf_ramp = pow(randf_range(-1.0, +1.0), 5.0)
p_pha_offset = pow(randf_range(-1.0, +1.0), 3.0)
p_pha_ramp = pow(randf_range(-1.0, +1.0), 3.0)
p_repeat_speed = randf_range(-1.0, +1.0)
p_arp_speed = randf_range(-1.0, +1.0)
p_arp_mod = randf_range(-1.0, +1.0)
func mutate() -> void:
if randi() % 2:
p_base_freq += rand_range(-0.05, +0.05)
p_base_freq += randf_range(-0.05, +0.05)
if randi() % 2:
p_freq_limit += rand_range(-0.05, +0.05)
p_freq_limit += randf_range(-0.05, +0.05)
if randi() % 2:
p_freq_ramp += rand_range(-0.05, +0.05)
p_freq_ramp += randf_range(-0.05, +0.05)
if randi() % 2:
p_freq_dramp += rand_range(-0.05, +0.05)
p_freq_dramp += randf_range(-0.05, +0.05)
if randi() % 2:
p_duty += rand_range(-0.05, +0.05)
p_duty += randf_range(-0.05, +0.05)
if randi() % 2:
p_duty_ramp += rand_range(-0.05, +0.05)
p_duty_ramp += randf_range(-0.05, +0.05)
if randi() % 2:
p_vib_strength += rand_range(-0.05, +0.05)
p_vib_strength += randf_range(-0.05, +0.05)
if randi() % 2:
p_vib_speed += rand_range(-0.05, +0.05)
p_vib_speed += randf_range(-0.05, +0.05)
# if randi() % 2:
# p_vib_delay += rand_range(-0.05, +0.05)
# p_vib_delay += randf_range(-0.05, +0.05)
if randi() % 2:
p_env_attack += rand_range(-0.05, +0.05)
p_env_attack += randf_range(-0.05, +0.05)
if randi() % 2:
p_env_sustain += rand_range(-0.05, +0.05)
p_env_sustain += randf_range(-0.05, +0.05)
if randi() % 2:
p_env_decay += rand_range(-0.05, +0.05)
p_env_decay += randf_range(-0.05, +0.05)
if randi() % 2:
p_env_punch += rand_range(-0.05, +0.05)
p_env_punch += randf_range(-0.05, +0.05)
if randi() % 2:
p_lpf_resonance += rand_range(-0.05, +0.05)
p_lpf_resonance += randf_range(-0.05, +0.05)
if randi() % 2:
p_lpf_freq += rand_range(-0.05, +0.05)
p_lpf_freq += randf_range(-0.05, +0.05)
if randi() % 2:
p_lpf_ramp += rand_range(-0.05, +0.05)
p_lpf_ramp += randf_range(-0.05, +0.05)
if randi() % 2:
p_hpf_freq += rand_range(-0.05, +0.05)
p_hpf_freq += randf_range(-0.05, +0.05)
if randi() % 2:
p_hpf_ramp += rand_range(-0.05, +0.05)
p_hpf_ramp += randf_range(-0.05, +0.05)
if randi() % 2:
p_pha_offset += rand_range(-0.05, +0.05)
p_pha_offset += randf_range(-0.05, +0.05)
if randi() % 2:
p_pha_ramp += rand_range(-0.05, +0.05)
p_pha_ramp += randf_range(-0.05, +0.05)
if randi() % 2:
p_repeat_speed += rand_range(-0.05, +0.05)
p_repeat_speed += randf_range(-0.05, +0.05)
if randi() % 2:
p_arp_speed += rand_range(-0.05, +0.05)
p_arp_speed += randf_range(-0.05, +0.05)
if randi() % 2:
p_arp_mod += rand_range(-0.05, +0.05)
p_arp_mod += randf_range(-0.05, +0.05)
func reset():
@ -395,7 +395,7 @@ func reset():
p_arp_mod = 0.0
func copy_from(other: Reference) -> void: # SFXRConfig
func copy_from(other: RefCounted) -> void: # SFXRConfig
wave_type = other.wave_type
p_env_attack = other.p_env_attack
@ -430,7 +430,7 @@ func copy_from(other: Reference) -> void: # SFXRConfig
sound_vol = other.sound_vol
func is_equal(other: Reference) -> bool: # SFXRConfig
func is_equal(other: RefCounted) -> bool: # SFXRConfig
return (
wave_type == other.wave_type
@ -465,3 +465,45 @@ func is_equal(other: Reference) -> bool: # SFXRConfig
and sound_vol == other.sound_vol
)
# Load base58 string copied from jsfxr
# See https://github.com/chr15m/jsfxr/blob/a708164e6ce200008d88202e1aaf2b9171a17ec2/sfxr.js#L132-L175
func load_from_base58(v: String) -> int: # Error
var buffer := Base58.b58decode(v)
if not buffer:
return ERR_INVALID_DATA
if buffer.get_size() != 89:
return ERR_INVALID_DATA
var params_order = [
"p_env_attack",
"p_env_sustain",
"p_env_punch",
"p_env_decay",
"p_base_freq",
"p_freq_limit",
"p_freq_ramp",
"p_freq_dramp",
"p_vib_strength",
"p_vib_speed",
"p_arp_mod",
"p_arp_speed",
"p_duty",
"p_duty_ramp",
"p_repeat_speed",
"p_pha_offset",
"p_pha_ramp",
"p_lpf_freq",
"p_lpf_ramp",
"p_lpf_resonance",
"p_hpf_freq",
"p_hpf_ramp",
]
wave_type = buffer.get_8()
for param in params_order:
set(param, buffer.get_float())
return OK

View File

@ -5,6 +5,15 @@ const SFXRConfig := preload("SFXRConfig.gd")
const master_vol := 0.05
enum WavBits {
WAV_BITS_8,
WAV_BITS_16,
}
enum WavFreq {
WAV_FREQ_44100,
WAV_FREQ_22050,
}
var _config: SFXRConfig
var rep_time: int
@ -23,15 +32,15 @@ var vib_speed: float
var square_duty: float
var square_slide: float
var env_vol: float
var env_length := PoolIntArray([0, 0, 0])
var env_length := PackedInt32Array([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 noise_buffer := PackedFloat32Array([])
var phaser_buffer := PackedFloat32Array([])
var ipp: int
var fltp: float
var fltdp: float
@ -41,32 +50,42 @@ 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
func generate_audio_stream(
config: SFXRConfig,
wav_bits: int = WavBits.WAV_BITS_8,
wav_freq: int = WavFreq.WAV_FREQ_44100
) -> AudioStreamWAV:
var stream := AudioStreamWAV.new()
stream.format = AudioStreamWAV.FORMAT_8_BITS if wav_bits == WavBits.WAV_BITS_8 else AudioStreamWAV.FORMAT_16_BITS
stream.mix_rate = 44100 if wav_freq == WavFreq.WAV_FREQ_44100 else 22050
_config = config
stream.data = _generate_samples()
stream.data = _generate_samples(wav_bits, wav_freq).data_array
_config = null
return stream
func generate_samples(config: SFXRConfig) -> PoolByteArray:
func generate_samples(
config: SFXRConfig,
wav_bits: int = WavBits.WAV_BITS_8,
wav_freq: int = WavFreq.WAV_FREQ_44100
) -> PackedByteArray:
_config = config
var data := _generate_samples()
var data := _generate_samples(wav_bits, wav_freq).data_array
_config = null
return data
func _generate_samples() -> PoolByteArray:
func _generate_samples(wav_bits: int, wav_freq: int) -> StreamPeerBuffer:
_reset_sample(true)
var playing_sample := true
var env_stage := 0
var env_time := 0
var output := PoolByteArray([])
var filesample: float = 0
var fileacc := 0
var buffer := StreamPeerBuffer.new()
# SynthSample
while playing_sample:
@ -129,7 +148,7 @@ func _generate_samples() -> PoolByteArray:
phase %= period
if _config.wave_type == SFXRConfig.WaveType.NOISE:
for j in 32:
noise_buffer[j] = rand_range(-1.0, +1.0)
noise_buffer[j] = randf_range(-1.0, +1.0)
# base waveform
var fp := float(phase) / period
@ -174,15 +193,21 @@ func _generate_samples() -> PoolByteArray:
ssample *= 4.0 # arbitrary gain to get reasonable output volume...
ssample = clamp(ssample, -1.0, +1.0)
var filesample := int((1 + ssample) / 2 * 255)
filesample += ssample
fileacc += 1
# This is a hack, AudioStreamSample wants a int8_t directly interpreted as uint8_t
filesample += 128
if filesample > 255:
filesample -= 255
if wav_freq == WavFreq.WAV_FREQ_44100 or fileacc == 2:
filesample /= fileacc
fileacc = 0
output.push_back(filesample)
return output
if wav_bits == WavBits.WAV_BITS_8:
buffer.put_8(filesample * 255)
else:
buffer.put_16(filesample * 32000)
filesample = 0
return buffer
func _reset_sample(restart: bool) -> void:
@ -241,7 +266,7 @@ func _reset_sample(restart: bool) -> void:
noise_buffer.resize(32)
for i in noise_buffer.size():
noise_buffer[i] = rand_range(-1.0, +1.0)
noise_buffer[i] = randf_range(-1.0, +1.0)
rep_time = 0
rep_limit = int(pow(1.0 - _config.p_repeat_speed, 2.0) * 20000 + 32)

View File

@ -1,11 +1,12 @@
tool
@tool
extends Control
signal value_changed(value)
export var value: float = 0.0 setget set_value
export var min_value: float = 0.0
export var max_value: float = 1.0
@export var value: float = 0.0 :
set = set_value
@export var min_value: float = 0.0
@export var max_value: float = 1.0
var _line_edit: LineEdit
@ -17,6 +18,7 @@ var _stylebox_value: StyleBox
var _line_edit_just_closed := false
var _mouse_hovering := false
var _is_editing := false
var _is_cancel := false
var _drag_start_position: Vector2
var _drag_cancelled := true
@ -24,9 +26,9 @@ var _drag_dist := 0.0
var _drag_start_factor: float
func _init() -> void:
func _init():
mouse_default_cursor_shape = Control.CURSOR_HSIZE
rect_clip_content = true
clip_contents = true
focus_mode = Control.FOCUS_ALL
var style := StyleBoxEmpty.new()
@ -34,49 +36,51 @@ func _init() -> void:
style.content_margin_right = 8
_line_edit = LineEdit.new()
_line_edit.set_as_toplevel(true)
_line_edit.set_as_top_level(true)
_line_edit.visible = false
_line_edit.add_stylebox_override("normal", style)
_line_edit.add_stylebox_override("focus", StyleBoxEmpty.new())
_line_edit.add_theme_stylebox_override("normal", style)
_line_edit.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
var _ret: int
_ret = _line_edit.connect("focus_exited", self, "_on_line_edit_focus_exited")
_ret = _line_edit.connect("text_entered", self, "_on_line_edit_text_entered")
_ret = _line_edit.connect("visibility_changed", self, "_on_line_edit_visibility_changed")
_ret = _line_edit.focus_exited.connect(self._on_line_edit_focus_exited)
_ret = _line_edit.text_submitted.connect(self._on_line_edit_text_entered)
_ret = _line_edit.visibility_changed.connect(self._on_line_edit_visibility_changed)
_ret = _line_edit.gui_input.connect(self._on_line_edit_gui_input)
add_child(_line_edit)
func _draw() -> void:
var font := get_font("font", "LineEdit")
var color := get_color("highlighted_font_color" if _mouse_hovering else "font_color", "Editor")
var font := get_theme_font("font", "LineEdit")
var font_size := get_theme_font_size("font_size", "LineEdit")
var color := get_theme_color("highlighted_font_color" if _mouse_hovering else "font_color", "Editor")
var number_string := "%.3f" % value
var number_size := font.get_string_size(number_string)
var pos := Vector2(
(rect_size.x - number_size.x) / 2,
(rect_size.y - number_size.y) / 2 + font.get_ascent()
(size.x - number_size.x) / 2,
(size.y - number_size.y) / 2 + font.get_ascent()
)
var stylebox := _stylebox_editing if _is_editing else _stylebox_hover if _mouse_hovering else _stylebox_normal
if _line_edit.visible:
draw_style_box(stylebox, Rect2(Vector2.ZERO, rect_size))
draw_style_box(stylebox, Rect2(Vector2.ZERO, size))
else:
var value_width := rect_size.x * ((value - min_value) / (max_value - min_value))
draw_style_box(stylebox, Rect2(value_width, 0, rect_size.x - value_width, rect_size.y))
draw_style_box(_stylebox_value, Rect2(0, 0, value_width, rect_size.y))
draw_string(font, pos, number_string, color)
var value_width := size.x * ((value - min_value) / (max_value - min_value))
draw_style_box(stylebox, Rect2(value_width, 0, size.x - value_width, size.y))
draw_style_box(_stylebox_value, Rect2(0, 0, value_width, size.y))
draw_string(font, pos, number_string, HORIZONTAL_ALIGNMENT_CENTER, -1, font_size, color)
func _get_minimum_size() -> Vector2:
var ms := _stylebox_normal.get_minimum_size()
ms.y += get_font("font", "LineEdit").get_height()
var ms := _stylebox_normal.get_minimum_size() if _stylebox_normal else Vector2.ZERO
ms.y += get_theme_font("font", "LineEdit").get_height()
return ms
func _gui_input(event: InputEvent) -> void:
var mb := event as InputEventMouseButton
if mb and mb.button_index == BUTTON_LEFT:
if mb and mb.button_index == MOUSE_BUTTON_LEFT:
if mb.pressed:
_drag_prepare(mb)
else:
@ -85,10 +89,10 @@ func _gui_input(event: InputEvent) -> void:
_show_text_edit()
_drag_cancelled = true
_is_editing = mb.pressed
update()
queue_redraw()
var mm := event as InputEventMouseMotion
if mm and mm.button_mask & BUTTON_MASK_LEFT:
if mm and mm.button_mask & MOUSE_BUTTON_MASK_LEFT:
_drag_motion(mm)
_drag_cancelled = false
@ -100,11 +104,11 @@ func _notification(what: int) -> void:
NOTIFICATION_MOUSE_ENTER:
_mouse_hovering = true
update()
queue_redraw()
NOTIFICATION_MOUSE_EXIT:
_mouse_hovering = false
update()
queue_redraw()
NOTIFICATION_FOCUS_ENTER:
if (Input.is_action_pressed("ui_focus_next") or Input.is_action_pressed("ui_focus_prev")) and not _line_edit_just_closed:
@ -117,17 +121,17 @@ func set_value(v: float) -> void:
return
value = v
emit_signal("value_changed", value)
update()
queue_redraw()
func _update_stylebox() -> void:
_stylebox_normal = get_stylebox("normal", "LineEdit")
_stylebox_normal = get_theme_stylebox("normal", "LineEdit")
_stylebox_hover = StyleBoxFlat.new()
_stylebox_hover.bg_color = get_color("highlight_color", "Editor")
_stylebox_hover.bg_color = get_theme_color("highlight_color", "Editor")
_stylebox_editing = StyleBoxFlat.new()
_stylebox_editing.bg_color = get_color("dark_color_2", "Editor")
_stylebox_editing.bg_color = get_theme_color("dark_color_2", "Editor")
_stylebox_value = StyleBoxFlat.new()
_stylebox_value.bg_color = get_color("accent_color", "Editor") * Color(1, 1, 1, 0.4)
_stylebox_value.bg_color = get_theme_color("accent_color", "Editor") * Color(1, 1, 1, 0.4)
func _drag_prepare(mouse: InputEventMouse) -> void:
@ -141,9 +145,9 @@ func _drag_done() -> void:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
if _drag_cancelled:
Input.warp_mouse_position(_drag_start_position)
Input.warp_mouse(_drag_start_position)
else:
Input.warp_mouse_position(rect_global_position + rect_size * Vector2(
Input.warp_mouse(global_position + size * Vector2(
(value - min_value) / (max_value - min_value),
0.5
))
@ -152,32 +156,33 @@ func _drag_done() -> void:
func _drag_motion(motion: InputEventMouseMotion) -> void:
_drag_dist += motion.relative.x
var factor := _drag_start_factor + _drag_dist / rect_size.x
var factor := _drag_start_factor + _drag_dist / size.x
if factor < 0 or 1 < factor:
factor = clamp(factor, 0, 1)
_drag_dist = (factor - _drag_start_factor) * rect_size.x
_drag_dist = (factor - _drag_start_factor) * size.x
var v := factor * (max_value - min_value) + min_value
var snap := motion.command or motion.shift
var snap := motion.is_command_or_control_pressed() or motion.shift_pressed
if snap and not (is_equal_approx(v, min_value) or is_equal_approx(v, max_value)):
if motion.shift and motion.command:
if motion.shift_pressed and motion.is_command_or_control_pressed():
v = round(v * 1000.0) * 0.001
elif motion.shift:
elif motion.shift_pressed:
v = round(v * 100.0) * 0.01
else:
v = round(v * 10.0) * 0.1
set_value(clamp(v, min_value, max_value))
update()
queue_redraw()
func _show_text_edit() -> void:
var gr := get_global_rect()
_is_cancel = false
_line_edit.text = str(value)
_line_edit.set_position(gr.position)
_line_edit.set_size(gr.size)
_line_edit.show_modal()
_line_edit.show() # FIXME: no suitable solution for `show_modal` yet
_line_edit.select_all()
_line_edit.grab_focus()
_line_edit.focus_next = find_next_valid_focus().get_path()
@ -187,11 +192,11 @@ func _show_text_edit() -> void:
func _on_line_edit_focus_exited():
if _line_edit.get_menu().visible:
return
if _line_edit.text.is_valid_float():
if not _is_cancel and _line_edit.text.is_valid_float():
set_value(clamp(_line_edit.text.to_float(), min_value, max_value))
if not _line_edit_just_closed:
_line_edit.hide()
update()
queue_redraw()
func _on_line_edit_text_entered(_text: String):
@ -201,3 +206,9 @@ func _on_line_edit_text_entered(_text: String):
func _on_line_edit_visibility_changed():
if not _line_edit.visible:
_line_edit_just_closed = true
func _on_line_edit_gui_input(event: InputEvent):
if event.is_action_pressed("ui_cancel"):
_is_cancel = true
_line_edit.hide()

View File

@ -1,17 +1,23 @@
tool
@tool
extends Container
enum ExtraOption { SAVE_AS, COPY, PASTE, RECENT }
enum ExtraOption { SAVE_AS, COPY, PASTE, PASTE_JSFXR, RECENT }
enum DefaultFilename { EMPTY, GUESS_FOR_SAVE }
const SFXRConfig := preload("../SFXRConfig.gd")
const SFXRGenerator := preload("../SFXRGenerator.gd")
const Base58 := preload("../Base58.gd")
const NUM_RECENTS := 4
class RecentEntry:
var title: String
var config := SFXRConfig.new()
var plugin: EditorPlugin
var plugin: EditorPlugin:
set(v):
plugin = v
for child in get_children():
_hook_plugin(child)
var _config := SFXRConfig.new()
var _config_defaults := SFXRConfig.new()
@ -25,46 +31,44 @@ var _param_map := {}
var _syncing_ui := false # a hack since Range set_value emits value_changed
var _category_names := {}
onready var audio_player := $AudioStreamPlayer as AudioStreamPlayer
onready var filename_label := find_node("Filename") as Label
onready var save_button := find_node("Save") as Button
onready var restore_button := find_node("Restore") as Button
onready var extra_button := find_node("Extra") as MenuButton
onready var version_button := find_node("VersionButton")
onready var translator := $PluginTranslator
@onready var audio_player := $AudioStreamPlayer as AudioStreamPlayer
@onready var filename_label := find_child("Filename") as Label
@onready var save_button := find_child("Save") as Button
@onready var restore_button := find_child("Restore") as Button
@onready var extra_button := find_child("Extra") as MenuButton
@onready var version_button := find_child("VersionButton")
@onready var translator := $PluginTranslator
func _ready():
if not plugin:
return # Running in the edited scene instead of from Plugin
for child in get_children():
_hook_plugin(child)
var popup := extra_button.get_popup()
popup.add_item(translator.tr("Save As..."), ExtraOption.SAVE_AS)
popup.add_item(translator.t("Save As..."), ExtraOption.SAVE_AS)
popup.add_separator()
popup.add_item(translator.tr("Copy"), ExtraOption.COPY)
popup.add_item(translator.tr("Paste"), ExtraOption.PASTE)
popup.add_separator(translator.tr("Recently Generated"))
popup.connect("id_pressed", self, "_on_Extra_id_pressed")
popup.add_icon_item(get_theme_icon("ActionCopy", "EditorIcons"), translator.t("Copy"), ExtraOption.COPY)
popup.add_icon_item(get_theme_icon("ActionPaste", "EditorIcons"), translator.t("Paste"), ExtraOption.PASTE)
popup.add_item(translator.t("Paste from jsfxr"), ExtraOption.PASTE_JSFXR)
popup.add_separator(translator.t("Recently Generated"))
popup.id_pressed.connect(_on_Extra_id_pressed)
_category_names = {
SFXRConfig.Category.PICKUP_COIN: translator.tr("Pickup/Coin"),
SFXRConfig.Category.LASER_SHOOT: translator.tr("Laser/Shoot"),
SFXRConfig.Category.EXPLOSION: translator.tr("Explosion"),
SFXRConfig.Category.POWERUP: translator.tr("Powerup"),
SFXRConfig.Category.HIT_HURT: translator.tr("Hit/Hurt"),
SFXRConfig.Category.JUMP: translator.tr("Jump"),
SFXRConfig.Category.BLIP_SELECT: translator.tr("Blip/Select"),
SFXRConfig.Category.PICKUP_COIN: translator.t("Pickup/Coin"),
SFXRConfig.Category.LASER_SHOOT: translator.t("Laser/Shoot"),
SFXRConfig.Category.EXPLOSION: translator.t("Explosion"),
SFXRConfig.Category.POWERUP: translator.t("Powerup"),
SFXRConfig.Category.HIT_HURT: translator.t("Hit/Hurt"),
SFXRConfig.Category.JUMP: translator.t("Jump"),
SFXRConfig.Category.BLIP_SELECT: translator.t("Blip/Select"),
}
var params := find_node("Params") as Container
var params := find_child("Params") as Container
for category in params.get_children():
for control in category.get_children():
_param_map[control.parameter] = control
control.connect("param_changed", self, "_on_param_changed")
control.connect("param_reset", self, "_on_param_reset")
control.connect("param_changed", _on_param_changed)
control.connect("param_reset", _on_param_reset)
_set_editing_file("")
@ -75,19 +79,26 @@ func _notification(what: int):
match what:
NOTIFICATION_ENTER_TREE, NOTIFICATION_THEME_CHANGED:
find_node("ScrollContainer").add_stylebox_override("bg", get_stylebox("bg", "Tree"))
find_child("ScrollContainer").add_theme_stylebox_override("panel", get_theme_stylebox("panel", "Tree"))
if extra_button:
var popup = extra_button.get_popup()
popup.set_item_icon(popup.get_item_index(ExtraOption.COPY), get_icon("ActionCopy", "EditorIcons"))
popup.set_item_icon(popup.get_item_index(ExtraOption.PASTE), get_icon("ActionPaste", "EditorIcons"))
popup.set_item_icon(popup.get_item_index(ExtraOption.COPY), get_theme_icon("ActionCopy", "EditorIcons"))
popup.set_item_icon(popup.get_item_index(ExtraOption.PASTE), get_theme_icon("ActionPaste", "EditorIcons"))
func edit(path: String) -> void:
func edit(object: Variant) -> void:
if not object:
if not _modified:
_set_editing_file("")
# TODO: prompt?
return
var path := object.resource_path as String
if _modified:
_popup_confirm(
translator.tr("There are unsaved changes.\nOpen '%s' anyway?") % path,
"_set_editing_file", [path]
translator.t("There are unsaved changes.\nOpen '%s' anyway?") % path,
_set_editing_file.bind(path)
)
else:
_set_editing_file(path)
@ -114,34 +125,43 @@ func _push_recent(title: String) -> void:
_config_recents.push_front(recent)
func _popup_confirm(content: String, callback: String, binds := []) -> void:
func _popup_confirm(content: String, callback: Callable) -> 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.title = translator.t("SFXR Editor")
dialog.confirmed.connect(callback)
dialog.popup_centered()
dialog.visibility_changed.connect(dialog.queue_free)
func _popup_message(content: String) -> void:
var dialog := AcceptDialog.new()
add_child(dialog)
dialog.dialog_text = content
dialog.window_title = translator.tr("SFXR Editor")
dialog.connect("popup_hide", dialog, "queue_free")
dialog.title = translator.t("SFXR Editor")
dialog.popup_centered()
dialog.visibility_changed.connect(dialog.queue_free)
func _popup_file_dialog(mode: int, callback: String) -> void:
func _popup_file_dialog(mode: int, callback: Callable, default_filename := DefaultFilename.EMPTY) -> void:
var dialog := EditorFileDialog.new()
add_child(dialog)
dialog.access = EditorFileDialog.ACCESS_RESOURCES
dialog.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.file_mode = mode
match default_filename:
DefaultFilename.EMPTY:
pass
DefaultFilename.GUESS_FOR_SAVE:
if _path:
dialog.current_path = _generate_serial_path(_path)
dialog.add_filter("*.sfxr; %s" % translator.t("SFXR Audio"))
dialog.file_selected.connect(callback)
dialog.popup_centered_ratio()
dialog.visibility_changed.connect(dialog.queue_free)
func _reset_defaults() -> void:
@ -158,13 +178,13 @@ func _restore_from_config(config: SFXRConfig) -> void:
func _set_editing_file(path: String) -> int: # Error
if path.empty():
if path.is_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)
_popup_message(translator.t("'%s' is not a valid SFXR file.") % path)
return err
audio_player.stream = load(path)
@ -176,8 +196,8 @@ func _set_editing_file(path: String) -> int: # Error
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")
var has_file := not _path.is_empty()
var base = _path if has_file else translator.t("Unsaved sound")
if _modified:
base += "(*)"
filename_label.text = base
@ -195,6 +215,37 @@ func _sync_ui() -> void:
_syncing_ui = false
func _generate_serial_path(path: String) -> String:
var directory := DirAccess.open(path.get_base_dir())
if not directory:
return path
if not directory.file_exists(path.get_file()):
return path
var basename := path.get_basename()
var extension := path.get_extension()
# Extract trailing number.
var num_string: String
for i in range(basename.length() - 1, -1, -1):
var c: String = basename[i]
if "0" <= c and c <= "9":
num_string = c + num_string
else:
break
var number := num_string.to_int() if num_string else 0
var name_string: String = basename.substr(0, basename.length() - num_string.length())
while true:
number += 1
var attemp := "%s%d.%s" % [name_string, number, extension]
if not directory.file_exists(attemp):
return attemp
return path # Unreachable
func _on_param_changed(name, value):
if _syncing_ui:
return
@ -230,7 +281,7 @@ func _on_Play_pressed(force_regenerate := false):
func _on_Randomize_pressed(category: int):
if category == -1:
_config.randomize()
_push_recent(translator.tr("Randomize"))
_push_recent(translator.t("Randomize"))
else:
_config.randomize_in_category(category)
_push_recent(_category_names.get(category, "Unknown"))
@ -243,7 +294,7 @@ func _on_Randomize_pressed(category: int):
func _on_Mutate_pressed():
_config.mutate()
_push_recent(translator.tr("Mutate"))
_push_recent(translator.t("Mutate"))
_set_modified(true)
_sync_ui()
_on_Play_pressed(true)
@ -256,8 +307,8 @@ func _on_Restore_pressed():
func _on_New_pressed():
if _modified:
_popup_confirm(
translator.tr("There are unsaved changes.\nCreate a new one anyway?"),
"_on_New_confirmed"
translator.t("There are unsaved changes.\nCreate a new one anyway?"),
_on_New_confirmed
)
else:
_on_New_confirmed()
@ -268,8 +319,8 @@ func _on_New_confirmed() -> void:
func _on_Save_pressed():
if _path.empty():
_popup_file_dialog(EditorFileDialog.MODE_SAVE_FILE, "_on_SaveAsDialog_confirmed")
if _path.is_empty():
_popup_file_dialog(EditorFileDialog.FILE_MODE_SAVE_FILE, _on_SaveAsDialog_confirmed)
else:
_config.save(_path)
plugin.get_editor_interface().get_resource_filesystem().scan_sources()
@ -286,16 +337,17 @@ func _on_SaveAsDialog_confirmed(path: String):
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"]
translator.t("There are unsaved changes.\nLoad anyway?"),
_popup_file_dialog.bind(EditorFileDialog.FILE_MODE_OPEN_FILE, "_set_editing_file")
)
else:
_popup_file_dialog(EditorFileDialog.MODE_OPEN_FILE, "_set_editing_file")
_popup_file_dialog(EditorFileDialog.FILE_MODE_OPEN_FILE, _set_editing_file)
func _on_Extra_about_to_show():
var popup := extra_button.get_popup()
popup.set_item_disabled(popup.get_item_index(ExtraOption.PASTE), _config_clipboard == null)
popup.set_item_disabled(popup.get_item_index(ExtraOption.PASTE_JSFXR), not DisplayServer.clipboard_has())
# Rebuild recents menu everytime :)
var first_recent_index := popup.get_item_index(ExtraOption.RECENT)
@ -304,8 +356,8 @@ func _on_Extra_about_to_show():
for i in count - first_recent_index:
popup.remove_item(count - 1 - i)
if _config_recents.empty():
popup.add_item(translator.tr("None"), ExtraOption.RECENT)
if _config_recents.is_empty():
popup.add_item(translator.t("None"), ExtraOption.RECENT)
popup.set_item_disabled(popup.get_item_index(ExtraOption.RECENT), true)
else:
for i in _config_recents.size():
@ -315,7 +367,7 @@ func _on_Extra_about_to_show():
func _on_Extra_id_pressed(id: int) -> void:
match id:
ExtraOption.SAVE_AS:
_popup_file_dialog(EditorFileDialog.MODE_SAVE_FILE, "_on_SaveAsDialog_confirmed")
_popup_file_dialog(EditorFileDialog.FILE_MODE_SAVE_FILE, _on_SaveAsDialog_confirmed, DefaultFilename.GUESS_FOR_SAVE)
ExtraOption.COPY:
if not _config_clipboard:
@ -325,6 +377,13 @@ func _on_Extra_id_pressed(id: int) -> void:
ExtraOption.PASTE:
_restore_from_config(_config_clipboard)
ExtraOption.PASTE_JSFXR:
var pasted := SFXRConfig.new()
if pasted.load_from_base58(DisplayServer.clipboard_get()) == OK:
_restore_from_config(pasted)
else:
_popup_message(translator.t("Clipboard does not contain code copied from jsfxr."))
_:
var i := id - ExtraOption.RECENT as int
if i < 0 or _config_recents.size() <= i:

View File

@ -1,394 +1,297 @@
[gd_scene load_steps=7 format=2]
[gd_scene load_steps=7 format=3 uid="uid://dv8q33r8aan5b"]
[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]
[ext_resource type="Script" path="res://addons/gdfxr/editor/EditorIconButton.gd" id="1"]
[ext_resource type="Script" path="res://addons/gdfxr/editor/Editor.gd" id="2"]
[ext_resource type="PackedScene" uid="uid://p27e3x3kk5e0" path="res://addons/gdfxr/editor/ParamSlider.tscn" id="3"]
[ext_resource type="PackedScene" uid="uid://b8wt6fq8w6mxc" path="res://addons/gdfxr/editor/ParamOption.tscn" id="4"]
[ext_resource type="PackedScene" uid="uid://cewvefxbttrds" path="res://addons/gdfxr/editor/PluginTranslator.tscn" id="5"]
[ext_resource type="PackedScene" path="res://addons/gdfxr/editor/VersionButton.tscn" id="6"]
[node name="Editor" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource( 2 )
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("2")
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
[node name="Toolbar" type="HBoxContainer" parent="."]
margin_right = 1024.0
margin_bottom = 22.0
layout_mode = 2
[node name="New" type="ToolButton" parent="Toolbar"]
margin_right = 12.0
margin_bottom = 22.0
hint_tooltip = "New"
script = ExtResource( 1 )
[node name="New" type="Button" parent="Toolbar"]
layout_mode = 2
tooltip_text = "New"
flat = true
script = ExtResource("1")
icon_name = "New"
[node name="Load" type="ToolButton" parent="Toolbar"]
margin_left = 16.0
margin_right = 28.0
margin_bottom = 22.0
hint_tooltip = "Load"
script = ExtResource( 1 )
[node name="Load" type="Button" parent="Toolbar"]
layout_mode = 2
tooltip_text = "Load"
flat = true
script = ExtResource("1")
icon_name = "Load"
[node name="Save" type="ToolButton" parent="Toolbar"]
margin_left = 32.0
margin_right = 44.0
margin_bottom = 22.0
hint_tooltip = "Save"
script = ExtResource( 1 )
[node name="Save" type="Button" parent="Toolbar"]
layout_mode = 2
tooltip_text = "Save"
flat = true
script = ExtResource("1")
icon_name = "Save"
[node name="Extra" type="MenuButton" parent="Toolbar"]
margin_left = 48.0
margin_right = 60.0
margin_bottom = 22.0
hint_tooltip = "Extra Options"
script = ExtResource( 1 )
layout_mode = 2
tooltip_text = "Extra Options"
script = ExtResource("1")
icon_name = "GuiTabMenuHl"
[node name="VSeparator" type="VSeparator" parent="Toolbar"]
margin_left = 64.0
margin_right = 68.0
margin_bottom = 22.0
layout_mode = 2
[node name="Play" type="Button" parent="Toolbar"]
margin_left = 72.0
margin_right = 152.0
margin_bottom = 22.0
rect_min_size = Vector2( 80, 0 )
layout_mode = 2
size_flags_horizontal = 0
text = "Play"
script = ExtResource( 1 )
script = ExtResource("1")
icon_name = "Play"
[node name="Restore" type="Button" parent="Toolbar"]
margin_left = 156.0
margin_right = 236.0
margin_bottom = 22.0
rect_min_size = Vector2( 80, 0 )
hint_tooltip = "Restore"
layout_mode = 2
tooltip_text = "Restore"
disabled = true
text = "Restore"
script = ExtResource( 1 )
script = ExtResource("1")
icon_name = "Reload"
[node name="VSeparator2" type="VSeparator" parent="Toolbar"]
margin_left = 240.0
margin_right = 244.0
margin_bottom = 22.0
layout_mode = 2
[node name="Filename" type="Label" parent="Toolbar"]
margin_left = 248.0
margin_top = 4.0
margin_right = 1020.0
margin_bottom = 18.0
layout_mode = 2
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
[node name="VersionButton" parent="Toolbar" instance=ExtResource("6")]
layout_mode = 2
website = "https://github.com/timothyqiu/gdfxr"
[node name="HSeparator" type="HSeparator" parent="."]
margin_top = 26.0
margin_right = 1024.0
margin_bottom = 30.0
layout_mode = 2
[node name="Editor" type="HBoxContainer" parent="."]
margin_top = 34.0
margin_right = 1024.0
margin_bottom = 600.0
layout_mode = 2
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 )
layout_mode = 2
[node name="Button" type="Button" parent="Editor/Generators"]
margin_right = 128.0
margin_bottom = 20.0
layout_mode = 2
text = "Pickup/Coin"
[node name="Button2" type="Button" parent="Editor/Generators"]
margin_top = 24.0
margin_right = 128.0
margin_bottom = 44.0
layout_mode = 2
text = "Laser/Shoot"
[node name="Button3" type="Button" parent="Editor/Generators"]
margin_top = 48.0
margin_right = 128.0
margin_bottom = 68.0
layout_mode = 2
text = "Explosion"
[node name="Button4" type="Button" parent="Editor/Generators"]
margin_top = 72.0
margin_right = 128.0
margin_bottom = 92.0
layout_mode = 2
text = "Powerup"
[node name="Button5" type="Button" parent="Editor/Generators"]
margin_top = 96.0
margin_right = 128.0
margin_bottom = 116.0
layout_mode = 2
text = "Hit/Hurt"
[node name="Button6" type="Button" parent="Editor/Generators"]
margin_top = 120.0
margin_right = 128.0
margin_bottom = 140.0
layout_mode = 2
text = "Jump"
[node name="Button7" type="Button" parent="Editor/Generators"]
margin_top = 144.0
margin_right = 128.0
margin_bottom = 164.0
layout_mode = 2
text = "Blip/Select"
[node name="HSeparator" type="HSeparator" parent="Editor/Generators"]
margin_top = 168.0
margin_right = 128.0
margin_bottom = 172.0
layout_mode = 2
[node name="Button8" type="Button" parent="Editor/Generators"]
margin_top = 176.0
margin_right = 128.0
margin_bottom = 196.0
layout_mode = 2
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
layout_mode = 2
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
layout_mode = 2
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
layout_mode = 2
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
layout_mode = 2
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource( 3 )]
margin_right = 211.0
margin_bottom = 22.0
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource("3")]
layout_mode = 2
label = "Attack Time"
parameter = "p_env_attack"
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource( 3 )]
margin_top = 26.0
margin_right = 211.0
margin_bottom = 48.0
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource("3")]
layout_mode = 2
label = "Sustain Time"
parameter = "p_env_sustain"
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource( 3 )]
margin_top = 52.0
margin_right = 211.0
margin_bottom = 74.0
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource("3")]
layout_mode = 2
label = "Sustain Punch"
parameter = "p_env_punch"
[node name="ParamSlider4" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource( 3 )]
margin_top = 78.0
margin_right = 211.0
margin_bottom = 100.0
[node name="ParamSlider4" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource("3")]
layout_mode = 2
label = "Decay Time"
parameter = "p_env_decay"
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource( 3 )]
margin_top = 104.0
margin_right = 211.0
margin_bottom = 126.0
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource("3")]
layout_mode = 2
label = "Change Amount"
parameter = "p_arp_mod"
bipolar = true
[node name="ParamSlider6" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource( 3 )]
margin_top = 130.0
margin_right = 211.0
margin_bottom = 152.0
[node name="ParamSlider6" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource("3")]
layout_mode = 2
label = "Change Speed"
parameter = "p_arp_speed"
[node name="ParamSlider7" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource( 3 )]
margin_top = 156.0
margin_right = 211.0
margin_bottom = 178.0
[node name="ParamSlider7" parent="Editor/ScrollContainer/Params/Envolope" instance=ExtResource("3")]
layout_mode = 2
label = "Volume"
parameter = "sound_vol"
[node name="Frequency" type="VBoxContainer" parent="Editor/ScrollContainer/Params"]
margin_left = 215.0
margin_right = 425.0
margin_bottom = 178.0
layout_mode = 2
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource( 3 )]
margin_right = 210.0
margin_bottom = 22.0
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource("3")]
layout_mode = 2
label = "Start Frequency"
parameter = "p_base_freq"
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource( 3 )]
margin_top = 26.0
margin_right = 210.0
margin_bottom = 48.0
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource("3")]
layout_mode = 2
label = "Min Frequency"
parameter = "p_freq_limit"
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource( 3 )]
margin_top = 52.0
margin_right = 210.0
margin_bottom = 74.0
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource("3")]
layout_mode = 2
label = "Slide"
parameter = "p_freq_ramp"
bipolar = true
[node name="ParamSlider4" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource( 3 )]
margin_top = 78.0
margin_right = 210.0
margin_bottom = 100.0
[node name="ParamSlider4" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource("3")]
layout_mode = 2
label = "Delta Slide"
parameter = "p_freq_dramp"
bipolar = true
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource( 3 )]
margin_top = 104.0
margin_right = 210.0
margin_bottom = 126.0
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource("3")]
layout_mode = 2
label = "Vibrato Depth"
parameter = "p_vib_strength"
[node name="ParamSlider6" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource( 3 )]
margin_top = 130.0
margin_right = 210.0
margin_bottom = 152.0
[node name="ParamSlider6" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource("3")]
layout_mode = 2
label = "Vibrato Speed"
parameter = "p_vib_speed"
[node name="ParamSlider7" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource( 3 )]
margin_top = 156.0
margin_right = 210.0
margin_bottom = 178.0
[node name="ParamSlider7" parent="Editor/ScrollContainer/Params/Frequency" instance=ExtResource("3")]
layout_mode = 2
label = "Repeat Speed"
parameter = "p_repeat_speed"
[node name="Waveform" type="VBoxContainer" parent="Editor/ScrollContainer/Params"]
margin_left = 429.0
margin_right = 649.0
margin_bottom = 178.0
layout_mode = 2
[node name="WaveformOption" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource( 4 )]
margin_right = 220.0
margin_bottom = 22.0
options = [ "Square", "Sawtooth", "Sine", "Noise" ]
[node name="WaveformOption" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource("4")]
layout_mode = 2
options = ["Square", "Sawtooth", "Sine", "Noise"]
parameter = "wave_type"
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource( 3 )]
margin_top = 26.0
margin_right = 220.0
margin_bottom = 48.0
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource("3")]
layout_mode = 2
label = "Square Duty"
parameter = "p_duty"
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource( 3 )]
margin_top = 52.0
margin_right = 220.0
margin_bottom = 74.0
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource("3")]
layout_mode = 2
label = "Duty Sweep"
parameter = "p_duty_ramp"
bipolar = true
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource( 3 )]
margin_top = 78.0
margin_right = 220.0
margin_bottom = 100.0
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource("3")]
layout_mode = 2
label = "Phaser Offset"
parameter = "p_pha_offset"
bipolar = true
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource( 3 )]
margin_top = 104.0
margin_right = 220.0
margin_bottom = 126.0
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Waveform" instance=ExtResource("3")]
layout_mode = 2
label = "Phaser Sweep"
parameter = "p_pha_ramp"
bipolar = true
[node name="Filter" type="VBoxContainer" parent="Editor/ScrollContainer/Params"]
margin_left = 653.0
margin_right = 895.0
margin_bottom = 178.0
layout_mode = 2
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource( 3 )]
margin_right = 242.0
margin_bottom = 22.0
[node name="ParamSlider" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource("3")]
layout_mode = 2
label = "Low-pass Cutoff"
parameter = "p_lpf_freq"
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource( 3 )]
margin_top = 26.0
margin_right = 242.0
margin_bottom = 48.0
[node name="ParamSlider2" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource("3")]
layout_mode = 2
label = "Low-pass Sweep"
parameter = "p_lpf_ramp"
bipolar = true
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource( 3 )]
margin_top = 52.0
margin_right = 242.0
margin_bottom = 74.0
[node name="ParamSlider5" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource("3")]
layout_mode = 2
label = "Low-pass Resonance"
parameter = "p_lpf_resonance"
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource( 3 )]
margin_top = 78.0
margin_right = 242.0
margin_bottom = 100.0
[node name="ParamSlider3" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource("3")]
layout_mode = 2
label = "High-pass Cutoff"
parameter = "p_hpf_freq"
[node name="ParamSlider4" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource( 3 )]
margin_top = 104.0
margin_right = 242.0
margin_bottom = 126.0
[node name="ParamSlider4" parent="Editor/ScrollContainer/Params/Filter" instance=ExtResource("3")]
layout_mode = 2
label = "High-pass Sweep"
parameter = "p_hpf_ramp"
bipolar = true
[node name="PluginTranslator" parent="." instance=ExtResource( 5 )]
[node name="PluginTranslator" parent="." instance=ExtResource("5")]
[connection signal="pressed" from="Toolbar/New" to="." method="_on_New_pressed"]
[connection signal="pressed" from="Toolbar/Load" to="." method="_on_Load_pressed"]
[connection signal="pressed" from="Toolbar/Save" to="." method="_on_Save_pressed"]
[connection signal="about_to_show" from="Toolbar/Extra" to="." method="_on_Extra_about_to_show"]
[connection signal="about_to_popup" from="Toolbar/Extra" to="." method="_on_Extra_about_to_show"]
[connection signal="pressed" from="Toolbar/Play" to="." method="_on_Play_pressed"]
[connection signal="pressed" from="Toolbar/Restore" to="." method="_on_Restore_pressed"]
[connection signal="pressed" from="Editor/Generators/Button" to="." method="_on_Randomize_pressed" binds= [ 0 ]]
[connection signal="pressed" from="Editor/Generators/Button2" to="." method="_on_Randomize_pressed" binds= [ 1 ]]
[connection signal="pressed" from="Editor/Generators/Button3" to="." method="_on_Randomize_pressed" binds= [ 2 ]]
[connection signal="pressed" from="Editor/Generators/Button4" to="." method="_on_Randomize_pressed" binds= [ 3 ]]
[connection signal="pressed" from="Editor/Generators/Button5" to="." method="_on_Randomize_pressed" binds= [ 4 ]]
[connection signal="pressed" from="Editor/Generators/Button6" to="." method="_on_Randomize_pressed" binds= [ 5 ]]
[connection signal="pressed" from="Editor/Generators/Button7" to="." method="_on_Randomize_pressed" binds= [ 6 ]]
[connection signal="pressed" from="Editor/Generators/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 ]]
[connection signal="pressed" from="Editor/Generators/Button9" to="." method="_on_Randomize_pressed" binds= [-1]]

View File

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

View File

@ -1,13 +1,14 @@
tool
@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
@export var options: Array :
set = set_options
@export var parameter: String # Could be PackedStringArray, but pybabel won't catch that
onready var option_button := $OptionButton as OptionButton
@onready var option_button := $OptionButton as OptionButton
func _ready():

View File

@ -1,42 +1,34 @@
[gd_scene load_steps=3 format=2]
[gd_scene load_steps=3 format=3 uid="uid://b8wt6fq8w6mxc"]
[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]
[ext_resource type="Script" path="res://addons/gdfxr/editor/ParamOption.gd" id="1"]
[ext_resource type="Script" path="res://addons/gdfxr/editor/EditorIconButton.gd" id="2"]
[node name="WaveformOption" type="HBoxContainer"]
margin_right = 253.0
margin_bottom = 40.0
rect_pivot_offset = Vector2( 41, -65 )
offset_right = 253.0
offset_bottom = 40.0
pivot_offset = Vector2(41, -65)
size_flags_horizontal = 3
script = ExtResource( 1 )
script = ExtResource("1")
[node name="Label" type="Label" parent="."]
margin_top = 13.0
margin_right = 128.0
margin_bottom = 27.0
rect_min_size = Vector2( 100, 0 )
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "Waveform"
align = 2
horizontal_alignment = 2
[node name="OptionButton" type="OptionButton" parent="."]
margin_left = 132.0
margin_top = 10.0
margin_right = 237.0
margin_bottom = 30.0
rect_min_size = Vector2( 105, 0 )
custom_minimum_size = Vector2(105, 0)
layout_mode = 2
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
focus_mode = 0
[node name="Reset" type="Button" parent="."]
layout_mode = 2
size_flags_vertical = 4
enabled_focus_mode = 0
script = ExtResource( 2 )
focus_mode = 0
flat = true
script = ExtResource("2")
icon_name = "ReloadSmall"
[connection signal="item_selected" from="OptionButton" to="." method="_on_OptionButton_item_selected"]

View File

@ -1,21 +1,33 @@
tool
@tool
extends HBoxContainer
signal param_changed(name, value)
signal param_reset(name)
export var label: String setget set_label
export var parameter: String
export var bipolar := false setget set_bipolar
@export var label: String :
set = set_label
@export var parameter: String
@export var bipolar := false :
set = set_bipolar
func _ready():
set_label(label)
set_bipolar(bipolar)
func set_label(v: String) -> void:
label = v
if is_inside_tree():
$Label.text = v
func set_bipolar(v: bool) -> void:
bipolar = v
if not is_inside_tree():
return
if bipolar:
$HSlider.min_value = -1.0
else:

View File

@ -1,41 +1,35 @@
[gd_scene load_steps=4 format=2]
[gd_scene load_steps=4 format=3 uid="uid://p27e3x3kk5e0"]
[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]
[ext_resource path="res://addons/gdfxr/editor/EditSlider.gd" type="Script" id=3]
[ext_resource type="Script" path="res://addons/gdfxr/editor/ParamSlider.gd" id="1"]
[ext_resource type="Script" path="res://addons/gdfxr/editor/EditorIconButton.gd" id="2"]
[ext_resource type="Script" path="res://addons/gdfxr/editor/EditSlider.gd" id="3"]
[node name="ParamSlider" type="HBoxContainer"]
margin_right = 253.0
margin_bottom = 40.0
offset_right = 253.0
offset_bottom = 40.0
size_flags_horizontal = 3
script = ExtResource( 1 )
script = ExtResource("1")
[node name="Label" type="Label" parent="."]
margin_top = 13.0
margin_right = 128.0
margin_bottom = 27.0
rect_min_size = Vector2( 100, 0 )
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
size_flags_horizontal = 3
align = 2
horizontal_alignment = 2
[node name="HSlider" type="Control" parent="."]
margin_left = 132.0
margin_right = 237.0
margin_bottom = 40.0
rect_min_size = Vector2( 105, 0 )
rect_clip_content = true
clip_contents = true
custom_minimum_size = Vector2(105, 0)
layout_mode = 2
focus_mode = 2
mouse_default_cursor_shape = 10
script = ExtResource( 3 )
script = ExtResource("3")
[node name="Reset" type="ToolButton" parent="."]
margin_left = 241.0
margin_top = 9.0
margin_right = 253.0
margin_bottom = 31.0
focus_mode = 0
[node name="Reset" type="Button" parent="."]
layout_mode = 2
size_flags_vertical = 4
script = ExtResource( 2 )
focus_mode = 0
flat = true
script = ExtResource("2")
icon_name = "ReloadSmall"
[connection signal="value_changed" from="HSlider" to="." method="_on_HSlider_value_changed"]

View File

@ -1,7 +1,8 @@
tool
@tool
extends Node
var plugin: EditorPlugin setget set_plugin
var plugin: EditorPlugin :
set = set_plugin
var _translation: Translation
@ -17,40 +18,40 @@ func set_plugin(v: EditorPlugin) -> void:
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)
var path := script.resource_path.get_base_dir().path_join("translations/%s.po" % locale)
if ResourceLoader.exists(path):
_translation = ResourceLoader.load(path)
if _translation:
_translate_node(get_parent())
_translate_node.call_deferred(get_parent())
func tr(message: String) -> String:
func t(message: StringName) -> String:
if _translation:
var translated := _translation.get_message(message)
if not translated.empty():
return translated
return message
if translated:
return String(translated)
return String(message)
func _translate_node(node: Node):
if node is Control:
node.hint_tooltip = tr(node.hint_tooltip)
node.tooltip_text = t(node.tooltip_text)
if node is HBoxContainer and node.has_method("set_options"):
var options = []
for item in node.options:
options.append(tr(item))
options.append(t(item))
node.options = options
if node is Button and not node is OptionButton:
node.text = tr(node.text)
node.text = t(node.text)
if node is Label:
node.text = tr(node.text)
node.text = t(node.text)
if node is Slider:
node.hint_tooltip = tr(node.hint_tooltip)
node.tooltip_text = t(node.tooltip_text)
for child in node.get_children():
_translate_node(child)

View File

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

View File

@ -1,16 +1,17 @@
tool
@tool
extends LinkButton
export var website: String
@export var website: String
var plugin: EditorPlugin setget set_plugin
var plugin: EditorPlugin :
set = set_plugin
func set_plugin(v: EditorPlugin) -> void:
plugin = v
var script := get_script() as Script
var path := script.resource_path.get_base_dir().plus_file("../plugin.cfg")
var path := script.resource_path.get_base_dir().path_join("../plugin.cfg")
var cfg := ConfigFile.new()
var err := cfg.load(path)

View File

@ -4,10 +4,10 @@
[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
offset_left = 1024.0
offset_top = 5.0
offset_right = 1024.0
offset_bottom = 19.0
focus_mode = 2
size_flags_vertical = 4
underline = 1

View File

@ -8,14 +8,14 @@ msgid ""
msgstr ""
"Project-Id-Version: gdfxr 1.0\n"
"Report-Msgid-Bugs-To: timothyqiu32@gmail.com\n"
"POT-Creation-Date: 2022-09-20 14:01+0800\n"
"POT-Creation-Date: 2022-12-04 13:45+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"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.10.3\n"
"Generated-By: Babel 2.11.0\n"
#: addons/gdfxr/editor/Editor.gd
msgid "Save As..."
@ -29,6 +29,10 @@ msgstr ""
msgid "Paste"
msgstr ""
#: addons/gdfxr/editor/Editor.gd
msgid "Paste from jsfxr"
msgstr ""
#: addons/gdfxr/editor/Editor.gd
msgid "Recently Generated"
msgstr ""
@ -109,6 +113,10 @@ msgstr ""
msgid "None"
msgstr ""
#: addons/gdfxr/editor/Editor.gd
msgid "Clipboard does not contain code copied from jsfxr."
msgstr ""
#: addons/gdfxr/editor/Editor.tscn
msgid "New"
msgstr ""
@ -245,7 +253,3 @@ msgstr ""
msgid "Waveform"
msgstr ""
#: addons/gdfxr/editor/ParamSlider.tscn
msgid "Hold Ctrl to snap to 0.01 increments."
msgstr ""

View File

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gdfxr 1.0\n"
"Report-Msgid-Bugs-To: timothyqiu32@gmail.com\n"
"POT-Creation-Date: 2022-09-20 14:01+0800\n"
"PO-Revision-Date: 2022-09-20 14:01+0800\n"
"POT-Creation-Date: 2022-12-04 13:45+0800\n"
"PO-Revision-Date: 2022-12-04 13:45+0800\n"
"Last-Translator: Haoyu Qiu <timothyqiu32@gmail.com>\n"
"Language-Team: \n"
"Language: zh_CN\n"
@ -17,7 +17,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"Generated-By: Babel 2.9.1\n"
"X-Generator: Poedit 3.1\n"
"X-Generator: Poedit 3.2.1\n"
#: addons/gdfxr/editor/Editor.gd
msgid "Save As..."
@ -31,6 +31,10 @@ msgstr "复制"
msgid "Paste"
msgstr "粘贴"
#: addons/gdfxr/editor/Editor.gd
msgid "Paste from jsfxr"
msgstr "从 jsfxr 粘贴"
#: addons/gdfxr/editor/Editor.gd
msgid "Recently Generated"
msgstr "最近生成"
@ -117,6 +121,10 @@ msgstr ""
msgid "None"
msgstr "无"
#: addons/gdfxr/editor/Editor.gd
msgid "Clipboard does not contain code copied from jsfxr."
msgstr "剪贴板中没有从 jsfxr 复制的代码。"
#: addons/gdfxr/editor/Editor.tscn
msgid "New"
msgstr "新建"
@ -253,6 +261,5 @@ msgstr "高通变频"
msgid "Waveform"
msgstr "波形"
#: addons/gdfxr/editor/ParamSlider.tscn
msgid "Hold Ctrl to snap to 0.01 increments."
msgstr "按住 Ctrl 吸附到 0.01 增量。"
#~ msgid "Hold Ctrl to snap to 0.01 increments."
#~ msgstr "按住 Ctrl 吸附到 0.01 增量。"

View File

@ -1,62 +1,84 @@
tool
@tool
extends EditorImportPlugin
const SFXRConfig = preload("SFXRConfig.gd")
const SFXRGenerator = preload("SFXRGenerator.gd")
func get_importer_name():
func _get_importer_name():
return "com.timothyqiu.gdfxr.importer"
func get_visible_name():
func _get_import_order():
return ResourceImporter.IMPORT_ORDER_DEFAULT
func _get_priority():
return 1.0
func _get_visible_name():
return "SFXR Audio"
func get_recognized_extensions():
func _get_recognized_extensions():
return ["sfxr"]
func get_save_extension():
func _get_save_extension():
return "sample"
func get_resource_type():
return "AudioStreamSample"
func _get_resource_type():
return "AudioStreamWAV"
func get_preset_count():
func _get_preset_count():
return 1
func get_preset_name(preset):
func _get_preset_name(preset):
return "Default"
func get_import_options(preset):
func _get_import_options(path, preset):
return [
{
name="loop",
default_value=false,
},
{
name="bit_depth",
property_hint=PROPERTY_HINT_ENUM,
hint_string="8 Bits,16 Bits",
default_value=SFXRGenerator.WavBits.WAV_BITS_8,
},
{
name="sample_rate",
property_hint=PROPERTY_HINT_ENUM,
hint_string="44100 Hz,22050 Hz",
default_value=SFXRGenerator.WavFreq.WAV_FREQ_44100,
},
]
func get_option_visibility(option, options):
func _get_option_visibility(path, option, options):
return true
func import(source_file, save_path, options, platform_variants, gen_files):
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)
var stream := SFXRGenerator.new().generate_audio_stream(
config, options.bit_depth, options.sample_rate
)
if options.loop:
stream.loop_mode = AudioStreamSample.LOOP_FORWARD
stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
stream.loop_end = stream.data.size()
var filename = save_path + "." + get_save_extension()
return ResourceSaver.save(filename, stream)
var filename = save_path + "." + _get_save_extension()
return ResourceSaver.save(stream, filename)

View File

@ -3,5 +3,5 @@
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.2"
version="2.0"
script="plugin.gd"

View File

@ -1,4 +1,4 @@
tool
@tool
extends EditorPlugin
@ -10,7 +10,7 @@ func _enter_tree():
import_plugin = preload("import_plugin.gd").new()
add_import_plugin(import_plugin)
sfxr_editor = preload("editor/Editor.tscn").instance()
sfxr_editor = preload("editor/Editor.tscn").instantiate()
sfxr_editor.plugin = self
add_control_to_bottom_panel(sfxr_editor, "gdfxr")
@ -24,15 +24,15 @@ func _exit_tree():
import_plugin = null
func handles(object: Object) -> bool:
return object is AudioStreamSample and object.resource_path.ends_with(".sfxr")
func _handles(object: Object) -> bool:
return object is AudioStreamWAV and object.resource_path.ends_with(".sfxr")
func edit(object: Object):
sfxr_editor.edit(object.resource_path) # Should already passed `handles()` checks
func _edit(object: Object):
sfxr_editor.edit(object)
func make_visible(visible: bool):
func _make_visible(visible: bool):
if visible:
make_bottom_panel_item_visible(sfxr_editor)
elif sfxr_editor.is_visible_in_tree():

38
example/Example.gd Normal file
View File

@ -0,0 +1,38 @@
extends Container
# These two classes are for runtime generation.
const SFXRConfig = preload("res://addons/gdfxr/SFXRConfig.gd")
const SFXRGenerator = preload("res://addons/gdfxr/SFXRGenerator.gd")
@onready var audio_player: AudioStreamPlayer = $AudioPlayer
@onready var adhoc_audio_player: AudioStreamPlayer = $AdhocAudioPlayer
func _on_Play_pressed() -> void:
audio_player.play()
func _on_PlayFile_pressed() -> void:
adhoc_audio_player.stream = preload("res://example/example.sfxr")
adhoc_audio_player.play()
func _on_Generate_pressed() -> void:
var config := SFXRConfig.new()
# Fill the fields manually
# config.p_base_freq = 0.5
# Load from .sfxr file
# config.load("res://example/example.sfxr")
# Load from jsfxr base58 string
config.load_from_base58("34T6PkmKkNTf3aUynCpV3oetaq6ecj9Grh9W7tiTbccVYK8FxNKBbfBFXJCLzk8QTy4d7fbiCfY2gXDaiengXbENjdLWt5jZBtcz8QmSCXjHCSuooDCWp4SrT")
# generate_audio_stream() might freeze a bit when generating long sounds.
# It's recommended to pre-generate the sound effects in editor.
# If you do want to generate the sound effects on the fly, you might want
# to generate and cache the sound effects at the start of your game.
var generator := SFXRGenerator.new()
adhoc_audio_player.stream = generator.generate_audio_stream(config)
adhoc_audio_player.play()

View File

@ -1,23 +1,59 @@
[gd_scene load_steps=2 format=2]
[gd_scene load_steps=3 format=3 uid="uid://bv31mn2hs6wom"]
[ext_resource path="res://example/example.sfxr" type="AudioStream" id=1]
[ext_resource type="Script" path="res://example/Example.gd" id="1"]
[ext_resource type="AudioStream" uid="uid://byf7u7a25fuf4" path="res://example/example.sfxr" id="2"]
[node name="Example" type="CenterContainer"]
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Example" type="GridContainer"]
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/h_separation = 32
theme_override_constants/v_separation = 32
columns = 2
script = ExtResource("1")
[node name="Button" type="Button" parent="."]
margin_left = 448.0
margin_top = 284.0
margin_right = 576.0
margin_bottom = 316.0
rect_min_size = Vector2( 128, 32 )
[node name="AudioPlayer" type="AudioStreamPlayer" parent="."]
stream = ExtResource("2")
[node name="AdhocAudioPlayer" type="AudioStreamPlayer" parent="."]
[node name="Play" type="Button" parent="."]
layout_mode = 2
size_flags_vertical = 4
text = "Play"
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
stream = ExtResource( 1 )
[node name="Label" type="Label" parent="."]
custom_minimum_size = Vector2(500, 0)
layout_mode = 2
text = "A .sfxr file can be used as regular audio files like .wav, .ogg, and .mp3."
autowrap_mode = 3
[connection signal="pressed" from="Button" to="AudioStreamPlayer" method="play"]
[node name="PlayFile" type="Button" parent="."]
layout_mode = 2
size_flags_vertical = 4
text = "Load .sfxr File"
[node name="Label2" type="Label" parent="."]
custom_minimum_size = Vector2(500, 0)
layout_mode = 2
text = "A .sfxr file is a AudioStreamSample resource that can be loaded with load() or preload()."
autowrap_mode = 3
[node name="Generate" type="Button" parent="."]
layout_mode = 2
size_flags_vertical = 4
text = "Runtime Generation"
[node name="Label3" type="Label" parent="."]
custom_minimum_size = Vector2(500, 0)
layout_mode = 2
text = "You can generate the sound effect at runtime. However, due to performance constraints with GDScript, your game might freeze when generating long sounds."
autowrap_mode = 3
[connection signal="pressed" from="Play" to="." method="_on_Play_pressed"]
[connection signal="pressed" from="PlayFile" to="." method="_on_PlayFile_pressed"]
[connection signal="pressed" from="Generate" to="." method="_on_Generate_pressed"]

View File

@ -1,14 +1,17 @@
[remap]
importer="com.timothyqiu.gdfxr.importer"
type="AudioStreamSample"
path="res://.import/example.sfxr-e3731bcdcd8403a2391e92667a5d1076.sample"
type="AudioStreamWAV"
uid="uid://byf7u7a25fuf4"
path="res://.godot/imported/example.sfxr-e3731bcdcd8403a2391e92667a5d1076.sample"
[deps]
source_file="res://example/example.sfxr"
dest_files=[ "res://.import/example.sfxr-e3731bcdcd8403a2391e92667a5d1076.sample" ]
dest_files=["res://.godot/imported/example.sfxr-e3731bcdcd8403a2391e92667a5d1076.sample"]
[params]
loop=false
bit_depth=0
sample_rate=0

View File

@ -1,8 +1,9 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
type="CompressedTexture2D"
uid="uid://b16qnfl6slyy2"
path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"
metadata={
"vram_texture": false
}
@ -10,26 +11,24 @@ metadata={
[deps]
source_file="res://icon.png"
dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/hdr_compression=1
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
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
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@ -6,14 +6,15 @@
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=4
config_version=5
[application]
config/name="gdfxr"
run/main_scene="res://example/Example.tscn"
config/features=PackedStringArray("4.0")
config/icon="res://icon.png"
[editor_plugins]
enabled=PoolStringArray( "res://addons/gdfxr/plugin.cfg" )
enabled=PackedStringArray("res://addons/gdfxr/plugin.cfg")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 24 KiB