summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/classes/AudioEffectCapture.xml75
-rw-r--r--servers/audio/effects/audio_effect_capture.cpp140
-rw-r--r--servers/audio/effects/audio_effect_capture.h82
-rw-r--r--servers/register_server_types.cpp3
4 files changed, 300 insertions, 0 deletions
diff --git a/doc/classes/AudioEffectCapture.xml b/doc/classes/AudioEffectCapture.xml
new file mode 100644
index 0000000000..cf3d87c2e4
--- /dev/null
+++ b/doc/classes/AudioEffectCapture.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="AudioEffectCapture" inherits="AudioEffect" version="4.0">
+ <brief_description>
+ Captures audio from an audio bus in real-time.
+ </brief_description>
+ <description>
+ AudioEffectCapture is an AudioEffect which copies all audio frames from the attached audio effect bus into its internal ring buffer.
+ Application code should consume these audio frames from this ring buffer using [method get_buffer] and process it as needed, for example to capture data from a microphone, implement application defined effects, or to transmit audio over the network.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="can_get_buffer" qualifiers="const">
+ <return type="bool">
+ </return>
+ <argument index="0" name="frames" type="int">
+ </argument>
+ <description>
+ Returns [code]true[/code] if at least [code]frames[/code] audio frames are available to read in the internal ring buffer.
+ </description>
+ </method>
+ <method name="clear_buffer">
+ <return type="void">
+ </return>
+ <description>
+ Clears the internal ring buffer.
+ </description>
+ </method>
+ <method name="get_buffer">
+ <return type="PackedVector2Array">
+ </return>
+ <argument index="0" name="frames" type="int">
+ </argument>
+ <description>
+ Gets the next [code]frames[/code] audio samples from the internal ring buffer.
+ Returns a [PackedVector2Array] containing exactly [code]frames[/code] audio samples if available, or an empty [PackedVector2Array] if insufficient data was available.
+ </description>
+ </method>
+ <method name="get_buffer_length_frames" qualifiers="const">
+ <return type="int">
+ </return>
+ <description>
+ Returns the total size of the internal ring buffer in frames.
+ </description>
+ </method>
+ <method name="get_discarded_frames" qualifiers="const">
+ <return type="int">
+ </return>
+ <description>
+ Returns the number of audio frames discarded from the audio bus due to full buffer.
+ </description>
+ </method>
+ <method name="get_frames_available" qualifiers="const">
+ <return type="int">
+ </return>
+ <description>
+ Returns the number of frames available to read using [method get_buffer].
+ </description>
+ </method>
+ <method name="get_pushed_frames" qualifiers="const">
+ <return type="int">
+ </return>
+ <description>
+ Returns the number of audio frames inserted from the audio bus.
+ </description>
+ </method>
+ </methods>
+ <members>
+ <member name="buffer_length" type="float" setter="set_buffer_length" getter="get_buffer_length" default="0.1">
+ Length of the internal ring buffer, in seconds.
+ </member>
+ </members>
+ <constants>
+ </constants>
+</class>
diff --git a/servers/audio/effects/audio_effect_capture.cpp b/servers/audio/effects/audio_effect_capture.cpp
new file mode 100644
index 0000000000..f37938eec8
--- /dev/null
+++ b/servers/audio/effects/audio_effect_capture.cpp
@@ -0,0 +1,140 @@
+/*************************************************************************/
+/* audio_effect_capture.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 "audio_effect_capture.h"
+
+bool AudioEffectCapture::can_get_buffer(int p_frames) const {
+ return buffer.data_left() >= p_frames;
+}
+
+PackedVector2Array AudioEffectCapture::get_buffer(int p_frames) {
+ ERR_FAIL_COND_V(!buffer_initialized, PackedVector2Array());
+ ERR_FAIL_INDEX_V(p_frames, buffer.size(), PackedVector2Array());
+ int data_left = buffer.data_left();
+ if (data_left < p_frames || p_frames == 0) {
+ return PackedVector2Array();
+ }
+
+ PackedVector2Array ret;
+ ret.resize(p_frames);
+
+ Vector<AudioFrame> streaming_data;
+ streaming_data.resize(p_frames);
+ buffer.read(streaming_data.ptrw(), p_frames);
+ for (int32_t i = 0; i < p_frames; i++) {
+ ret.write[i] = Vector2(streaming_data[i].l, streaming_data[i].r);
+ }
+ return ret;
+}
+
+void AudioEffectCapture::clear_buffer() {
+ const int32_t data_left = buffer.data_left();
+ buffer.advance_read(data_left);
+}
+
+void AudioEffectCapture::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("can_get_buffer", "frames"), &AudioEffectCapture::can_get_buffer);
+ ClassDB::bind_method(D_METHOD("get_buffer", "frames"), &AudioEffectCapture::get_buffer);
+ ClassDB::bind_method(D_METHOD("clear_buffer"), &AudioEffectCapture::clear_buffer);
+ ClassDB::bind_method(D_METHOD("set_buffer_length", "buffer_length_seconds"), &AudioEffectCapture::set_buffer_length);
+ ClassDB::bind_method(D_METHOD("get_buffer_length"), &AudioEffectCapture::get_buffer_length);
+ ClassDB::bind_method(D_METHOD("get_frames_available"), &AudioEffectCapture::get_frames_available);
+ ClassDB::bind_method(D_METHOD("get_discarded_frames"), &AudioEffectCapture::get_discarded_frames);
+ ClassDB::bind_method(D_METHOD("get_buffer_length_frames"), &AudioEffectCapture::get_buffer_length_frames);
+ ClassDB::bind_method(D_METHOD("get_pushed_frames"), &AudioEffectCapture::get_pushed_frames);
+
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "buffer_length", PROPERTY_HINT_RANGE, "0.01,10,0.01"), "set_buffer_length", "get_buffer_length");
+}
+
+Ref<AudioEffectInstance> AudioEffectCapture::instance() {
+ if (!buffer_initialized) {
+ float target_buffer_size = AudioServer::get_singleton()->get_mix_rate() * buffer_length_seconds;
+ ERR_FAIL_COND_V(target_buffer_size <= 0 || target_buffer_size >= (1 << 27), Ref<AudioEffectInstance>());
+ buffer.resize(nearest_shift((int)target_buffer_size));
+ buffer_initialized = true;
+ }
+
+ clear_buffer();
+
+ Ref<AudioEffectCaptureInstance> ins;
+ ins.instance();
+ ins->base = Ref<AudioEffectCapture>(this);
+
+ return ins;
+}
+
+void AudioEffectCapture::set_buffer_length(float p_buffer_length_seconds) {
+ ERR_FAIL_COND(buffer_initialized);
+
+ buffer_length_seconds = p_buffer_length_seconds;
+}
+
+float AudioEffectCapture::get_buffer_length() {
+ return buffer_length_seconds;
+}
+
+int AudioEffectCapture::get_frames_available() const {
+ ERR_FAIL_COND_V(!buffer_initialized, 0);
+ return buffer.data_left();
+}
+
+int64_t AudioEffectCapture::get_discarded_frames() const {
+ return discarded_frames;
+}
+
+int AudioEffectCapture::get_buffer_length_frames() const {
+ ERR_FAIL_COND_V(!buffer_initialized, 0);
+ return buffer.size();
+}
+
+int64_t AudioEffectCapture::get_pushed_frames() const {
+ return pushed_frames;
+}
+
+void AudioEffectCaptureInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
+ RingBuffer<AudioFrame> &buffer = base->buffer;
+
+ for (int i = 0; i < p_frame_count; i++) {
+ p_dst_frames[i] = p_src_frames[i];
+ }
+
+ if (buffer.space_left() >= p_frame_count) {
+ // Add incoming audio frames to the IO ring buffer
+ int32_t ret = buffer.write(p_src_frames, p_frame_count);
+ ERR_FAIL_COND_MSG(ret != p_frame_count, "Failed to add data to effect capture ring buffer despite sufficient space.");
+ atomic_add(&base->pushed_frames, p_frame_count);
+ } else {
+ atomic_add(&base->discarded_frames, p_frame_count);
+ }
+}
+
+bool AudioEffectCaptureInstance::process_silence() const {
+ return true;
+}
diff --git a/servers/audio/effects/audio_effect_capture.h b/servers/audio/effects/audio_effect_capture.h
new file mode 100644
index 0000000000..b154be85de
--- /dev/null
+++ b/servers/audio/effects/audio_effect_capture.h
@@ -0,0 +1,82 @@
+/*************************************************************************/
+/* audio_effect_capture.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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. */
+/*************************************************************************/
+
+#ifndef AUDIO_EFFECT_CAPTURE_H
+#define AUDIO_EFFECT_CAPTURE_H
+
+#include "core/config/engine.h"
+#include "core/math/audio_frame.h"
+#include "core/object/reference.h"
+#include "core/templates/vector.h"
+#include "servers/audio/audio_effect.h"
+#include "servers/audio_server.h"
+
+class AudioEffectCapture;
+
+class AudioEffectCaptureInstance : public AudioEffectInstance {
+ GDCLASS(AudioEffectCaptureInstance, AudioEffectInstance);
+ friend class AudioEffectCapture;
+ Ref<AudioEffectCapture> base;
+
+public:
+ virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
+ virtual bool process_silence() const override;
+};
+
+class AudioEffectCapture : public AudioEffect {
+ GDCLASS(AudioEffectCapture, AudioEffect)
+ friend class AudioEffectCaptureInstance;
+
+ RingBuffer<AudioFrame> buffer;
+ uint64_t discarded_frames = 0;
+ uint64_t pushed_frames = 0;
+ float buffer_length_seconds = 0.1f;
+ bool buffer_initialized = false;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual Ref<AudioEffectInstance> instance() override;
+
+ void set_buffer_length(float p_buffer_length_seconds);
+ float get_buffer_length();
+
+ bool can_get_buffer(int p_frames) const;
+ PackedVector2Array get_buffer(int p_len);
+ void clear_buffer();
+
+ int get_frames_available() const;
+ int64_t get_discarded_frames() const;
+ int get_buffer_length_frames() const;
+ int64_t get_pushed_frames() const;
+};
+
+#endif // AUDIO_EFFECT_CAPTURE_H
diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp
index 58bcdf5802..50efd7c554 100644
--- a/servers/register_server_types.cpp
+++ b/servers/register_server_types.cpp
@@ -36,6 +36,7 @@
#include "audio/audio_effect.h"
#include "audio/audio_stream.h"
#include "audio/effects/audio_effect_amplify.h"
+#include "audio/effects/audio_effect_capture.h"
#include "audio/effects/audio_effect_chorus.h"
#include "audio/effects/audio_effect_compressor.h"
#include "audio/effects/audio_effect_delay.h"
@@ -166,6 +167,8 @@ void register_server_types() {
ClassDB::register_class<AudioEffectRecord>();
ClassDB::register_class<AudioEffectSpectrumAnalyzer>();
ClassDB::register_virtual_class<AudioEffectSpectrumAnalyzerInstance>();
+
+ ClassDB::register_class<AudioEffectCapture>();
}
ClassDB::register_virtual_class<RenderingDevice>();