summaryrefslogtreecommitdiff
path: root/modules/vorbis
diff options
context:
space:
mode:
Diffstat (limited to 'modules/vorbis')
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.cpp134
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.h26
-rw-r--r--modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml6
-rw-r--r--modules/vorbis/resource_importer_ogg_vorbis.cpp70
-rw-r--r--modules/vorbis/resource_importer_ogg_vorbis.h7
5 files changed, 213 insertions, 30 deletions
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp
index 89a6b03ff8..76f7317daa 100644
--- a/modules/vorbis/audio_stream_ogg_vorbis.cpp
+++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp
@@ -43,28 +43,93 @@ int AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_fram
int todo = p_frames;
- int start_buffer = 0;
+ int beat_length_frames = -1;
+ bool beat_loop = vorbis_stream->has_loop();
+ if (beat_loop && vorbis_stream->get_bpm() > 0 && vorbis_stream->get_beat_count() > 0) {
+ beat_length_frames = vorbis_stream->get_beat_count() * vorbis_data->get_sampling_rate() * 60 / vorbis_stream->get_bpm();
+ }
while (todo > 0 && active) {
AudioFrame *buffer = p_buffer;
- if (start_buffer > 0) {
- buffer = buffer + start_buffer;
+ buffer += p_frames - todo;
+
+ int to_mix = todo;
+ if (beat_length_frames >= 0 && (beat_length_frames - (int)frames_mixed) < to_mix) {
+ to_mix = MAX(0, beat_length_frames - (int)frames_mixed);
}
- int mixed = _mix_frames_vorbis(buffer, todo);
+
+ int mixed = _mix_frames_vorbis(buffer, to_mix);
ERR_FAIL_COND_V(mixed < 0, 0);
todo -= mixed;
frames_mixed += mixed;
- start_buffer += mixed;
+
+ if (loop_fade_remaining < FADE_SIZE) {
+ int to_fade = loop_fade_remaining + MIN(FADE_SIZE - loop_fade_remaining, mixed);
+ for (int i = loop_fade_remaining; i < to_fade; i++) {
+ buffer[i - loop_fade_remaining] += loop_fade[i] * (float(FADE_SIZE - i) / float(FADE_SIZE));
+ }
+ loop_fade_remaining = to_fade;
+ }
+
+ if (beat_length_frames >= 0) {
+ /**
+ * Length determined by beat length
+ * This code is commented out because, in practice, it is prefered that the fade
+ * is done by the transitioner and this stream just goes on until it ends while fading out.
+ *
+ * End fade implementation is left here for reference in case at some point this feature
+ * is desired.
+
+ if (!beat_loop && (int)frames_mixed > beat_length_frames - FADE_SIZE) {
+ print_line("beat length fade/after mix?");
+ //No loop, just fade and finish
+ for (int i = 0; i < mixed; i++) {
+ int idx = frames_mixed + i - mixed;
+ buffer[i] *= 1.0 - float(MAX(0, (idx - (beat_length_frames - FADE_SIZE)))) / float(FADE_SIZE);
+ }
+ if ((int)frames_mixed == beat_length_frames) {
+ for (int i = p_frames - todo; i < p_frames; i++) {
+ p_buffer[i] = AudioFrame(0, 0);
+ }
+ active = false;
+ break;
+ }
+ } else
+ **/
+
+ if (beat_loop && beat_length_frames <= (int)frames_mixed) {
+ // End of file when doing beat-based looping. <= used instead of == because importer editing
+ if (!have_packets_left && !have_samples_left) {
+ //Nothing remaining, so do nothing.
+ loop_fade_remaining = FADE_SIZE;
+ } else {
+ // Add some loop fade;
+ int faded_mix = _mix_frames_vorbis(loop_fade, FADE_SIZE);
+
+ for (int i = faded_mix; i < FADE_SIZE; i++) {
+ // In case lesss was mixed, pad with zeros
+ loop_fade[i] = AudioFrame(0, 0);
+ }
+ loop_fade_remaining = 0;
+ }
+
+ seek(vorbis_stream->loop_offset);
+ loops++;
+ // We still have buffer to fill, start from this element in the next iteration.
+ continue;
+ }
+ }
+
if (!have_packets_left && !have_samples_left) {
- //end of file!
+ // Actual end of file!
bool is_not_empty = mixed > 0 || vorbis_stream->get_length() > 0;
if (vorbis_stream->loop && is_not_empty) {
//loop
seek(vorbis_stream->loop_offset);
loops++;
- // we still have buffer to fill, start from this element in the next iteration.
- start_buffer = p_frames - todo;
+ // We still have buffer to fill, start from this element in the next iteration.
+
} else {
for (int i = p_frames - todo; i < p_frames; i++) {
p_buffer[i] = AudioFrame(0, 0);
@@ -130,7 +195,7 @@ bool AudioStreamPlaybackOGGVorbis::_alloc_vorbis() {
comment_is_allocated = true;
ERR_FAIL_COND_V(vorbis_data.is_null(), false);
- vorbis_data_playback = vorbis_data->instance_playback();
+ vorbis_data_playback = vorbis_data->instantiate_playback();
ogg_packet *packet;
int err;
@@ -160,6 +225,7 @@ bool AudioStreamPlaybackOGGVorbis::_alloc_vorbis() {
void AudioStreamPlaybackOGGVorbis::start(float p_from_pos) {
ERR_FAIL_COND(!ready);
+ loop_fade_remaining = FADE_SIZE;
active = true;
seek(p_from_pos);
loops = 0;
@@ -182,6 +248,10 @@ float AudioStreamPlaybackOGGVorbis::get_playback_position() const {
return float(frames_mixed) / vorbis_data->get_sampling_rate();
}
+void AudioStreamPlaybackOGGVorbis::tag_used_streams() {
+ vorbis_stream->tag_used(get_playback_position());
+}
+
void AudioStreamPlaybackOGGVorbis::seek(float p_time) {
ERR_FAIL_COND(!ready);
ERR_FAIL_COND(vorbis_stream.is_null());
@@ -315,7 +385,7 @@ AudioStreamPlaybackOGGVorbis::~AudioStreamPlaybackOGGVorbis() {
}
}
-Ref<AudioStreamPlayback> AudioStreamOGGVorbis::instance_playback() {
+Ref<AudioStreamPlayback> AudioStreamOGGVorbis::instantiate_playback() {
Ref<AudioStreamPlaybackOGGVorbis> ovs;
ERR_FAIL_COND_V(packet_sequence.is_null(), nullptr);
@@ -347,7 +417,7 @@ void AudioStreamOGGVorbis::maybe_update_info() {
vorbis_info_init(&info);
vorbis_comment_init(&comment);
- Ref<OGGPacketSequencePlayback> packet_sequence_playback = packet_sequence->instance_playback();
+ Ref<OGGPacketSequencePlayback> packet_sequence_playback = packet_sequence->instantiate_playback();
for (int i = 0; i < 3; i++) {
ogg_packet *packet;
@@ -405,6 +475,36 @@ float AudioStreamOGGVorbis::get_length() const {
return packet_sequence->get_length();
}
+void AudioStreamOGGVorbis::set_bpm(double p_bpm) {
+ ERR_FAIL_COND(p_bpm < 0);
+ bpm = p_bpm;
+ emit_changed();
+}
+
+double AudioStreamOGGVorbis::get_bpm() const {
+ return bpm;
+}
+
+void AudioStreamOGGVorbis::set_beat_count(int p_beat_count) {
+ ERR_FAIL_COND(p_beat_count < 0);
+ beat_count = p_beat_count;
+ emit_changed();
+}
+
+int AudioStreamOGGVorbis::get_beat_count() const {
+ return beat_count;
+}
+
+void AudioStreamOGGVorbis::set_bar_beats(int p_bar_beats) {
+ ERR_FAIL_COND(p_bar_beats < 2);
+ bar_beats = p_bar_beats;
+ emit_changed();
+}
+
+int AudioStreamOGGVorbis::get_bar_beats() const {
+ return bar_beats;
+}
+
bool AudioStreamOGGVorbis::is_monophonic() const {
return false;
}
@@ -419,7 +519,19 @@ void AudioStreamOGGVorbis::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_loop_offset", "seconds"), &AudioStreamOGGVorbis::set_loop_offset);
ClassDB::bind_method(D_METHOD("get_loop_offset"), &AudioStreamOGGVorbis::get_loop_offset);
+ ClassDB::bind_method(D_METHOD("set_bpm", "bpm"), &AudioStreamOGGVorbis::set_bpm);
+ ClassDB::bind_method(D_METHOD("get_bpm"), &AudioStreamOGGVorbis::get_bpm);
+
+ ClassDB::bind_method(D_METHOD("set_beat_count", "count"), &AudioStreamOGGVorbis::set_beat_count);
+ ClassDB::bind_method(D_METHOD("get_beat_count"), &AudioStreamOGGVorbis::get_beat_count);
+
+ ClassDB::bind_method(D_METHOD("set_bar_beats", "count"), &AudioStreamOGGVorbis::set_bar_beats);
+ ClassDB::bind_method(D_METHOD("get_bar_beats"), &AudioStreamOGGVorbis::get_bar_beats);
+
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "packet_sequence", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_packet_sequence", "get_packet_sequence");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bpm", PROPERTY_HINT_RANGE, "0,400,0.01,or_greater"), "set_bpm", "get_bpm");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "beat_count", PROPERTY_HINT_RANGE, "0,512,1,or_greater"), "set_beat_count", "get_beat_count");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "bar_beats", PROPERTY_HINT_RANGE, "2,32,1,or_greater"), "set_bar_beats", "get_bar_beats");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "loop_offset"), "set_loop_offset", "get_loop_offset");
}
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.h b/modules/vorbis/audio_stream_ogg_vorbis.h
index b09ef9ff5d..22c2eb4d73 100644
--- a/modules/vorbis/audio_stream_ogg_vorbis.h
+++ b/modules/vorbis/audio_stream_ogg_vorbis.h
@@ -45,6 +45,12 @@ class AudioStreamPlaybackOGGVorbis : public AudioStreamPlaybackResampled {
bool active = false;
int loops = 0;
+ enum {
+ FADE_SIZE = 256
+ };
+ AudioFrame loop_fade[FADE_SIZE];
+ int loop_fade_remaining = FADE_SIZE;
+
vorbis_info info;
vorbis_comment comment;
vorbis_dsp_state dsp_state;
@@ -66,6 +72,7 @@ class AudioStreamPlaybackOGGVorbis : public AudioStreamPlaybackResampled {
Ref<OGGPacketSequencePlayback> vorbis_data_playback;
Ref<AudioStreamOGGVorbis> vorbis_stream;
+ int _mix_frames(AudioFrame *p_buffer, int p_frames);
int _mix_frames_vorbis(AudioFrame *p_buffer, int p_frames);
// Allocates vorbis data structures. Returns true upon success, false on failure.
@@ -85,6 +92,8 @@ public:
virtual float get_playback_position() const override;
virtual void seek(float p_time) override;
+ virtual void tag_used_streams() override;
+
AudioStreamPlaybackOGGVorbis() {}
~AudioStreamPlaybackOGGVorbis();
};
@@ -107,17 +116,30 @@ class AudioStreamOGGVorbis : public AudioStream {
Ref<OGGPacketSequence> packet_sequence;
+ double bpm = 0;
+ int beat_count = 0;
+ int bar_beats = 4;
+
protected:
static void _bind_methods();
public:
void set_loop(bool p_enable);
- bool has_loop() const;
+ virtual bool has_loop() const override;
void set_loop_offset(float p_seconds);
float get_loop_offset() const;
- virtual Ref<AudioStreamPlayback> instance_playback() override;
+ void set_bpm(double p_bpm);
+ virtual double get_bpm() const override;
+
+ void set_beat_count(int p_beat_count);
+ virtual int get_beat_count() const override;
+
+ void set_bar_beats(int p_bar_beats);
+ virtual int get_bar_beats() const override;
+
+ virtual Ref<AudioStreamPlayback> instantiate_playback() override;
virtual String get_stream_name() const override;
void set_packet_sequence(Ref<OGGPacketSequence> p_packet_sequence);
diff --git a/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml b/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml
index 2f210a6cb4..f87296dcd8 100644
--- a/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml
+++ b/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml
@@ -7,6 +7,12 @@
<tutorials>
</tutorials>
<members>
+ <member name="bar_beats" type="int" setter="set_bar_beats" getter="get_bar_beats" default="4">
+ </member>
+ <member name="beat_count" type="int" setter="set_beat_count" getter="get_beat_count" default="0">
+ </member>
+ <member name="bpm" type="float" setter="set_bpm" getter="get_bpm" default="0.0">
+ </member>
<member name="loop" type="bool" setter="set_loop" getter="has_loop" default="false">
If [code]true[/code], the stream will automatically loop when it reaches the end.
</member>
diff --git a/modules/vorbis/resource_importer_ogg_vorbis.cpp b/modules/vorbis/resource_importer_ogg_vorbis.cpp
index 7ee6446313..9461d531fd 100644
--- a/modules/vorbis/resource_importer_ogg_vorbis.cpp
+++ b/modules/vorbis/resource_importer_ogg_vorbis.cpp
@@ -30,13 +30,16 @@
#include "resource_importer_ogg_vorbis.h"
-#include "audio_stream_ogg_vorbis.h"
#include "core/io/file_access.h"
#include "core/io/resource_saver.h"
#include "scene/resources/texture.h"
#include "thirdparty/libogg/ogg/ogg.h"
#include "thirdparty/libvorbis/vorbis/codec.h"
+#ifdef TOOLS_ENABLED
+#include "editor/import/audio_stream_import_settings.h"
+#endif
+
String ResourceImporterOGGVorbis::get_importer_name() const {
return "oggvorbisstr";
}
@@ -72,14 +75,14 @@ String ResourceImporterOGGVorbis::get_preset_name(int p_idx) const {
void ResourceImporterOGGVorbis::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "loop"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "loop_offset"), 0));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "bpm", PROPERTY_HINT_RANGE, "0,400,0.01,or_greater"), 0));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "beat_count", PROPERTY_HINT_RANGE, "0,512,or_greater"), 0));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "bar_beats", PROPERTY_HINT_RANGE, "2,32,or_greater"), 4));
}
-Error ResourceImporterOGGVorbis::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
- bool loop = p_options["loop"];
- float loop_offset = p_options["loop_offset"];
-
- Ref<FileAccess> f = FileAccess::open(p_source_file, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, "Cannot open file '" + p_source_file + "'.");
+Ref<AudioStreamOGGVorbis> ResourceImporterOGGVorbis::import_ogg_vorbis(const String &p_path) {
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(f.is_null(), Ref<AudioStreamOGGVorbis>(), "Cannot open file '" + p_path + "'.");
uint64_t len = f->get_length();
@@ -107,16 +110,16 @@ Error ResourceImporterOGGVorbis::import(const String &p_source_file, const Strin
size_t packet_count = 0;
bool done = false;
while (!done) {
- ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
+ ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Ref<AudioStreamOGGVorbis>(), "Ogg sync error " + itos(err));
while (ogg_sync_pageout(&sync_state, &page) != 1) {
if (cursor >= len) {
done = true;
break;
}
- ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
+ ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Ref<AudioStreamOGGVorbis>(), "Ogg sync error " + itos(err));
char *sync_buf = ogg_sync_buffer(&sync_state, OGG_SYNC_BUFFER_SIZE);
- ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
- ERR_FAIL_COND_V(cursor > len, Error::ERR_INVALID_DATA);
+ ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Ref<AudioStreamOGGVorbis>(), "Ogg sync error " + itos(err));
+ ERR_FAIL_COND_V(cursor > len, Ref<AudioStreamOGGVorbis>());
size_t copy_size = len - cursor;
if (copy_size > OGG_SYNC_BUFFER_SIZE) {
copy_size = OGG_SYNC_BUFFER_SIZE;
@@ -124,22 +127,22 @@ Error ResourceImporterOGGVorbis::import(const String &p_source_file, const Strin
memcpy(sync_buf, &file_data[cursor], copy_size);
ogg_sync_wrote(&sync_state, copy_size);
cursor += copy_size;
- ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
+ ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Ref<AudioStreamOGGVorbis>(), "Ogg sync error " + itos(err));
}
if (done) {
break;
}
- ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err));
+ ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Ref<AudioStreamOGGVorbis>(), "Ogg sync error " + itos(err));
// Have a page now.
if (!initialized_stream) {
if (ogg_stream_init(&stream_state, ogg_page_serialno(&page))) {
- ERR_FAIL_V_MSG(Error::ERR_OUT_OF_MEMORY, "Failed allocating memory for OGG Vorbis stream.");
+ ERR_FAIL_V_MSG(Ref<AudioStreamOGGVorbis>(), "Failed allocating memory for OGG Vorbis stream.");
}
initialized_stream = true;
}
ogg_stream_pagein(&stream_state, &page);
- ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err));
+ ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Ref<AudioStreamOGGVorbis>(), "Ogg stream error " + itos(err));
int desync_iters = 0;
Vector<Vector<uint8_t>> packet_data;
@@ -150,7 +153,7 @@ Error ResourceImporterOGGVorbis::import(const String &p_source_file, const Strin
if (err == -1) {
// According to the docs this is usually recoverable, but don't sit here spinning forever.
desync_iters++;
- ERR_FAIL_COND_V_MSG(desync_iters > 100, Error::ERR_INVALID_DATA, "Packet sync issue during ogg import");
+ ERR_FAIL_COND_V_MSG(desync_iters > 100, Ref<AudioStreamOGGVorbis>(), "Packet sync issue during ogg import");
continue;
} else if (err == 0) {
// Not enough data to fully reconstruct a packet. Go on to the next page.
@@ -183,12 +186,45 @@ Error ResourceImporterOGGVorbis::import(const String &p_source_file, const Strin
ogg_sync_clear(&sync_state);
if (ogg_packet_sequence->get_packet_granule_positions().is_empty()) {
- ERR_FAIL_V_MSG(Error::ERR_FILE_CORRUPT, "OGG Vorbis decoding failed. Check that your data is a valid OGG Vorbis audio stream.");
+ ERR_FAIL_V_MSG(Ref<AudioStreamOGGVorbis>(), "OGG Vorbis decoding failed. Check that your data is a valid OGG Vorbis audio stream.");
}
ogg_vorbis_stream->set_packet_sequence(ogg_packet_sequence);
+
+ return ogg_vorbis_stream;
+}
+
+#ifdef TOOLS_ENABLED
+
+bool ResourceImporterOGGVorbis::has_advanced_options() const {
+ return true;
+}
+
+void ResourceImporterOGGVorbis::show_advanced_options(const String &p_path) {
+ Ref<AudioStreamOGGVorbis> ogg_stream = import_ogg_vorbis(p_path);
+ if (ogg_stream.is_valid()) {
+ AudioStreamImportSettings::get_singleton()->edit(p_path, "oggvorbisstr", ogg_stream);
+ }
+}
+#endif
+
+Error ResourceImporterOGGVorbis::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
+ bool loop = p_options["loop"];
+ float loop_offset = p_options["loop_offset"];
+ double bpm = p_options["bpm"];
+ int beat_count = p_options["beat_count"];
+ int bar_beats = p_options["bar_beats"];
+
+ Ref<AudioStreamOGGVorbis> ogg_vorbis_stream = import_ogg_vorbis(p_source_file);
+ if (ogg_vorbis_stream.is_null()) {
+ return ERR_CANT_OPEN;
+ }
+
ogg_vorbis_stream->set_loop(loop);
ogg_vorbis_stream->set_loop_offset(loop_offset);
+ ogg_vorbis_stream->set_bpm(bpm);
+ ogg_vorbis_stream->set_beat_count(beat_count);
+ ogg_vorbis_stream->set_bar_beats(bar_beats);
return ResourceSaver::save(p_save_path + ".oggvorbisstr", ogg_vorbis_stream);
}
diff --git a/modules/vorbis/resource_importer_ogg_vorbis.h b/modules/vorbis/resource_importer_ogg_vorbis.h
index 3b4a68a1fd..e6e98a29c1 100644
--- a/modules/vorbis/resource_importer_ogg_vorbis.h
+++ b/modules/vorbis/resource_importer_ogg_vorbis.h
@@ -31,6 +31,7 @@
#ifndef RESOURCE_IMPORTER_OGG_VORBIS_H
#define RESOURCE_IMPORTER_OGG_VORBIS_H
+#include "audio_stream_ogg_vorbis.h"
#include "core/io/resource_importer.h"
class ResourceImporterOGGVorbis : public ResourceImporter {
@@ -43,7 +44,13 @@ class ResourceImporterOGGVorbis : public ResourceImporter {
private:
// virtual int get_samples_in_packet(Vector<uint8_t> p_packet) = 0;
+ static Ref<AudioStreamOGGVorbis> import_ogg_vorbis(const String &p_path);
+
public:
+#ifdef TOOLS_ENABLED
+ virtual bool has_advanced_options() const override;
+ virtual void show_advanced_options(const String &p_path) override;
+#endif
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
virtual String get_save_extension() const override;
virtual String get_resource_type() const override;