diff --git a/doc/classes/AudioStreamWAV.xml b/doc/classes/AudioStreamWAV.xml
index e3685ce949a4..abb7f674f7c5 100644
--- a/doc/classes/AudioStreamWAV.xml
+++ b/doc/classes/AudioStreamWAV.xml
@@ -11,6 +11,12 @@
$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html
+
+
+
+ Creates a placeholder version of this resource ([PlaceholderAudioStream]).
+
+
diff --git a/doc/classes/PlaceholderAudioStream.xml b/doc/classes/PlaceholderAudioStream.xml
new file mode 100644
index 000000000000..a802c305adaf
--- /dev/null
+++ b/doc/classes/PlaceholderAudioStream.xml
@@ -0,0 +1,46 @@
+
+
+
+ Placeholder class for WAV, Ogg Vorbis or MP3 audio.
+
+
+ This class is used when loading a project that uses [AudioStreamWAV], [AudioStreamOggVorbis] or [AudioStreamMP3], if the project was exported in dedicated server mode. In this mode, only the resource reference is kept, with no actual audio data. This allows reducing the exported PCK's size significantly.
+ [b]Note:[/b] This is not intended to be used as an actual resource for audio playback.
+
+
+
+
+
+ The audio stream's length in seconds.
+
+
+ The loop start point (in number of samples, relative to the beginning of the stream).
+
+
+ The loop end point (in number of samples, relative to the beginning of the stream).
+
+
+ The loop mode.
+
+
+ Contains user-defined tags if found in the WAV or Ogg Vorbis data.
+ Commonly used tags include [code]title[/code], [code]artist[/code], [code]album[/code], [code]tracknumber[/code], and [code]date[/code] ([code]date[/code] does not have a standard date format).
+ [b]Note:[/b] No tag is [i]guaranteed[/i] to be present in every file, so make sure to account for the keys not always existing.
+ [b]Note:[/b] Only WAV files using a [code]LIST[/code] chunk with an identifier of [code]INFO[/code] to encode the tags are currently supported.
+
+
+
+
+ Audio does not loop.
+
+
+ Audio loops the data between [member loop_begin] and [member loop_end], playing forward only.
+
+
+ Audio loops the data between [member loop_begin] and [member loop_end], playing back and forth.
+
+
+ Audio loops the data between [member loop_begin] and [member loop_end], playing backward only.
+
+
+
diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp
index 6b955f3a118a..92efa8079fbb 100644
--- a/modules/minimp3/audio_stream_mp3.cpp
+++ b/modules/minimp3/audio_stream_mp3.cpp
@@ -34,6 +34,7 @@
#include "audio_stream_mp3.h"
#include "core/io/file_access.h"
+#include "scene/resources/placeholder_audio_stream.h"
int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) {
if (!active) {
@@ -327,6 +328,14 @@ Ref AudioStreamMP3::load_from_file(const String &p_path) {
return load_from_buffer(stream_data);
}
+Ref AudioStreamMP3::create_placeholder() const {
+ Ref placeholder;
+ placeholder.instantiate();
+ placeholder->set_length(get_length());
+ placeholder->set_tags(get_tags());
+ return placeholder;
+}
+
void AudioStreamMP3::_bind_methods() {
ClassDB::bind_static_method("AudioStreamMP3", D_METHOD("load_from_buffer", "stream_data"), &AudioStreamMP3::load_from_buffer);
ClassDB::bind_static_method("AudioStreamMP3", D_METHOD("load_from_file", "path"), &AudioStreamMP3::load_from_file);
@@ -349,6 +358,8 @@ void AudioStreamMP3::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bar_beats", "count"), &AudioStreamMP3::set_bar_beats);
ClassDB::bind_method(D_METHOD("get_bar_beats"), &AudioStreamMP3::get_bar_beats);
+ ClassDB::bind_method(D_METHOD("create_placeholder"), &AudioStreamMP3::create_placeholder);
+
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_data", "get_data");
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");
diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h
index 9612e6a3b038..ee20e8683059 100644
--- a/modules/minimp3/audio_stream_mp3.h
+++ b/modules/minimp3/audio_stream_mp3.h
@@ -147,6 +147,8 @@ class AudioStreamMP3 : public AudioStream {
virtual void get_parameter_list(List *r_parameters) override;
+ virtual Ref create_placeholder() const;
+
AudioStreamMP3();
virtual ~AudioStreamMP3();
};
diff --git a/modules/minimp3/doc_classes/AudioStreamMP3.xml b/modules/minimp3/doc_classes/AudioStreamMP3.xml
index 6eb665b04891..420a95dd88de 100644
--- a/modules/minimp3/doc_classes/AudioStreamMP3.xml
+++ b/modules/minimp3/doc_classes/AudioStreamMP3.xml
@@ -10,6 +10,12 @@
+
+
+
+ Creates a placeholder version of this resource ([PlaceholderAudioStream]).
+
+
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp
index 62c7e73956a1..efe460f4c056 100644
--- a/modules/vorbis/audio_stream_ogg_vorbis.cpp
+++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp
@@ -30,6 +30,7 @@
#include "audio_stream_ogg_vorbis.h"
#include "core/io/file_access.h"
+#include "scene/resources/placeholder_audio_stream.h"
#include "core/templates/rb_map.h"
@@ -703,6 +704,14 @@ Ref AudioStreamOggVorbis::load_from_file(const String &p_p
return load_from_buffer(stream_data);
}
+Ref AudioStreamOggVorbis::create_placeholder() const {
+ Ref placeholder;
+ placeholder.instantiate();
+ placeholder->set_length(get_length());
+ placeholder->set_tags(get_tags());
+ return placeholder;
+}
+
void AudioStreamOggVorbis::_bind_methods() {
ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_buffer", "stream_data"), &AudioStreamOggVorbis::load_from_buffer);
ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_file", "path"), &AudioStreamOggVorbis::load_from_file);
@@ -728,6 +737,8 @@ void AudioStreamOggVorbis::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_tags", "tags"), &AudioStreamOggVorbis::set_tags);
ClassDB::bind_method(D_METHOD("get_tags"), &AudioStreamOggVorbis::get_tags);
+ ClassDB::bind_method(D_METHOD("create_placeholder"), &AudioStreamOggVorbis::create_placeholder);
+
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");
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.h b/modules/vorbis/audio_stream_ogg_vorbis.h
index e14df33490ae..6e906da83053 100644
--- a/modules/vorbis/audio_stream_ogg_vorbis.h
+++ b/modules/vorbis/audio_stream_ogg_vorbis.h
@@ -38,6 +38,7 @@
#include
class AudioStreamOggVorbis;
+class PlaceholderAudioStream;
class AudioStreamPlaybackOggVorbis : public AudioStreamPlaybackResampled {
GDCLASS(AudioStreamPlaybackOggVorbis, AudioStreamPlaybackResampled);
@@ -177,6 +178,8 @@ class AudioStreamOggVorbis : public AudioStream {
}
virtual Ref generate_sample() const override;
+ virtual Ref create_placeholder() const;
+
AudioStreamOggVorbis();
virtual ~AudioStreamOggVorbis();
};
diff --git a/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml b/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml
index 5f144baa319b..1d8aea288fca 100644
--- a/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml
+++ b/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml
@@ -10,6 +10,12 @@
$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html
+
+
+
+ Creates a placeholder version of this resource ([PlaceholderAudioStream]).
+
+
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 57135e034836..ce7c8a60948b 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -138,6 +138,7 @@
#include "scene/resources/dpi_texture.h"
#include "scene/resources/packed_scene.h"
#include "scene/resources/particle_process_material.h"
+#include "scene/resources/placeholder_audio_stream.h"
#include "scene/resources/placeholder_textures.h"
#include "scene/resources/portable_compressed_texture.h"
#include "scene/resources/resource_format_text.h"
@@ -1116,6 +1117,7 @@ void register_scene_types() {
GDREGISTER_CLASS(AudioStreamPlayer);
GDREGISTER_CLASS(AudioStreamWAV);
GDREGISTER_CLASS(AudioStreamPolyphonic);
+ GDREGISTER_CLASS(PlaceholderAudioStream);
GDREGISTER_ABSTRACT_CLASS(AudioStreamPlaybackPolyphonic);
OS::get_singleton()->yield(); // may take time to init
diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp
index 949ab8236d51..ec608cc9bf71 100644
--- a/scene/resources/audio_stream_wav.cpp
+++ b/scene/resources/audio_stream_wav.cpp
@@ -32,6 +32,7 @@
#include "core/io/file_access_memory.h"
#include "core/io/marshalls.h"
+#include "scene/resources/placeholder_audio_stream.h"
const float TRIM_DB_LIMIT = -50;
const int TRIM_FADE_OUT_FRAMES = 500;
@@ -1204,6 +1205,14 @@ Ref AudioStreamWAV::load_from_file(const String &p_path, const D
return load_from_buffer(stream_data, p_options);
}
+Ref AudioStreamWAV::create_placeholder() const {
+ Ref placeholder;
+ placeholder.instantiate();
+ placeholder->set_length(get_length());
+ placeholder->set_tags(get_tags());
+ return placeholder;
+}
+
void AudioStreamWAV::_bind_methods() {
ClassDB::bind_static_method("AudioStreamWAV", D_METHOD("load_from_buffer", "stream_data", "options"), &AudioStreamWAV::load_from_buffer, DEFVAL(Dictionary()));
ClassDB::bind_static_method("AudioStreamWAV", D_METHOD("load_from_file", "path", "options"), &AudioStreamWAV::load_from_file, DEFVAL(Dictionary()));
@@ -1234,6 +1243,8 @@ void AudioStreamWAV::_bind_methods() {
ClassDB::bind_method(D_METHOD("save_to_wav", "path"), &AudioStreamWAV::save_to_wav);
+ ClassDB::bind_method(D_METHOD("create_placeholder"), &AudioStreamWAV::create_placeholder);
+
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_data", "get_data");
ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA ADPCM,Quite OK Audio"), "set_format", "get_format");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong,Backward"), "set_loop_mode", "get_loop_mode");
diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h
index fd67d7b14b6e..ec26b248778b 100644
--- a/scene/resources/audio_stream_wav.h
+++ b/scene/resources/audio_stream_wav.h
@@ -171,6 +171,8 @@ class AudioStreamWAV : public AudioStream {
}
virtual Ref generate_sample() const override;
+ virtual Ref create_placeholder() const;
+
static void _compress_ima_adpcm(const Vector &p_data, Vector &r_dst_data) {
static const int16_t _ima_adpcm_step_table[89] = {
7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
diff --git a/scene/resources/placeholder_audio_stream.cpp b/scene/resources/placeholder_audio_stream.cpp
new file mode 100644
index 000000000000..b4aeea869600
--- /dev/null
+++ b/scene/resources/placeholder_audio_stream.cpp
@@ -0,0 +1,298 @@
+/**************************************************************************/
+/* placeholder_audio_stream.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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. */
+/**************************************************************************/
+
+#include "placeholder_audio_stream.h"
+
+void PlaceholderAudioStreamPlayback::start(double p_from_pos) {
+ seek(p_from_pos);
+ sign = 1;
+ active = true;
+}
+
+void PlaceholderAudioStreamPlayback::stop() {
+ active = false;
+}
+
+bool PlaceholderAudioStreamPlayback::is_playing() const {
+ return active;
+}
+
+int PlaceholderAudioStreamPlayback::get_loop_count() const {
+ return 0;
+}
+
+double PlaceholderAudioStreamPlayback::get_playback_position() const {
+ return double(offset);
+}
+
+void PlaceholderAudioStreamPlayback::seek(double p_time) {
+ double max = base->get_length();
+ if (p_time < 0) {
+ p_time = 0;
+ } else if (p_time >= max) {
+ p_time = max - 0.001;
+ }
+
+ offset = int64_t(p_time);
+}
+
+void PlaceholderAudioStreamPlayback::tag_used_streams() {
+ base->tag_used(get_playback_position());
+}
+
+void PlaceholderAudioStreamPlayback::set_is_sample(bool p_is_sample) {
+ _is_sample = p_is_sample;
+}
+
+bool PlaceholderAudioStreamPlayback::get_is_sample() const {
+ return _is_sample;
+}
+
+Ref PlaceholderAudioStreamPlayback::get_sample_playback() const {
+ return sample_playback;
+}
+
+void PlaceholderAudioStreamPlayback::set_sample_playback(const Ref &p_playback) {
+ sample_playback = p_playback;
+ if (sample_playback.is_valid()) {
+ sample_playback->stream_playback = Ref(this);
+ }
+}
+
+int PlaceholderAudioStreamPlayback::_mix_internal(AudioFrame *p_buffer, int p_frames) {
+ if (!active) {
+ for (int i = 0; i < p_frames; i++) {
+ p_buffer[i] = AudioFrame(0, 0);
+ }
+ return 0;
+ }
+
+ uint64_t len = base->get_length() * 1000; // TODO: Calculate length correctly.
+
+ int64_t loop_begin = base->loop_begin;
+ int64_t loop_end = base->loop_end;
+ int64_t begin_limit = (base->loop_mode != PlaceholderAudioStream::LOOP_DISABLED) ? loop_begin : 0;
+ int64_t end_limit = (base->loop_mode != PlaceholderAudioStream::LOOP_DISABLED) ? loop_end : len - 1;
+
+ int32_t todo = p_frames;
+
+ if (base->loop_mode == PlaceholderAudioStream::LOOP_BACKWARD) {
+ sign = -1;
+ }
+
+ int8_t increment = sign;
+
+ //looping
+
+ PlaceholderAudioStream::LoopMode loop_format = base->loop_mode;
+
+ /* audio data */
+
+ AudioFrame *dst_buff = p_buffer;
+
+ while (todo > 0) {
+ int64_t limit = 0;
+ int32_t target = 0, aux = 0;
+
+ /** LOOP CHECKING **/
+
+ if (increment < 0) {
+ /* going backwards */
+
+ if (loop_format != PlaceholderAudioStream::LOOP_DISABLED && offset < loop_begin) {
+ /* loopstart reached */
+ if (loop_format == PlaceholderAudioStream::LOOP_PINGPONG) {
+ /* bounce ping pong */
+ offset = loop_begin + (loop_begin - offset);
+ increment = -increment;
+ sign *= -1;
+ } else {
+ /* go to loop-end */
+ offset = loop_end - (loop_begin - offset);
+ }
+ } else {
+ /* check for sample not reaching beginning */
+ if (offset < 0) {
+ active = false;
+ break;
+ }
+ }
+ } else {
+ /* going forward */
+ if (loop_format != PlaceholderAudioStream::LOOP_DISABLED && offset >= loop_end) {
+ /* loopend reached */
+
+ if (loop_format == PlaceholderAudioStream::LOOP_PINGPONG) {
+ /* bounce ping pong */
+ offset = loop_end - (offset - loop_end);
+ increment = -increment;
+ sign *= -1;
+ } else {
+ /* go to loop-begin */
+
+ offset = loop_begin + (offset - loop_end);
+ }
+ } else {
+ /* no loop, check for end of sample */
+ if ((uint64_t)offset >= len) {
+ active = false;
+ break;
+ }
+ }
+ }
+
+ /** MIXCOUNT COMPUTING **/
+
+ /* next possible limit (looppoints or sample begin/end */
+ limit = (increment < 0) ? begin_limit : end_limit;
+
+ /* compute what is shorter, the todo or the limit? */
+ aux = (limit - offset) / increment + 1;
+ target = (aux < todo) ? aux : todo; /* mix target is the shorter buffer */
+
+ /* check just in case */
+ if (target <= 0) {
+ active = false;
+ break;
+ }
+
+ todo -= target;
+
+ dst_buff += target;
+ }
+
+ if (todo) {
+ int mixed_frames = p_frames - todo;
+ //bit was missing from mix
+ int todo_ofs = p_frames - todo;
+ for (int i = todo_ofs; i < p_frames; i++) {
+ p_buffer[i] = AudioFrame(0, 0);
+ }
+ return mixed_frames;
+ }
+ return p_frames;
+}
+
+int PlaceholderAudioStreamPlayback::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
+ constexpr int INTERNAL_BUFFER_LEN = 128;
+ int mixed_frames_total = -1;
+
+ int i;
+ for (i = 0; i < p_frames; i++) {
+ AudioFrame af;
+ _mix_internal(&af, INTERNAL_BUFFER_LEN);
+ }
+ if (mixed_frames_total == -1 && i == p_frames) {
+ mixed_frames_total = p_frames;
+ }
+ return mixed_frames_total;
+}
+
+///////////
+
+void PlaceholderAudioStream::set_length(double p_length) {
+ length = p_length;
+}
+
+double PlaceholderAudioStream::get_length() const {
+ return length;
+}
+
+void PlaceholderAudioStream::set_loop_mode(const LoopMode p_loop_mode) {
+ loop_mode = p_loop_mode;
+}
+
+PlaceholderAudioStream::LoopMode PlaceholderAudioStream::get_loop_mode() const {
+ return loop_mode;
+}
+
+void PlaceholderAudioStream::set_loop_begin(int p_frame) {
+ loop_begin = p_frame;
+}
+
+int PlaceholderAudioStream::get_loop_begin() const {
+ return loop_begin;
+}
+
+void PlaceholderAudioStream::set_loop_end(int p_frame) {
+ loop_end = p_frame;
+}
+
+int PlaceholderAudioStream::get_loop_end() const {
+ return loop_end;
+}
+
+void PlaceholderAudioStream::set_tags(const Dictionary &p_tags) {
+ tags = p_tags;
+}
+
+Dictionary PlaceholderAudioStream::get_tags() const {
+ return tags;
+}
+
+Ref PlaceholderAudioStream::instantiate_playback() {
+ Ref sample;
+ sample.instantiate();
+ sample->base = Ref(this);
+ return sample;
+}
+
+void PlaceholderAudioStream::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_length"), &PlaceholderAudioStream::set_length);
+
+ ClassDB::bind_method(D_METHOD("set_loop_mode"), &PlaceholderAudioStream::set_loop_mode);
+ ClassDB::bind_method(D_METHOD("get_loop_mode"), &PlaceholderAudioStream::get_loop_mode);
+
+ ClassDB::bind_method(D_METHOD("set_loop_begin", "loop_begin"), &PlaceholderAudioStream::set_loop_begin);
+ ClassDB::bind_method(D_METHOD("get_loop_begin"), &PlaceholderAudioStream::get_loop_begin);
+
+ ClassDB::bind_method(D_METHOD("set_loop_end", "loop_end"), &PlaceholderAudioStream::set_loop_end);
+ ClassDB::bind_method(D_METHOD("get_loop_end"), &PlaceholderAudioStream::get_loop_end);
+
+ ClassDB::bind_method(D_METHOD("set_tags"), &PlaceholderAudioStream::set_tags);
+ ClassDB::bind_method(D_METHOD("get_tags"), &PlaceholderAudioStream::get_tags);
+
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.0,100.0,0.001,or_greater,suffix:s"), "set_length", "get_length");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong,Backward"), "set_loop_mode", "get_loop_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_begin"), "set_loop_begin", "get_loop_begin");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_end"), "set_loop_end", "get_loop_end");
+ ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "tags", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_tags", "get_tags");
+
+ BIND_ENUM_CONSTANT(LOOP_DISABLED);
+ BIND_ENUM_CONSTANT(LOOP_FORWARD);
+ BIND_ENUM_CONSTANT(LOOP_PINGPONG);
+ BIND_ENUM_CONSTANT(LOOP_BACKWARD);
+}
+
+PlaceholderAudioStream::PlaceholderAudioStream() {
+}
+
+PlaceholderAudioStream::~PlaceholderAudioStream() {
+}
diff --git a/scene/resources/placeholder_audio_stream.h b/scene/resources/placeholder_audio_stream.h
new file mode 100644
index 000000000000..6c3a0a3c4196
--- /dev/null
+++ b/scene/resources/placeholder_audio_stream.h
@@ -0,0 +1,119 @@
+/**************************************************************************/
+/* placeholder_audio_stream.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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. */
+/**************************************************************************/
+
+#pragma once
+
+#include "servers/audio/audio_stream.h"
+
+class PlaceholderAudioStream;
+
+class PlaceholderAudioStreamPlayback : public AudioStreamPlayback {
+ GDCLASS(PlaceholderAudioStreamPlayback, AudioStreamPlayback);
+
+ int64_t offset = 0;
+ int8_t sign = 1;
+ bool active = false;
+ Ref base;
+
+ friend class PlaceholderAudioStream;
+
+ bool _is_sample = false;
+ Ref sample_playback;
+
+ virtual int _mix_internal(AudioFrame *p_buffer, int p_frames);
+
+public:
+ virtual void start(double p_from_pos = 0.0) override;
+ virtual void stop() override;
+ virtual bool is_playing() const override;
+
+ virtual int get_loop_count() const override;
+
+ virtual double get_playback_position() const override;
+ virtual void seek(double p_time) override;
+
+ virtual void tag_used_streams() override;
+
+ virtual void set_is_sample(bool p_is_sample) override;
+ virtual bool get_is_sample() const override;
+ virtual Ref get_sample_playback() const override;
+ virtual void set_sample_playback(const Ref &p_playback) override;
+
+ virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
+};
+
+///////////
+
+class PlaceholderAudioStream : public AudioStream {
+ GDCLASS(PlaceholderAudioStream, AudioStream)
+
+public:
+ enum LoopMode {
+ LOOP_DISABLED,
+ LOOP_FORWARD,
+ LOOP_PINGPONG,
+ LOOP_BACKWARD
+ };
+
+private:
+ double length = 0.0;
+ LoopMode loop_mode = LOOP_DISABLED;
+ int64_t loop_begin = 0;
+ int64_t loop_end = 0;
+ Dictionary tags;
+
+ friend class PlaceholderAudioStreamPlayback;
+
+protected:
+ static void _bind_methods();
+
+public:
+ void set_length(double p_length);
+ double get_length() const override;
+
+ void set_loop_mode(LoopMode p_loop_mode);
+ LoopMode get_loop_mode() const;
+
+ void set_loop_begin(int p_frame);
+ int get_loop_begin() const;
+
+ void set_loop_end(int p_frame);
+ int get_loop_end() const;
+
+ void set_tags(const Dictionary &p_tags);
+ Dictionary get_tags() const override;
+
+ virtual Ref instantiate_playback() override;
+
+ PlaceholderAudioStream();
+ ~PlaceholderAudioStream();
+};
+
+VARIANT_ENUM_CAST(PlaceholderAudioStream::LoopMode);