Compare commits

...

6 Commits
1.2 ... master

Author SHA1 Message Date
d1e4b34cdc Fix everything for Pandemonium. 2023-08-12 10:02:42 +02:00
Haoyu Qiu
7b3194f500 1.3 Release 2023-02-23 21:20:46 +08:00
Haoyu Qiu
4562f4b77c Add default filename for Save As dialog
Trailing number incremented.

Co-Authored-By: Russell Matney <russell.matney@gmail.com>
2023-02-16 09:53:49 +08:00
Haoyu Qiu
576708bb4a Add import options for bit depth and sample rate 2023-01-04 17:49:34 +08:00
Haoyu Qiu
5b910afb64 Implement paste from jsfxr 2022-12-04 14:01:44 +08:00
Haoyu Qiu
9acce6be58 Add Godot 4 information in README 2022-10-16 11:48:06 +08:00
18 changed files with 359 additions and 69 deletions

View File

@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.3.0] - 2023-02-23
### Added
- Bit depth and sample rate import options.
- Paste from JSXFR.
- Add default filename for Save As dialog, with trailing number incremented.
## [1.2.0] - 2022-10-13
### Added
- Implement custom slider.
@ -28,3 +34,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.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
[1.3.0]: https://github.com/timothyqiu/gdfxr/releases/tag/1.3

View File

@ -10,6 +10,8 @@
你可以在 Godot 中把 sfxr 音效文件当作普通的音频文件使用,也可以像在原始的 sfxr 中一样对音效进行编辑。
> 🚧 如果你想在 Godot 4 里使用这个插件,请移步 [godot-4](https://github.com/timothyqiu/gdfxr/tree/godot-4) 分支。
## 安装
这是一个普通的 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 [godot-4](https://github.com/timothyqiu/gdfxr/tree/godot-4) branch if you want to use this plugin in Godot 4.
## 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 := 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

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
@ -465,3 +467,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
@ -41,32 +50,42 @@ var fltdmp: float
var fltphp: float
func generate_audio_stream(config: SFXRConfig) -> AudioStreamSample:
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
stream.mix_rate = 44100
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()
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
) -> PoolByteArray:
_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:
@ -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
output.push_back(filesample)
return output
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:

View File

@ -36,8 +36,8 @@ func _init() -> void:
_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())
_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")
@ -48,8 +48,8 @@ func _init() -> void:
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 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(
@ -70,7 +70,7 @@ func _draw() -> void:
func _get_minimum_size() -> Vector2:
var ms := _stylebox_normal.get_minimum_size()
ms.y += get_font("font", "LineEdit").get_height()
ms.y += get_theme_font("font", "LineEdit").get_height()
return ms
@ -121,13 +121,13 @@ func set_value(v: float) -> void:
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:

View File

@ -1,10 +1,12 @@
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:
@ -46,6 +48,7 @@ func _ready():
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")
@ -75,18 +78,18 @@ func _notification(what: int):
match what:
NOTIFICATION_ENTER_TREE, NOTIFICATION_THEME_CHANGED:
find_node("ScrollContainer").add_stylebox_override("bg", get_stylebox("bg", "Tree"))
find_node("ScrollContainer").add_theme_stylebox_override("bg", get_theme_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"))
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:
if _modified:
_popup_confirm(
translator.tr("There are unsaved changes.\nOpen '%s' anyway?") % path,
str(translator.tr("There are unsaved changes.\nOpen '%s' anyway?")) % path,
"_set_editing_file", [path]
)
else:
@ -133,11 +136,20 @@ func _popup_message(content: String) -> void:
dialog.popup_centered()
func _popup_file_dialog(mode: int, callback: String) -> void:
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)
@ -164,7 +176,7 @@ func _set_editing_file(path: String) -> int: # Error
else:
var err := _config.load(path)
if err != OK:
_popup_message(translator.tr("'%s' is not a valid SFXR file.") % path)
_popup_message(str(translator.tr("'%s' is not a valid SFXR file.")) % path)
return err
audio_player.stream = load(path)
@ -177,7 +189,7 @@ 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 base = _path if has_file else str(translator.tr("Unsaved sound"))
if _modified:
base += "(*)"
filename_label.text = base
@ -195,6 +207,37 @@ func _sync_ui() -> void:
_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
@ -296,6 +339,7 @@ func _on_Load_pressed():
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)
@ -315,7 +359,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.MODE_SAVE_FILE, "_on_SaveAsDialog_confirmed", DefaultFilename.GUESS_FOR_SAVE)
ExtraOption.COPY:
if not _config_clipboard:
@ -325,6 +369,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(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:

View File

@ -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

@ -25,10 +25,10 @@ func set_plugin(v: EditorPlugin) -> void:
_translate_node(get_parent())
func tr(message: String) -> String:
func tr(message: StringName) -> StringName:
if _translation:
var translated := _translation.get_message(message)
if not translated.empty():
if translated != "":
return translated
return message

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

@ -36,8 +36,20 @@ func get_preset_name(preset):
func get_import_options(preset):
return [
{
name="loop",
default_value=false,
"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,
},
]
@ -53,7 +65,9 @@ func import(source_file, save_path, options, platform_variants, gen_files):
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_end = stream.data.size()

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="1.3"
script="plugin.gd"

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,74 @@
[gd_scene load_steps=2 format=2]
[gd_scene load_steps=3 format=2]
[ext_resource path="res://example/example.sfxr" type="AudioStream" id=1]
[ext_resource path="res://example/Example.gd" type="Script" id=1]
[ext_resource path="res://example/example.sfxr" type="AudioStream" 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"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
grow_horizontal = 2
grow_vertical = 2
custom_constants/hseparation = 32
custom_constants/vseparation = 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
[node name="AudioPlayer" type="AudioStreamPlayer" parent="."]
stream = ExtResource( 2 )
[node name="AdhocAudioPlayer" type="AudioStreamPlayer" parent="."]
[node name="Play" type="Button" parent="."]
margin_right = 141.0
margin_bottom = 32.0
rect_min_size = Vector2( 128, 32 )
size_flags_vertical = 4
text = "Play"
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
stream = ExtResource( 1 )
[node name="Label" type="Label" parent="."]
margin_left = 173.0
margin_right = 473.0
margin_bottom = 31.0
rect_min_size = Vector2( 300, 0 )
text = "A .sfxr file can be used as regular audio files like .wav, .ogg, and .mp3."
autowrap = true
[connection signal="pressed" from="Button" to="AudioStreamPlayer" method="play"]
[node name="PlayFile" type="Button" parent="."]
margin_top = 64.0
margin_right = 141.0
margin_bottom = 96.0
rect_min_size = Vector2( 128, 32 )
size_flags_vertical = 4
text = "Load .sfxr File"
[node name="Label2" type="Label" parent="."]
margin_left = 173.0
margin_top = 64.0
margin_right = 473.0
margin_bottom = 95.0
rect_min_size = Vector2( 300, 0 )
text = "A .sfxr file is a AudioStreamSample resource that can be loaded with load() or preload()."
autowrap = true
[node name="Generate" type="Button" parent="."]
margin_top = 144.0
margin_right = 141.0
margin_bottom = 176.0
rect_min_size = Vector2( 128, 32 )
size_flags_vertical = 4
text = "Runtime Generation"
[node name="Label3" type="Label" parent="."]
margin_left = 173.0
margin_top = 128.0
margin_right = 473.0
margin_bottom = 193.0
rect_min_size = Vector2( 300, 0 )
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 = true
[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

@ -12,3 +12,5 @@ dest_files=[ "res://.import/example.sfxr-e3731bcdcd8403a2391e92667a5d1076.sample
[params]
loop=false
bit_depth=0
sample_rate=0