Glass of water, Furniture, Points, Timer

This commit is contained in:
Andus 2024-03-17 18:36:33 +01:00
parent 786882d6bb
commit ef8447c873
87 changed files with 3115 additions and 28 deletions

View file

@ -0,0 +1,3 @@
source_md5="e48338fdf043e69d0c0549bc35a336d3"
dest_md5="80b05a4161e3e44efe54c5f703abc6a1"

View file

@ -0,0 +1,3 @@
source_md5="ae48a5a22aa6a99422a8318ffed1feaa"
dest_md5="d181a180dfaeadaadbdfd93dc388012c"

View file

@ -0,0 +1,3 @@
source_md5="45ec5f2645e0daca30e1b8b1cac95ed7"
dest_md5="36d9e9829a4519107ca15adfa13d9043"

View file

@ -0,0 +1,3 @@
source_md5="9fd4edb2bc0911b9a18417c61fe8cdd1"
dest_md5="911caff935a9e06a4c65b7d79a85fbc8"

View file

@ -0,0 +1,3 @@
source_md5="2d4fa181450a5668b22bdb6e9831919f"
dest_md5="768bf56328bd931108e594a6f275fa0c"

View file

@ -0,0 +1,3 @@
source_md5="42cdc1e1592e52083e73e551f21c2fa9"
dest_md5="45d459381756e367f5d750e422e98474"

View file

@ -0,0 +1,3 @@
source_md5="7e380c1c224c308f607885c419a9cf73"
dest_md5="3c31581c74d855625078446a4795fe21"

View file

@ -0,0 +1,3 @@
source_md5="71bc1217289de16605b99e80fd1fae32"
dest_md5="84842298ad49aff82aaae5f911bfa156"

View file

@ -0,0 +1,3 @@
source_md5="d607510048c43b15d9f908666abc10ce"
dest_md5="f56c36e2e2d12453433c9dfc95b37c0f"

Binary file not shown.

View file

@ -0,0 +1,3 @@
source_md5="24213acfa75e1afe51371161b047d97d"
dest_md5="403bc96e74c0979fac5728f0848ea056"

View file

@ -1,3 +1,3 @@
source_md5="3196a70615a1d1f1c96fca3d269ddeff" source_md5="1f4a7c7ec834bc28fa063d92219b7203"
dest_md5="4c22a8d7c72569ce9d922ab1d5c3ab8e" dest_md5="870ad9fa6e7fb0b91f691e9c3514386c"

View file

@ -1,3 +1,3 @@
source_md5="47313fa4c47a9963fddd764e1ec6e4a8" source_md5="bc69b7f51a5c3e10bbb3a38f62ff6284"
dest_md5="26ea799ea0a3da9e753b3ebe822e0570" dest_md5="253c7a0063b8268d86be8cb737ff1111"

View file

@ -0,0 +1,3 @@
source_md5="ad923036df9b2572bacd6749ae2024f0"
dest_md5="245c6fd0f699a6ee53e6c878da599b50"

View file

@ -0,0 +1,3 @@
source_md5="653c99928be1524b21d2add32ae8d13f"
dest_md5="892389819376be6bfe0deb52ff6fed11"

View file

@ -0,0 +1,3 @@
source_md5="85a9cb49e771d72d839a8409b3f9b9ce"
dest_md5="c3251c8663f0f35e65cb0f58382bc6b0"

View file

@ -0,0 +1,3 @@
source_md5="9dd651cc55c5cf69f617ef98c9759a3a"
dest_md5="2da991840fabf6730dc8918b7e936144"

View file

@ -0,0 +1,3 @@
source_md5="31cd1f8a917c5075e663ae258d562cd7"
dest_md5="65f32199154fd5ec5e092e0d77471819"

View file

@ -0,0 +1,3 @@
source_md5="f3ddc145ab52891047b639168d925bc9"
dest_md5="3afb1cf0f019a3c071a05c5995aec7d8"

44
Scenes/End.tscn Normal file
View file

@ -0,0 +1,44 @@
[gd_scene load_steps=5 format=2]
[ext_resource path="res://fonts/dotty.ttf" type="DynamicFontData" id=2]
[ext_resource path="res://Scripts/End.gd" type="Script" id=3]
[sub_resource type="DynamicFont" id=1]
size = 128
font_data = ExtResource( 2 )
[sub_resource type="DynamicFont" id=2]
size = 64
font_data = ExtResource( 2 )
[node name="Control" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource( 3 )
[node name="Label" type="Label" parent="."]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -288.0
margin_top = -76.0
margin_right = 288.0
margin_bottom = 4.0
custom_fonts/font = SubResource( 1 )
text = "Game Over!"
align = 1
valign = 1
[node name="Points" type="Label" parent="."]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -128.0
margin_top = 4.0
margin_right = 128.0
margin_bottom = 52.0
custom_fonts/font = SubResource( 2 )
text = "Points:"
align = 1

File diff suppressed because one or more lines are too long

View file

@ -1,10 +1,13 @@
[gd_scene load_steps=6 format=2] [gd_scene load_steps=7 format=2]
[ext_resource path="res://Sprites/player.png" type="Texture" id=1] [ext_resource path="res://Sprites/player.png" type="Texture" id=1]
[ext_resource path="res://Scripts/Player.gd" type="Script" id=2] [ext_resource path="res://Scripts/Player.gd" type="Script" id=2]
[sub_resource type="RectangleShape2D" id=1] [sub_resource type="CircleShape2D" id=5]
extents = Vector2( 5, 10.2143 ) radius = 6.0
[sub_resource type="RectangleShape2D" id=4]
extents = Vector2( 5.71429, 11.4286 )
[sub_resource type="Animation" id=2] [sub_resource type="Animation" id=2]
length = 0.001 length = 0.001
@ -46,9 +49,16 @@ script = ExtResource( 2 )
texture = ExtResource( 1 ) texture = ExtResource( 1 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="."] [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2( 0, 1.64286 ) position = Vector2( 0, 5.92858 )
scale = Vector2( 1, 1 ) scale = Vector2( 1, 1 )
shape = SubResource( 1 ) shape = SubResource( 5 )
[node name="PlayerArea" type="Area2D" parent="."]
scale = Vector2( 1, 1 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="PlayerArea"]
position = Vector2( -1.70299e-07, -3.40598e-07 )
shape = SubResource( 4 )
[node name="AnimationPlayer" type="AnimationPlayer" parent="."] [node name="AnimationPlayer" type="AnimationPlayer" parent="."]
anims/RESET = SubResource( 2 ) anims/RESET = SubResource( 2 )

86
Scenes/Start.tscn Normal file
View file

@ -0,0 +1,86 @@
[gd_scene load_steps=13 format=2]
[ext_resource path="res://fonts/game over.ttf" type="DynamicFontData" id=1]
[ext_resource path="res://Sprites/bg.png" type="Texture" id=2]
[ext_resource path="res://fonts/dotty.ttf" type="DynamicFontData" id=3]
[ext_resource path="res://Scripts/Start.gd" type="Script" id=4]
[sub_resource type="DynamicFont" id=1]
size = 64
font_data = ExtResource( 1 )
[sub_resource type="StyleBoxEmpty" id=2]
[sub_resource type="StyleBoxEmpty" id=3]
[sub_resource type="StyleBoxEmpty" id=4]
[sub_resource type="StyleBoxEmpty" id=5]
[sub_resource type="StyleBoxEmpty" id=6]
[sub_resource type="DynamicFont" id=7]
size = 128
font_data = ExtResource( 1 )
[sub_resource type="DynamicFont" id=8]
size = 64
font_data = ExtResource( 3 )
[node name="Control" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource( 4 )
[node name="Background" type="TextureRect" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
texture = ExtResource( 2 )
expand = true
[node name="Button" type="Button" parent="."]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -80.0
margin_top = 84.0
margin_right = 80.0
margin_bottom = 130.0
custom_colors/font_color_hover = Color( 0.290196, 0.517647, 1, 1 )
custom_fonts/font = SubResource( 1 )
custom_styles/hover = SubResource( 2 )
custom_styles/pressed = SubResource( 3 )
custom_styles/focus = SubResource( 4 )
custom_styles/disabled = SubResource( 5 )
custom_styles/normal = SubResource( 6 )
text = "Start"
[node name="Label" type="Label" parent="."]
anchor_left = 0.5
anchor_right = 0.5
margin_left = -200.0
margin_top = 64.0
margin_right = 200.0
margin_bottom = 144.0
custom_fonts/font = SubResource( 7 )
text = "H2Grow"
[node name="Label2" type="Label" parent="."]
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
margin_left = -512.0
margin_top = -64.0
margin_right = 512.0
custom_colors/font_color = Color( 1, 0.262745, 0.262745, 1 )
custom_colors/font_outline_modulate = Color( 1, 1, 1, 1 )
custom_fonts/font = SubResource( 8 )
text = "Read instructions on itch.io before playing the game!"
align = 1
valign = 1
autowrap = true
clip_text = true
[connection signal="pressed" from="Button" to="." method="_on_Button_pressed"]

View file

@ -0,0 +1,28 @@
[gd_scene load_steps=6 format=2]
[ext_resource path="res://Scripts/WaterThing.gd" type="Script" id=1]
[ext_resource path="res://Sprites/Water/Glass.png" type="Texture" id=2]
[ext_resource path="res://Sounds/Sfxr/break.sfxr" type="AudioStream" id=3]
[ext_resource path="res://Sounds/Sfxr/punch.sfxr" type="AudioStream" id=4]
[sub_resource type="RectangleShape2D" id=1]
extents = Vector2( 5, 6 )
[node name="Glass" type="Area2D"]
scale = Vector2( 0.5, 0.5 )
script = ExtResource( 1 )
[node name="Sprite" type="Sprite" parent="."]
texture = ExtResource( 2 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource( 1 )
[node name="PunchPlayer" type="AudioStreamPlayer" parent="."]
stream = ExtResource( 4 )
[node name="BreakPlayer" type="AudioStreamPlayer" parent="."]
stream = ExtResource( 3 )
[connection signal="area_entered" from="." to="." method="_on_body_entered"]
[connection signal="area_exited" from="." to="." method="_on_body_exited"]

View file

@ -1,7 +1,9 @@
[gd_scene load_steps=4 format=2] [gd_scene load_steps=6 format=2]
[ext_resource path="res://Sprites/Water/Jug.png" type="Texture" id=1] [ext_resource path="res://Sprites/Water/Jug.png" type="Texture" id=1]
[ext_resource path="res://Scripts/WaterThing.gd" type="Script" id=2] [ext_resource path="res://Scripts/WaterThing.gd" type="Script" id=2]
[ext_resource path="res://Sounds/Sfxr/punch.sfxr" type="AudioStream" id=3]
[ext_resource path="res://Sounds/Sfxr/break.sfxr" type="AudioStream" id=4]
[sub_resource type="RectangleShape2D" id=1] [sub_resource type="RectangleShape2D" id=1]
extents = Vector2( 5, 6 ) extents = Vector2( 5, 6 )
@ -17,5 +19,11 @@ texture = ExtResource( 1 )
position = Vector2( 1, 0 ) position = Vector2( 1, 0 )
shape = SubResource( 1 ) shape = SubResource( 1 )
[connection signal="body_entered" from="." to="." method="_on_body_entered"] [node name="PunchPlayer" type="AudioStreamPlayer" parent="."]
[connection signal="body_exited" from="." to="." method="_on_body_exited"] stream = ExtResource( 3 )
[node name="BreakPlayer" type="AudioStreamPlayer" parent="."]
stream = ExtResource( 4 )
[connection signal="area_entered" from="." to="." method="_on_body_entered"]
[connection signal="area_exited" from="." to="." method="_on_body_exited"]

4
Scripts/End.gd Normal file
View file

@ -0,0 +1,4 @@
extends Control
func _ready():
$Points.text += " " + str(Utils.points)

7
Scripts/Game.gd Normal file
View file

@ -0,0 +1,7 @@
extends Node2D
func _process(delta):
$UI/TimeLeft.text = "Time left: " + str($Timer.time_left)
$UI/Points.text = "Points: " + str(Utils.points)
if ($Timer.time_left == 0):
get_tree().change_scene("res://Scenes/End.tscn")

View file

@ -9,6 +9,7 @@ func _ready():
func _physics_process(delta): func _physics_process(delta):
velocity = Vector2() velocity = Vector2()
# Input
if Input.is_action_pressed("down"): if Input.is_action_pressed("down"):
velocity.y += speed velocity.y += speed
if Input.is_action_pressed("up"): if Input.is_action_pressed("up"):
@ -18,6 +19,7 @@ func _physics_process(delta):
if Input.is_action_pressed("right"): if Input.is_action_pressed("right"):
velocity.x += speed velocity.x += speed
# Animation
if Input.is_action_pressed("right") || Input.is_action_pressed("left") \ if Input.is_action_pressed("right") || Input.is_action_pressed("left") \
|| Input.is_action_pressed("up") || Input.is_action_pressed("down"): || Input.is_action_pressed("up") || Input.is_action_pressed("down"):
$AnimationPlayer.play("Walk") $AnimationPlayer.play("Walk")

4
Scripts/Start.gd Normal file
View file

@ -0,0 +1,4 @@
extends Control
func _on_Button_pressed():
get_tree().change_scene("res://Scenes/Game.tscn")

3
Scripts/Utils.gd Normal file
View file

@ -0,0 +1,3 @@
extends Node
var points = 0

View file

@ -1,7 +1,7 @@
extends Area2D extends Area2D
var broken = false var broken = false
var hits_left = 2 var hits_left = 3
var player = false var player = false
var player_body = null var player_body = null
@ -13,12 +13,12 @@ func replace_numbers(name: String) -> String:
return new_name return new_name
func _on_body_entered(body): func _on_body_entered(body):
if body.name == "Player" and !broken: if body.get_parent().name == "Player":
player = true player = true
player_body = body player_body = body.get_parent()
func _on_body_exited(body): func _on_body_exited(body):
if body.name == "Player" and !broken: if body.get_parent().name == "Player":
player = false player = false
player_body = null player_body = null
@ -26,8 +26,16 @@ func _process(delta):
if player && player_body != null: if player && player_body != null:
if Input.is_action_just_pressed("punch"): if Input.is_action_just_pressed("punch"):
if hits_left > 0: if hits_left > 0:
$PunchPlayer.play()
hits_left -= 1 hits_left -= 1
if hits_left == 0 && broken == false: if hits_left == 0 && !broken:
# Mod Thing
$BreakPlayer.play()
$Sprite.texture = load("res://Sprites/Water/Broken/" + replace_numbers(name) + ".png") $Sprite.texture = load("res://Sprites/Water/Broken/" + replace_numbers(name) + ".png")
player_body.scale += Vector2(0.05, 0.05)
broken = true broken = true
# Mod Player
player_body.scale += Vector2(0.05, 0.05)
player_body.get_node("CollisionShape2D").scale -= Vector2(0.05, 0.05)
player_body.speed -= 0.5
# Add points
Utils.points += 1

BIN
Sounds/Sfxr/break.sfxr Normal file

Binary file not shown.

View file

@ -0,0 +1,16 @@
[remap]
importer="com.timothyqiu.gdfxr.importer"
type="AudioStreamSample"
path="res://.import/break.sfxr-c1c1594a22628fd278421b7f8a79a149.sample"
[deps]
source_file="res://Sounds/Sfxr/break.sfxr"
dest_files=[ "res://.import/break.sfxr-c1c1594a22628fd278421b7f8a79a149.sample" ]
[params]
loop=false
bit_depth=0
sample_rate=0

BIN
Sounds/Sfxr/punch.sfxr Normal file

Binary file not shown.

View file

@ -0,0 +1,16 @@
[remap]
importer="com.timothyqiu.gdfxr.importer"
type="AudioStreamSample"
path="res://.import/punch.sfxr-ce24cf25ac6de08473bbda5685c58ae5.sample"
[deps]
source_file="res://Sounds/Sfxr/punch.sfxr"
dest_files=[ "res://.import/punch.sfxr-ce24cf25ac6de08473bbda5685c58ae5.sample" ]
[params]
loop=false
bit_depth=0
sample_rate=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/Glass.png-3b2015c0dd629c60d3c1b09d9013c59c.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Sprites/Water/Broken/Glass.png"
dest_files=[ "res://.import/Glass.png-3b2015c0dd629c60d3c1b09d9013c59c.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=false
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=false
svg/scale=1.0

BIN
Sprites/Water/Glass.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/Glass.png-8678c0300c4ceb28841173a60adcbab3.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Sprites/Water/Glass.png"
dest_files=[ "res://.import/Glass.png-8678c0300c4ceb28841173a60adcbab3.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=false
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=false
svg/scale=1.0

BIN
Sprites/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

35
Sprites/bg.png.import Normal file
View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/bg.png-5046840dce817dc9e1d61a818df550fc.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Sprites/bg.png"
dest_files=[ "res://.import/bg.png-5046840dce817dc9e1d61a818df550fc.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=false
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=false
svg/scale=1.0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 B

After

Width:  |  Height:  |  Size: 115 B

68
Sprites/furniture.tres Normal file
View file

@ -0,0 +1,68 @@
[gd_resource type="TileSet" load_steps=5 format=2]
[ext_resource path="res://Sprites/wardrobe.png" type="Texture" id=1]
[ext_resource path="res://Sprites/table.png" type="Texture" id=2]
[sub_resource type="ConvexPolygonShape2D" id=1]
points = PoolVector2Array( 16, 11.2241, 0, 11.3539, 0, 0, 16, 0 )
[sub_resource type="ConvexPolygonShape2D" id=2]
points = PoolVector2Array( 12, 12, 0, 12, 0, 0, 12, 0 )
[resource]
0/name = "wardrobe.png 0"
0/texture = ExtResource( 1 )
0/tex_offset = Vector2( 0, 0 )
0/modulate = Color( 1, 1, 1, 1 )
0/region = Rect2( 0, 0, 16, 16 )
0/tile_mode = 0
0/occluder_offset = Vector2( 0, 0 )
0/navigation_offset = Vector2( 0, 0 )
0/shape_offset = Vector2( 0, 0 )
0/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
0/shape = SubResource( 1 )
0/shape_one_way = false
0/shape_one_way_margin = 1.0
0/shapes = [ {
"autotile_coord": Vector2( 0, 0 ),
"one_way": false,
"one_way_margin": 1.0,
"shape": SubResource( 1 ),
"shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 )
} ]
0/z_index = 0
2/name = "table.png 2"
2/texture = ExtResource( 2 )
2/tex_offset = Vector2( 0, 0 )
2/modulate = Color( 1, 1, 1, 1 )
2/region = Rect2( 3, 1, 0, 0 )
2/tile_mode = 0
2/occluder_offset = Vector2( 0, 0 )
2/navigation_offset = Vector2( 0, 0 )
2/shape_offset = Vector2( 0, 0 )
2/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
2/shape_one_way = false
2/shape_one_way_margin = 0.0
2/shapes = [ ]
2/z_index = 0
3/name = "table.png 3"
3/texture = ExtResource( 2 )
3/tex_offset = Vector2( 0, 0 )
3/modulate = Color( 1, 1, 1, 1 )
3/region = Rect2( 0, 0, 12, 12 )
3/tile_mode = 0
3/occluder_offset = Vector2( 0, 0 )
3/navigation_offset = Vector2( 0, 0 )
3/shape_offset = Vector2( 0, 0 )
3/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
3/shape = SubResource( 2 )
3/shape_one_way = false
3/shape_one_way_margin = 1.0
3/shapes = [ {
"autotile_coord": Vector2( 0, 0 ),
"one_way": false,
"one_way_margin": 1.0,
"shape": SubResource( 2 ),
"shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 )
} ]
3/z_index = 0

BIN
Sprites/table.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

35
Sprites/table.png.import Normal file
View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/table.png-6287c5224e3a2223ad79685cc0bf8f85.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Sprites/table.png"
dest_files=[ "res://.import/table.png-6287c5224e3a2223ad79685cc0bf8f85.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=false
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=false
svg/scale=1.0

BIN
Sprites/wardrobe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/wardrobe.png-67445500d100c0d821e09597f4b6463a.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Sprites/wardrobe.png"
dest_files=[ "res://.import/wardrobe.png-67445500d100c0d821e09597f4b6463a.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=false
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=false
svg/scale=1.0

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 := PoolByteArray()
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.subarray(buffer.size() - length, -1))
result.seek(0)
return result

7
addons/gdfxr/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.

511
addons/gdfxr/SFXRConfig.gd Normal file
View file

@ -0,0 +1,511 @@
# 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,
}
const Base58 := preload("res://addons/gdfxr/Base58.gd")
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
)
# 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

@ -0,0 +1,274 @@
# GDScript port of the original SFXR
# https://www.drpetter.se/project_sfxr.html
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
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,
wav_bits: int = WavBits.WAV_BITS_8,
wav_freq: int = WavFreq.WAV_FREQ_44100
) -> AudioStreamSample:
var stream := AudioStreamSample.new()
stream.format = AudioStreamSample.FORMAT_8_BITS if wav_bits == WavBits.WAV_BITS_8 else AudioStreamSample.FORMAT_16_BITS
stream.mix_rate = 44100 if wav_freq == WavFreq.WAV_FREQ_44100 else 22050
_config = config
stream.data = _generate_samples(wav_bits, wav_freq).data_array
_config = null
return stream
func generate_samples(
config: SFXRConfig,
wav_bits: int = WavBits.WAV_BITS_8,
wav_freq: int = WavFreq.WAV_FREQ_44100
) -> PoolByteArray:
_config = config
var data := _generate_samples(wav_bits, wav_freq).data_array
_config = null
return data
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 filesample: float = 0
var fileacc := 0
var buffer := StreamPeerBuffer.new()
# 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)
filesample += ssample
fileacc += 1
if wav_freq == WavFreq.WAV_FREQ_44100 or fileacc == 2:
filesample /= fileacc
fileacc = 0
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:
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,203 @@
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
var _line_edit: LineEdit
var _stylebox_normal: StyleBox
var _stylebox_hover: StyleBox
var _stylebox_editing: StyleBox
var _stylebox_value: StyleBox
var _line_edit_just_closed := false
var _mouse_hovering := false
var _is_editing := false
var _drag_start_position: Vector2
var _drag_cancelled := true
var _drag_dist := 0.0
var _drag_start_factor: float
func _init() -> void:
mouse_default_cursor_shape = Control.CURSOR_HSIZE
rect_clip_content = true
focus_mode = Control.FOCUS_ALL
var style := StyleBoxEmpty.new()
style.content_margin_left = 8
style.content_margin_right = 8
_line_edit = LineEdit.new()
_line_edit.set_as_toplevel(true)
_line_edit.visible = false
_line_edit.add_stylebox_override("normal", style)
_line_edit.add_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")
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 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()
)
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))
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)
func _get_minimum_size() -> Vector2:
var ms := _stylebox_normal.get_minimum_size()
ms.y += get_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.pressed:
_drag_prepare(mb)
else:
_drag_done()
if _drag_cancelled:
_show_text_edit()
_drag_cancelled = true
_is_editing = mb.pressed
update()
var mm := event as InputEventMouseMotion
if mm and mm.button_mask & BUTTON_MASK_LEFT:
_drag_motion(mm)
_drag_cancelled = false
func _notification(what: int) -> void:
match what:
NOTIFICATION_ENTER_TREE, NOTIFICATION_THEME_CHANGED:
_update_stylebox()
NOTIFICATION_MOUSE_ENTER:
_mouse_hovering = true
update()
NOTIFICATION_MOUSE_EXIT:
_mouse_hovering = false
update()
NOTIFICATION_FOCUS_ENTER:
if (Input.is_action_pressed("ui_focus_next") or Input.is_action_pressed("ui_focus_prev")) and not _line_edit_just_closed:
_show_text_edit()
_line_edit_just_closed = false
func set_value(v: float) -> void:
if is_equal_approx(v, value):
return
value = v
emit_signal("value_changed", value)
update()
func _update_stylebox() -> void:
_stylebox_normal = get_stylebox("normal", "LineEdit")
_stylebox_hover = StyleBoxFlat.new()
_stylebox_hover.bg_color = get_color("highlight_color", "Editor")
_stylebox_editing = StyleBoxFlat.new()
_stylebox_editing.bg_color = get_color("dark_color_2", "Editor")
_stylebox_value = StyleBoxFlat.new()
_stylebox_value.bg_color = get_color("accent_color", "Editor") * Color(1, 1, 1, 0.4)
func _drag_prepare(mouse: InputEventMouse) -> void:
_drag_dist = 0.0
_drag_start_factor = (value - min_value) / (max_value - min_value)
_drag_start_position = mouse.global_position
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _drag_done() -> void:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
if _drag_cancelled:
Input.warp_mouse_position(_drag_start_position)
else:
Input.warp_mouse_position(rect_global_position + rect_size * Vector2(
(value - min_value) / (max_value - min_value),
0.5
))
func _drag_motion(motion: InputEventMouseMotion) -> void:
_drag_dist += motion.relative.x
var factor := _drag_start_factor + _drag_dist / rect_size.x
if factor < 0 or 1 < factor:
factor = clamp(factor, 0, 1)
_drag_dist = (factor - _drag_start_factor) * rect_size.x
var v := factor * (max_value - min_value) + min_value
var snap := motion.command or motion.shift
if snap and not (is_equal_approx(v, min_value) or is_equal_approx(v, max_value)):
if motion.shift and motion.command:
v = round(v * 1000.0) * 0.001
elif motion.shift:
v = round(v * 100.0) * 0.01
else:
v = round(v * 10.0) * 0.1
set_value(clamp(v, min_value, max_value))
update()
func _show_text_edit() -> void:
var gr := get_global_rect()
_line_edit.text = str(value)
_line_edit.set_position(gr.position)
_line_edit.set_size(gr.size)
_line_edit.show_modal()
_line_edit.select_all()
_line_edit.grab_focus()
_line_edit.focus_next = find_next_valid_focus().get_path()
_line_edit.focus_previous = find_prev_valid_focus().get_path()
func _on_line_edit_focus_exited():
if _line_edit.get_menu().visible:
return
if _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()
func _on_line_edit_text_entered(_text: String):
_line_edit.hide()
func _on_line_edit_visibility_changed():
if not _line_edit.visible:
_line_edit_just_closed = true

View file

@ -0,0 +1,387 @@
tool
extends Container
enum ExtraOption { SAVE_AS, COPY, PASTE, PASTE_JSFXR, RECENT }
enum DefaultFilename { EMPTY, GUESS_FOR_SAVE }
const SFXRConfig := preload("../SFXRConfig.gd")
const SFXRGenerator := preload("../SFXRGenerator.gd")
const Base58 := preload("../Base58.gd")
const NUM_RECENTS := 4
class RecentEntry:
var title: String
var config := SFXRConfig.new()
var plugin: EditorPlugin
var _config := SFXRConfig.new()
var _config_defaults := SFXRConfig.new()
var _config_clipboard: SFXRConfig
var _config_recents: Array
var _recents_id := 0
var _generator := SFXRGenerator.new()
var _path: String
var _modified := false
var _param_map := {}
var _syncing_ui := false # a hack since Range set_value emits value_changed
var _category_names := {}
onready var audio_player := $AudioStreamPlayer as AudioStreamPlayer
onready var filename_label := find_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
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_separator()
popup.add_item(translator.tr("Copy"), ExtraOption.COPY)
popup.add_item(translator.tr("Paste"), ExtraOption.PASTE)
popup.add_item(translator.tr("Paste from jsfxr"), ExtraOption.PASTE_JSFXR)
popup.add_separator(translator.tr("Recently Generated"))
popup.connect("id_pressed", self, "_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"),
}
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"))
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"))
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 _push_recent(title: String) -> void:
var recent: RecentEntry
if _config_recents.size() < NUM_RECENTS:
recent = RecentEntry.new()
else:
recent = _config_recents.pop_back()
_recents_id += 1
recent.title = "#%d %s" % [_recents_id, title]
recent.config.copy_from(_config)
_config_recents.push_front(recent)
func _popup_confirm(content: String, callback: 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, default_filename: int = DefaultFilename.EMPTY) -> void:
var dialog := EditorFileDialog.new()
add_child(dialog)
dialog.access = EditorFileDialog.ACCESS_RESOURCES
dialog.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.tr("SFXR Audio"))
dialog.connect("popup_hide", dialog, "queue_free")
dialog.connect("file_selected", self, callback)
dialog.popup_centered_ratio()
func _reset_defaults() -> void:
_config_defaults.copy_from(_config)
_set_modified(false)
_sync_ui()
func _restore_from_config(config: SFXRConfig) -> void:
_config.copy_from(config)
_sync_ui()
_set_modified(not config.is_equal(_config_defaults))
audio_player.stream = null
func _set_editing_file(path: String) -> int: # Error
if path.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 _generate_serial_path(path: String) -> String:
var directory := Directory.new()
if directory.open(path.get_base_dir()) != OK:
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
_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()
_push_recent(translator.tr("Randomize"))
else:
_config.randomize_in_category(category)
_push_recent(_category_names.get(category, "Unknown"))
_set_modified(true)
_sync_ui()
_on_Play_pressed(true)
func _on_Mutate_pressed():
_config.mutate()
_push_recent(translator.tr("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")
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 OS.has_clipboard())
# Rebuild recents menu everytime :)
var first_recent_index := popup.get_item_index(ExtraOption.RECENT)
if first_recent_index != -1:
var count := popup.get_item_count()
for i in count - first_recent_index:
popup.remove_item(count - 1 - i)
if _config_recents.empty():
popup.add_item(translator.tr("None"), ExtraOption.RECENT)
popup.set_item_disabled(popup.get_item_index(ExtraOption.RECENT), true)
else:
for i in _config_recents.size():
popup.add_item(_config_recents[i].title, ExtraOption.RECENT + i)
func _on_Extra_id_pressed(id: int) -> void:
match id:
ExtraOption.SAVE_AS:
_popup_file_dialog(EditorFileDialog.MODE_SAVE_FILE, "_on_SaveAsDialog_confirmed", DefaultFilename.GUESS_FOR_SAVE)
ExtraOption.COPY:
if not _config_clipboard:
_config_clipboard = SFXRConfig.new()
_config_clipboard.copy_from(_config)
ExtraOption.PASTE:
_restore_from_config(_config_clipboard)
ExtraOption.PASTE_JSFXR:
var pasted := SFXRConfig.new()
if pasted.load_from_base58(OS.clipboard) == OK:
_restore_from_config(pasted)
else:
_popup_message(translator.tr("Clipboard does not contain code copied from jsfxr."))
_:
var i := id - ExtraOption.RECENT as int
if i < 0 or _config_recents.size() <= i:
printerr("Bad index %d (%d in total)" % [i, _config_recents.size()])
return
var recent: RecentEntry = _config_recents[i]
_restore_from_config(recent.config)
_on_Play_pressed()

View file

@ -0,0 +1,394 @@
[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 )
[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="Extra" type="MenuButton" parent="Toolbar"]
margin_left = 48.0
margin_right = 60.0
margin_bottom = 22.0
hint_tooltip = "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
[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 )
size_flags_horizontal = 0
text = "Play"
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"
disabled = true
text = "Restore"
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
[node name="Filename" type="Label" parent="Toolbar"]
margin_left = 248.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"
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
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"
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
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
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"
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
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
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
[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"
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
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"
bipolar = true
[node name="PluginTranslator" parent="." instance=ExtResource( 5 )]
[connection signal="pressed" from="Toolbar/New" to="." method="_on_New_pressed"]
[connection signal="pressed" from="Toolbar/Load" to="." method="_on_Load_pressed"]
[connection signal="pressed" from="Toolbar/Save" to="." method="_on_Save_pressed"]
[connection signal="about_to_show" from="Toolbar/Extra" to="." method="_on_Extra_about_to_show"]
[connection signal="pressed" from="Toolbar/Play" to="." method="_on_Play_pressed"]
[connection signal="pressed" from="Toolbar/Restore" to="." method="_on_Restore_pressed"]
[connection signal="pressed" from="Editor/Generators/Button" to="." method="_on_Randomize_pressed" binds= [ 0 ]]
[connection signal="pressed" from="Editor/Generators/Button2" to="." method="_on_Randomize_pressed" binds= [ 1 ]]
[connection signal="pressed" from="Editor/Generators/Button3" to="." method="_on_Randomize_pressed" binds= [ 2 ]]
[connection signal="pressed" from="Editor/Generators/Button4" to="." method="_on_Randomize_pressed" binds= [ 3 ]]
[connection signal="pressed" from="Editor/Generators/Button5" to="." method="_on_Randomize_pressed" binds= [ 4 ]]
[connection signal="pressed" from="Editor/Generators/Button6" to="." method="_on_Randomize_pressed" binds= [ 5 ]]
[connection signal="pressed" from="Editor/Generators/Button7" to="." method="_on_Randomize_pressed" binds= [ 6 ]]
[connection signal="pressed" from="Editor/Generators/Button8" to="." method="_on_Mutate_pressed"]
[connection signal="pressed" from="Editor/Generators/Button9" to="." method="_on_Randomize_pressed" binds= [ -1 ]]

View file

@ -0,0 +1,16 @@
tool
extends Button
export var icon_name: String
var plugin: EditorPlugin # a hack to know if this is executing as plugin
func _notification(what: int):
if not plugin:
return
match what:
NOTIFICATION_ENTER_TREE, NOTIFICATION_THEME_CHANGED:
if icon_name:
icon = get_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,43 @@
[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 )
[node name="Label" type="Label" parent="."]
margin_top = 13.0
margin_right = 128.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 = 132.0
margin_top = 10.0
margin_right = 237.0
margin_bottom = 30.0
rect_min_size = Vector2( 105, 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
focus_mode = 0
size_flags_vertical = 4
enabled_focus_mode = 0
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,43 @@
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
func set_label(v: String) -> void:
label = v
$Label.text = v
func set_bipolar(v: bool) -> void:
bipolar = v
if bipolar:
$HSlider.min_value = -1.0
else:
$HSlider.min_value = 0.0
func set_value(v: float) -> void:
$HSlider.value = v
func get_value() -> float:
return $HSlider.value
func set_resetable(v: bool) -> void:
$Reset.disabled = not v
func _on_HSlider_value_changed(value: float):
emit_signal("param_changed", parameter, value)
func _on_Reset_pressed():
emit_signal("param_reset", parameter)

View file

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

View file

@ -0,0 +1,76 @@
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,
},
{
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):
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, options.bit_depth, options.sample_rate
)
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.3"
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()

BIN
fonts/dotty.ttf Normal file

Binary file not shown.

BIN
fonts/game over.ttf Normal file

Binary file not shown.

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 660 B

View file

@ -11,13 +11,22 @@ config_version=4
[application] [application]
config/name="H2Grow" config/name="H2Grow"
run/main_scene="res://Scenes/Start.tscn"
config/icon="res://icon.png" config/icon="res://icon.png"
[autoload]
Utils="*res://Scripts/Utils.gd"
[display] [display]
window/size/resizable=false window/size/resizable=false
window/vsync/use_vsync=false window/vsync/use_vsync=false
[editor_plugins]
enabled=PoolStringArray( "res://addons/gdfxr/plugin.cfg" )
[gui] [gui]
common/drop_mouse_on_gui_input_disabled=true common/drop_mouse_on_gui_input_disabled=true