initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
6
servers/audio/effects/SCsub
Normal file
6
servers/audio/effects/SCsub
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.servers_sources, "*.cpp")
|
83
servers/audio/effects/audio_effect_amplify.cpp
Normal file
83
servers/audio/effects/audio_effect_amplify.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_amplify.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 "audio_effect_amplify.h"
|
||||
|
||||
void AudioEffectAmplifyInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
//multiply volume interpolating to avoid clicks if this changes
|
||||
float volume_db = base->volume_db;
|
||||
float vol = Math::db_to_linear(mix_volume_db);
|
||||
float vol_inc = (Math::db_to_linear(volume_db) - vol) / float(p_frame_count);
|
||||
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
p_dst_frames[i] = p_src_frames[i] * vol;
|
||||
vol += vol_inc;
|
||||
}
|
||||
//set volume for next mix
|
||||
mix_volume_db = volume_db;
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectAmplify::instantiate() {
|
||||
Ref<AudioEffectAmplifyInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectAmplify>(this);
|
||||
ins->mix_volume_db = volume_db;
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectAmplify::set_volume_db(float p_volume) {
|
||||
volume_db = p_volume;
|
||||
}
|
||||
|
||||
float AudioEffectAmplify::get_volume_db() const {
|
||||
return volume_db;
|
||||
}
|
||||
|
||||
void AudioEffectAmplify::set_volume_linear(float p_volume) {
|
||||
set_volume_db(Math::linear_to_db(p_volume));
|
||||
}
|
||||
|
||||
float AudioEffectAmplify::get_volume_linear() const {
|
||||
return Math::db_to_linear(get_volume_db());
|
||||
}
|
||||
|
||||
void AudioEffectAmplify::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_volume_db", "volume"), &AudioEffectAmplify::set_volume_db);
|
||||
ClassDB::bind_method(D_METHOD("get_volume_db"), &AudioEffectAmplify::get_volume_db);
|
||||
ClassDB::bind_method(D_METHOD("set_volume_linear", "volume"), &AudioEffectAmplify::set_volume_linear);
|
||||
ClassDB::bind_method(D_METHOD("get_volume_linear"), &AudioEffectAmplify::get_volume_linear);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,0.01,suffix:dB"), "set_volume_db", "get_volume_db");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_linear", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_volume_linear", "get_volume_linear");
|
||||
}
|
||||
|
||||
AudioEffectAmplify::AudioEffectAmplify() {
|
||||
volume_db = 0;
|
||||
}
|
66
servers/audio/effects/audio_effect_amplify.h
Normal file
66
servers/audio/effects/audio_effect_amplify.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_amplify.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_effect.h"
|
||||
|
||||
class AudioEffectAmplify;
|
||||
|
||||
class AudioEffectAmplifyInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectAmplifyInstance, AudioEffectInstance);
|
||||
friend class AudioEffectAmplify;
|
||||
Ref<AudioEffectAmplify> base;
|
||||
|
||||
float mix_volume_db;
|
||||
|
||||
public:
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
};
|
||||
|
||||
class AudioEffectAmplify : public AudioEffect {
|
||||
GDCLASS(AudioEffectAmplify, AudioEffect);
|
||||
|
||||
friend class AudioEffectAmplifyInstance;
|
||||
float volume_db;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
void set_volume_db(float p_volume);
|
||||
float get_volume_db() const;
|
||||
|
||||
void set_volume_linear(float p_volume);
|
||||
float get_volume_linear() const;
|
||||
|
||||
AudioEffectAmplify();
|
||||
};
|
140
servers/audio/effects/audio_effect_capture.cpp
Normal file
140
servers/audio/effects/audio_effect_capture.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_capture.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 "audio_effect_capture.h"
|
||||
|
||||
#include "servers/audio_server.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].left, streaming_data[i].right);
|
||||
}
|
||||
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,suffix:s"), "set_buffer_length", "get_buffer_length");
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectCapture::instantiate() {
|
||||
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((uint32_t)target_buffer_size));
|
||||
buffer_initialized = true;
|
||||
}
|
||||
|
||||
clear_buffer();
|
||||
|
||||
Ref<AudioEffectCaptureInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectCapture>(this);
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectCapture::set_buffer_length(float p_buffer_length_seconds) {
|
||||
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.get();
|
||||
}
|
||||
|
||||
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.get();
|
||||
}
|
||||
|
||||
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.");
|
||||
base->pushed_frames.add(p_frame_count);
|
||||
} else {
|
||||
base->discarded_frames.add(p_frame_count);
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioEffectCaptureInstance::process_silence() const {
|
||||
return true;
|
||||
}
|
77
servers/audio/effects/audio_effect_capture.h
Normal file
77
servers/audio/effects/audio_effect_capture.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_capture.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 "core/math/audio_frame.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/templates/ring_buffer.h"
|
||||
#include "servers/audio/audio_effect.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;
|
||||
SafeNumeric<uint64_t> discarded_frames;
|
||||
SafeNumeric<uint64_t> pushed_frames;
|
||||
float buffer_length_seconds = 0.1f;
|
||||
bool buffer_initialized = false;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual Ref<AudioEffectInstance> instantiate() 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;
|
||||
};
|
360
servers/audio/effects/audio_effect_chorus.cpp
Normal file
360
servers/audio/effects/audio_effect_chorus.cpp
Normal file
@@ -0,0 +1,360 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_chorus.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 "audio_effect_chorus.h"
|
||||
#include "core/math/math_funcs.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
void AudioEffectChorusInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
int todo = p_frame_count;
|
||||
|
||||
while (todo) {
|
||||
int to_mix = MIN(todo, 256); //can't mix too much
|
||||
|
||||
_process_chunk(p_src_frames, p_dst_frames, to_mix);
|
||||
|
||||
p_src_frames += to_mix;
|
||||
p_dst_frames += to_mix;
|
||||
|
||||
todo -= to_mix;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEffectChorusInstance::_process_chunk(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
//fill ringbuffer
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
audio_buffer.write[(buffer_pos + i) & buffer_mask] = p_src_frames[i];
|
||||
p_dst_frames[i] = p_src_frames[i] * base->dry;
|
||||
}
|
||||
|
||||
float mix_rate = AudioServer::get_singleton()->get_mix_rate();
|
||||
|
||||
/* process voices */
|
||||
for (int vc = 0; vc < base->voice_count; vc++) {
|
||||
AudioEffectChorus::Voice &v = base->voice[vc];
|
||||
|
||||
double time_to_mix = (float)p_frame_count / mix_rate;
|
||||
double cycles_to_mix = time_to_mix * v.rate;
|
||||
|
||||
unsigned int local_rb_pos = buffer_pos;
|
||||
AudioFrame *dst_buff = p_dst_frames;
|
||||
AudioFrame *rb_buff = audio_buffer.ptrw();
|
||||
|
||||
double delay_msec = v.delay;
|
||||
unsigned int delay_frames = Math::fast_ftoi((delay_msec / 1000.0) * mix_rate);
|
||||
float max_depth_frames = (v.depth / 1000.0) * mix_rate;
|
||||
|
||||
uint64_t local_cycles = cycles[vc];
|
||||
uint64_t increment = std::rint(cycles_to_mix / (double)p_frame_count * (double)(1 << AudioEffectChorus::CYCLES_FRAC));
|
||||
|
||||
//check the LFO doesn't read ahead of the write pos
|
||||
if ((((unsigned int)max_depth_frames) + 10) > delay_frames) { //10 as some threshold to avoid precision stuff
|
||||
delay_frames += (int)max_depth_frames - delay_frames;
|
||||
delay_frames += 10; //threshold to avoid precision stuff
|
||||
}
|
||||
|
||||
//low pass filter
|
||||
if (v.cutoff == 0) {
|
||||
continue;
|
||||
}
|
||||
float auxlp = std::exp(-Math::TAU * v.cutoff / mix_rate);
|
||||
float c1 = 1.0 - auxlp;
|
||||
float c2 = auxlp;
|
||||
AudioFrame h = filter_h[vc];
|
||||
if (v.cutoff >= AudioEffectChorus::MS_CUTOFF_MAX) {
|
||||
c1 = 1.0;
|
||||
c2 = 0.0;
|
||||
}
|
||||
|
||||
//vol modifier
|
||||
|
||||
AudioFrame vol_modifier = AudioFrame(base->wet, base->wet) * Math::db_to_linear(v.level);
|
||||
vol_modifier.left *= CLAMP(1.0 - v.pan, 0, 1);
|
||||
vol_modifier.right *= CLAMP(1.0 + v.pan, 0, 1);
|
||||
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
/** COMPUTE WAVEFORM **/
|
||||
|
||||
float phase = (float)(local_cycles & AudioEffectChorus::CYCLES_MASK) / (float)(1 << AudioEffectChorus::CYCLES_FRAC);
|
||||
|
||||
float wave_delay = std::sin(phase * Math::TAU) * max_depth_frames;
|
||||
|
||||
int wave_delay_frames = std::rint(std::floor(wave_delay));
|
||||
float wave_delay_frac = wave_delay - (float)wave_delay_frames;
|
||||
|
||||
/** COMPUTE RINGBUFFER POS**/
|
||||
|
||||
unsigned int rb_source = local_rb_pos;
|
||||
rb_source -= delay_frames;
|
||||
|
||||
rb_source -= wave_delay_frames;
|
||||
|
||||
/** READ FROM RINGBUFFER, LINEARLY INTERPOLATE */
|
||||
|
||||
AudioFrame val = rb_buff[rb_source & buffer_mask];
|
||||
AudioFrame val_next = rb_buff[(rb_source - 1) & buffer_mask];
|
||||
|
||||
val += (val_next - val) * wave_delay_frac;
|
||||
|
||||
val = val * c1 + h * c2;
|
||||
h = val;
|
||||
|
||||
/** MIX VALUE TO OUTPUT **/
|
||||
|
||||
dst_buff[i] += val * vol_modifier;
|
||||
|
||||
local_cycles += increment;
|
||||
local_rb_pos++;
|
||||
}
|
||||
|
||||
filter_h[vc] = h;
|
||||
cycles[vc] += Math::fast_ftoi(cycles_to_mix * (double)(1 << AudioEffectChorus::CYCLES_FRAC));
|
||||
}
|
||||
|
||||
buffer_pos += p_frame_count;
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectChorus::instantiate() {
|
||||
Ref<AudioEffectChorusInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectChorus>(this);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ins->filter_h[i] = AudioFrame(0, 0);
|
||||
ins->cycles[i] = 0;
|
||||
}
|
||||
|
||||
float ring_buffer_max_size = AudioEffectChorus::MAX_DELAY_MS + AudioEffectChorus::MAX_DEPTH_MS + AudioEffectChorus::MAX_WIDTH_MS;
|
||||
|
||||
ring_buffer_max_size *= 2; //just to avoid complications
|
||||
ring_buffer_max_size /= 1000.0; //convert to seconds
|
||||
ring_buffer_max_size *= AudioServer::get_singleton()->get_mix_rate();
|
||||
|
||||
int ringbuff_size = ring_buffer_max_size;
|
||||
|
||||
int bits = 0;
|
||||
|
||||
while (ringbuff_size > 0) {
|
||||
bits++;
|
||||
ringbuff_size /= 2;
|
||||
}
|
||||
|
||||
ringbuff_size = 1 << bits;
|
||||
ins->buffer_mask = ringbuff_size - 1;
|
||||
ins->buffer_pos = 0;
|
||||
ins->audio_buffer.resize(ringbuff_size);
|
||||
for (int i = 0; i < ringbuff_size; i++) {
|
||||
ins->audio_buffer.write[i] = AudioFrame(0, 0);
|
||||
}
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectChorus::set_voice_count(int p_voices) {
|
||||
ERR_FAIL_COND(p_voices < 1 || p_voices > MAX_VOICES);
|
||||
voice_count = p_voices;
|
||||
}
|
||||
|
||||
int AudioEffectChorus::get_voice_count() const {
|
||||
return voice_count;
|
||||
}
|
||||
|
||||
void AudioEffectChorus::set_voice_delay_ms(int p_voice, float p_delay_ms) {
|
||||
ERR_FAIL_INDEX(p_voice, MAX_VOICES);
|
||||
|
||||
voice[p_voice].delay = p_delay_ms;
|
||||
}
|
||||
|
||||
float AudioEffectChorus::get_voice_delay_ms(int p_voice) const {
|
||||
ERR_FAIL_INDEX_V(p_voice, MAX_VOICES, 0);
|
||||
return voice[p_voice].delay;
|
||||
}
|
||||
|
||||
void AudioEffectChorus::set_voice_rate_hz(int p_voice, float p_rate_hz) {
|
||||
ERR_FAIL_INDEX(p_voice, MAX_VOICES);
|
||||
|
||||
voice[p_voice].rate = p_rate_hz;
|
||||
}
|
||||
|
||||
float AudioEffectChorus::get_voice_rate_hz(int p_voice) const {
|
||||
ERR_FAIL_INDEX_V(p_voice, MAX_VOICES, 0);
|
||||
|
||||
return voice[p_voice].rate;
|
||||
}
|
||||
|
||||
void AudioEffectChorus::set_voice_depth_ms(int p_voice, float p_depth_ms) {
|
||||
ERR_FAIL_INDEX(p_voice, MAX_VOICES);
|
||||
|
||||
voice[p_voice].depth = p_depth_ms;
|
||||
}
|
||||
|
||||
float AudioEffectChorus::get_voice_depth_ms(int p_voice) const {
|
||||
ERR_FAIL_INDEX_V(p_voice, MAX_VOICES, 0);
|
||||
|
||||
return voice[p_voice].depth;
|
||||
}
|
||||
|
||||
void AudioEffectChorus::set_voice_level_db(int p_voice, float p_level_db) {
|
||||
ERR_FAIL_INDEX(p_voice, MAX_VOICES);
|
||||
|
||||
voice[p_voice].level = p_level_db;
|
||||
}
|
||||
|
||||
float AudioEffectChorus::get_voice_level_db(int p_voice) const {
|
||||
ERR_FAIL_INDEX_V(p_voice, MAX_VOICES, 0);
|
||||
|
||||
return voice[p_voice].level;
|
||||
}
|
||||
|
||||
void AudioEffectChorus::set_voice_cutoff_hz(int p_voice, float p_cutoff_hz) {
|
||||
ERR_FAIL_INDEX(p_voice, MAX_VOICES);
|
||||
|
||||
voice[p_voice].cutoff = p_cutoff_hz;
|
||||
}
|
||||
|
||||
float AudioEffectChorus::get_voice_cutoff_hz(int p_voice) const {
|
||||
ERR_FAIL_INDEX_V(p_voice, MAX_VOICES, 0);
|
||||
|
||||
return voice[p_voice].cutoff;
|
||||
}
|
||||
|
||||
void AudioEffectChorus::set_voice_pan(int p_voice, float p_pan) {
|
||||
ERR_FAIL_INDEX(p_voice, MAX_VOICES);
|
||||
|
||||
voice[p_voice].pan = p_pan;
|
||||
}
|
||||
|
||||
float AudioEffectChorus::get_voice_pan(int p_voice) const {
|
||||
ERR_FAIL_INDEX_V(p_voice, MAX_VOICES, 0);
|
||||
|
||||
return voice[p_voice].pan;
|
||||
}
|
||||
|
||||
void AudioEffectChorus::set_wet(float amount) {
|
||||
wet = amount;
|
||||
}
|
||||
|
||||
float AudioEffectChorus::get_wet() const {
|
||||
return wet;
|
||||
}
|
||||
|
||||
void AudioEffectChorus::set_dry(float amount) {
|
||||
dry = amount;
|
||||
}
|
||||
|
||||
float AudioEffectChorus::get_dry() const {
|
||||
return dry;
|
||||
}
|
||||
|
||||
void AudioEffectChorus::_validate_property(PropertyInfo &p_property) const {
|
||||
if (p_property.name.begins_with("voice/")) {
|
||||
int voice_idx = p_property.name.get_slicec('/', 1).to_int();
|
||||
if (voice_idx > voice_count) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEffectChorus::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_voice_count", "voices"), &AudioEffectChorus::set_voice_count);
|
||||
ClassDB::bind_method(D_METHOD("get_voice_count"), &AudioEffectChorus::get_voice_count);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_voice_delay_ms", "voice_idx", "delay_ms"), &AudioEffectChorus::set_voice_delay_ms);
|
||||
ClassDB::bind_method(D_METHOD("get_voice_delay_ms", "voice_idx"), &AudioEffectChorus::get_voice_delay_ms);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_voice_rate_hz", "voice_idx", "rate_hz"), &AudioEffectChorus::set_voice_rate_hz);
|
||||
ClassDB::bind_method(D_METHOD("get_voice_rate_hz", "voice_idx"), &AudioEffectChorus::get_voice_rate_hz);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_voice_depth_ms", "voice_idx", "depth_ms"), &AudioEffectChorus::set_voice_depth_ms);
|
||||
ClassDB::bind_method(D_METHOD("get_voice_depth_ms", "voice_idx"), &AudioEffectChorus::get_voice_depth_ms);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_voice_level_db", "voice_idx", "level_db"), &AudioEffectChorus::set_voice_level_db);
|
||||
ClassDB::bind_method(D_METHOD("get_voice_level_db", "voice_idx"), &AudioEffectChorus::get_voice_level_db);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_voice_cutoff_hz", "voice_idx", "cutoff_hz"), &AudioEffectChorus::set_voice_cutoff_hz);
|
||||
ClassDB::bind_method(D_METHOD("get_voice_cutoff_hz", "voice_idx"), &AudioEffectChorus::get_voice_cutoff_hz);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_voice_pan", "voice_idx", "pan"), &AudioEffectChorus::set_voice_pan);
|
||||
ClassDB::bind_method(D_METHOD("get_voice_pan", "voice_idx"), &AudioEffectChorus::get_voice_pan);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_wet", "amount"), &AudioEffectChorus::set_wet);
|
||||
ClassDB::bind_method(D_METHOD("get_wet"), &AudioEffectChorus::get_wet);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_dry", "amount"), &AudioEffectChorus::set_dry);
|
||||
ClassDB::bind_method(D_METHOD("get_dry"), &AudioEffectChorus::get_dry);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "voice_count", PROPERTY_HINT_RANGE, "1,4,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_voice_count", "get_voice_count");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dry", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_dry", "get_dry");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wet", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_wet", "get_wet");
|
||||
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/1/delay_ms", PROPERTY_HINT_RANGE, "0,50,0.01,suffix:ms"), "set_voice_delay_ms", "get_voice_delay_ms", 0);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/1/rate_hz", PROPERTY_HINT_RANGE, "0.1,20,0.1,suffix:Hz"), "set_voice_rate_hz", "get_voice_rate_hz", 0);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/1/depth_ms", PROPERTY_HINT_RANGE, "0,20,0.01,suffix:ms"), "set_voice_depth_ms", "get_voice_depth_ms", 0);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/1/level_db", PROPERTY_HINT_RANGE, "-60,24,0.1,suffix:dB"), "set_voice_level_db", "get_voice_level_db", 0);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/1/cutoff_hz", PROPERTY_HINT_RANGE, "1,20500,1,suffix:Hz"), "set_voice_cutoff_hz", "get_voice_cutoff_hz", 0);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/1/pan", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_voice_pan", "get_voice_pan", 0);
|
||||
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/2/delay_ms", PROPERTY_HINT_RANGE, "0,50,0.01,suffix:ms"), "set_voice_delay_ms", "get_voice_delay_ms", 1);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/2/rate_hz", PROPERTY_HINT_RANGE, "0.1,20,0.1,suffix:Hz"), "set_voice_rate_hz", "get_voice_rate_hz", 1);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/2/depth_ms", PROPERTY_HINT_RANGE, "0,20,0.01,suffix:ms"), "set_voice_depth_ms", "get_voice_depth_ms", 1);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/2/level_db", PROPERTY_HINT_RANGE, "-60,24,0.1,suffix:dB"), "set_voice_level_db", "get_voice_level_db", 1);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/2/cutoff_hz", PROPERTY_HINT_RANGE, "1,20500,1,suffix:Hz"), "set_voice_cutoff_hz", "get_voice_cutoff_hz", 1);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/2/pan", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_voice_pan", "get_voice_pan", 1);
|
||||
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/3/delay_ms", PROPERTY_HINT_RANGE, "0,50,0.01,suffix:ms"), "set_voice_delay_ms", "get_voice_delay_ms", 2);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/3/rate_hz", PROPERTY_HINT_RANGE, "0.1,20,0.1,suffix:Hz"), "set_voice_rate_hz", "get_voice_rate_hz", 2);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/3/depth_ms", PROPERTY_HINT_RANGE, "0,20,0.01,suffix:ms"), "set_voice_depth_ms", "get_voice_depth_ms", 2);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/3/level_db", PROPERTY_HINT_RANGE, "-60,24,0.1,suffix:dB"), "set_voice_level_db", "get_voice_level_db", 2);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/3/cutoff_hz", PROPERTY_HINT_RANGE, "1,20500,1,suffix:Hz"), "set_voice_cutoff_hz", "get_voice_cutoff_hz", 2);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/3/pan", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_voice_pan", "get_voice_pan", 2);
|
||||
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/4/delay_ms", PROPERTY_HINT_RANGE, "0,50,0.01,suffix:ms"), "set_voice_delay_ms", "get_voice_delay_ms", 3);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/4/rate_hz", PROPERTY_HINT_RANGE, "0.1,20,0.1,suffix:Hz"), "set_voice_rate_hz", "get_voice_rate_hz", 3);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/4/depth_ms", PROPERTY_HINT_RANGE, "0,20,0.01,suffix:ms"), "set_voice_depth_ms", "get_voice_depth_ms", 3);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/4/level_db", PROPERTY_HINT_RANGE, "-60,24,0.1,suffix:dB"), "set_voice_level_db", "get_voice_level_db", 3);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/4/cutoff_hz", PROPERTY_HINT_RANGE, "1,20500,1,suffix:Hz"), "set_voice_cutoff_hz", "get_voice_cutoff_hz", 3);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "voice/4/pan", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_voice_pan", "get_voice_pan", 3);
|
||||
}
|
||||
|
||||
AudioEffectChorus::AudioEffectChorus() {
|
||||
voice_count = 2;
|
||||
voice[0].delay = 15;
|
||||
voice[1].delay = 20;
|
||||
voice[0].rate = 0.8;
|
||||
voice[1].rate = 1.2;
|
||||
voice[0].depth = 2;
|
||||
voice[1].depth = 3;
|
||||
voice[0].cutoff = 8000;
|
||||
voice[1].cutoff = 8000;
|
||||
voice[0].pan = -0.5;
|
||||
voice[1].pan = 0.5;
|
||||
|
||||
wet = 0.5;
|
||||
dry = 1.0;
|
||||
}
|
131
servers/audio/effects/audio_effect_chorus.h
Normal file
131
servers/audio/effects/audio_effect_chorus.h
Normal file
@@ -0,0 +1,131 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_chorus.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_effect.h"
|
||||
|
||||
class AudioEffectChorus;
|
||||
|
||||
class AudioEffectChorusInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectChorusInstance, AudioEffectInstance);
|
||||
friend class AudioEffectChorus;
|
||||
Ref<AudioEffectChorus> base;
|
||||
|
||||
Vector<AudioFrame> audio_buffer;
|
||||
unsigned int buffer_pos;
|
||||
unsigned int buffer_mask;
|
||||
|
||||
AudioFrame filter_h[4];
|
||||
uint64_t cycles[4];
|
||||
|
||||
void _process_chunk(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count);
|
||||
|
||||
public:
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
};
|
||||
|
||||
class AudioEffectChorus : public AudioEffect {
|
||||
GDCLASS(AudioEffectChorus, AudioEffect);
|
||||
|
||||
friend class AudioEffectChorusInstance;
|
||||
|
||||
public:
|
||||
static constexpr int32_t MAX_DELAY_MS = 50;
|
||||
static constexpr int32_t MAX_DEPTH_MS = 20;
|
||||
static constexpr int32_t MAX_WIDTH_MS = 50;
|
||||
static constexpr int32_t MAX_VOICES = 4;
|
||||
static constexpr int32_t CYCLES_FRAC = 16;
|
||||
static constexpr int32_t CYCLES_MASK = (1 << CYCLES_FRAC) - 1;
|
||||
static constexpr int32_t MAX_CHANNELS = 4;
|
||||
static constexpr int32_t MS_CUTOFF_MAX = 16000;
|
||||
|
||||
private:
|
||||
struct Voice {
|
||||
float delay;
|
||||
float rate;
|
||||
float depth;
|
||||
float level;
|
||||
float cutoff;
|
||||
float pan;
|
||||
|
||||
Voice() {
|
||||
delay = 12.0;
|
||||
rate = 1;
|
||||
depth = 0;
|
||||
level = 0;
|
||||
cutoff = MS_CUTOFF_MAX;
|
||||
pan = 0;
|
||||
}
|
||||
|
||||
} voice[MAX_VOICES];
|
||||
|
||||
int voice_count;
|
||||
|
||||
float wet;
|
||||
float dry;
|
||||
|
||||
protected:
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_voice_count(int p_voices);
|
||||
int get_voice_count() const;
|
||||
|
||||
void set_voice_delay_ms(int p_voice, float p_delay_ms);
|
||||
float get_voice_delay_ms(int p_voice) const;
|
||||
|
||||
void set_voice_rate_hz(int p_voice, float p_rate_hz);
|
||||
float get_voice_rate_hz(int p_voice) const;
|
||||
|
||||
void set_voice_depth_ms(int p_voice, float p_depth_ms);
|
||||
float get_voice_depth_ms(int p_voice) const;
|
||||
|
||||
void set_voice_level_db(int p_voice, float p_level_db);
|
||||
float get_voice_level_db(int p_voice) const;
|
||||
|
||||
void set_voice_cutoff_hz(int p_voice, float p_cutoff_hz);
|
||||
float get_voice_cutoff_hz(int p_voice) const;
|
||||
|
||||
void set_voice_pan(int p_voice, float p_pan);
|
||||
float get_voice_pan(int p_voice) const;
|
||||
|
||||
void set_wet(float amount);
|
||||
float get_wet() const;
|
||||
|
||||
void set_dry(float amount);
|
||||
float get_dry() const;
|
||||
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
|
||||
AudioEffectChorus();
|
||||
};
|
240
servers/audio/effects/audio_effect_compressor.cpp
Normal file
240
servers/audio/effects/audio_effect_compressor.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_compressor.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 "audio_effect_compressor.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
void AudioEffectCompressorInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
float threshold = Math::db_to_linear(base->threshold);
|
||||
float sample_rate = AudioServer::get_singleton()->get_mix_rate();
|
||||
|
||||
float ratatcoef = std::exp(-1 / (0.00001f * sample_rate));
|
||||
float ratrelcoef = std::exp(-1 / (0.5f * sample_rate));
|
||||
float attime = base->attack_us / 1000000.0;
|
||||
float reltime = base->release_ms / 1000.0;
|
||||
float atcoef = std::exp(-1 / (attime * sample_rate));
|
||||
float relcoef = std::exp(-1 / (reltime * sample_rate));
|
||||
|
||||
float makeup = Math::db_to_linear(base->gain);
|
||||
|
||||
float mix = base->mix;
|
||||
float gr_meter_decay = std::exp(1 / (1 * sample_rate));
|
||||
|
||||
const AudioFrame *src = p_src_frames;
|
||||
|
||||
if (base->sidechain != StringName() && current_channel != -1) {
|
||||
int bus = AudioServer::get_singleton()->thread_find_bus_index(base->sidechain);
|
||||
if (bus >= 0) {
|
||||
src = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus, current_channel);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
AudioFrame s = src[i];
|
||||
//convert to positive
|
||||
s.left = Math::abs(s.left);
|
||||
s.right = Math::abs(s.right);
|
||||
|
||||
float peak = MAX(s.left, s.right);
|
||||
|
||||
float overdb = 2.08136898f * Math::linear_to_db(peak / threshold);
|
||||
|
||||
if (overdb < 0.0) { //we only care about what goes over to compress
|
||||
overdb = 0.0;
|
||||
}
|
||||
|
||||
if (overdb - rundb > 5) { // diffeence is too large
|
||||
averatio = 4;
|
||||
}
|
||||
|
||||
if (overdb > rundb) {
|
||||
rundb = overdb + atcoef * (rundb - overdb);
|
||||
runratio = averatio + ratatcoef * (runratio - averatio);
|
||||
} else {
|
||||
rundb = overdb + relcoef * (rundb - overdb);
|
||||
runratio = averatio + ratrelcoef * (runratio - averatio);
|
||||
}
|
||||
|
||||
overdb = rundb;
|
||||
averatio = runratio;
|
||||
|
||||
float cratio;
|
||||
|
||||
if (false) { //rato all-in
|
||||
cratio = 12 + averatio;
|
||||
} else {
|
||||
cratio = base->ratio;
|
||||
}
|
||||
|
||||
float gr = -overdb * (cratio - 1) / cratio;
|
||||
float grv = Math::db_to_linear(gr);
|
||||
|
||||
runmax = maxover + relcoef * (runmax - maxover); // highest peak for setting att/rel decays in reltime
|
||||
maxover = runmax;
|
||||
|
||||
if (grv < gr_meter) {
|
||||
gr_meter = grv;
|
||||
} else {
|
||||
gr_meter *= gr_meter_decay;
|
||||
if (gr_meter > 1) {
|
||||
gr_meter = 1;
|
||||
}
|
||||
}
|
||||
|
||||
p_dst_frames[i] = p_src_frames[i] * grv * makeup * mix + p_src_frames[i] * (1.0 - mix);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectCompressor::instantiate() {
|
||||
Ref<AudioEffectCompressorInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectCompressor>(this);
|
||||
ins->rundb = 0;
|
||||
ins->runratio = 0;
|
||||
ins->averatio = 0;
|
||||
ins->runmax = 0;
|
||||
ins->maxover = 0;
|
||||
ins->gr_meter = 1.0;
|
||||
ins->current_channel = -1;
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectCompressor::set_threshold(float p_threshold) {
|
||||
threshold = p_threshold;
|
||||
}
|
||||
|
||||
float AudioEffectCompressor::get_threshold() const {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
void AudioEffectCompressor::set_ratio(float p_ratio) {
|
||||
ratio = p_ratio;
|
||||
}
|
||||
|
||||
float AudioEffectCompressor::get_ratio() const {
|
||||
return ratio;
|
||||
}
|
||||
|
||||
void AudioEffectCompressor::set_gain(float p_gain) {
|
||||
gain = p_gain;
|
||||
}
|
||||
|
||||
float AudioEffectCompressor::get_gain() const {
|
||||
return gain;
|
||||
}
|
||||
|
||||
void AudioEffectCompressor::set_attack_us(float p_attack_us) {
|
||||
attack_us = p_attack_us;
|
||||
}
|
||||
|
||||
float AudioEffectCompressor::get_attack_us() const {
|
||||
return attack_us;
|
||||
}
|
||||
|
||||
void AudioEffectCompressor::set_release_ms(float p_release_ms) {
|
||||
release_ms = p_release_ms;
|
||||
}
|
||||
|
||||
float AudioEffectCompressor::get_release_ms() const {
|
||||
return release_ms;
|
||||
}
|
||||
|
||||
void AudioEffectCompressor::set_mix(float p_mix) {
|
||||
mix = p_mix;
|
||||
}
|
||||
|
||||
float AudioEffectCompressor::get_mix() const {
|
||||
return mix;
|
||||
}
|
||||
|
||||
void AudioEffectCompressor::set_sidechain(const StringName &p_sidechain) {
|
||||
AudioServer::get_singleton()->lock();
|
||||
sidechain = p_sidechain;
|
||||
AudioServer::get_singleton()->unlock();
|
||||
}
|
||||
|
||||
StringName AudioEffectCompressor::get_sidechain() const {
|
||||
return sidechain;
|
||||
}
|
||||
|
||||
void AudioEffectCompressor::_validate_property(PropertyInfo &p_property) const {
|
||||
if (!Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
if (p_property.name == "sidechain") {
|
||||
String buses = "";
|
||||
for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) {
|
||||
buses += ",";
|
||||
buses += AudioServer::get_singleton()->get_bus_name(i);
|
||||
}
|
||||
|
||||
p_property.hint_string = buses;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEffectCompressor::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_threshold", "threshold"), &AudioEffectCompressor::set_threshold);
|
||||
ClassDB::bind_method(D_METHOD("get_threshold"), &AudioEffectCompressor::get_threshold);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_ratio", "ratio"), &AudioEffectCompressor::set_ratio);
|
||||
ClassDB::bind_method(D_METHOD("get_ratio"), &AudioEffectCompressor::get_ratio);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_gain", "gain"), &AudioEffectCompressor::set_gain);
|
||||
ClassDB::bind_method(D_METHOD("get_gain"), &AudioEffectCompressor::get_gain);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_attack_us", "attack_us"), &AudioEffectCompressor::set_attack_us);
|
||||
ClassDB::bind_method(D_METHOD("get_attack_us"), &AudioEffectCompressor::get_attack_us);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_release_ms", "release_ms"), &AudioEffectCompressor::set_release_ms);
|
||||
ClassDB::bind_method(D_METHOD("get_release_ms"), &AudioEffectCompressor::get_release_ms);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_mix", "mix"), &AudioEffectCompressor::set_mix);
|
||||
ClassDB::bind_method(D_METHOD("get_mix"), &AudioEffectCompressor::get_mix);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_sidechain", "sidechain"), &AudioEffectCompressor::set_sidechain);
|
||||
ClassDB::bind_method(D_METHOD("get_sidechain"), &AudioEffectCompressor::get_sidechain);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "threshold", PROPERTY_HINT_RANGE, "-60,0,0.1"), "set_threshold", "get_threshold");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ratio", PROPERTY_HINT_RANGE, "1,48,0.1"), "set_ratio", "get_ratio");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gain", PROPERTY_HINT_RANGE, "-20,20,0.1"), "set_gain", "get_gain");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attack_us", PROPERTY_HINT_RANGE, U"20,2000,1,suffix:\u00B5s"), "set_attack_us", "get_attack_us");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "release_ms", PROPERTY_HINT_RANGE, "20,2000,1,suffix:ms"), "set_release_ms", "get_release_ms");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mix", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_mix", "get_mix");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "sidechain", PROPERTY_HINT_ENUM), "set_sidechain", "get_sidechain");
|
||||
}
|
||||
|
||||
AudioEffectCompressor::AudioEffectCompressor() {
|
||||
threshold = 0;
|
||||
ratio = 4;
|
||||
gain = 0;
|
||||
attack_us = 20;
|
||||
release_ms = 250;
|
||||
mix = 1;
|
||||
}
|
91
servers/audio/effects/audio_effect_compressor.h
Normal file
91
servers/audio/effects/audio_effect_compressor.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_compressor.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_effect.h"
|
||||
|
||||
class AudioEffectCompressor;
|
||||
|
||||
class AudioEffectCompressorInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectCompressorInstance, AudioEffectInstance);
|
||||
friend class AudioEffectCompressor;
|
||||
Ref<AudioEffectCompressor> base;
|
||||
|
||||
float rundb, averatio, runratio, runmax, maxover, gr_meter;
|
||||
int current_channel;
|
||||
|
||||
public:
|
||||
void set_current_channel(int p_channel) { current_channel = p_channel; }
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
};
|
||||
|
||||
class AudioEffectCompressor : public AudioEffect {
|
||||
GDCLASS(AudioEffectCompressor, AudioEffect);
|
||||
|
||||
friend class AudioEffectCompressorInstance;
|
||||
float threshold;
|
||||
float ratio;
|
||||
float gain;
|
||||
float attack_us;
|
||||
float release_ms;
|
||||
float mix;
|
||||
StringName sidechain;
|
||||
|
||||
protected:
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
|
||||
void set_threshold(float p_threshold);
|
||||
float get_threshold() const;
|
||||
|
||||
void set_ratio(float p_ratio);
|
||||
float get_ratio() const;
|
||||
|
||||
void set_gain(float p_gain);
|
||||
float get_gain() const;
|
||||
|
||||
void set_attack_us(float p_attack_us);
|
||||
float get_attack_us() const;
|
||||
|
||||
void set_release_ms(float p_release_ms);
|
||||
float get_release_ms() const;
|
||||
|
||||
void set_mix(float p_mix);
|
||||
float get_mix() const;
|
||||
|
||||
void set_sidechain(const StringName &p_sidechain);
|
||||
StringName get_sidechain() const;
|
||||
|
||||
AudioEffectCompressor();
|
||||
};
|
307
servers/audio/effects/audio_effect_delay.cpp
Normal file
307
servers/audio/effects/audio_effect_delay.cpp
Normal file
@@ -0,0 +1,307 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_delay.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 "audio_effect_delay.h"
|
||||
|
||||
#include "core/math/math_funcs.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
void AudioEffectDelayInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
int todo = p_frame_count;
|
||||
|
||||
while (todo) {
|
||||
int to_mix = MIN(todo, 256); //can't mix too much
|
||||
|
||||
_process_chunk(p_src_frames, p_dst_frames, to_mix);
|
||||
|
||||
p_src_frames += to_mix;
|
||||
p_dst_frames += to_mix;
|
||||
|
||||
todo -= to_mix;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEffectDelayInstance::_process_chunk(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
float main_level_f = base->dry;
|
||||
|
||||
float mix_rate = AudioServer::get_singleton()->get_mix_rate();
|
||||
|
||||
float tap_1_level_f = base->tap_1_active ? Math::db_to_linear(base->tap_1_level) : 0.0;
|
||||
int tap_1_delay_frames = int((base->tap_1_delay_ms / 1000.0) * mix_rate);
|
||||
|
||||
float tap_2_level_f = base->tap_2_active ? Math::db_to_linear(base->tap_2_level) : 0.0;
|
||||
int tap_2_delay_frames = int((base->tap_2_delay_ms / 1000.0) * mix_rate);
|
||||
|
||||
float feedback_level_f = base->feedback_active ? Math::db_to_linear(base->feedback_level) : 0.0;
|
||||
unsigned int feedback_delay_frames = int((base->feedback_delay_ms / 1000.0) * mix_rate);
|
||||
|
||||
AudioFrame tap1_vol = AudioFrame(tap_1_level_f, tap_1_level_f);
|
||||
|
||||
tap1_vol.left *= CLAMP(1.0 - base->tap_1_pan, 0, 1);
|
||||
tap1_vol.right *= CLAMP(1.0 + base->tap_1_pan, 0, 1);
|
||||
|
||||
AudioFrame tap2_vol = AudioFrame(tap_2_level_f, tap_2_level_f);
|
||||
|
||||
tap2_vol.left *= CLAMP(1.0 - base->tap_2_pan, 0, 1);
|
||||
tap2_vol.right *= CLAMP(1.0 + base->tap_2_pan, 0, 1);
|
||||
|
||||
// feedback lowpass here
|
||||
float lpf_c = std::exp(-Math::TAU * base->feedback_lowpass / mix_rate); // 0 .. 10khz
|
||||
float lpf_ic = 1.0 - lpf_c;
|
||||
|
||||
const AudioFrame *src = p_src_frames;
|
||||
AudioFrame *dst = p_dst_frames;
|
||||
AudioFrame *rb_buf = ring_buffer.ptrw();
|
||||
AudioFrame *fb_buf = feedback_buffer.ptrw();
|
||||
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
rb_buf[ring_buffer_pos & ring_buffer_mask] = src[i];
|
||||
|
||||
AudioFrame main_val = src[i] * main_level_f;
|
||||
AudioFrame tap_1_val = rb_buf[(ring_buffer_pos - tap_1_delay_frames) & ring_buffer_mask] * tap1_vol;
|
||||
AudioFrame tap_2_val = rb_buf[(ring_buffer_pos - tap_2_delay_frames) & ring_buffer_mask] * tap2_vol;
|
||||
|
||||
AudioFrame out = main_val + tap_1_val + tap_2_val;
|
||||
|
||||
out += fb_buf[feedback_buffer_pos];
|
||||
|
||||
//apply lowpass and feedback gain
|
||||
AudioFrame fb_in = out * feedback_level_f * lpf_ic + h * lpf_c;
|
||||
fb_in.undenormalize(); //avoid denormals
|
||||
|
||||
h = fb_in;
|
||||
fb_buf[feedback_buffer_pos] = fb_in;
|
||||
|
||||
dst[i] = out;
|
||||
|
||||
ring_buffer_pos++;
|
||||
|
||||
if ((++feedback_buffer_pos) >= feedback_delay_frames) {
|
||||
feedback_buffer_pos = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectDelay::instantiate() {
|
||||
Ref<AudioEffectDelayInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectDelay>(this);
|
||||
|
||||
float ring_buffer_max_size = MAX_DELAY_MS + 100; //add 100ms of extra room, just in case
|
||||
ring_buffer_max_size /= 1000.0; //convert to seconds
|
||||
ring_buffer_max_size *= AudioServer::get_singleton()->get_mix_rate();
|
||||
|
||||
int ringbuff_size = ring_buffer_max_size;
|
||||
|
||||
int bits = 0;
|
||||
|
||||
while (ringbuff_size > 0) {
|
||||
bits++;
|
||||
ringbuff_size /= 2;
|
||||
}
|
||||
|
||||
ringbuff_size = 1 << bits;
|
||||
ins->ring_buffer_mask = ringbuff_size - 1;
|
||||
ins->ring_buffer_pos = 0;
|
||||
|
||||
ins->ring_buffer.resize(ringbuff_size);
|
||||
ins->feedback_buffer.resize(ringbuff_size);
|
||||
|
||||
ins->feedback_buffer_pos = 0;
|
||||
|
||||
ins->h = AudioFrame(0, 0);
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectDelay::set_dry(float p_dry) {
|
||||
dry = p_dry;
|
||||
}
|
||||
|
||||
float AudioEffectDelay::get_dry() {
|
||||
return dry;
|
||||
}
|
||||
|
||||
void AudioEffectDelay::set_tap1_active(bool p_active) {
|
||||
tap_1_active = p_active;
|
||||
}
|
||||
|
||||
bool AudioEffectDelay::is_tap1_active() const {
|
||||
return tap_1_active;
|
||||
}
|
||||
|
||||
void AudioEffectDelay::set_tap1_delay_ms(float p_delay_ms) {
|
||||
tap_1_delay_ms = p_delay_ms;
|
||||
}
|
||||
|
||||
float AudioEffectDelay::get_tap1_delay_ms() const {
|
||||
return tap_1_delay_ms;
|
||||
}
|
||||
|
||||
void AudioEffectDelay::set_tap1_level_db(float p_level_db) {
|
||||
tap_1_level = p_level_db;
|
||||
}
|
||||
|
||||
float AudioEffectDelay::get_tap1_level_db() const {
|
||||
return tap_1_level;
|
||||
}
|
||||
|
||||
void AudioEffectDelay::set_tap1_pan(float p_pan) {
|
||||
tap_1_pan = p_pan;
|
||||
}
|
||||
|
||||
float AudioEffectDelay::get_tap1_pan() const {
|
||||
return tap_1_pan;
|
||||
}
|
||||
|
||||
void AudioEffectDelay::set_tap2_active(bool p_active) {
|
||||
tap_2_active = p_active;
|
||||
}
|
||||
|
||||
bool AudioEffectDelay::is_tap2_active() const {
|
||||
return tap_2_active;
|
||||
}
|
||||
|
||||
void AudioEffectDelay::set_tap2_delay_ms(float p_delay_ms) {
|
||||
tap_2_delay_ms = p_delay_ms;
|
||||
}
|
||||
|
||||
float AudioEffectDelay::get_tap2_delay_ms() const {
|
||||
return tap_2_delay_ms;
|
||||
}
|
||||
|
||||
void AudioEffectDelay::set_tap2_level_db(float p_level_db) {
|
||||
tap_2_level = p_level_db;
|
||||
}
|
||||
|
||||
float AudioEffectDelay::get_tap2_level_db() const {
|
||||
return tap_2_level;
|
||||
}
|
||||
|
||||
void AudioEffectDelay::set_tap2_pan(float p_pan) {
|
||||
tap_2_pan = p_pan;
|
||||
}
|
||||
|
||||
float AudioEffectDelay::get_tap2_pan() const {
|
||||
return tap_2_pan;
|
||||
}
|
||||
|
||||
void AudioEffectDelay::set_feedback_active(bool p_active) {
|
||||
feedback_active = p_active;
|
||||
}
|
||||
|
||||
bool AudioEffectDelay::is_feedback_active() const {
|
||||
return feedback_active;
|
||||
}
|
||||
|
||||
void AudioEffectDelay::set_feedback_delay_ms(float p_delay_ms) {
|
||||
feedback_delay_ms = p_delay_ms;
|
||||
}
|
||||
|
||||
float AudioEffectDelay::get_feedback_delay_ms() const {
|
||||
return feedback_delay_ms;
|
||||
}
|
||||
|
||||
void AudioEffectDelay::set_feedback_level_db(float p_level_db) {
|
||||
feedback_level = p_level_db;
|
||||
}
|
||||
|
||||
float AudioEffectDelay::get_feedback_level_db() const {
|
||||
return feedback_level;
|
||||
}
|
||||
|
||||
void AudioEffectDelay::set_feedback_lowpass(float p_lowpass) {
|
||||
feedback_lowpass = p_lowpass;
|
||||
}
|
||||
|
||||
float AudioEffectDelay::get_feedback_lowpass() const {
|
||||
return feedback_lowpass;
|
||||
}
|
||||
|
||||
void AudioEffectDelay::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_dry", "amount"), &AudioEffectDelay::set_dry);
|
||||
ClassDB::bind_method(D_METHOD("get_dry"), &AudioEffectDelay::get_dry);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_tap1_active", "amount"), &AudioEffectDelay::set_tap1_active);
|
||||
ClassDB::bind_method(D_METHOD("is_tap1_active"), &AudioEffectDelay::is_tap1_active);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_tap1_delay_ms", "amount"), &AudioEffectDelay::set_tap1_delay_ms);
|
||||
ClassDB::bind_method(D_METHOD("get_tap1_delay_ms"), &AudioEffectDelay::get_tap1_delay_ms);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_tap1_level_db", "amount"), &AudioEffectDelay::set_tap1_level_db);
|
||||
ClassDB::bind_method(D_METHOD("get_tap1_level_db"), &AudioEffectDelay::get_tap1_level_db);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_tap1_pan", "amount"), &AudioEffectDelay::set_tap1_pan);
|
||||
ClassDB::bind_method(D_METHOD("get_tap1_pan"), &AudioEffectDelay::get_tap1_pan);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_tap2_active", "amount"), &AudioEffectDelay::set_tap2_active);
|
||||
ClassDB::bind_method(D_METHOD("is_tap2_active"), &AudioEffectDelay::is_tap2_active);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_tap2_delay_ms", "amount"), &AudioEffectDelay::set_tap2_delay_ms);
|
||||
ClassDB::bind_method(D_METHOD("get_tap2_delay_ms"), &AudioEffectDelay::get_tap2_delay_ms);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_tap2_level_db", "amount"), &AudioEffectDelay::set_tap2_level_db);
|
||||
ClassDB::bind_method(D_METHOD("get_tap2_level_db"), &AudioEffectDelay::get_tap2_level_db);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_tap2_pan", "amount"), &AudioEffectDelay::set_tap2_pan);
|
||||
ClassDB::bind_method(D_METHOD("get_tap2_pan"), &AudioEffectDelay::get_tap2_pan);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_feedback_active", "amount"), &AudioEffectDelay::set_feedback_active);
|
||||
ClassDB::bind_method(D_METHOD("is_feedback_active"), &AudioEffectDelay::is_feedback_active);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_feedback_delay_ms", "amount"), &AudioEffectDelay::set_feedback_delay_ms);
|
||||
ClassDB::bind_method(D_METHOD("get_feedback_delay_ms"), &AudioEffectDelay::get_feedback_delay_ms);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_feedback_level_db", "amount"), &AudioEffectDelay::set_feedback_level_db);
|
||||
ClassDB::bind_method(D_METHOD("get_feedback_level_db"), &AudioEffectDelay::get_feedback_level_db);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_feedback_lowpass", "amount"), &AudioEffectDelay::set_feedback_lowpass);
|
||||
ClassDB::bind_method(D_METHOD("get_feedback_lowpass"), &AudioEffectDelay::get_feedback_lowpass);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dry", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_dry", "get_dry");
|
||||
|
||||
ADD_GROUP("Tap 1", "tap1_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tap1_active"), "set_tap1_active", "is_tap1_active");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tap1_delay_ms", PROPERTY_HINT_RANGE, "0,1500,1,suffix:ms"), "set_tap1_delay_ms", "get_tap1_delay_ms");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tap1_level_db", PROPERTY_HINT_RANGE, "-60,0,0.01,suffix:dB"), "set_tap1_level_db", "get_tap1_level_db");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tap1_pan", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_tap1_pan", "get_tap1_pan");
|
||||
|
||||
ADD_GROUP("Tap 2", "tap2_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tap2_active"), "set_tap2_active", "is_tap2_active");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tap2_delay_ms", PROPERTY_HINT_RANGE, "0,1500,1,suffix:ms"), "set_tap2_delay_ms", "get_tap2_delay_ms");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tap2_level_db", PROPERTY_HINT_RANGE, "-60,0,0.01,suffix:dB"), "set_tap2_level_db", "get_tap2_level_db");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tap2_pan", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_tap2_pan", "get_tap2_pan");
|
||||
|
||||
ADD_GROUP("Feedback", "feedback_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "feedback_active"), "set_feedback_active", "is_feedback_active");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "feedback_delay_ms", PROPERTY_HINT_RANGE, "0,1500,1,suffix:ms"), "set_feedback_delay_ms", "get_feedback_delay_ms");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "feedback_level_db", PROPERTY_HINT_RANGE, "-60,0,0.01,suffix:dB"), "set_feedback_level_db", "get_feedback_level_db");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "feedback_lowpass", PROPERTY_HINT_RANGE, "1,16000,1"), "set_feedback_lowpass", "get_feedback_lowpass");
|
||||
}
|
132
servers/audio/effects/audio_effect_delay.h
Normal file
132
servers/audio/effects/audio_effect_delay.h
Normal file
@@ -0,0 +1,132 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_delay.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_effect.h"
|
||||
|
||||
class AudioEffectDelay;
|
||||
|
||||
class AudioEffectDelayInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectDelayInstance, AudioEffectInstance);
|
||||
|
||||
friend class AudioEffectDelay;
|
||||
Ref<AudioEffectDelay> base;
|
||||
|
||||
Vector<AudioFrame> ring_buffer;
|
||||
|
||||
unsigned int ring_buffer_pos;
|
||||
unsigned int ring_buffer_mask;
|
||||
|
||||
/* feedback buffer */
|
||||
Vector<AudioFrame> feedback_buffer;
|
||||
|
||||
unsigned int feedback_buffer_pos;
|
||||
|
||||
AudioFrame h;
|
||||
void _process_chunk(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count);
|
||||
|
||||
public:
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
};
|
||||
|
||||
class AudioEffectDelay : public AudioEffect {
|
||||
GDCLASS(AudioEffectDelay, AudioEffect);
|
||||
|
||||
friend class AudioEffectDelayInstance;
|
||||
enum {
|
||||
MAX_DELAY_MS = 3000,
|
||||
MAX_TAPS = 2
|
||||
};
|
||||
|
||||
float dry = 1.0f;
|
||||
|
||||
bool tap_1_active = true;
|
||||
float tap_1_delay_ms = 250.0f;
|
||||
float tap_1_level = -6.0f;
|
||||
float tap_1_pan = 0.2f;
|
||||
|
||||
bool tap_2_active = true;
|
||||
float tap_2_delay_ms = 500.0f;
|
||||
float tap_2_level = -12.0f;
|
||||
float tap_2_pan = -0.4f;
|
||||
|
||||
bool feedback_active = false;
|
||||
float feedback_delay_ms = 340.0f;
|
||||
float feedback_level = -6.0f;
|
||||
float feedback_lowpass = 16000.0f;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_dry(float p_dry);
|
||||
float get_dry();
|
||||
|
||||
void set_tap1_active(bool p_active);
|
||||
bool is_tap1_active() const;
|
||||
|
||||
void set_tap1_delay_ms(float p_delay_ms);
|
||||
float get_tap1_delay_ms() const;
|
||||
|
||||
void set_tap1_level_db(float p_level_db);
|
||||
float get_tap1_level_db() const;
|
||||
|
||||
void set_tap1_pan(float p_pan);
|
||||
float get_tap1_pan() const;
|
||||
|
||||
void set_tap2_active(bool p_active);
|
||||
bool is_tap2_active() const;
|
||||
|
||||
void set_tap2_delay_ms(float p_delay_ms);
|
||||
float get_tap2_delay_ms() const;
|
||||
|
||||
void set_tap2_level_db(float p_level_db);
|
||||
float get_tap2_level_db() const;
|
||||
|
||||
void set_tap2_pan(float p_pan);
|
||||
float get_tap2_pan() const;
|
||||
|
||||
void set_feedback_active(bool p_active);
|
||||
bool is_feedback_active() const;
|
||||
|
||||
void set_feedback_delay_ms(float p_delay_ms);
|
||||
float get_feedback_delay_ms() const;
|
||||
|
||||
void set_feedback_level_db(float p_level_db);
|
||||
float get_feedback_level_db() const;
|
||||
|
||||
void set_feedback_lowpass(float p_lowpass);
|
||||
float get_feedback_lowpass() const;
|
||||
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
|
||||
AudioEffectDelay() {}
|
||||
};
|
181
servers/audio/effects/audio_effect_distortion.cpp
Normal file
181
servers/audio/effects/audio_effect_distortion.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_distortion.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 "audio_effect_distortion.h"
|
||||
#include "core/math/math_funcs.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
void AudioEffectDistortionInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
const float *src = (const float *)p_src_frames;
|
||||
float *dst = (float *)p_dst_frames;
|
||||
|
||||
//float lpf_c=std::exp(-Math::TAU*keep_hf_hz.get()/(mix_rate*(float)OVERSAMPLE));
|
||||
float lpf_c = std::exp(-Math::TAU * base->keep_hf_hz / (AudioServer::get_singleton()->get_mix_rate()));
|
||||
float lpf_ic = 1.0 - lpf_c;
|
||||
|
||||
float drive_f = base->drive;
|
||||
float pregain_f = Math::db_to_linear(base->pre_gain);
|
||||
float postgain_f = Math::db_to_linear(base->post_gain);
|
||||
|
||||
float atan_mult = std::pow(10, drive_f * drive_f * 3.0) - 1.0 + 0.001;
|
||||
float atan_div = 1.0 / (std::atan(atan_mult) * (1.0 + drive_f * 8));
|
||||
|
||||
float lofi_mult = std::pow(2.0, 2.0 + (1.0 - drive_f) * 14); //goes from 16 to 2 bits
|
||||
|
||||
for (int i = 0; i < p_frame_count * 2; i++) {
|
||||
float out = undenormalize(src[i] * lpf_ic + lpf_c * h[i & 1]);
|
||||
h[i & 1] = out;
|
||||
float a = out;
|
||||
float ha = src[i] - out; //high freqs
|
||||
a *= pregain_f;
|
||||
|
||||
switch (base->mode) {
|
||||
case AudioEffectDistortion::MODE_CLIP: {
|
||||
float a_sign = a < 0 ? -1.0f : 1.0f;
|
||||
a = std::pow(std::abs(a), 1.0001 - drive_f) * a_sign;
|
||||
if (a > 1.0) {
|
||||
a = 1.0;
|
||||
} else if (a < (-1.0)) {
|
||||
a = -1.0;
|
||||
}
|
||||
|
||||
} break;
|
||||
case AudioEffectDistortion::MODE_ATAN: {
|
||||
a = std::atan(a * atan_mult) * atan_div;
|
||||
|
||||
} break;
|
||||
case AudioEffectDistortion::MODE_LOFI: {
|
||||
a = std::floor(a * lofi_mult + 0.5) / lofi_mult;
|
||||
|
||||
} break;
|
||||
case AudioEffectDistortion::MODE_OVERDRIVE: {
|
||||
const double x = a * 0.686306;
|
||||
const double z = 1 + std::exp(std::sqrt(std::abs(x)) * -0.75);
|
||||
a = (std::exp(x) - std::exp(-x * z)) / (std::exp(x) + std::exp(-x));
|
||||
} break;
|
||||
case AudioEffectDistortion::MODE_WAVESHAPE: {
|
||||
float x = a;
|
||||
float k = 2 * drive_f / (1.00001 - drive_f);
|
||||
|
||||
a = (1.0 + k) * x / (1.0 + k * std::abs(x));
|
||||
|
||||
} break;
|
||||
}
|
||||
|
||||
dst[i] = a * postgain_f + ha;
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectDistortion::instantiate() {
|
||||
Ref<AudioEffectDistortionInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectDistortion>(this);
|
||||
ins->h[0] = 0;
|
||||
ins->h[1] = 0;
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectDistortion::set_mode(Mode p_mode) {
|
||||
mode = p_mode;
|
||||
}
|
||||
|
||||
AudioEffectDistortion::Mode AudioEffectDistortion::get_mode() const {
|
||||
return mode;
|
||||
}
|
||||
|
||||
void AudioEffectDistortion::set_pre_gain(float p_pre_gain) {
|
||||
pre_gain = p_pre_gain;
|
||||
}
|
||||
|
||||
float AudioEffectDistortion::get_pre_gain() const {
|
||||
return pre_gain;
|
||||
}
|
||||
|
||||
void AudioEffectDistortion::set_keep_hf_hz(float p_keep_hf_hz) {
|
||||
keep_hf_hz = p_keep_hf_hz;
|
||||
}
|
||||
|
||||
float AudioEffectDistortion::get_keep_hf_hz() const {
|
||||
return keep_hf_hz;
|
||||
}
|
||||
|
||||
void AudioEffectDistortion::set_drive(float p_drive) {
|
||||
drive = p_drive;
|
||||
}
|
||||
|
||||
float AudioEffectDistortion::get_drive() const {
|
||||
return drive;
|
||||
}
|
||||
|
||||
void AudioEffectDistortion::set_post_gain(float p_post_gain) {
|
||||
post_gain = p_post_gain;
|
||||
}
|
||||
|
||||
float AudioEffectDistortion::get_post_gain() const {
|
||||
return post_gain;
|
||||
}
|
||||
|
||||
void AudioEffectDistortion::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_mode", "mode"), &AudioEffectDistortion::set_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_mode"), &AudioEffectDistortion::get_mode);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_pre_gain", "pre_gain"), &AudioEffectDistortion::set_pre_gain);
|
||||
ClassDB::bind_method(D_METHOD("get_pre_gain"), &AudioEffectDistortion::get_pre_gain);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_keep_hf_hz", "keep_hf_hz"), &AudioEffectDistortion::set_keep_hf_hz);
|
||||
ClassDB::bind_method(D_METHOD("get_keep_hf_hz"), &AudioEffectDistortion::get_keep_hf_hz);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_drive", "drive"), &AudioEffectDistortion::set_drive);
|
||||
ClassDB::bind_method(D_METHOD("get_drive"), &AudioEffectDistortion::get_drive);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_post_gain", "post_gain"), &AudioEffectDistortion::set_post_gain);
|
||||
ClassDB::bind_method(D_METHOD("get_post_gain"), &AudioEffectDistortion::get_post_gain);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Clip,ATan,LoFi,Overdrive,Wave Shape"), "set_mode", "get_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pre_gain", PROPERTY_HINT_RANGE, "-60,60,0.01,suffix:dB"), "set_pre_gain", "get_pre_gain");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "keep_hf_hz", PROPERTY_HINT_RANGE, "1,20500,1,suffix:Hz"), "set_keep_hf_hz", "get_keep_hf_hz");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "drive", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drive", "get_drive");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "post_gain", PROPERTY_HINT_RANGE, "-80,24,0.01,suffix:dB"), "set_post_gain", "get_post_gain");
|
||||
|
||||
BIND_ENUM_CONSTANT(MODE_CLIP);
|
||||
BIND_ENUM_CONSTANT(MODE_ATAN);
|
||||
BIND_ENUM_CONSTANT(MODE_LOFI);
|
||||
BIND_ENUM_CONSTANT(MODE_OVERDRIVE);
|
||||
BIND_ENUM_CONSTANT(MODE_WAVESHAPE);
|
||||
}
|
||||
|
||||
AudioEffectDistortion::AudioEffectDistortion() {
|
||||
mode = MODE_CLIP;
|
||||
pre_gain = 0;
|
||||
post_gain = 0;
|
||||
keep_hf_hz = 16000;
|
||||
drive = 0;
|
||||
}
|
90
servers/audio/effects/audio_effect_distortion.h
Normal file
90
servers/audio/effects/audio_effect_distortion.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_distortion.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_effect.h"
|
||||
|
||||
class AudioEffectDistortion;
|
||||
|
||||
class AudioEffectDistortionInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectDistortionInstance, AudioEffectInstance);
|
||||
friend class AudioEffectDistortion;
|
||||
Ref<AudioEffectDistortion> base;
|
||||
float h[2];
|
||||
|
||||
public:
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
};
|
||||
|
||||
class AudioEffectDistortion : public AudioEffect {
|
||||
GDCLASS(AudioEffectDistortion, AudioEffect);
|
||||
|
||||
public:
|
||||
enum Mode {
|
||||
MODE_CLIP,
|
||||
MODE_ATAN,
|
||||
MODE_LOFI,
|
||||
MODE_OVERDRIVE,
|
||||
MODE_WAVESHAPE,
|
||||
};
|
||||
|
||||
friend class AudioEffectDistortionInstance;
|
||||
Mode mode;
|
||||
float pre_gain;
|
||||
float post_gain;
|
||||
float keep_hf_hz;
|
||||
float drive;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
|
||||
void set_mode(Mode p_mode);
|
||||
Mode get_mode() const;
|
||||
|
||||
void set_pre_gain(float p_pre_gain);
|
||||
float get_pre_gain() const;
|
||||
|
||||
void set_keep_hf_hz(float p_keep_hf_hz);
|
||||
float get_keep_hf_hz() const;
|
||||
|
||||
void set_drive(float p_drive);
|
||||
float get_drive() const;
|
||||
|
||||
void set_post_gain(float p_post_gain);
|
||||
float get_post_gain() const;
|
||||
|
||||
AudioEffectDistortion();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AudioEffectDistortion::Mode)
|
135
servers/audio/effects/audio_effect_eq.cpp
Normal file
135
servers/audio/effects/audio_effect_eq.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_eq.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 "audio_effect_eq.h"
|
||||
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
void AudioEffectEQInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
int band_count = bands[0].size();
|
||||
EQ::BandProcess *proc_l = bands[0].ptrw();
|
||||
EQ::BandProcess *proc_r = bands[1].ptrw();
|
||||
float *bgain = gains.ptrw();
|
||||
for (int i = 0; i < band_count; i++) {
|
||||
bgain[i] = Math::db_to_linear(base->gain[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
AudioFrame src = p_src_frames[i];
|
||||
AudioFrame dst = AudioFrame(0, 0);
|
||||
|
||||
for (int j = 0; j < band_count; j++) {
|
||||
float l = src.left;
|
||||
float r = src.right;
|
||||
|
||||
proc_l[j].process_one(l);
|
||||
proc_r[j].process_one(r);
|
||||
|
||||
dst.left += l * bgain[j];
|
||||
dst.right += r * bgain[j];
|
||||
}
|
||||
|
||||
p_dst_frames[i] = dst;
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectEQ::instantiate() {
|
||||
Ref<AudioEffectEQInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectEQ>(this);
|
||||
ins->gains.resize(eq.get_band_count());
|
||||
for (int i = 0; i < 2; i++) {
|
||||
ins->bands[i].resize(eq.get_band_count());
|
||||
for (int j = 0; j < ins->bands[i].size(); j++) {
|
||||
ins->bands[i].write[j] = eq.get_band_processor(j);
|
||||
}
|
||||
}
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectEQ::set_band_gain_db(int p_band, float p_volume) {
|
||||
ERR_FAIL_INDEX(p_band, gain.size());
|
||||
gain.write[p_band] = p_volume;
|
||||
}
|
||||
|
||||
float AudioEffectEQ::get_band_gain_db(int p_band) const {
|
||||
ERR_FAIL_INDEX_V(p_band, gain.size(), 0);
|
||||
|
||||
return gain[p_band];
|
||||
}
|
||||
|
||||
int AudioEffectEQ::get_band_count() const {
|
||||
return gain.size();
|
||||
}
|
||||
|
||||
bool AudioEffectEQ::_set(const StringName &p_name, const Variant &p_value) {
|
||||
HashMap<StringName, int>::ConstIterator E = prop_band_map.find(p_name);
|
||||
if (E) {
|
||||
set_band_gain_db(E->value, p_value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioEffectEQ::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
HashMap<StringName, int>::ConstIterator E = prop_band_map.find(p_name);
|
||||
if (E) {
|
||||
r_ret = get_band_gain_db(E->value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioEffectEQ::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
for (int i = 0; i < band_names.size(); i++) {
|
||||
p_list->push_back(PropertyInfo(Variant::FLOAT, band_names[i], PROPERTY_HINT_RANGE, "-60,24,0.1"));
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEffectEQ::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_band_gain_db", "band_idx", "volume_db"), &AudioEffectEQ::set_band_gain_db);
|
||||
ClassDB::bind_method(D_METHOD("get_band_gain_db", "band_idx"), &AudioEffectEQ::get_band_gain_db);
|
||||
ClassDB::bind_method(D_METHOD("get_band_count"), &AudioEffectEQ::get_band_count);
|
||||
}
|
||||
|
||||
AudioEffectEQ::AudioEffectEQ(EQ::Preset p_preset) {
|
||||
eq.set_mix_rate(AudioServer::get_singleton()->get_mix_rate());
|
||||
eq.set_preset_band_mode(p_preset);
|
||||
gain.resize(eq.get_band_count());
|
||||
for (int i = 0; i < gain.size(); i++) {
|
||||
gain.write[i] = 0.0;
|
||||
String band_name = "band_db/" + itos(eq.get_band_frequency(i)) + "_hz";
|
||||
prop_band_map[band_name] = i;
|
||||
band_names.push_back(band_name);
|
||||
}
|
||||
}
|
98
servers/audio/effects/audio_effect_eq.h
Normal file
98
servers/audio/effects/audio_effect_eq.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_eq.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_effect.h"
|
||||
#include "servers/audio/effects/eq_filter.h"
|
||||
|
||||
class AudioEffectEQ;
|
||||
|
||||
class AudioEffectEQInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectEQInstance, AudioEffectInstance);
|
||||
friend class AudioEffectEQ;
|
||||
Ref<AudioEffectEQ> base;
|
||||
|
||||
Vector<EQ::BandProcess> bands[2];
|
||||
Vector<float> gains;
|
||||
|
||||
public:
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
};
|
||||
|
||||
class AudioEffectEQ : public AudioEffect {
|
||||
GDCLASS(AudioEffectEQ, AudioEffect);
|
||||
|
||||
friend class AudioEffectEQInstance;
|
||||
|
||||
EQ eq;
|
||||
Vector<float> gain;
|
||||
HashMap<StringName, int> prop_band_map;
|
||||
Vector<String> band_names;
|
||||
|
||||
protected:
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
void set_band_gain_db(int p_band, float p_volume);
|
||||
float get_band_gain_db(int p_band) const;
|
||||
int get_band_count() const;
|
||||
|
||||
AudioEffectEQ(EQ::Preset p_preset = EQ::PRESET_6_BANDS);
|
||||
};
|
||||
|
||||
class AudioEffectEQ6 : public AudioEffectEQ {
|
||||
GDCLASS(AudioEffectEQ6, AudioEffectEQ);
|
||||
|
||||
public:
|
||||
AudioEffectEQ6() :
|
||||
AudioEffectEQ(EQ::PRESET_6_BANDS) {}
|
||||
};
|
||||
|
||||
class AudioEffectEQ10 : public AudioEffectEQ {
|
||||
GDCLASS(AudioEffectEQ10, AudioEffectEQ);
|
||||
|
||||
public:
|
||||
AudioEffectEQ10() :
|
||||
AudioEffectEQ(EQ::PRESET_10_BANDS) {}
|
||||
};
|
||||
|
||||
class AudioEffectEQ21 : public AudioEffectEQ {
|
||||
GDCLASS(AudioEffectEQ21, AudioEffectEQ);
|
||||
|
||||
public:
|
||||
AudioEffectEQ21() :
|
||||
AudioEffectEQ(EQ::PRESET_21_BANDS) {}
|
||||
};
|
173
servers/audio/effects/audio_effect_filter.cpp
Normal file
173
servers/audio/effects/audio_effect_filter.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_filter.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 "audio_effect_filter.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
template <int S>
|
||||
void AudioEffectFilterInstance::_process_filter(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
float f = p_src_frames[i].left;
|
||||
filter_process[0][0].process_one(f);
|
||||
if constexpr (S > 1) {
|
||||
filter_process[0][1].process_one(f);
|
||||
}
|
||||
if constexpr (S > 2) {
|
||||
filter_process[0][2].process_one(f);
|
||||
}
|
||||
if constexpr (S > 3) {
|
||||
filter_process[0][3].process_one(f);
|
||||
}
|
||||
|
||||
p_dst_frames[i].left = f;
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
float f = p_src_frames[i].right;
|
||||
filter_process[1][0].process_one(f);
|
||||
if constexpr (S > 1) {
|
||||
filter_process[1][1].process_one(f);
|
||||
}
|
||||
if constexpr (S > 2) {
|
||||
filter_process[1][2].process_one(f);
|
||||
}
|
||||
if constexpr (S > 3) {
|
||||
filter_process[1][3].process_one(f);
|
||||
}
|
||||
|
||||
p_dst_frames[i].right = f;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEffectFilterInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
filter.set_cutoff(base->cutoff);
|
||||
filter.set_gain(base->gain);
|
||||
filter.set_resonance(base->resonance);
|
||||
filter.set_mode(base->mode);
|
||||
int stages = int(base->db) + 1;
|
||||
filter.set_stages(stages);
|
||||
filter.set_sampling_rate(AudioServer::get_singleton()->get_mix_rate());
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
filter_process[i][j].update_coeffs();
|
||||
}
|
||||
}
|
||||
|
||||
if (stages == 1) {
|
||||
_process_filter<1>(p_src_frames, p_dst_frames, p_frame_count);
|
||||
} else if (stages == 2) {
|
||||
_process_filter<2>(p_src_frames, p_dst_frames, p_frame_count);
|
||||
} else if (stages == 3) {
|
||||
_process_filter<3>(p_src_frames, p_dst_frames, p_frame_count);
|
||||
} else if (stages == 4) {
|
||||
_process_filter<4>(p_src_frames, p_dst_frames, p_frame_count);
|
||||
}
|
||||
}
|
||||
|
||||
AudioEffectFilterInstance::AudioEffectFilterInstance() {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
filter_process[i][j].set_filter(&filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectFilter::instantiate() {
|
||||
Ref<AudioEffectFilterInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectFilter>(this);
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectFilter::set_cutoff(float p_freq) {
|
||||
cutoff = p_freq;
|
||||
}
|
||||
|
||||
float AudioEffectFilter::get_cutoff() const {
|
||||
return cutoff;
|
||||
}
|
||||
|
||||
void AudioEffectFilter::set_resonance(float p_amount) {
|
||||
resonance = p_amount;
|
||||
}
|
||||
|
||||
float AudioEffectFilter::get_resonance() const {
|
||||
return resonance;
|
||||
}
|
||||
|
||||
void AudioEffectFilter::set_gain(float p_amount) {
|
||||
gain = p_amount;
|
||||
}
|
||||
|
||||
float AudioEffectFilter::get_gain() const {
|
||||
return gain;
|
||||
}
|
||||
|
||||
void AudioEffectFilter::set_db(FilterDB p_db) {
|
||||
db = p_db;
|
||||
}
|
||||
|
||||
AudioEffectFilter::FilterDB AudioEffectFilter::get_db() const {
|
||||
return db;
|
||||
}
|
||||
|
||||
void AudioEffectFilter::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_cutoff", "freq"), &AudioEffectFilter::set_cutoff);
|
||||
ClassDB::bind_method(D_METHOD("get_cutoff"), &AudioEffectFilter::get_cutoff);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_resonance", "amount"), &AudioEffectFilter::set_resonance);
|
||||
ClassDB::bind_method(D_METHOD("get_resonance"), &AudioEffectFilter::get_resonance);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_gain", "amount"), &AudioEffectFilter::set_gain);
|
||||
ClassDB::bind_method(D_METHOD("get_gain"), &AudioEffectFilter::get_gain);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_db", "amount"), &AudioEffectFilter::set_db);
|
||||
ClassDB::bind_method(D_METHOD("get_db"), &AudioEffectFilter::get_db);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cutoff_hz", PROPERTY_HINT_RANGE, "1,20500,1,suffix:Hz"), "set_cutoff", "get_cutoff");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "resonance", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_resonance", "get_resonance");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gain", PROPERTY_HINT_RANGE, "0,4,0.01"), "set_gain", "get_gain");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "db", PROPERTY_HINT_ENUM, "6 dB,12 dB,18 dB,24 dB"), "set_db", "get_db");
|
||||
|
||||
BIND_ENUM_CONSTANT(FILTER_6DB);
|
||||
BIND_ENUM_CONSTANT(FILTER_12DB);
|
||||
BIND_ENUM_CONSTANT(FILTER_18DB);
|
||||
BIND_ENUM_CONSTANT(FILTER_24DB);
|
||||
}
|
||||
|
||||
AudioEffectFilter::AudioEffectFilter(AudioFilterSW::Mode p_mode) {
|
||||
mode = p_mode;
|
||||
cutoff = 2000;
|
||||
resonance = 0.5;
|
||||
gain = 1.0;
|
||||
db = FILTER_6DB;
|
||||
}
|
167
servers/audio/effects/audio_effect_filter.h
Normal file
167
servers/audio/effects/audio_effect_filter.h
Normal file
@@ -0,0 +1,167 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_filter.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_effect.h"
|
||||
#include "servers/audio/audio_filter_sw.h"
|
||||
|
||||
class AudioEffectFilter;
|
||||
|
||||
class AudioEffectFilterInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectFilterInstance, AudioEffectInstance);
|
||||
friend class AudioEffectFilter;
|
||||
|
||||
Ref<AudioEffectFilter> base;
|
||||
|
||||
AudioFilterSW filter;
|
||||
AudioFilterSW::Processor filter_process[2][4];
|
||||
|
||||
template <int S>
|
||||
void _process_filter(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count);
|
||||
|
||||
public:
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
|
||||
AudioEffectFilterInstance();
|
||||
};
|
||||
|
||||
class AudioEffectFilter : public AudioEffect {
|
||||
GDCLASS(AudioEffectFilter, AudioEffect);
|
||||
|
||||
public:
|
||||
enum FilterDB {
|
||||
FILTER_6DB,
|
||||
FILTER_12DB,
|
||||
FILTER_18DB,
|
||||
FILTER_24DB,
|
||||
};
|
||||
friend class AudioEffectFilterInstance;
|
||||
|
||||
AudioFilterSW::Mode mode;
|
||||
float cutoff;
|
||||
float resonance;
|
||||
float gain;
|
||||
FilterDB db;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_cutoff(float p_freq);
|
||||
float get_cutoff() const;
|
||||
|
||||
void set_resonance(float p_amount);
|
||||
float get_resonance() const;
|
||||
|
||||
void set_gain(float p_amount);
|
||||
float get_gain() const;
|
||||
|
||||
void set_db(FilterDB p_db);
|
||||
FilterDB get_db() const;
|
||||
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
|
||||
AudioEffectFilter(AudioFilterSW::Mode p_mode = AudioFilterSW::LOWPASS);
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AudioEffectFilter::FilterDB)
|
||||
|
||||
class AudioEffectLowPassFilter : public AudioEffectFilter {
|
||||
GDCLASS(AudioEffectLowPassFilter, AudioEffectFilter);
|
||||
|
||||
void _validate_property(PropertyInfo &p_property) const {
|
||||
if (p_property.name == "gain") {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
AudioEffectLowPassFilter() :
|
||||
AudioEffectFilter(AudioFilterSW::LOWPASS) {}
|
||||
};
|
||||
|
||||
class AudioEffectHighPassFilter : public AudioEffectFilter {
|
||||
GDCLASS(AudioEffectHighPassFilter, AudioEffectFilter);
|
||||
void _validate_property(PropertyInfo &p_property) const {
|
||||
if (p_property.name == "gain") {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
AudioEffectHighPassFilter() :
|
||||
AudioEffectFilter(AudioFilterSW::HIGHPASS) {}
|
||||
};
|
||||
|
||||
class AudioEffectBandPassFilter : public AudioEffectFilter {
|
||||
GDCLASS(AudioEffectBandPassFilter, AudioEffectFilter);
|
||||
void _validate_property(PropertyInfo &p_property) const {
|
||||
if (p_property.name == "gain") {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
AudioEffectBandPassFilter() :
|
||||
AudioEffectFilter(AudioFilterSW::BANDPASS) {}
|
||||
};
|
||||
|
||||
class AudioEffectNotchFilter : public AudioEffectFilter {
|
||||
GDCLASS(AudioEffectNotchFilter, AudioEffectFilter);
|
||||
|
||||
public:
|
||||
AudioEffectNotchFilter() :
|
||||
AudioEffectFilter(AudioFilterSW::NOTCH) {}
|
||||
};
|
||||
|
||||
class AudioEffectBandLimitFilter : public AudioEffectFilter {
|
||||
GDCLASS(AudioEffectBandLimitFilter, AudioEffectFilter);
|
||||
|
||||
public:
|
||||
AudioEffectBandLimitFilter() :
|
||||
AudioEffectFilter(AudioFilterSW::BANDLIMIT) {}
|
||||
};
|
||||
|
||||
class AudioEffectLowShelfFilter : public AudioEffectFilter {
|
||||
GDCLASS(AudioEffectLowShelfFilter, AudioEffectFilter);
|
||||
|
||||
public:
|
||||
AudioEffectLowShelfFilter() :
|
||||
AudioEffectFilter(AudioFilterSW::LOWSHELF) {}
|
||||
};
|
||||
|
||||
class AudioEffectHighShelfFilter : public AudioEffectFilter {
|
||||
GDCLASS(AudioEffectHighShelfFilter, AudioEffectFilter);
|
||||
|
||||
public:
|
||||
AudioEffectHighShelfFilter() :
|
||||
AudioEffectFilter(AudioFilterSW::HIGHSHELF) {}
|
||||
};
|
161
servers/audio/effects/audio_effect_hard_limiter.cpp
Normal file
161
servers/audio/effects/audio_effect_hard_limiter.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_hard_limiter.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 "audio_effect_hard_limiter.h"
|
||||
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
void AudioEffectHardLimiterInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
float sample_rate = AudioServer::get_singleton()->get_mix_rate();
|
||||
|
||||
float ceiling = Math::db_to_linear(base->ceiling);
|
||||
float release = base->release;
|
||||
float attack = base->attack;
|
||||
float pre_gain = Math::db_to_linear(base->pre_gain);
|
||||
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
float sample_left = p_src_frames[i].left;
|
||||
float sample_right = p_src_frames[i].right;
|
||||
|
||||
sample_left *= pre_gain;
|
||||
sample_right *= pre_gain;
|
||||
|
||||
float largest_sample = MAX(Math::abs(sample_left), Math::abs(sample_right));
|
||||
|
||||
release_factor = MAX(0.0, release_factor - 1.0 / sample_rate);
|
||||
release_factor = MIN(release_factor, release);
|
||||
|
||||
if (release_factor > 0.0) {
|
||||
gain = Math::lerp(gain_target, 1.0f, 1.0f - release_factor / release);
|
||||
}
|
||||
|
||||
if (largest_sample * gain > ceiling) {
|
||||
gain_target = ceiling / largest_sample;
|
||||
release_factor = release;
|
||||
attack_factor = attack;
|
||||
}
|
||||
|
||||
// Lerp gain over attack time to avoid distortion.
|
||||
attack_factor = MAX(0.0f, attack_factor - 1.0f / sample_rate);
|
||||
if (attack_factor > 0.0) {
|
||||
gain = Math::lerp(gain_target, gain, 1.0f - attack_factor / attack);
|
||||
}
|
||||
|
||||
int bucket_id = gain_bucket_cursor / gain_bucket_size;
|
||||
|
||||
// If first item within the current bucket, reset the bucket.
|
||||
if (gain_bucket_cursor % gain_bucket_size == 0) {
|
||||
gain_buckets[bucket_id] = 1.0f;
|
||||
}
|
||||
|
||||
gain_buckets[bucket_id] = MIN(gain_buckets[bucket_id], gain);
|
||||
|
||||
gain_bucket_cursor = (gain_bucket_cursor + 1) % gain_samples_to_store;
|
||||
|
||||
for (int j = 0; j < (int)gain_buckets.size(); j++) {
|
||||
gain = MIN(gain, gain_buckets[j]);
|
||||
}
|
||||
|
||||
// Introduce latency by grabbing the AudioFrame stored previously,
|
||||
// then overwrite it with current audioframe, then update circular
|
||||
// buffer cursor.
|
||||
float dst_buffer_left = sample_buffer_left[sample_cursor];
|
||||
float dst_buffer_right = sample_buffer_right[sample_cursor];
|
||||
|
||||
sample_buffer_left[sample_cursor] = sample_left;
|
||||
sample_buffer_right[sample_cursor] = sample_right;
|
||||
|
||||
sample_cursor = (sample_cursor + 1) % sample_buffer_left.size();
|
||||
|
||||
p_dst_frames[i].left = dst_buffer_left * gain;
|
||||
p_dst_frames[i].right = dst_buffer_right * gain;
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectHardLimiter::instantiate() {
|
||||
Ref<AudioEffectHardLimiterInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectHardLimiter>(this);
|
||||
|
||||
float mix_rate = AudioServer::get_singleton()->get_mix_rate();
|
||||
|
||||
for (int i = 0; i < (int)Math::ceil(mix_rate * attack) + 1; i++) {
|
||||
ins->sample_buffer_left.push_back(0.0f);
|
||||
ins->sample_buffer_right.push_back(0.0f);
|
||||
}
|
||||
|
||||
ins->gain_samples_to_store = (int)Math::ceil(mix_rate * (attack + sustain) + 1);
|
||||
ins->gain_bucket_size = (int)(mix_rate * attack);
|
||||
|
||||
for (int i = 0; i < ins->gain_samples_to_store; i += ins->gain_bucket_size) {
|
||||
ins->gain_buckets.push_back(1.0f);
|
||||
}
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectHardLimiter::set_ceiling_db(float p_ceiling) {
|
||||
ceiling = p_ceiling;
|
||||
}
|
||||
|
||||
float AudioEffectHardLimiter::get_ceiling_db() const {
|
||||
return ceiling;
|
||||
}
|
||||
|
||||
float AudioEffectHardLimiter::get_pre_gain_db() const {
|
||||
return pre_gain;
|
||||
}
|
||||
|
||||
void AudioEffectHardLimiter::set_pre_gain_db(const float p_pre_gain) {
|
||||
pre_gain = p_pre_gain;
|
||||
}
|
||||
|
||||
float AudioEffectHardLimiter::get_release() const {
|
||||
return release;
|
||||
}
|
||||
|
||||
void AudioEffectHardLimiter::set_release(const float p_release) {
|
||||
release = p_release;
|
||||
}
|
||||
|
||||
void AudioEffectHardLimiter::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_ceiling_db", "ceiling"), &AudioEffectHardLimiter::set_ceiling_db);
|
||||
ClassDB::bind_method(D_METHOD("get_ceiling_db"), &AudioEffectHardLimiter::get_ceiling_db);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_pre_gain_db", "p_pre_gain"), &AudioEffectHardLimiter::set_pre_gain_db);
|
||||
ClassDB::bind_method(D_METHOD("get_pre_gain_db"), &AudioEffectHardLimiter::get_pre_gain_db);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_release", "p_release"), &AudioEffectHardLimiter::set_release);
|
||||
ClassDB::bind_method(D_METHOD("get_release"), &AudioEffectHardLimiter::get_release);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pre_gain_db", PROPERTY_HINT_RANGE, "-24,24,0.01,suffix:dB"), "set_pre_gain_db", "get_pre_gain_db");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ceiling_db", PROPERTY_HINT_RANGE, "-24,0.0,0.01,suffix:dB"), "set_ceiling_db", "get_ceiling_db");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "release", PROPERTY_HINT_RANGE, "0.01,3,0.01"), "set_release", "get_release");
|
||||
}
|
86
servers/audio/effects/audio_effect_hard_limiter.h
Normal file
86
servers/audio/effects/audio_effect_hard_limiter.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_hard_limiter.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_effect.h"
|
||||
|
||||
class AudioEffectHardLimiter;
|
||||
|
||||
class AudioEffectHardLimiterInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectHardLimiterInstance, AudioEffectInstance);
|
||||
friend class AudioEffectHardLimiter;
|
||||
Ref<AudioEffectHardLimiter> base;
|
||||
|
||||
private:
|
||||
int sample_cursor = 0;
|
||||
|
||||
float release_factor = 0;
|
||||
float attack_factor = 0;
|
||||
float gain = 1;
|
||||
float gain_target = 1;
|
||||
|
||||
LocalVector<float> sample_buffer_left;
|
||||
LocalVector<float> sample_buffer_right;
|
||||
|
||||
int gain_samples_to_store = 0;
|
||||
int gain_bucket_cursor = 0;
|
||||
int gain_bucket_size = 0;
|
||||
LocalVector<float> gain_buckets;
|
||||
|
||||
public:
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
};
|
||||
|
||||
class AudioEffectHardLimiter : public AudioEffect {
|
||||
GDCLASS(AudioEffectHardLimiter, AudioEffect);
|
||||
|
||||
friend class AudioEffectHardLimiterInstance;
|
||||
float pre_gain = 0.0f;
|
||||
float ceiling = -0.3f;
|
||||
float sustain = 0.02f;
|
||||
float release = 0.1f;
|
||||
const float attack = 0.002;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_ceiling_db(float p_ceiling);
|
||||
float get_ceiling_db() const;
|
||||
|
||||
void set_release(float p_release);
|
||||
float get_release() const;
|
||||
|
||||
void set_pre_gain_db(float p_pre_gain);
|
||||
float get_pre_gain_db() const;
|
||||
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
};
|
134
servers/audio/effects/audio_effect_limiter.cpp
Normal file
134
servers/audio/effects/audio_effect_limiter.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_limiter.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 "audio_effect_limiter.h"
|
||||
|
||||
void AudioEffectLimiterInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
float threshdb = base->threshold;
|
||||
float ceiling = Math::db_to_linear(base->ceiling);
|
||||
float ceildb = base->ceiling;
|
||||
float makeup = Math::db_to_linear(ceildb - threshdb);
|
||||
float sc = -base->soft_clip;
|
||||
float scv = Math::db_to_linear(sc);
|
||||
float peakdb = ceildb + 25;
|
||||
float scmult = Math::abs((ceildb - sc) / (peakdb - sc));
|
||||
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
float spl0 = p_src_frames[i].left;
|
||||
float spl1 = p_src_frames[i].right;
|
||||
spl0 = spl0 * makeup;
|
||||
spl1 = spl1 * makeup;
|
||||
float sign0 = (spl0 < 0.0 ? -1.0 : 1.0);
|
||||
float sign1 = (spl1 < 0.0 ? -1.0 : 1.0);
|
||||
float abs0 = Math::abs(spl0);
|
||||
float abs1 = Math::abs(spl1);
|
||||
float overdb0 = Math::linear_to_db(abs0) - ceildb;
|
||||
float overdb1 = Math::linear_to_db(abs1) - ceildb;
|
||||
|
||||
if (abs0 > scv) {
|
||||
spl0 = sign0 * (scv + Math::db_to_linear(overdb0 * scmult));
|
||||
}
|
||||
if (abs1 > scv) {
|
||||
spl1 = sign1 * (scv + Math::db_to_linear(overdb1 * scmult));
|
||||
}
|
||||
|
||||
spl0 = MIN(ceiling, Math::abs(spl0)) * (spl0 < 0.0 ? -1.0 : 1.0);
|
||||
spl1 = MIN(ceiling, Math::abs(spl1)) * (spl1 < 0.0 ? -1.0 : 1.0);
|
||||
|
||||
p_dst_frames[i].left = spl0;
|
||||
p_dst_frames[i].right = spl1;
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectLimiter::instantiate() {
|
||||
Ref<AudioEffectLimiterInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectLimiter>(this);
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectLimiter::set_threshold_db(float p_threshold) {
|
||||
threshold = p_threshold;
|
||||
}
|
||||
|
||||
float AudioEffectLimiter::get_threshold_db() const {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
void AudioEffectLimiter::set_ceiling_db(float p_ceiling) {
|
||||
ceiling = p_ceiling;
|
||||
}
|
||||
|
||||
float AudioEffectLimiter::get_ceiling_db() const {
|
||||
return ceiling;
|
||||
}
|
||||
|
||||
void AudioEffectLimiter::set_soft_clip_db(float p_soft_clip) {
|
||||
soft_clip = p_soft_clip;
|
||||
}
|
||||
|
||||
float AudioEffectLimiter::get_soft_clip_db() const {
|
||||
return soft_clip;
|
||||
}
|
||||
|
||||
void AudioEffectLimiter::set_soft_clip_ratio(float p_soft_clip) {
|
||||
soft_clip_ratio = p_soft_clip;
|
||||
}
|
||||
|
||||
float AudioEffectLimiter::get_soft_clip_ratio() const {
|
||||
return soft_clip_ratio;
|
||||
}
|
||||
|
||||
void AudioEffectLimiter::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_ceiling_db", "ceiling"), &AudioEffectLimiter::set_ceiling_db);
|
||||
ClassDB::bind_method(D_METHOD("get_ceiling_db"), &AudioEffectLimiter::get_ceiling_db);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_threshold_db", "threshold"), &AudioEffectLimiter::set_threshold_db);
|
||||
ClassDB::bind_method(D_METHOD("get_threshold_db"), &AudioEffectLimiter::get_threshold_db);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_soft_clip_db", "soft_clip"), &AudioEffectLimiter::set_soft_clip_db);
|
||||
ClassDB::bind_method(D_METHOD("get_soft_clip_db"), &AudioEffectLimiter::get_soft_clip_db);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_soft_clip_ratio", "soft_clip"), &AudioEffectLimiter::set_soft_clip_ratio);
|
||||
ClassDB::bind_method(D_METHOD("get_soft_clip_ratio"), &AudioEffectLimiter::get_soft_clip_ratio);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ceiling_db", PROPERTY_HINT_RANGE, "-20,-0.1,0.1,suffix:dB"), "set_ceiling_db", "get_ceiling_db");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "threshold_db", PROPERTY_HINT_RANGE, "-30,0,0.1,suffix:dB"), "set_threshold_db", "get_threshold_db");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "soft_clip_db", PROPERTY_HINT_RANGE, "0,6,0.1,suffix:dB"), "set_soft_clip_db", "get_soft_clip_db");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "soft_clip_ratio", PROPERTY_HINT_RANGE, "3,20,0.1"), "set_soft_clip_ratio", "get_soft_clip_ratio");
|
||||
}
|
||||
|
||||
AudioEffectLimiter::AudioEffectLimiter() {
|
||||
threshold = 0;
|
||||
ceiling = -0.1;
|
||||
soft_clip = 2;
|
||||
soft_clip_ratio = 10;
|
||||
}
|
76
servers/audio/effects/audio_effect_limiter.h
Normal file
76
servers/audio/effects/audio_effect_limiter.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_limiter.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_effect.h"
|
||||
|
||||
class AudioEffectLimiter;
|
||||
|
||||
class AudioEffectLimiterInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectLimiterInstance, AudioEffectInstance);
|
||||
friend class AudioEffectLimiter;
|
||||
Ref<AudioEffectLimiter> base;
|
||||
|
||||
float mix_volume_db;
|
||||
|
||||
public:
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
};
|
||||
|
||||
class AudioEffectLimiter : public AudioEffect {
|
||||
GDCLASS(AudioEffectLimiter, AudioEffect);
|
||||
|
||||
friend class AudioEffectLimiterInstance;
|
||||
float threshold;
|
||||
float ceiling;
|
||||
float soft_clip;
|
||||
float soft_clip_ratio;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_threshold_db(float p_threshold);
|
||||
float get_threshold_db() const;
|
||||
|
||||
void set_ceiling_db(float p_ceiling);
|
||||
float get_ceiling_db() const;
|
||||
|
||||
void set_soft_clip_db(float p_soft_clip);
|
||||
float get_soft_clip_db() const;
|
||||
|
||||
void set_soft_clip_ratio(float p_soft_clip);
|
||||
float get_soft_clip_ratio() const;
|
||||
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
|
||||
AudioEffectLimiter();
|
||||
};
|
67
servers/audio/effects/audio_effect_panner.cpp
Normal file
67
servers/audio/effects/audio_effect_panner.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_panner.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 "audio_effect_panner.h"
|
||||
|
||||
void AudioEffectPannerInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
float lvol = CLAMP(1.0 - base->pan, 0, 1);
|
||||
float rvol = CLAMP(1.0 + base->pan, 0, 1);
|
||||
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
p_dst_frames[i].left = p_src_frames[i].left * lvol + p_src_frames[i].right * (1.0 - rvol);
|
||||
p_dst_frames[i].right = p_src_frames[i].right * rvol + p_src_frames[i].left * (1.0 - lvol);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectPanner::instantiate() {
|
||||
Ref<AudioEffectPannerInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectPanner>(this);
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectPanner::set_pan(float p_cpanume) {
|
||||
pan = p_cpanume;
|
||||
}
|
||||
|
||||
float AudioEffectPanner::get_pan() const {
|
||||
return pan;
|
||||
}
|
||||
|
||||
void AudioEffectPanner::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_pan", "cpanume"), &AudioEffectPanner::set_pan);
|
||||
ClassDB::bind_method(D_METHOD("get_pan"), &AudioEffectPanner::get_pan);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pan", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_pan", "get_pan");
|
||||
}
|
||||
|
||||
AudioEffectPanner::AudioEffectPanner() {
|
||||
pan = 0;
|
||||
}
|
61
servers/audio/effects/audio_effect_panner.h
Normal file
61
servers/audio/effects/audio_effect_panner.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_panner.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_effect.h"
|
||||
|
||||
class AudioEffectPanner;
|
||||
|
||||
class AudioEffectPannerInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectPannerInstance, AudioEffectInstance);
|
||||
friend class AudioEffectPanner;
|
||||
Ref<AudioEffectPanner> base;
|
||||
|
||||
public:
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
};
|
||||
|
||||
class AudioEffectPanner : public AudioEffect {
|
||||
GDCLASS(AudioEffectPanner, AudioEffect);
|
||||
|
||||
friend class AudioEffectPannerInstance;
|
||||
float pan;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
void set_pan(float p_cpanume);
|
||||
float get_pan() const;
|
||||
|
||||
AudioEffectPanner();
|
||||
};
|
159
servers/audio/effects/audio_effect_phaser.cpp
Normal file
159
servers/audio/effects/audio_effect_phaser.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_phaser.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 "audio_effect_phaser.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
void AudioEffectPhaserInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
float sampling_rate = AudioServer::get_singleton()->get_mix_rate();
|
||||
|
||||
float dmin = base->range_min / (sampling_rate / 2.0);
|
||||
float dmax = base->range_max / (sampling_rate / 2.0);
|
||||
|
||||
float increment = Math::TAU * (base->rate / sampling_rate);
|
||||
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
phase += increment;
|
||||
|
||||
while (phase >= Math::TAU) {
|
||||
phase -= Math::TAU;
|
||||
}
|
||||
|
||||
float d = dmin + (dmax - dmin) * ((std::sin(phase) + 1.f) / 2.f);
|
||||
|
||||
//update filter coeffs
|
||||
for (int j = 0; j < 6; j++) {
|
||||
allpass[0][j].delay(d);
|
||||
allpass[1][j].delay(d);
|
||||
}
|
||||
|
||||
//calculate output
|
||||
float y = allpass[0][0].update(
|
||||
allpass[0][1].update(
|
||||
allpass[0][2].update(
|
||||
allpass[0][3].update(
|
||||
allpass[0][4].update(
|
||||
allpass[0][5].update(p_src_frames[i].left + h.left * base->feedback))))));
|
||||
h.left = y;
|
||||
|
||||
p_dst_frames[i].left = p_src_frames[i].left + y * base->depth;
|
||||
|
||||
y = allpass[1][0].update(
|
||||
allpass[1][1].update(
|
||||
allpass[1][2].update(
|
||||
allpass[1][3].update(
|
||||
allpass[1][4].update(
|
||||
allpass[1][5].update(p_src_frames[i].right + h.right * base->feedback))))));
|
||||
h.right = y;
|
||||
|
||||
p_dst_frames[i].right = p_src_frames[i].right + y * base->depth;
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectPhaser::instantiate() {
|
||||
Ref<AudioEffectPhaserInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectPhaser>(this);
|
||||
ins->phase = 0;
|
||||
ins->h = AudioFrame(0, 0);
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectPhaser::set_range_min_hz(float p_hz) {
|
||||
range_min = p_hz;
|
||||
}
|
||||
|
||||
float AudioEffectPhaser::get_range_min_hz() const {
|
||||
return range_min;
|
||||
}
|
||||
|
||||
void AudioEffectPhaser::set_range_max_hz(float p_hz) {
|
||||
range_max = p_hz;
|
||||
}
|
||||
|
||||
float AudioEffectPhaser::get_range_max_hz() const {
|
||||
return range_max;
|
||||
}
|
||||
|
||||
void AudioEffectPhaser::set_rate_hz(float p_hz) {
|
||||
rate = p_hz;
|
||||
}
|
||||
|
||||
float AudioEffectPhaser::get_rate_hz() const {
|
||||
return rate;
|
||||
}
|
||||
|
||||
void AudioEffectPhaser::set_feedback(float p_fbk) {
|
||||
feedback = p_fbk;
|
||||
}
|
||||
|
||||
float AudioEffectPhaser::get_feedback() const {
|
||||
return feedback;
|
||||
}
|
||||
|
||||
void AudioEffectPhaser::set_depth(float p_depth) {
|
||||
depth = p_depth;
|
||||
}
|
||||
|
||||
float AudioEffectPhaser::get_depth() const {
|
||||
return depth;
|
||||
}
|
||||
|
||||
void AudioEffectPhaser::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_range_min_hz", "hz"), &AudioEffectPhaser::set_range_min_hz);
|
||||
ClassDB::bind_method(D_METHOD("get_range_min_hz"), &AudioEffectPhaser::get_range_min_hz);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_range_max_hz", "hz"), &AudioEffectPhaser::set_range_max_hz);
|
||||
ClassDB::bind_method(D_METHOD("get_range_max_hz"), &AudioEffectPhaser::get_range_max_hz);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_rate_hz", "hz"), &AudioEffectPhaser::set_rate_hz);
|
||||
ClassDB::bind_method(D_METHOD("get_rate_hz"), &AudioEffectPhaser::get_rate_hz);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_feedback", "fbk"), &AudioEffectPhaser::set_feedback);
|
||||
ClassDB::bind_method(D_METHOD("get_feedback"), &AudioEffectPhaser::get_feedback);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_depth", "depth"), &AudioEffectPhaser::set_depth);
|
||||
ClassDB::bind_method(D_METHOD("get_depth"), &AudioEffectPhaser::get_depth);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "range_min_hz", PROPERTY_HINT_RANGE, "10,10000,suffix:Hz"), "set_range_min_hz", "get_range_min_hz");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "range_max_hz", PROPERTY_HINT_RANGE, "10,10000,suffix:Hz"), "set_range_max_hz", "get_range_max_hz");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rate_hz", PROPERTY_HINT_RANGE, "0.01,20,suffix:Hz"), "set_rate_hz", "get_rate_hz");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "feedback", PROPERTY_HINT_RANGE, "0.1,0.9,0.1"), "set_feedback", "get_feedback");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth", PROPERTY_HINT_RANGE, "0.1,4,0.1"), "set_depth", "get_depth");
|
||||
}
|
||||
|
||||
AudioEffectPhaser::AudioEffectPhaser() {
|
||||
range_min = 440;
|
||||
range_max = 1600;
|
||||
rate = 0.5;
|
||||
feedback = 0.7;
|
||||
depth = 1;
|
||||
}
|
103
servers/audio/effects/audio_effect_phaser.h
Normal file
103
servers/audio/effects/audio_effect_phaser.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_phaser.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_effect.h"
|
||||
|
||||
class AudioEffectPhaser;
|
||||
|
||||
class AudioEffectPhaserInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectPhaserInstance, AudioEffectInstance);
|
||||
friend class AudioEffectPhaser;
|
||||
Ref<AudioEffectPhaser> base;
|
||||
|
||||
float phase;
|
||||
AudioFrame h;
|
||||
|
||||
class AllpassDelay {
|
||||
float a, h;
|
||||
|
||||
public:
|
||||
_ALWAYS_INLINE_ void delay(float d) {
|
||||
a = (1.f - d) / (1.f + d);
|
||||
}
|
||||
|
||||
_ALWAYS_INLINE_ float update(float s) {
|
||||
float y = s * -a + h;
|
||||
h = y * a + s;
|
||||
return y;
|
||||
}
|
||||
|
||||
AllpassDelay() {
|
||||
a = 0;
|
||||
h = 0;
|
||||
}
|
||||
};
|
||||
|
||||
AllpassDelay allpass[2][6];
|
||||
|
||||
public:
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
};
|
||||
|
||||
class AudioEffectPhaser : public AudioEffect {
|
||||
GDCLASS(AudioEffectPhaser, AudioEffect);
|
||||
|
||||
friend class AudioEffectPhaserInstance;
|
||||
float range_min;
|
||||
float range_max;
|
||||
float rate;
|
||||
float feedback;
|
||||
float depth;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
|
||||
void set_range_min_hz(float p_hz);
|
||||
float get_range_min_hz() const;
|
||||
|
||||
void set_range_max_hz(float p_hz);
|
||||
float get_range_max_hz() const;
|
||||
|
||||
void set_rate_hz(float p_hz);
|
||||
float get_rate_hz() const;
|
||||
|
||||
void set_feedback(float p_fbk);
|
||||
float get_feedback() const;
|
||||
|
||||
void set_depth(float p_depth);
|
||||
float get_depth() const;
|
||||
|
||||
AudioEffectPhaser();
|
||||
};
|
371
servers/audio/effects/audio_effect_pitch_shift.cpp
Normal file
371
servers/audio/effects/audio_effect_pitch_shift.cpp
Normal file
@@ -0,0 +1,371 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_pitch_shift.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 "audio_effect_pitch_shift.h"
|
||||
|
||||
#include "core/math/math_funcs.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
/* Thirdparty code, so disable clang-format with Godot style */
|
||||
/* clang-format off */
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* NAME: smbPitchShift.cpp
|
||||
* VERSION: 1.2
|
||||
* HOME URL: https://blogs.zynaptiq.com/bernsee
|
||||
* KNOWN BUGS: none
|
||||
*
|
||||
* SYNOPSIS: Routine for doing pitch shifting while maintaining
|
||||
* duration using the Short Time Fourier Transform.
|
||||
*
|
||||
* DESCRIPTION: The routine takes a pitchShift factor value which is between 0.5
|
||||
* (one octave down) and 2. (one octave up). A value of exactly 1 does not change
|
||||
* the pitch. numSampsToProcess tells the routine how many samples in indata[0...
|
||||
* numSampsToProcess-1] should be pitch shifted and moved to outdata[0 ...
|
||||
* numSampsToProcess-1]. The two buffers can be identical (ie. it can process the
|
||||
* data in-place). fftFrameSize defines the FFT frame size used for the
|
||||
* processing. Typical values are 1024, 2048 and 4096. It may be any value <=
|
||||
* MAX_FRAME_LENGTH but it MUST be a power of 2. osamp is the STFT
|
||||
* oversampling factor which also determines the overlap between adjacent STFT
|
||||
* frames. It should at least be 4 for moderate scaling ratios. A value of 32 is
|
||||
* recommended for best quality. sampleRate takes the sample rate for the signal
|
||||
* in unit Hz, ie. 44100 for 44.1 kHz audio. The data passed to the routine in
|
||||
* indata[] should be in the range [-1.0, 1.0), which is also the output range
|
||||
* for the data, make sure you scale the data accordingly (for 16bit signed integers
|
||||
* you would have to divide (and multiply) by 32768).
|
||||
*
|
||||
* COPYRIGHT 1999-2015 Stephan M. Bernsee <s.bernsee [AT] zynaptiq [DOT] com>
|
||||
*
|
||||
* The Wide Open License (WOL)
|
||||
*
|
||||
* Permission to use, copy, modify, distribute and sell this software and its
|
||||
* documentation for any purpose is hereby granted without fee, provided that
|
||||
* the above copyright notice and this license appear in all source copies.
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF
|
||||
* ANY KIND. See https://dspguru.com/wide-open-license/ for more information.
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
void SMBPitchShift::PitchShift(float pitchShift, long numSampsToProcess, long fftFrameSize, long osamp, float sampleRate, float *indata, float *outdata,int stride) {
|
||||
|
||||
|
||||
/*
|
||||
Routine smbPitchShift(). See top of file for explanation
|
||||
Purpose: doing pitch shifting while maintaining duration using the Short
|
||||
Time Fourier Transform.
|
||||
Author: (c)1999-2015 Stephan M. Bernsee <s.bernsee [AT] zynaptiq [DOT] com>
|
||||
*/
|
||||
|
||||
double magn, phase, tmp, window, real, imag;
|
||||
double freqPerBin, expct;
|
||||
long i,k, qpd, index, inFifoLatency, stepSize, fftFrameSize2;
|
||||
|
||||
/* set up some handy variables */
|
||||
fftFrameSize2 = fftFrameSize/2;
|
||||
stepSize = fftFrameSize/osamp;
|
||||
freqPerBin = sampleRate/(double)fftFrameSize;
|
||||
expct = 2.*Math::PI*(double)stepSize/(double)fftFrameSize;
|
||||
inFifoLatency = fftFrameSize-stepSize;
|
||||
if (gRover == 0) { gRover = inFifoLatency;
|
||||
}
|
||||
|
||||
/* initialize our static arrays */
|
||||
|
||||
/* main processing loop */
|
||||
for (i = 0; i < numSampsToProcess; i++){
|
||||
/* As long as we have not yet collected enough data just read in */
|
||||
gInFIFO[gRover] = indata[i*stride];
|
||||
outdata[i*stride] = gOutFIFO[gRover-inFifoLatency];
|
||||
gRover++;
|
||||
|
||||
/* now we have enough data for processing */
|
||||
if (gRover >= fftFrameSize) {
|
||||
gRover = inFifoLatency;
|
||||
|
||||
/* do windowing and re,im interleave */
|
||||
for (k = 0; k < fftFrameSize;k++) {
|
||||
window = -.5*std::cos(2.*Math::PI*(double)k/(double)fftFrameSize)+.5;
|
||||
gFFTworksp[2*k] = gInFIFO[k] * window;
|
||||
gFFTworksp[2*k+1] = 0.;
|
||||
}
|
||||
|
||||
|
||||
/* ***************** ANALYSIS ******************* */
|
||||
/* do transform */
|
||||
smbFft(gFFTworksp, fftFrameSize, -1);
|
||||
|
||||
/* this is the analysis step */
|
||||
for (k = 0; k <= fftFrameSize2; k++) {
|
||||
/* de-interlace FFT buffer */
|
||||
real = gFFTworksp[2*k];
|
||||
imag = gFFTworksp[2*k+1];
|
||||
|
||||
/* compute magnitude and phase */
|
||||
magn = 2.*std::sqrt(real*real + imag*imag);
|
||||
phase = std::atan2(imag,real);
|
||||
|
||||
/* compute phase difference */
|
||||
tmp = phase - gLastPhase[k];
|
||||
gLastPhase[k] = phase;
|
||||
|
||||
/* subtract expected phase difference */
|
||||
tmp -= (double)k*expct;
|
||||
|
||||
/* map delta phase into +/- Pi interval */
|
||||
qpd = tmp/Math::PI;
|
||||
if (qpd >= 0) { qpd += qpd&1;
|
||||
} else { qpd -= qpd&1;
|
||||
}
|
||||
tmp -= Math::PI*(double)qpd;
|
||||
|
||||
/* get deviation from bin frequency from the +/- Pi interval */
|
||||
tmp = osamp*tmp/(2.*Math::PI);
|
||||
|
||||
/* compute the k-th partials' true frequency */
|
||||
tmp = (double)k*freqPerBin + tmp*freqPerBin;
|
||||
|
||||
/* store magnitude and true frequency in analysis arrays */
|
||||
gAnaMagn[k] = magn;
|
||||
gAnaFreq[k] = tmp;
|
||||
|
||||
}
|
||||
|
||||
/* ***************** PROCESSING ******************* */
|
||||
/* this does the actual pitch shifting */
|
||||
size_t fftBufferSize = static_cast<size_t>(fftFrameSize) * sizeof(float);
|
||||
if (unlikely(fftBufferSize > MAX_FRAME_LENGTH)) {
|
||||
ERR_PRINT_ONCE("Invalid FFT frame size for PitchShift. This is a bug, please report.");
|
||||
return;
|
||||
}
|
||||
memset(gSynMagn, 0, fftBufferSize);
|
||||
memset(gSynFreq, 0, fftBufferSize);
|
||||
for (k = 0; k <= fftFrameSize2; k++) {
|
||||
index = k*pitchShift;
|
||||
if (index <= fftFrameSize2) {
|
||||
gSynMagn[index] += gAnaMagn[k];
|
||||
gSynFreq[index] = gAnaFreq[k] * pitchShift;
|
||||
}
|
||||
}
|
||||
|
||||
/* ***************** SYNTHESIS ******************* */
|
||||
/* this is the synthesis step */
|
||||
for (k = 0; k <= fftFrameSize2; k++) {
|
||||
/* get magnitude and true frequency from synthesis arrays */
|
||||
magn = gSynMagn[k];
|
||||
tmp = gSynFreq[k];
|
||||
|
||||
/* subtract bin mid frequency */
|
||||
tmp -= (double)k*freqPerBin;
|
||||
|
||||
/* get bin deviation from freq deviation */
|
||||
tmp /= freqPerBin;
|
||||
|
||||
/* take osamp into account */
|
||||
tmp = 2.*Math::PI*tmp/osamp;
|
||||
|
||||
/* add the overlap phase advance back in */
|
||||
tmp += (double)k*expct;
|
||||
|
||||
/* accumulate delta phase to get bin phase */
|
||||
gSumPhase[k] += tmp;
|
||||
phase = gSumPhase[k];
|
||||
|
||||
/* get real and imag part and re-interleave */
|
||||
gFFTworksp[2*k] = magn*std::cos(phase);
|
||||
gFFTworksp[2*k+1] = magn*std::sin(phase);
|
||||
}
|
||||
|
||||
/* zero negative frequencies */
|
||||
for (k = fftFrameSize+2; k < 2*fftFrameSize; k++) { gFFTworksp[k] = 0.;
|
||||
}
|
||||
|
||||
/* do inverse transform */
|
||||
smbFft(gFFTworksp, fftFrameSize, 1);
|
||||
|
||||
/* do windowing and add to output accumulator */
|
||||
for(k=0; k < fftFrameSize; k++) {
|
||||
window = -.5*std::cos(2.*Math::PI*(double)k/(double)fftFrameSize)+.5;
|
||||
gOutputAccum[k] += 2.*window*gFFTworksp[2*k]/(fftFrameSize2*osamp);
|
||||
}
|
||||
for (k = 0; k < stepSize; k++) { gOutFIFO[k] = gOutputAccum[k];
|
||||
}
|
||||
|
||||
/* shift accumulator */
|
||||
memmove(gOutputAccum, gOutputAccum+stepSize, fftBufferSize);
|
||||
|
||||
/* move input FIFO */
|
||||
for (k = 0; k < inFifoLatency; k++) { gInFIFO[k] = gInFIFO[k+stepSize];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SMBPitchShift::smbFft(float *fftBuffer, long fftFrameSize, long sign)
|
||||
/*
|
||||
FFT routine, (C)1996 S.M.Bernsee. Sign = -1 is FFT, 1 is iFFT (inverse)
|
||||
Fills fftBuffer[0...2*fftFrameSize-1] with the Fourier transform of the
|
||||
time domain data in fftBuffer[0...2*fftFrameSize-1]. The FFT array takes
|
||||
and returns the cosine and sine parts in an interleaved manner, ie.
|
||||
fftBuffer[0] = cosPart[0], fftBuffer[1] = sinPart[0], asf. fftFrameSize
|
||||
must be a power of 2. It expects a complex input signal (see footnote 2),
|
||||
ie. when working with 'common' audio signals our input signal has to be
|
||||
passed as {in[0],0.,in[1],0.,in[2],0.,...} asf. In that case, the transform
|
||||
of the frequencies of interest is in fftBuffer[0...fftFrameSize].
|
||||
*/
|
||||
{
|
||||
float wr, wi, arg, *p1, *p2, temp;
|
||||
float tr, ti, ur, ui, *p1r, *p1i, *p2r, *p2i;
|
||||
long i, bitm, j, le, le2, k;
|
||||
|
||||
for (i = 2; i < 2*fftFrameSize-2; i += 2) {
|
||||
for (bitm = 2, j = 0; bitm < 2*fftFrameSize; bitm <<= 1) {
|
||||
if (i & bitm) { j++;
|
||||
}
|
||||
j <<= 1;
|
||||
}
|
||||
if (i < j) {
|
||||
p1 = fftBuffer+i; p2 = fftBuffer+j;
|
||||
temp = *p1; *(p1++) = *p2;
|
||||
*(p2++) = temp; temp = *p1;
|
||||
*p1 = *p2; *p2 = temp;
|
||||
}
|
||||
}
|
||||
for (k = 0, le = 2; k < (long)(std::log((double)fftFrameSize)/std::log(2.)+.5); k++) {
|
||||
le <<= 1;
|
||||
le2 = le>>1;
|
||||
ur = 1.0;
|
||||
ui = 0.0;
|
||||
arg = Math::PI / (le2>>1);
|
||||
wr = std::cos(arg);
|
||||
wi = sign*std::sin(arg);
|
||||
for (j = 0; j < le2; j += 2) {
|
||||
p1r = fftBuffer+j; p1i = p1r+1;
|
||||
p2r = p1r+le2; p2i = p2r+1;
|
||||
for (i = j; i < 2*fftFrameSize; i += le) {
|
||||
tr = *p2r * ur - *p2i * ui;
|
||||
ti = *p2r * ui + *p2i * ur;
|
||||
*p2r = *p1r - tr; *p2i = *p1i - ti;
|
||||
*p1r += tr; *p1i += ti;
|
||||
p1r += le; p1i += le;
|
||||
p2r += le; p2i += le;
|
||||
}
|
||||
tr = ur*wr - ui*wi;
|
||||
ui = ur*wi + ui*wr;
|
||||
ur = tr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Godot code again */
|
||||
/* clang-format on */
|
||||
|
||||
void AudioEffectPitchShiftInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
// Avoid distortion by skipping processing if pitch_scale is 1.0.
|
||||
if (Math::is_equal_approx(base->pitch_scale, 1.0f)) {
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
p_dst_frames[i] = p_src_frames[i];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
float sample_rate = AudioServer::get_singleton()->get_mix_rate();
|
||||
|
||||
float *in_l = (float *)p_src_frames;
|
||||
float *in_r = in_l + 1;
|
||||
|
||||
float *out_l = (float *)p_dst_frames;
|
||||
float *out_r = out_l + 1;
|
||||
|
||||
shift_l.PitchShift(base->pitch_scale, p_frame_count, fft_size, base->oversampling, sample_rate, in_l, out_l, 2);
|
||||
shift_r.PitchShift(base->pitch_scale, p_frame_count, fft_size, base->oversampling, sample_rate, in_r, out_r, 2);
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectPitchShift::instantiate() {
|
||||
Ref<AudioEffectPitchShiftInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectPitchShift>(this);
|
||||
static const int fft_sizes[FFT_SIZE_MAX] = { 256, 512, 1024, 2048, 4096 };
|
||||
ins->fft_size = fft_sizes[fft_size];
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectPitchShift::set_pitch_scale(float p_pitch_scale) {
|
||||
ERR_FAIL_COND(!(p_pitch_scale > 0.0));
|
||||
pitch_scale = p_pitch_scale;
|
||||
}
|
||||
|
||||
float AudioEffectPitchShift::get_pitch_scale() const {
|
||||
return pitch_scale;
|
||||
}
|
||||
|
||||
void AudioEffectPitchShift::set_oversampling(int p_oversampling) {
|
||||
ERR_FAIL_COND(p_oversampling < 4);
|
||||
oversampling = p_oversampling;
|
||||
}
|
||||
|
||||
int AudioEffectPitchShift::get_oversampling() const {
|
||||
return oversampling;
|
||||
}
|
||||
|
||||
void AudioEffectPitchShift::set_fft_size(FFTSize p_fft_size) {
|
||||
ERR_FAIL_INDEX(p_fft_size, FFT_SIZE_MAX);
|
||||
fft_size = p_fft_size;
|
||||
}
|
||||
|
||||
AudioEffectPitchShift::FFTSize AudioEffectPitchShift::get_fft_size() const {
|
||||
return fft_size;
|
||||
}
|
||||
|
||||
void AudioEffectPitchShift::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_pitch_scale", "rate"), &AudioEffectPitchShift::set_pitch_scale);
|
||||
ClassDB::bind_method(D_METHOD("get_pitch_scale"), &AudioEffectPitchShift::get_pitch_scale);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_oversampling", "amount"), &AudioEffectPitchShift::set_oversampling);
|
||||
ClassDB::bind_method(D_METHOD("get_oversampling"), &AudioEffectPitchShift::get_oversampling);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_fft_size", "size"), &AudioEffectPitchShift::set_fft_size);
|
||||
ClassDB::bind_method(D_METHOD("get_fft_size"), &AudioEffectPitchShift::get_fft_size);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_pitch_scale", "get_pitch_scale");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "4,32,1"), "set_oversampling", "get_oversampling");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "fft_size", PROPERTY_HINT_ENUM, "256,512,1024,2048,4096"), "set_fft_size", "get_fft_size");
|
||||
|
||||
BIND_ENUM_CONSTANT(FFT_SIZE_256);
|
||||
BIND_ENUM_CONSTANT(FFT_SIZE_512);
|
||||
BIND_ENUM_CONSTANT(FFT_SIZE_1024);
|
||||
BIND_ENUM_CONSTANT(FFT_SIZE_2048);
|
||||
BIND_ENUM_CONSTANT(FFT_SIZE_4096);
|
||||
BIND_ENUM_CONSTANT(FFT_SIZE_MAX);
|
||||
}
|
111
servers/audio/effects/audio_effect_pitch_shift.h
Normal file
111
servers/audio/effects/audio_effect_pitch_shift.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_pitch_shift.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_effect.h"
|
||||
|
||||
class SMBPitchShift {
|
||||
enum {
|
||||
MAX_FRAME_LENGTH = 8192
|
||||
};
|
||||
|
||||
float gInFIFO[MAX_FRAME_LENGTH] = {};
|
||||
float gOutFIFO[MAX_FRAME_LENGTH] = {};
|
||||
float gFFTworksp[2 * MAX_FRAME_LENGTH] = {};
|
||||
float gLastPhase[MAX_FRAME_LENGTH / 2 + 1] = {};
|
||||
float gSumPhase[MAX_FRAME_LENGTH / 2 + 1] = {};
|
||||
float gOutputAccum[2 * MAX_FRAME_LENGTH] = {};
|
||||
float gAnaFreq[MAX_FRAME_LENGTH] = {};
|
||||
float gAnaMagn[MAX_FRAME_LENGTH] = {};
|
||||
float gSynFreq[MAX_FRAME_LENGTH] = {};
|
||||
float gSynMagn[MAX_FRAME_LENGTH] = {};
|
||||
long gRover = 0;
|
||||
|
||||
void smbFft(float *fftBuffer, long fftFrameSize, long sign);
|
||||
|
||||
public:
|
||||
void PitchShift(float pitchShift, long numSampsToProcess, long fftFrameSize, long osamp, float sampleRate, float *indata, float *outdata, int stride);
|
||||
};
|
||||
|
||||
class AudioEffectPitchShift;
|
||||
|
||||
class AudioEffectPitchShiftInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectPitchShiftInstance, AudioEffectInstance);
|
||||
friend class AudioEffectPitchShift;
|
||||
Ref<AudioEffectPitchShift> base;
|
||||
|
||||
int fft_size = 0;
|
||||
SMBPitchShift shift_l;
|
||||
SMBPitchShift shift_r;
|
||||
|
||||
public:
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
};
|
||||
|
||||
class AudioEffectPitchShift : public AudioEffect {
|
||||
GDCLASS(AudioEffectPitchShift, AudioEffect);
|
||||
|
||||
public:
|
||||
friend class AudioEffectPitchShiftInstance;
|
||||
|
||||
enum FFTSize : unsigned int {
|
||||
FFT_SIZE_256,
|
||||
FFT_SIZE_512,
|
||||
FFT_SIZE_1024,
|
||||
FFT_SIZE_2048,
|
||||
FFT_SIZE_4096,
|
||||
FFT_SIZE_MAX
|
||||
};
|
||||
|
||||
float pitch_scale = 1.0;
|
||||
int oversampling = 4;
|
||||
FFTSize fft_size = FFT_SIZE_2048;
|
||||
float wet = 0.0;
|
||||
float dry = 0.0;
|
||||
bool filter = false;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
|
||||
void set_pitch_scale(float p_pitch_scale);
|
||||
float get_pitch_scale() const;
|
||||
|
||||
void set_oversampling(int p_oversampling);
|
||||
int get_oversampling() const;
|
||||
|
||||
void set_fft_size(FFTSize);
|
||||
FFTSize get_fft_size() const;
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AudioEffectPitchShift::FFTSize);
|
294
servers/audio/effects/audio_effect_record.cpp
Normal file
294
servers/audio/effects/audio_effect_record.cpp
Normal file
@@ -0,0 +1,294 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_record.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 "audio_effect_record.h"
|
||||
|
||||
#include "core/io/marshalls.h"
|
||||
|
||||
void AudioEffectRecordInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
if (!is_recording) {
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
p_dst_frames[i] = p_src_frames[i];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//Add incoming audio frames to the IO ring buffer
|
||||
const AudioFrame *src = p_src_frames;
|
||||
AudioFrame *rb_buf = ring_buffer.ptrw();
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
p_dst_frames[i] = p_src_frames[i];
|
||||
rb_buf[ring_buffer_pos & ring_buffer_mask] = src[i];
|
||||
ring_buffer_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEffectRecordInstance::_update_buffer() {
|
||||
//Case: Frames are remaining in the buffer
|
||||
while (ring_buffer_read_pos < ring_buffer_pos) {
|
||||
//Read from the buffer into recording_data
|
||||
_io_store_buffer();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEffectRecordInstance::_update(void *userdata) {
|
||||
AudioEffectRecordInstance *ins = static_cast<AudioEffectRecordInstance *>(userdata);
|
||||
ins->_update_buffer();
|
||||
}
|
||||
|
||||
bool AudioEffectRecordInstance::process_silence() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioEffectRecordInstance::_io_thread_process() {
|
||||
while (is_recording) {
|
||||
_update_buffer();
|
||||
if (is_recording) {
|
||||
//Wait to avoid too much busy-wait
|
||||
OS::get_singleton()->delay_usec(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEffectRecordInstance::_io_store_buffer() {
|
||||
int to_read = ring_buffer_pos - ring_buffer_read_pos;
|
||||
|
||||
AudioFrame *rb_buf = ring_buffer.ptrw();
|
||||
|
||||
while (to_read) {
|
||||
AudioFrame buffered_frame = rb_buf[ring_buffer_read_pos & ring_buffer_mask];
|
||||
recording_data.push_back(buffered_frame.left);
|
||||
recording_data.push_back(buffered_frame.right);
|
||||
|
||||
ring_buffer_read_pos++;
|
||||
to_read--;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEffectRecordInstance::_thread_callback(void *_instance) {
|
||||
AudioEffectRecordInstance *aeri = reinterpret_cast<AudioEffectRecordInstance *>(_instance);
|
||||
|
||||
aeri->_io_thread_process();
|
||||
}
|
||||
|
||||
void AudioEffectRecordInstance::init() {
|
||||
//Reset recorder status
|
||||
ring_buffer_pos = 0;
|
||||
ring_buffer_read_pos = 0;
|
||||
|
||||
//We start a new recording
|
||||
recording_data.clear(); //Clear data completely and reset length
|
||||
is_recording = true;
|
||||
|
||||
io_thread.start(_thread_callback, this);
|
||||
}
|
||||
|
||||
void AudioEffectRecordInstance::finish() {
|
||||
is_recording = false;
|
||||
if (io_thread.is_started()) {
|
||||
io_thread.wait_to_finish();
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectRecord::instantiate() {
|
||||
Ref<AudioEffectRecordInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->is_recording = false;
|
||||
|
||||
// Reusing the buffer size calculations from audio_effect_delay.cpp.
|
||||
float ring_buffer_max_size = IO_BUFFER_SIZE_MS;
|
||||
ring_buffer_max_size /= 1000.0; //convert to seconds
|
||||
ring_buffer_max_size *= AudioServer::get_singleton()->get_mix_rate();
|
||||
|
||||
int ringbuff_size = ring_buffer_max_size;
|
||||
|
||||
int bits = 0;
|
||||
|
||||
while (ringbuff_size > 0) {
|
||||
bits++;
|
||||
ringbuff_size /= 2;
|
||||
}
|
||||
|
||||
ringbuff_size = 1 << bits;
|
||||
ins->ring_buffer_mask = ringbuff_size - 1;
|
||||
ins->ring_buffer_pos = 0;
|
||||
|
||||
ins->ring_buffer.resize(ringbuff_size);
|
||||
|
||||
ins->ring_buffer_read_pos = 0;
|
||||
|
||||
ensure_thread_stopped();
|
||||
bool is_currently_recording = false;
|
||||
if (current_instance.is_valid()) {
|
||||
is_currently_recording = current_instance->is_recording;
|
||||
}
|
||||
if (is_currently_recording) {
|
||||
ins->init();
|
||||
}
|
||||
current_instance = ins;
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectRecord::ensure_thread_stopped() {
|
||||
if (current_instance.is_valid()) {
|
||||
current_instance->finish();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEffectRecord::set_recording_active(bool p_record) {
|
||||
if (p_record) {
|
||||
if (current_instance.is_null()) {
|
||||
WARN_PRINT("Recording should not be set as active before Godot has initialized.");
|
||||
return;
|
||||
}
|
||||
ensure_thread_stopped();
|
||||
current_instance->init();
|
||||
} else {
|
||||
if (current_instance.is_valid()) {
|
||||
current_instance->is_recording = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioEffectRecord::is_recording_active() const {
|
||||
if (current_instance.is_null()) {
|
||||
return false;
|
||||
} else {
|
||||
return current_instance->is_recording;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEffectRecord::set_format(AudioStreamWAV::Format p_format) {
|
||||
format = p_format;
|
||||
}
|
||||
|
||||
AudioStreamWAV::Format AudioEffectRecord::get_format() const {
|
||||
return format;
|
||||
}
|
||||
|
||||
Ref<AudioStreamWAV> AudioEffectRecord::get_recording() const {
|
||||
AudioStreamWAV::Format dst_format = format;
|
||||
bool stereo = true; //forcing mono is not implemented
|
||||
|
||||
Vector<uint8_t> dst_data;
|
||||
|
||||
ERR_FAIL_COND_V(current_instance.is_null(), nullptr);
|
||||
ERR_FAIL_COND_V(current_instance->recording_data.is_empty(), nullptr);
|
||||
|
||||
if (dst_format == AudioStreamWAV::FORMAT_8_BITS) {
|
||||
int data_size = current_instance->recording_data.size();
|
||||
dst_data.resize(data_size);
|
||||
uint8_t *w = dst_data.ptrw();
|
||||
|
||||
for (int i = 0; i < data_size; i++) {
|
||||
int8_t v = CLAMP(current_instance->recording_data[i] * 128, -128, 127);
|
||||
w[i] = v;
|
||||
}
|
||||
} else if (dst_format == AudioStreamWAV::FORMAT_16_BITS) {
|
||||
int data_size = current_instance->recording_data.size();
|
||||
dst_data.resize(data_size * 2);
|
||||
uint8_t *w = dst_data.ptrw();
|
||||
|
||||
for (int i = 0; i < data_size; i++) {
|
||||
int16_t v = CLAMP(current_instance->recording_data[i] * 32768, -32768, 32767);
|
||||
encode_uint16(v, &w[i * 2]);
|
||||
}
|
||||
} else if (dst_format == AudioStreamWAV::FORMAT_IMA_ADPCM) {
|
||||
//byte interleave
|
||||
Vector<float> left;
|
||||
Vector<float> right;
|
||||
|
||||
int tframes = current_instance->recording_data.size() / 2;
|
||||
left.resize(tframes);
|
||||
right.resize(tframes);
|
||||
|
||||
for (int i = 0; i < tframes; i++) {
|
||||
left.set(i, current_instance->recording_data[i * 2 + 0]);
|
||||
right.set(i, current_instance->recording_data[i * 2 + 1]);
|
||||
}
|
||||
|
||||
Vector<uint8_t> bleft;
|
||||
Vector<uint8_t> bright;
|
||||
|
||||
AudioStreamWAV::_compress_ima_adpcm(left, bleft);
|
||||
AudioStreamWAV::_compress_ima_adpcm(right, bright);
|
||||
|
||||
int dl = bleft.size();
|
||||
dst_data.resize(dl * 2);
|
||||
|
||||
uint8_t *w = dst_data.ptrw();
|
||||
const uint8_t *rl = bleft.ptr();
|
||||
const uint8_t *rr = bright.ptr();
|
||||
|
||||
for (int i = 0; i < dl; i++) {
|
||||
w[i * 2 + 0] = rl[i];
|
||||
w[i * 2 + 1] = rr[i];
|
||||
}
|
||||
} else if (dst_format == AudioStreamWAV::FORMAT_QOA) {
|
||||
qoa_desc desc = {};
|
||||
desc.samples = current_instance->recording_data.size() / 2;
|
||||
desc.samplerate = AudioServer::get_singleton()->get_mix_rate();
|
||||
desc.channels = 2;
|
||||
AudioStreamWAV::_compress_qoa(current_instance->recording_data, dst_data, &desc);
|
||||
} else {
|
||||
ERR_PRINT("Format not implemented.");
|
||||
}
|
||||
|
||||
Ref<AudioStreamWAV> sample;
|
||||
sample.instantiate();
|
||||
sample->set_data(dst_data);
|
||||
sample->set_format(dst_format);
|
||||
sample->set_mix_rate(AudioServer::get_singleton()->get_mix_rate());
|
||||
sample->set_loop_mode(AudioStreamWAV::LOOP_DISABLED);
|
||||
sample->set_loop_begin(0);
|
||||
sample->set_loop_end(0);
|
||||
sample->set_stereo(stereo);
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
void AudioEffectRecord::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_recording_active", "record"), &AudioEffectRecord::set_recording_active);
|
||||
ClassDB::bind_method(D_METHOD("is_recording_active"), &AudioEffectRecord::is_recording_active);
|
||||
ClassDB::bind_method(D_METHOD("set_format", "format"), &AudioEffectRecord::set_format);
|
||||
ClassDB::bind_method(D_METHOD("get_format"), &AudioEffectRecord::get_format);
|
||||
ClassDB::bind_method(D_METHOD("get_recording"), &AudioEffectRecord::get_recording);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA ADPCM,Quite OK Audio"), "set_format", "get_format");
|
||||
}
|
||||
|
||||
AudioEffectRecord::AudioEffectRecord() {
|
||||
format = AudioStreamWAV::FORMAT_16_BITS;
|
||||
}
|
||||
|
||||
AudioEffectRecord::~AudioEffectRecord() {
|
||||
ensure_thread_stopped();
|
||||
}
|
95
servers/audio/effects/audio_effect_record.h
Normal file
95
servers/audio/effects/audio_effect_record.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_record.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 "core/os/thread.h"
|
||||
#include "scene/resources/audio_stream_wav.h"
|
||||
#include "servers/audio/audio_effect.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
class AudioEffectRecord;
|
||||
|
||||
class AudioEffectRecordInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectRecordInstance, AudioEffectInstance);
|
||||
friend class AudioEffectRecord;
|
||||
|
||||
bool is_recording;
|
||||
Thread io_thread;
|
||||
|
||||
Vector<AudioFrame> ring_buffer;
|
||||
Vector<float> recording_data;
|
||||
|
||||
unsigned int ring_buffer_pos;
|
||||
unsigned int ring_buffer_mask;
|
||||
unsigned int ring_buffer_read_pos;
|
||||
|
||||
void _io_thread_process();
|
||||
void _io_store_buffer();
|
||||
static void _thread_callback(void *_instance);
|
||||
void _init_recording();
|
||||
void _update_buffer();
|
||||
static void _update(void *userdata);
|
||||
|
||||
public:
|
||||
void init();
|
||||
void finish();
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
virtual bool process_silence() const override;
|
||||
};
|
||||
|
||||
class AudioEffectRecord : public AudioEffect {
|
||||
GDCLASS(AudioEffectRecord, AudioEffect);
|
||||
|
||||
friend class AudioEffectRecordInstance;
|
||||
|
||||
enum {
|
||||
IO_BUFFER_SIZE_MS = 1500
|
||||
};
|
||||
|
||||
Ref<AudioEffectRecordInstance> current_instance;
|
||||
|
||||
AudioStreamWAV::Format format;
|
||||
|
||||
void ensure_thread_stopped();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
void set_recording_active(bool p_record);
|
||||
bool is_recording_active() const;
|
||||
void set_format(AudioStreamWAV::Format p_format);
|
||||
AudioStreamWAV::Format get_format() const;
|
||||
Ref<AudioStreamWAV> get_recording() const;
|
||||
AudioEffectRecord();
|
||||
~AudioEffectRecord();
|
||||
};
|
199
servers/audio/effects/audio_effect_reverb.cpp
Normal file
199
servers/audio/effects/audio_effect_reverb.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_reverb.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 "audio_effect_reverb.h"
|
||||
#include "servers/audio_server.h"
|
||||
void AudioEffectReverbInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
Reverb &r = reverb[i];
|
||||
|
||||
r.set_predelay(base->predelay);
|
||||
r.set_predelay_feedback(base->predelay_fb);
|
||||
r.set_highpass(base->hpf);
|
||||
r.set_room_size(base->room_size);
|
||||
r.set_damp(base->damping);
|
||||
r.set_extra_spread(base->spread);
|
||||
r.set_wet(base->wet);
|
||||
r.set_dry(base->dry);
|
||||
}
|
||||
|
||||
int todo = p_frame_count;
|
||||
int offset = 0;
|
||||
|
||||
while (todo) {
|
||||
int to_mix = MIN(todo, Reverb::INPUT_BUFFER_MAX_SIZE);
|
||||
|
||||
for (int j = 0; j < to_mix; j++) {
|
||||
tmp_src[j] = p_src_frames[offset + j].left;
|
||||
}
|
||||
|
||||
reverb[0].process(tmp_src, tmp_dst, to_mix);
|
||||
|
||||
for (int j = 0; j < to_mix; j++) {
|
||||
p_dst_frames[offset + j].left = tmp_dst[j];
|
||||
tmp_src[j] = p_src_frames[offset + j].right;
|
||||
}
|
||||
|
||||
reverb[1].process(tmp_src, tmp_dst, to_mix);
|
||||
|
||||
for (int j = 0; j < to_mix; j++) {
|
||||
p_dst_frames[offset + j].right = tmp_dst[j];
|
||||
}
|
||||
|
||||
offset += to_mix;
|
||||
todo -= to_mix;
|
||||
}
|
||||
}
|
||||
|
||||
AudioEffectReverbInstance::AudioEffectReverbInstance() {
|
||||
reverb[0].set_mix_rate(AudioServer::get_singleton()->get_mix_rate());
|
||||
reverb[0].set_extra_spread_base(0);
|
||||
reverb[1].set_mix_rate(AudioServer::get_singleton()->get_mix_rate());
|
||||
reverb[1].set_extra_spread_base(0.000521); //for stereo effect
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectReverb::instantiate() {
|
||||
Ref<AudioEffectReverbInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectReverb>(this);
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectReverb::set_predelay_msec(float p_msec) {
|
||||
predelay = p_msec;
|
||||
}
|
||||
|
||||
void AudioEffectReverb::set_predelay_feedback(float p_feedback) {
|
||||
predelay_fb = CLAMP(p_feedback, 0, 0.98);
|
||||
}
|
||||
|
||||
void AudioEffectReverb::set_room_size(float p_size) {
|
||||
room_size = p_size;
|
||||
}
|
||||
|
||||
void AudioEffectReverb::set_damping(float p_damping) {
|
||||
damping = p_damping;
|
||||
}
|
||||
|
||||
void AudioEffectReverb::set_spread(float p_spread) {
|
||||
spread = p_spread;
|
||||
}
|
||||
|
||||
void AudioEffectReverb::set_dry(float p_dry) {
|
||||
dry = p_dry;
|
||||
}
|
||||
|
||||
void AudioEffectReverb::set_wet(float p_wet) {
|
||||
wet = p_wet;
|
||||
}
|
||||
|
||||
void AudioEffectReverb::set_hpf(float p_hpf) {
|
||||
hpf = p_hpf;
|
||||
}
|
||||
|
||||
float AudioEffectReverb::get_predelay_msec() const {
|
||||
return predelay;
|
||||
}
|
||||
|
||||
float AudioEffectReverb::get_predelay_feedback() const {
|
||||
return predelay_fb;
|
||||
}
|
||||
|
||||
float AudioEffectReverb::get_room_size() const {
|
||||
return room_size;
|
||||
}
|
||||
|
||||
float AudioEffectReverb::get_damping() const {
|
||||
return damping;
|
||||
}
|
||||
|
||||
float AudioEffectReverb::get_spread() const {
|
||||
return spread;
|
||||
}
|
||||
|
||||
float AudioEffectReverb::get_dry() const {
|
||||
return dry;
|
||||
}
|
||||
|
||||
float AudioEffectReverb::get_wet() const {
|
||||
return wet;
|
||||
}
|
||||
|
||||
float AudioEffectReverb::get_hpf() const {
|
||||
return hpf;
|
||||
}
|
||||
|
||||
void AudioEffectReverb::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_predelay_msec", "msec"), &AudioEffectReverb::set_predelay_msec);
|
||||
ClassDB::bind_method(D_METHOD("get_predelay_msec"), &AudioEffectReverb::get_predelay_msec);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_predelay_feedback", "feedback"), &AudioEffectReverb::set_predelay_feedback);
|
||||
ClassDB::bind_method(D_METHOD("get_predelay_feedback"), &AudioEffectReverb::get_predelay_feedback);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_room_size", "size"), &AudioEffectReverb::set_room_size);
|
||||
ClassDB::bind_method(D_METHOD("get_room_size"), &AudioEffectReverb::get_room_size);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_damping", "amount"), &AudioEffectReverb::set_damping);
|
||||
ClassDB::bind_method(D_METHOD("get_damping"), &AudioEffectReverb::get_damping);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_spread", "amount"), &AudioEffectReverb::set_spread);
|
||||
ClassDB::bind_method(D_METHOD("get_spread"), &AudioEffectReverb::get_spread);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_dry", "amount"), &AudioEffectReverb::set_dry);
|
||||
ClassDB::bind_method(D_METHOD("get_dry"), &AudioEffectReverb::get_dry);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_wet", "amount"), &AudioEffectReverb::set_wet);
|
||||
ClassDB::bind_method(D_METHOD("get_wet"), &AudioEffectReverb::get_wet);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_hpf", "amount"), &AudioEffectReverb::set_hpf);
|
||||
ClassDB::bind_method(D_METHOD("get_hpf"), &AudioEffectReverb::get_hpf);
|
||||
|
||||
ADD_GROUP("Predelay", "predelay_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "predelay_msec", PROPERTY_HINT_RANGE, "20,500,1,suffix:ms"), "set_predelay_msec", "get_predelay_msec");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "predelay_feedback", PROPERTY_HINT_RANGE, "0,0.98,0.01"), "set_predelay_feedback", "get_predelay_feedback");
|
||||
ADD_GROUP("", "");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "room_size", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_room_size", "get_room_size");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_damping", "get_damping");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "spread", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_spread", "get_spread");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "hipass", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_hpf", "get_hpf");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dry", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_dry", "get_dry");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wet", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_wet", "get_wet");
|
||||
}
|
||||
|
||||
AudioEffectReverb::AudioEffectReverb() {
|
||||
predelay = 150;
|
||||
predelay_fb = 0.4;
|
||||
hpf = 0;
|
||||
room_size = 0.8;
|
||||
damping = 0.5;
|
||||
spread = 1.0;
|
||||
dry = 1.0;
|
||||
wet = 0.5;
|
||||
}
|
94
servers/audio/effects/audio_effect_reverb.h
Normal file
94
servers/audio/effects/audio_effect_reverb.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_reverb.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_effect.h"
|
||||
#include "servers/audio/effects/reverb_filter.h"
|
||||
|
||||
class AudioEffectReverb;
|
||||
|
||||
class AudioEffectReverbInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectReverbInstance, AudioEffectInstance);
|
||||
|
||||
Ref<AudioEffectReverb> base;
|
||||
|
||||
float tmp_src[Reverb::INPUT_BUFFER_MAX_SIZE];
|
||||
float tmp_dst[Reverb::INPUT_BUFFER_MAX_SIZE];
|
||||
|
||||
friend class AudioEffectReverb;
|
||||
|
||||
Reverb reverb[2];
|
||||
|
||||
public:
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
AudioEffectReverbInstance();
|
||||
};
|
||||
|
||||
class AudioEffectReverb : public AudioEffect {
|
||||
GDCLASS(AudioEffectReverb, AudioEffect);
|
||||
|
||||
friend class AudioEffectReverbInstance;
|
||||
|
||||
float predelay;
|
||||
float predelay_fb;
|
||||
float hpf;
|
||||
float room_size;
|
||||
float damping;
|
||||
float spread;
|
||||
float dry;
|
||||
float wet;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_predelay_msec(float p_msec);
|
||||
void set_predelay_feedback(float p_feedback);
|
||||
void set_room_size(float p_size);
|
||||
void set_damping(float p_damping);
|
||||
void set_spread(float p_spread);
|
||||
void set_dry(float p_dry);
|
||||
void set_wet(float p_wet);
|
||||
void set_hpf(float p_hpf);
|
||||
|
||||
float get_predelay_msec() const;
|
||||
float get_predelay_feedback() const;
|
||||
float get_room_size() const;
|
||||
float get_damping() const;
|
||||
float get_spread() const;
|
||||
float get_dry() const;
|
||||
float get_wet() const;
|
||||
float get_hpf() const;
|
||||
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
|
||||
AudioEffectReverb();
|
||||
};
|
283
servers/audio/effects/audio_effect_spectrum_analyzer.cpp
Normal file
283
servers/audio/effects/audio_effect_spectrum_analyzer.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_spectrum_analyzer.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 "audio_effect_spectrum_analyzer.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
static void smbFft(float *fftBuffer, long fftFrameSize, long sign)
|
||||
/*
|
||||
FFT routine, (C)1996 S.M.Bernsee. Sign = -1 is FFT, 1 is iFFT (inverse)
|
||||
Fills fftBuffer[0...2*fftFrameSize-1] with the Fourier transform of the
|
||||
time domain data in fftBuffer[0...2*fftFrameSize-1]. The FFT array takes
|
||||
and returns the cosine and sine parts in an interleaved manner, ie.
|
||||
fftBuffer[0] = cosPart[0], fftBuffer[1] = sinPart[0], asf. fftFrameSize
|
||||
must be a power of 2. It expects a complex input signal (see footnote 2),
|
||||
ie. when working with 'common' audio signals our input signal has to be
|
||||
passed as {in[0],0.,in[1],0.,in[2],0.,...} asf. In that case, the transform
|
||||
of the frequencies of interest is in fftBuffer[0...fftFrameSize].
|
||||
*/
|
||||
{
|
||||
float wr, wi, arg, *p1, *p2, temp;
|
||||
float tr, ti, ur, ui, *p1r, *p1i, *p2r, *p2i;
|
||||
long i, bitm, j, le, le2, k;
|
||||
|
||||
for (i = 2; i < 2 * fftFrameSize - 2; i += 2) {
|
||||
for (bitm = 2, j = 0; bitm < 2 * fftFrameSize; bitm <<= 1) {
|
||||
if (i & bitm) {
|
||||
j++;
|
||||
}
|
||||
j <<= 1;
|
||||
}
|
||||
if (i < j) {
|
||||
p1 = fftBuffer + i;
|
||||
p2 = fftBuffer + j;
|
||||
temp = *p1;
|
||||
*(p1++) = *p2;
|
||||
*(p2++) = temp;
|
||||
temp = *p1;
|
||||
*p1 = *p2;
|
||||
*p2 = temp;
|
||||
}
|
||||
}
|
||||
for (k = 0, le = 2; k < (long)(std::log((double)fftFrameSize) / std::log(2.) + .5); k++) {
|
||||
le <<= 1;
|
||||
le2 = le >> 1;
|
||||
ur = 1.0;
|
||||
ui = 0.0;
|
||||
arg = Math::PI / (le2 >> 1);
|
||||
wr = std::cos(arg);
|
||||
wi = sign * std::sin(arg);
|
||||
for (j = 0; j < le2; j += 2) {
|
||||
p1r = fftBuffer + j;
|
||||
p1i = p1r + 1;
|
||||
p2r = p1r + le2;
|
||||
p2i = p2r + 1;
|
||||
for (i = j; i < 2 * fftFrameSize; i += le) {
|
||||
tr = *p2r * ur - *p2i * ui;
|
||||
ti = *p2r * ui + *p2i * ur;
|
||||
*p2r = *p1r - tr;
|
||||
*p2i = *p1i - ti;
|
||||
*p1r += tr;
|
||||
*p1i += ti;
|
||||
p1r += le;
|
||||
p1i += le;
|
||||
p2r += le;
|
||||
p2i += le;
|
||||
}
|
||||
tr = ur * wr - ui * wi;
|
||||
ui = ur * wi + ui * wr;
|
||||
ur = tr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEffectSpectrumAnalyzerInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
uint64_t time = OS::get_singleton()->get_ticks_usec();
|
||||
|
||||
//copy everything over first, since this only really does capture
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
p_dst_frames[i] = p_src_frames[i];
|
||||
}
|
||||
|
||||
//capture spectrum
|
||||
while (p_frame_count) {
|
||||
int to_fill = fft_size * 2 - temporal_fft_pos;
|
||||
to_fill = MIN(to_fill, p_frame_count);
|
||||
const double to_fill_step = Math::TAU / (double)fft_size;
|
||||
|
||||
float *fftw = temporal_fft.ptrw();
|
||||
for (int i = 0; i < to_fill; i++) { //left and right buffers
|
||||
float window = -0.5 * Math::cos(to_fill_step * (double)temporal_fft_pos) + 0.5;
|
||||
fftw[temporal_fft_pos * 2] = window * p_src_frames->left;
|
||||
fftw[temporal_fft_pos * 2 + 1] = 0;
|
||||
fftw[(temporal_fft_pos + fft_size * 2) * 2] = window * p_src_frames->right;
|
||||
fftw[(temporal_fft_pos + fft_size * 2) * 2 + 1] = 0;
|
||||
++p_src_frames;
|
||||
++temporal_fft_pos;
|
||||
}
|
||||
|
||||
p_frame_count -= to_fill;
|
||||
|
||||
if (temporal_fft_pos == fft_size * 2) {
|
||||
//time to do a FFT
|
||||
smbFft(fftw, fft_size * 2, -1);
|
||||
smbFft(fftw + fft_size * 4, fft_size * 2, -1);
|
||||
int next = (fft_pos + 1) % fft_count;
|
||||
|
||||
AudioFrame *hw = (AudioFrame *)fft_history[next].ptr(); //do not use write, avoid cow
|
||||
|
||||
for (int i = 0; i < fft_size; i++) {
|
||||
//abs(vec)/fft_size normalizes each frequency
|
||||
hw[i].left = Vector2(fftw[i * 2], fftw[i * 2 + 1]).length() / float(fft_size);
|
||||
hw[i].right = Vector2(fftw[fft_size * 4 + i * 2], fftw[fft_size * 4 + i * 2 + 1]).length() / float(fft_size);
|
||||
}
|
||||
|
||||
fft_pos = next; //swap
|
||||
temporal_fft_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//determine time of capture
|
||||
double remainder_sec = (temporal_fft_pos / mix_rate); //subtract remainder from mix time
|
||||
last_fft_time = time - uint64_t(remainder_sec * 1000000.0);
|
||||
}
|
||||
|
||||
void AudioEffectSpectrumAnalyzerInstance::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_magnitude_for_frequency_range", "from_hz", "to_hz", "mode"), &AudioEffectSpectrumAnalyzerInstance::get_magnitude_for_frequency_range, DEFVAL(MAGNITUDE_MAX));
|
||||
BIND_ENUM_CONSTANT(MAGNITUDE_AVERAGE);
|
||||
BIND_ENUM_CONSTANT(MAGNITUDE_MAX);
|
||||
}
|
||||
|
||||
Vector2 AudioEffectSpectrumAnalyzerInstance::get_magnitude_for_frequency_range(float p_begin, float p_end, MagnitudeMode p_mode) const {
|
||||
if (last_fft_time == 0) {
|
||||
return Vector2();
|
||||
}
|
||||
uint64_t time = OS::get_singleton()->get_ticks_usec();
|
||||
float diff = double(time - last_fft_time) / 1000000.0 + base->get_tap_back_pos();
|
||||
diff -= AudioServer::get_singleton()->get_output_latency();
|
||||
float fft_time_size = float(fft_size) / mix_rate;
|
||||
|
||||
int fft_index = fft_pos;
|
||||
|
||||
while (diff > fft_time_size) {
|
||||
diff -= fft_time_size;
|
||||
fft_index -= 1;
|
||||
if (fft_index < 0) {
|
||||
fft_index = fft_count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
int begin_pos = p_begin * fft_size / (mix_rate * 0.5);
|
||||
int end_pos = p_end * fft_size / (mix_rate * 0.5);
|
||||
|
||||
begin_pos = CLAMP(begin_pos, 0, fft_size - 1);
|
||||
end_pos = CLAMP(end_pos, 0, fft_size - 1);
|
||||
|
||||
if (begin_pos > end_pos) {
|
||||
SWAP(begin_pos, end_pos);
|
||||
}
|
||||
const AudioFrame *r = fft_history[fft_index].ptr();
|
||||
|
||||
if (p_mode == MAGNITUDE_AVERAGE) {
|
||||
Vector2 avg;
|
||||
|
||||
for (int i = begin_pos; i <= end_pos; i++) {
|
||||
avg += Vector2(r[i]);
|
||||
}
|
||||
|
||||
avg /= float(end_pos - begin_pos + 1);
|
||||
|
||||
return avg;
|
||||
} else {
|
||||
Vector2 max;
|
||||
|
||||
for (int i = begin_pos; i <= end_pos; i++) {
|
||||
max.x = MAX(max.x, r[i].left);
|
||||
max.y = MAX(max.y, r[i].right);
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectSpectrumAnalyzer::instantiate() {
|
||||
Ref<AudioEffectSpectrumAnalyzerInstance> ins;
|
||||
ins.instantiate();
|
||||
ins->base = Ref<AudioEffectSpectrumAnalyzer>(this);
|
||||
static const int fft_sizes[FFT_SIZE_MAX] = { 256, 512, 1024, 2048, 4096 };
|
||||
ins->fft_size = fft_sizes[fft_size];
|
||||
ins->mix_rate = AudioServer::get_singleton()->get_mix_rate();
|
||||
ins->fft_count = (buffer_length / (float(ins->fft_size) / ins->mix_rate)) + 1;
|
||||
ins->fft_pos = 0;
|
||||
ins->last_fft_time = 0;
|
||||
ins->fft_history.resize(ins->fft_count);
|
||||
ins->temporal_fft.resize(ins->fft_size * 8); //x2 stereo, x2 amount of samples for freqs, x2 for input
|
||||
ins->temporal_fft_pos = 0;
|
||||
for (int i = 0; i < ins->fft_count; i++) {
|
||||
ins->fft_history.write[i].resize(ins->fft_size); //only magnitude matters
|
||||
for (int j = 0; j < ins->fft_size; j++) {
|
||||
ins->fft_history.write[i].write[j] = AudioFrame(0, 0);
|
||||
}
|
||||
}
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectSpectrumAnalyzer::set_buffer_length(float p_seconds) {
|
||||
buffer_length = p_seconds;
|
||||
}
|
||||
|
||||
float AudioEffectSpectrumAnalyzer::get_buffer_length() const {
|
||||
return buffer_length;
|
||||
}
|
||||
|
||||
void AudioEffectSpectrumAnalyzer::set_tap_back_pos(float p_seconds) {
|
||||
tapback_pos = p_seconds;
|
||||
}
|
||||
|
||||
float AudioEffectSpectrumAnalyzer::get_tap_back_pos() const {
|
||||
return tapback_pos;
|
||||
}
|
||||
|
||||
void AudioEffectSpectrumAnalyzer::set_fft_size(FFTSize p_fft_size) {
|
||||
ERR_FAIL_INDEX(p_fft_size, FFT_SIZE_MAX);
|
||||
fft_size = p_fft_size;
|
||||
}
|
||||
|
||||
AudioEffectSpectrumAnalyzer::FFTSize AudioEffectSpectrumAnalyzer::get_fft_size() const {
|
||||
return fft_size;
|
||||
}
|
||||
|
||||
void AudioEffectSpectrumAnalyzer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_buffer_length", "seconds"), &AudioEffectSpectrumAnalyzer::set_buffer_length);
|
||||
ClassDB::bind_method(D_METHOD("get_buffer_length"), &AudioEffectSpectrumAnalyzer::get_buffer_length);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_tap_back_pos", "seconds"), &AudioEffectSpectrumAnalyzer::set_tap_back_pos);
|
||||
ClassDB::bind_method(D_METHOD("get_tap_back_pos"), &AudioEffectSpectrumAnalyzer::get_tap_back_pos);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_fft_size", "size"), &AudioEffectSpectrumAnalyzer::set_fft_size);
|
||||
ClassDB::bind_method(D_METHOD("get_fft_size"), &AudioEffectSpectrumAnalyzer::get_fft_size);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "buffer_length", PROPERTY_HINT_RANGE, "0.1,4,0.1,suffix:s"), "set_buffer_length", "get_buffer_length");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tap_back_pos", PROPERTY_HINT_RANGE, "0.1,4,0.1"), "set_tap_back_pos", "get_tap_back_pos");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "fft_size", PROPERTY_HINT_ENUM, "256,512,1024,2048,4096"), "set_fft_size", "get_fft_size");
|
||||
|
||||
BIND_ENUM_CONSTANT(FFT_SIZE_256);
|
||||
BIND_ENUM_CONSTANT(FFT_SIZE_512);
|
||||
BIND_ENUM_CONSTANT(FFT_SIZE_1024);
|
||||
BIND_ENUM_CONSTANT(FFT_SIZE_2048);
|
||||
BIND_ENUM_CONSTANT(FFT_SIZE_4096);
|
||||
BIND_ENUM_CONSTANT(FFT_SIZE_MAX);
|
||||
}
|
||||
|
||||
AudioEffectSpectrumAnalyzer::AudioEffectSpectrumAnalyzer() {
|
||||
buffer_length = 2;
|
||||
tapback_pos = 0.01;
|
||||
fft_size = FFT_SIZE_1024;
|
||||
}
|
104
servers/audio/effects/audio_effect_spectrum_analyzer.h
Normal file
104
servers/audio/effects/audio_effect_spectrum_analyzer.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_spectrum_analyzer.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_effect.h"
|
||||
|
||||
class AudioEffectSpectrumAnalyzer;
|
||||
|
||||
class AudioEffectSpectrumAnalyzerInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectSpectrumAnalyzerInstance, AudioEffectInstance);
|
||||
|
||||
public:
|
||||
enum MagnitudeMode {
|
||||
MAGNITUDE_AVERAGE,
|
||||
MAGNITUDE_MAX,
|
||||
};
|
||||
|
||||
private:
|
||||
friend class AudioEffectSpectrumAnalyzer;
|
||||
Ref<AudioEffectSpectrumAnalyzer> base;
|
||||
|
||||
Vector<Vector<AudioFrame>> fft_history;
|
||||
Vector<float> temporal_fft;
|
||||
int temporal_fft_pos;
|
||||
int fft_size;
|
||||
int fft_count;
|
||||
int fft_pos;
|
||||
float mix_rate;
|
||||
uint64_t last_fft_time;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
Vector2 get_magnitude_for_frequency_range(float p_begin, float p_end, MagnitudeMode p_mode = MAGNITUDE_MAX) const;
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AudioEffectSpectrumAnalyzerInstance::MagnitudeMode)
|
||||
|
||||
class AudioEffectSpectrumAnalyzer : public AudioEffect {
|
||||
GDCLASS(AudioEffectSpectrumAnalyzer, AudioEffect);
|
||||
|
||||
public:
|
||||
enum FFTSize {
|
||||
FFT_SIZE_256,
|
||||
FFT_SIZE_512,
|
||||
FFT_SIZE_1024,
|
||||
FFT_SIZE_2048,
|
||||
FFT_SIZE_4096,
|
||||
FFT_SIZE_MAX
|
||||
};
|
||||
|
||||
public:
|
||||
friend class AudioEffectSpectrumAnalyzerInstance;
|
||||
float buffer_length;
|
||||
float tapback_pos;
|
||||
FFTSize fft_size;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
void set_buffer_length(float p_seconds);
|
||||
float get_buffer_length() const;
|
||||
void set_tap_back_pos(float p_seconds);
|
||||
float get_tap_back_pos() const;
|
||||
|
||||
void set_fft_size(FFTSize);
|
||||
FFTSize get_fft_size() const;
|
||||
|
||||
AudioEffectSpectrumAnalyzer();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AudioEffectSpectrumAnalyzer::FFTSize);
|
145
servers/audio/effects/audio_effect_stereo_enhance.cpp
Normal file
145
servers/audio/effects/audio_effect_stereo_enhance.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_stereo_enhance.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 "audio_effect_stereo_enhance.h"
|
||||
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
void AudioEffectStereoEnhanceInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
|
||||
float intensity = base->pan_pullout;
|
||||
bool surround_mode = base->surround > 0;
|
||||
float surround_amount = base->surround;
|
||||
unsigned int delay_frames = (base->time_pullout / 1000.0) * AudioServer::get_singleton()->get_mix_rate();
|
||||
|
||||
for (int i = 0; i < p_frame_count; i++) {
|
||||
float left = p_src_frames[i].left;
|
||||
float right = p_src_frames[i].right;
|
||||
|
||||
float center = (left + right) / 2.0f;
|
||||
|
||||
left = (center + (left - center) * intensity);
|
||||
right = (center + (right - center) * intensity);
|
||||
|
||||
if (surround_mode) {
|
||||
float val = (left + right) / 2.0;
|
||||
|
||||
delay_ringbuff[ringbuff_pos & ringbuff_mask] = val;
|
||||
|
||||
float out = delay_ringbuff[(ringbuff_pos - delay_frames) & ringbuff_mask] * surround_amount;
|
||||
|
||||
left += out;
|
||||
right += -out;
|
||||
} else {
|
||||
float val = right;
|
||||
|
||||
delay_ringbuff[ringbuff_pos & ringbuff_mask] = val;
|
||||
|
||||
// The right channel is delayed.
|
||||
right = delay_ringbuff[(ringbuff_pos - delay_frames) & ringbuff_mask];
|
||||
}
|
||||
|
||||
p_dst_frames[i].left = left;
|
||||
p_dst_frames[i].right = right;
|
||||
ringbuff_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
AudioEffectStereoEnhanceInstance::~AudioEffectStereoEnhanceInstance() {
|
||||
memdelete_arr(delay_ringbuff);
|
||||
}
|
||||
|
||||
Ref<AudioEffectInstance> AudioEffectStereoEnhance::instantiate() {
|
||||
Ref<AudioEffectStereoEnhanceInstance> ins;
|
||||
ins.instantiate();
|
||||
|
||||
ins->base = Ref<AudioEffectStereoEnhance>(this);
|
||||
|
||||
float ring_buffer_max_size = AudioEffectStereoEnhanceInstance::MAX_DELAY_MS + 2;
|
||||
ring_buffer_max_size /= 1000.0; //convert to seconds
|
||||
ring_buffer_max_size *= AudioServer::get_singleton()->get_mix_rate();
|
||||
|
||||
int ringbuff_size = (int)ring_buffer_max_size;
|
||||
|
||||
int bits = 0;
|
||||
|
||||
while (ringbuff_size > 0) {
|
||||
bits++;
|
||||
ringbuff_size /= 2;
|
||||
}
|
||||
|
||||
ringbuff_size = 1 << bits;
|
||||
ins->ringbuff_mask = ringbuff_size - 1;
|
||||
ins->ringbuff_pos = 0;
|
||||
|
||||
ins->delay_ringbuff = memnew_arr(float, ringbuff_size);
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
void AudioEffectStereoEnhance::set_pan_pullout(float p_amount) {
|
||||
pan_pullout = p_amount;
|
||||
}
|
||||
|
||||
float AudioEffectStereoEnhance::get_pan_pullout() const {
|
||||
return pan_pullout;
|
||||
}
|
||||
|
||||
void AudioEffectStereoEnhance::set_time_pullout(float p_amount) {
|
||||
time_pullout = p_amount;
|
||||
}
|
||||
|
||||
float AudioEffectStereoEnhance::get_time_pullout() const {
|
||||
return time_pullout;
|
||||
}
|
||||
|
||||
void AudioEffectStereoEnhance::set_surround(float p_amount) {
|
||||
surround = p_amount;
|
||||
}
|
||||
|
||||
float AudioEffectStereoEnhance::get_surround() const {
|
||||
return surround;
|
||||
}
|
||||
|
||||
void AudioEffectStereoEnhance::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_pan_pullout", "amount"), &AudioEffectStereoEnhance::set_pan_pullout);
|
||||
ClassDB::bind_method(D_METHOD("get_pan_pullout"), &AudioEffectStereoEnhance::get_pan_pullout);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_time_pullout", "amount"), &AudioEffectStereoEnhance::set_time_pullout);
|
||||
ClassDB::bind_method(D_METHOD("get_time_pullout"), &AudioEffectStereoEnhance::get_time_pullout);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_surround", "amount"), &AudioEffectStereoEnhance::set_surround);
|
||||
ClassDB::bind_method(D_METHOD("get_surround"), &AudioEffectStereoEnhance::get_surround);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pan_pullout", PROPERTY_HINT_RANGE, "0,4,0.01"), "set_pan_pullout", "get_pan_pullout");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_pullout_ms", PROPERTY_HINT_RANGE, "0,50,0.01,suffix:ms"), "set_time_pullout", "get_time_pullout");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "surround", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_surround", "get_surround");
|
||||
}
|
||||
|
||||
AudioEffectStereoEnhance::AudioEffectStereoEnhance() {}
|
82
servers/audio/effects/audio_effect_stereo_enhance.h
Normal file
82
servers/audio/effects/audio_effect_stereo_enhance.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/**************************************************************************/
|
||||
/* audio_effect_stereo_enhance.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_effect.h"
|
||||
|
||||
class AudioEffectStereoEnhance;
|
||||
|
||||
class AudioEffectStereoEnhanceInstance : public AudioEffectInstance {
|
||||
GDCLASS(AudioEffectStereoEnhanceInstance, AudioEffectInstance);
|
||||
friend class AudioEffectStereoEnhance;
|
||||
Ref<AudioEffectStereoEnhance> base;
|
||||
|
||||
enum {
|
||||
MAX_DELAY_MS = 50
|
||||
};
|
||||
|
||||
float *delay_ringbuff = nullptr;
|
||||
unsigned int ringbuff_pos = 0;
|
||||
unsigned int ringbuff_mask = 0;
|
||||
|
||||
public:
|
||||
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override;
|
||||
|
||||
~AudioEffectStereoEnhanceInstance();
|
||||
};
|
||||
|
||||
class AudioEffectStereoEnhance : public AudioEffect {
|
||||
GDCLASS(AudioEffectStereoEnhance, AudioEffect);
|
||||
|
||||
friend class AudioEffectStereoEnhanceInstance;
|
||||
float volume_db = 0.0f;
|
||||
|
||||
float pan_pullout = 1.0f;
|
||||
float time_pullout = 0.0f;
|
||||
float surround = 0.0f;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Ref<AudioEffectInstance> instantiate() override;
|
||||
|
||||
void set_pan_pullout(float p_amount);
|
||||
float get_pan_pullout() const;
|
||||
|
||||
void set_time_pullout(float p_amount);
|
||||
float get_time_pullout() const;
|
||||
|
||||
void set_surround(float p_amount);
|
||||
float get_surround() const;
|
||||
|
||||
AudioEffectStereoEnhance();
|
||||
};
|
244
servers/audio/effects/audio_stream_generator.cpp
Normal file
244
servers/audio/effects/audio_stream_generator.cpp
Normal file
@@ -0,0 +1,244 @@
|
||||
/**************************************************************************/
|
||||
/* audio_stream_generator.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 "audio_stream_generator.h"
|
||||
|
||||
void AudioStreamGenerator::set_mix_rate(float p_mix_rate) {
|
||||
mix_rate = p_mix_rate;
|
||||
}
|
||||
|
||||
float AudioStreamGenerator::get_mix_rate() const {
|
||||
return mix_rate;
|
||||
}
|
||||
|
||||
void AudioStreamGenerator::set_mix_rate_mode(AudioStreamGenerator::AudioStreamGeneratorMixRate p_mix_rate_mode) {
|
||||
ERR_FAIL_INDEX(p_mix_rate_mode, AudioStreamGeneratorMixRate::MIX_RATE_MAX);
|
||||
mix_rate_mode = p_mix_rate_mode;
|
||||
}
|
||||
|
||||
AudioStreamGenerator::AudioStreamGeneratorMixRate AudioStreamGenerator::get_mix_rate_mode() const {
|
||||
return mix_rate_mode;
|
||||
}
|
||||
|
||||
void AudioStreamGenerator::set_buffer_length(float p_seconds) {
|
||||
buffer_len = p_seconds;
|
||||
}
|
||||
|
||||
float AudioStreamGenerator::get_buffer_length() const {
|
||||
return buffer_len;
|
||||
}
|
||||
|
||||
float AudioStreamGenerator::_get_target_rate() const {
|
||||
switch (mix_rate_mode) {
|
||||
case AudioStreamGeneratorMixRate::MIX_RATE_OUTPUT:
|
||||
return AudioServer::get_singleton()->get_mix_rate();
|
||||
case AudioStreamGeneratorMixRate::MIX_RATE_INPUT:
|
||||
return AudioServer::get_singleton()->get_input_mix_rate();
|
||||
default:
|
||||
return mix_rate;
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AudioStreamPlayback> AudioStreamGenerator::instantiate_playback() {
|
||||
Ref<AudioStreamGeneratorPlayback> playback;
|
||||
playback.instantiate();
|
||||
playback->generator = this;
|
||||
uint32_t target_buffer_size = _get_target_rate() * buffer_len;
|
||||
playback->buffer.resize(nearest_shift(target_buffer_size));
|
||||
playback->buffer.clear();
|
||||
return playback;
|
||||
}
|
||||
|
||||
String AudioStreamGenerator::get_stream_name() const {
|
||||
return "UserFeed";
|
||||
}
|
||||
|
||||
double AudioStreamGenerator::get_length() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AudioStreamGenerator::is_monophonic() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioStreamGenerator::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_mix_rate", "hz"), &AudioStreamGenerator::set_mix_rate);
|
||||
ClassDB::bind_method(D_METHOD("get_mix_rate"), &AudioStreamGenerator::get_mix_rate);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_mix_rate_mode", "mode"), &AudioStreamGenerator::set_mix_rate_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_mix_rate_mode"), &AudioStreamGenerator::get_mix_rate_mode);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_buffer_length", "seconds"), &AudioStreamGenerator::set_buffer_length);
|
||||
ClassDB::bind_method(D_METHOD("get_buffer_length"), &AudioStreamGenerator::get_buffer_length);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_rate_mode", PROPERTY_HINT_ENUM, "System Output Rate,System Input Rate,Custom Rate"), "set_mix_rate_mode", "get_mix_rate_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mix_rate", PROPERTY_HINT_RANGE, "20,192000,1,suffix:Hz"), "set_mix_rate", "get_mix_rate");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "buffer_length", PROPERTY_HINT_RANGE, "0.01,10,0.01,suffix:s"), "set_buffer_length", "get_buffer_length");
|
||||
|
||||
BIND_ENUM_CONSTANT(MIX_RATE_OUTPUT);
|
||||
BIND_ENUM_CONSTANT(MIX_RATE_INPUT);
|
||||
BIND_ENUM_CONSTANT(MIX_RATE_CUSTOM);
|
||||
BIND_ENUM_CONSTANT(MIX_RATE_MAX);
|
||||
}
|
||||
|
||||
////////////////
|
||||
|
||||
bool AudioStreamGeneratorPlayback::push_frame(const Vector2 &p_frame) {
|
||||
if (buffer.space_left() < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioFrame f = p_frame;
|
||||
|
||||
buffer.write(&f, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioStreamGeneratorPlayback::can_push_buffer(int p_frames) const {
|
||||
return buffer.space_left() >= p_frames;
|
||||
}
|
||||
|
||||
bool AudioStreamGeneratorPlayback::push_buffer(const PackedVector2Array &p_frames) {
|
||||
int to_write = p_frames.size();
|
||||
if (buffer.space_left() < to_write) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Vector2 *r = p_frames.ptr();
|
||||
if constexpr (sizeof(real_t) == 4) {
|
||||
//write directly
|
||||
buffer.write((const AudioFrame *)r, to_write);
|
||||
} else {
|
||||
//convert from double
|
||||
AudioFrame buf[2048];
|
||||
int ofs = 0;
|
||||
while (to_write) {
|
||||
int w = MIN(to_write, 2048);
|
||||
for (int i = 0; i < w; i++) {
|
||||
buf[i] = r[i + ofs];
|
||||
}
|
||||
buffer.write(buf, w);
|
||||
ofs += w;
|
||||
to_write -= w;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int AudioStreamGeneratorPlayback::get_frames_available() const {
|
||||
return buffer.space_left();
|
||||
}
|
||||
|
||||
int AudioStreamGeneratorPlayback::get_skips() const {
|
||||
return skips;
|
||||
}
|
||||
|
||||
void AudioStreamGeneratorPlayback::clear_buffer() {
|
||||
ERR_FAIL_COND(active);
|
||||
buffer.clear();
|
||||
mixed = 0;
|
||||
}
|
||||
|
||||
int AudioStreamGeneratorPlayback::_mix_internal(AudioFrame *p_buffer, int p_frames) {
|
||||
if (!active) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int read_amount = buffer.data_left();
|
||||
if (p_frames < read_amount) {
|
||||
read_amount = p_frames;
|
||||
}
|
||||
|
||||
buffer.read(p_buffer, read_amount);
|
||||
|
||||
if (read_amount < p_frames) {
|
||||
// Fill with zeros as fallback in case of buffer underrun.
|
||||
for (int i = read_amount; i < p_frames; i++) {
|
||||
p_buffer[i] = AudioFrame(0, 0);
|
||||
}
|
||||
skips++;
|
||||
}
|
||||
|
||||
mixed += p_frames / generator->_get_target_rate();
|
||||
return p_frames;
|
||||
}
|
||||
|
||||
float AudioStreamGeneratorPlayback::get_stream_sampling_rate() {
|
||||
return generator->_get_target_rate();
|
||||
}
|
||||
|
||||
void AudioStreamGeneratorPlayback::start(double p_from_pos) {
|
||||
if (mixed == 0.0) {
|
||||
begin_resample();
|
||||
}
|
||||
skips = 0;
|
||||
active = true;
|
||||
mixed = 0.0;
|
||||
}
|
||||
|
||||
void AudioStreamGeneratorPlayback::stop() {
|
||||
active = false;
|
||||
}
|
||||
|
||||
bool AudioStreamGeneratorPlayback::is_playing() const {
|
||||
return active;
|
||||
}
|
||||
|
||||
int AudioStreamGeneratorPlayback::get_loop_count() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double AudioStreamGeneratorPlayback::get_playback_position() const {
|
||||
return mixed;
|
||||
}
|
||||
|
||||
void AudioStreamGeneratorPlayback::seek(double p_time) {
|
||||
//no seek possible
|
||||
}
|
||||
|
||||
void AudioStreamGeneratorPlayback::tag_used_streams() {
|
||||
generator->tag_used(0);
|
||||
}
|
||||
|
||||
void AudioStreamGeneratorPlayback::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("push_frame", "frame"), &AudioStreamGeneratorPlayback::push_frame);
|
||||
ClassDB::bind_method(D_METHOD("can_push_buffer", "amount"), &AudioStreamGeneratorPlayback::can_push_buffer);
|
||||
ClassDB::bind_method(D_METHOD("push_buffer", "frames"), &AudioStreamGeneratorPlayback::push_buffer);
|
||||
ClassDB::bind_method(D_METHOD("get_frames_available"), &AudioStreamGeneratorPlayback::get_frames_available);
|
||||
ClassDB::bind_method(D_METHOD("get_skips"), &AudioStreamGeneratorPlayback::get_skips);
|
||||
ClassDB::bind_method(D_METHOD("clear_buffer"), &AudioStreamGeneratorPlayback::clear_buffer);
|
||||
}
|
||||
|
||||
AudioStreamGeneratorPlayback::AudioStreamGeneratorPlayback() {
|
||||
generator = nullptr;
|
||||
skips = 0;
|
||||
active = false;
|
||||
mixed = 0;
|
||||
}
|
113
servers/audio/effects/audio_stream_generator.h
Normal file
113
servers/audio/effects/audio_stream_generator.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/**************************************************************************/
|
||||
/* audio_stream_generator.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 "core/templates/ring_buffer.h"
|
||||
#include "servers/audio/audio_stream.h"
|
||||
|
||||
class AudioStreamGenerator : public AudioStream {
|
||||
GDCLASS(AudioStreamGenerator, AudioStream);
|
||||
|
||||
public:
|
||||
enum AudioStreamGeneratorMixRate {
|
||||
MIX_RATE_OUTPUT,
|
||||
MIX_RATE_INPUT,
|
||||
MIX_RATE_CUSTOM,
|
||||
MIX_RATE_MAX,
|
||||
};
|
||||
|
||||
private:
|
||||
AudioStreamGeneratorMixRate mix_rate_mode = MIX_RATE_CUSTOM;
|
||||
float mix_rate = 44100;
|
||||
float buffer_len = 0.5;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
float _get_target_rate() const;
|
||||
|
||||
void set_mix_rate(float p_mix_rate);
|
||||
float get_mix_rate() const;
|
||||
|
||||
void set_mix_rate_mode(AudioStreamGeneratorMixRate p_mix_rate_mode);
|
||||
AudioStreamGeneratorMixRate get_mix_rate_mode() const;
|
||||
|
||||
void set_buffer_length(float p_seconds);
|
||||
float get_buffer_length() const;
|
||||
|
||||
virtual Ref<AudioStreamPlayback> instantiate_playback() override;
|
||||
virtual String get_stream_name() const override;
|
||||
|
||||
virtual double get_length() const override;
|
||||
virtual bool is_monophonic() const override;
|
||||
AudioStreamGenerator() {}
|
||||
};
|
||||
|
||||
class AudioStreamGeneratorPlayback : public AudioStreamPlaybackResampled {
|
||||
GDCLASS(AudioStreamGeneratorPlayback, AudioStreamPlaybackResampled);
|
||||
friend class AudioStreamGenerator;
|
||||
RingBuffer<AudioFrame> buffer;
|
||||
int skips;
|
||||
bool active;
|
||||
float mixed;
|
||||
AudioStreamGenerator *generator = nullptr;
|
||||
|
||||
protected:
|
||||
virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override;
|
||||
virtual float get_stream_sampling_rate() override;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
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; //times it looped
|
||||
|
||||
virtual double get_playback_position() const override;
|
||||
virtual void seek(double p_time) override;
|
||||
|
||||
bool push_frame(const Vector2 &p_frame);
|
||||
bool can_push_buffer(int p_frames) const;
|
||||
bool push_buffer(const PackedVector2Array &p_frames);
|
||||
int get_frames_available() const;
|
||||
int get_skips() const;
|
||||
|
||||
virtual void tag_used_streams() override;
|
||||
|
||||
void clear_buffer();
|
||||
|
||||
AudioStreamGeneratorPlayback();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AudioStreamGenerator::AudioStreamGeneratorMixRate);
|
201
servers/audio/effects/eq_filter.cpp
Normal file
201
servers/audio/effects/eq_filter.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
/**************************************************************************/
|
||||
/* eq_filter.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 "eq_filter.h"
|
||||
|
||||
#include "core/error/error_macros.h"
|
||||
#include "core/math/math_funcs.h"
|
||||
|
||||
#define POW2(v) ((v) * (v))
|
||||
|
||||
/* Helper */
|
||||
static int solve_quadratic(double a, double b, double c, double *r1, double *r2) {
|
||||
//solves quadractic and returns number of roots
|
||||
|
||||
double base = 2 * a;
|
||||
if (base == 0.0f) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double squared = b * b - 4 * a * c;
|
||||
if (squared < 0.0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
squared = std::sqrt(squared);
|
||||
|
||||
*r1 = (-b + squared) / base;
|
||||
*r2 = (-b - squared) / base;
|
||||
|
||||
if (*r1 == *r2) {
|
||||
return 1;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
EQ::BandProcess::BandProcess() {
|
||||
c1 = c2 = c3 = history.a1 = history.a2 = history.a3 = 0;
|
||||
history.b1 = history.b2 = history.b3 = 0;
|
||||
}
|
||||
|
||||
void EQ::recalculate_band_coefficients() {
|
||||
#define BAND_LOG(m_f) (std::log((m_f)) / std::log(2.))
|
||||
|
||||
for (int i = 0; i < band.size(); i++) {
|
||||
double octave_size;
|
||||
|
||||
double frq = band[i].freq;
|
||||
|
||||
if (i == 0) {
|
||||
octave_size = BAND_LOG(band[1].freq) - BAND_LOG(frq);
|
||||
} else if (i == (band.size() - 1)) {
|
||||
octave_size = BAND_LOG(frq) - BAND_LOG(band[i - 1].freq);
|
||||
} else {
|
||||
double next = BAND_LOG(band[i + 1].freq) - BAND_LOG(frq);
|
||||
double prev = BAND_LOG(frq) - BAND_LOG(band[i - 1].freq);
|
||||
octave_size = (next + prev) / 2.0;
|
||||
}
|
||||
|
||||
double frq_l = std::round(frq / std::pow(2.0, octave_size / 2.0));
|
||||
|
||||
double side_gain2 = POW2(Math::SQRT12);
|
||||
double th = Math::TAU * frq / mix_rate;
|
||||
double th_l = Math::TAU * frq_l / mix_rate;
|
||||
|
||||
double c2a = side_gain2 * POW2(std::cos(th)) - 2.0 * side_gain2 * std::cos(th_l) * std::cos(th) + side_gain2 - POW2(std::sin(th_l));
|
||||
|
||||
double c2b = 2.0 * side_gain2 * POW2(std::cos(th_l)) + side_gain2 * POW2(std::cos(th)) - 2.0 * side_gain2 * std::cos(th_l) * std::cos(th) - side_gain2 + POW2(std::sin(th_l));
|
||||
|
||||
double c2c = 0.25 * side_gain2 * POW2(std::cos(th)) - 0.5 * side_gain2 * std::cos(th_l) * std::cos(th) + 0.25 * side_gain2 - 0.25 * POW2(std::sin(th_l));
|
||||
|
||||
//printf("band %i, precoefs = %f,%f,%f\n",i,c2a,c2b,c2c);
|
||||
|
||||
// Default initializing to silence compiler warning about potential uninitialized use.
|
||||
// Both variables are properly set in _solve_quadratic before use, or we continue if roots == 0.
|
||||
double r1 = 0, r2 = 0; //roots
|
||||
int roots = solve_quadratic(c2a, c2b, c2c, &r1, &r2);
|
||||
|
||||
ERR_CONTINUE(roots == 0);
|
||||
|
||||
band.write[i].c1 = 2.0 * ((0.5 - r1) / 2.0);
|
||||
band.write[i].c2 = 2.0 * r1;
|
||||
band.write[i].c3 = 2.0 * (0.5 + r1) * std::cos(th);
|
||||
//printf("band %i, coefs = %f,%f,%f\n",i,(float)bands[i].c1,(float)bands[i].c2,(float)bands[i].c3);
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::set_preset_band_mode(Preset p_preset) {
|
||||
band.clear();
|
||||
|
||||
#define PUSH_BANDS(m_bands) \
|
||||
for (int i = 0; i < m_bands; i++) { \
|
||||
Band b; \
|
||||
b.freq = bands[i]; \
|
||||
b.c1 = b.c2 = b.c3 = 0; \
|
||||
band.push_back(b); \
|
||||
}
|
||||
|
||||
switch (p_preset) {
|
||||
case PRESET_6_BANDS: {
|
||||
static const double bands[] = { 32, 100, 320, 1e3, 3200, 10e3 };
|
||||
PUSH_BANDS(6);
|
||||
|
||||
} break;
|
||||
|
||||
case PRESET_8_BANDS: {
|
||||
static const double bands[] = { 32, 72, 192, 512, 1200, 3000, 7500, 16e3 };
|
||||
|
||||
PUSH_BANDS(8);
|
||||
} break;
|
||||
|
||||
case PRESET_10_BANDS: {
|
||||
static const double bands[] = { 31.25, 62.5, 125, 250, 500, 1e3, 2e3, 4e3, 8e3, 16e3 };
|
||||
|
||||
PUSH_BANDS(10);
|
||||
|
||||
} break;
|
||||
|
||||
case PRESET_21_BANDS: {
|
||||
static const double bands[] = { 22, 32, 44, 63, 90, 125, 175, 250, 350, 500, 700, 1e3, 1400, 2e3, 2800, 4e3, 5600, 8e3, 11e3, 16e3, 22e3 };
|
||||
PUSH_BANDS(21);
|
||||
|
||||
} break;
|
||||
|
||||
case PRESET_31_BANDS: {
|
||||
static const double bands[] = { 20, 25, 31.5, 40, 50, 63, 80, 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1e3, 1250, 1600, 2e3, 2500, 3150, 4e3, 5e3, 6300, 8e3, 10e3, 12500, 16e3, 20e3 };
|
||||
PUSH_BANDS(31);
|
||||
} break;
|
||||
};
|
||||
|
||||
recalculate_band_coefficients();
|
||||
}
|
||||
|
||||
int EQ::get_band_count() const {
|
||||
return band.size();
|
||||
}
|
||||
|
||||
float EQ::get_band_frequency(int p_band) {
|
||||
ERR_FAIL_INDEX_V(p_band, band.size(), 0);
|
||||
return band[p_band].freq;
|
||||
}
|
||||
|
||||
void EQ::set_bands(const Vector<float> &p_bands) {
|
||||
band.resize(p_bands.size());
|
||||
for (int i = 0; i < p_bands.size(); i++) {
|
||||
band.write[i].freq = p_bands[i];
|
||||
}
|
||||
|
||||
recalculate_band_coefficients();
|
||||
}
|
||||
|
||||
void EQ::set_mix_rate(float p_mix_rate) {
|
||||
mix_rate = p_mix_rate;
|
||||
recalculate_band_coefficients();
|
||||
}
|
||||
|
||||
EQ::BandProcess EQ::get_band_processor(int p_band) const {
|
||||
EQ::BandProcess band_proc;
|
||||
|
||||
ERR_FAIL_INDEX_V(p_band, band.size(), band_proc);
|
||||
|
||||
band_proc.c1 = band[p_band].c1;
|
||||
band_proc.c2 = band[p_band].c2;
|
||||
band_proc.c3 = band[p_band].c3;
|
||||
|
||||
return band_proc;
|
||||
}
|
||||
|
||||
EQ::EQ() {
|
||||
mix_rate = 44100;
|
||||
}
|
||||
|
||||
EQ::~EQ() {
|
||||
}
|
98
servers/audio/effects/eq_filter.h
Normal file
98
servers/audio/effects/eq_filter.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/**************************************************************************/
|
||||
/* eq_filter.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 "core/templates/vector.h"
|
||||
|
||||
class EQ {
|
||||
public:
|
||||
enum Preset {
|
||||
PRESET_6_BANDS,
|
||||
PRESET_8_BANDS,
|
||||
PRESET_10_BANDS,
|
||||
PRESET_21_BANDS,
|
||||
PRESET_31_BANDS
|
||||
};
|
||||
|
||||
class BandProcess {
|
||||
friend class EQ;
|
||||
float c1, c2, c3;
|
||||
struct History {
|
||||
float a1, a2, a3;
|
||||
float b1, b2, b3;
|
||||
|
||||
} history;
|
||||
|
||||
public:
|
||||
inline void process_one(float &p_data);
|
||||
|
||||
BandProcess();
|
||||
};
|
||||
|
||||
private:
|
||||
struct Band {
|
||||
float freq;
|
||||
float c1, c2, c3;
|
||||
};
|
||||
|
||||
Vector<Band> band;
|
||||
|
||||
float mix_rate;
|
||||
|
||||
void recalculate_band_coefficients();
|
||||
|
||||
public:
|
||||
void set_mix_rate(float p_mix_rate);
|
||||
|
||||
int get_band_count() const;
|
||||
void set_preset_band_mode(Preset p_preset);
|
||||
void set_bands(const Vector<float> &p_bands);
|
||||
BandProcess get_band_processor(int p_band) const;
|
||||
float get_band_frequency(int p_band);
|
||||
|
||||
EQ();
|
||||
~EQ();
|
||||
};
|
||||
|
||||
/* Inline Function */
|
||||
|
||||
inline void EQ::BandProcess::process_one(float &p_data) {
|
||||
history.a1 = p_data;
|
||||
|
||||
history.b1 = c1 * (history.a1 - history.a3) + c3 * history.b2 - c2 * history.b3;
|
||||
|
||||
p_data = history.b1;
|
||||
|
||||
history.a3 = history.a2;
|
||||
history.a2 = history.a1;
|
||||
history.b3 = history.b2;
|
||||
history.b2 = history.b1;
|
||||
}
|
340
servers/audio/effects/reverb_filter.cpp
Normal file
340
servers/audio/effects/reverb_filter.cpp
Normal file
@@ -0,0 +1,340 @@
|
||||
/**************************************************************************/
|
||||
/* reverb_filter.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 "reverb_filter.h"
|
||||
|
||||
#include "core/math/audio_frame.h"
|
||||
#include "core/os/memory.h"
|
||||
|
||||
const float Reverb::comb_tunings[MAX_COMBS] = {
|
||||
//freeverb comb tunings
|
||||
0.025306122448979593f,
|
||||
0.026938775510204082f,
|
||||
0.028956916099773241f,
|
||||
0.03074829931972789f,
|
||||
0.032244897959183672f,
|
||||
0.03380952380952381f,
|
||||
0.035306122448979592f,
|
||||
0.036666666666666667f
|
||||
};
|
||||
|
||||
const float Reverb::allpass_tunings[MAX_ALLPASS] = {
|
||||
//freeverb allpass tunings
|
||||
0.0051020408163265302f,
|
||||
0.007732426303854875f,
|
||||
0.01f,
|
||||
0.012607709750566893f
|
||||
};
|
||||
|
||||
void Reverb::process(float *p_src, float *p_dst, int p_frames) {
|
||||
if (p_frames > INPUT_BUFFER_MAX_SIZE) {
|
||||
p_frames = INPUT_BUFFER_MAX_SIZE;
|
||||
}
|
||||
|
||||
int predelay_frames = std::rint((params.predelay / 1000.0) * params.mix_rate);
|
||||
if (predelay_frames < 10) {
|
||||
predelay_frames = 10;
|
||||
}
|
||||
if (predelay_frames >= echo_buffer_size) {
|
||||
predelay_frames = echo_buffer_size - 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_frames; i++) {
|
||||
if (echo_buffer_pos >= echo_buffer_size) {
|
||||
echo_buffer_pos = 0;
|
||||
}
|
||||
|
||||
int read_pos = echo_buffer_pos - predelay_frames;
|
||||
while (read_pos < 0) {
|
||||
read_pos += echo_buffer_size;
|
||||
}
|
||||
|
||||
float in = undenormalize(echo_buffer[read_pos] * params.predelay_fb + p_src[i]);
|
||||
|
||||
echo_buffer[echo_buffer_pos] = in;
|
||||
|
||||
input_buffer[i] = in;
|
||||
|
||||
p_dst[i] = 0; //take the chance and clear this
|
||||
|
||||
echo_buffer_pos++;
|
||||
}
|
||||
|
||||
if (params.hpf > 0) {
|
||||
float hpaux = std::exp(-Math::TAU * params.hpf * 6000 / params.mix_rate);
|
||||
float hp_a1 = (1.0 + hpaux) / 2.0;
|
||||
float hp_a2 = -(1.0 + hpaux) / 2.0;
|
||||
float hp_b1 = hpaux;
|
||||
|
||||
for (int i = 0; i < p_frames; i++) {
|
||||
float in = input_buffer[i];
|
||||
input_buffer[i] = in * hp_a1 + hpf_h1 * hp_a2 + hpf_h2 * hp_b1;
|
||||
hpf_h2 = input_buffer[i];
|
||||
hpf_h1 = in;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_COMBS; i++) {
|
||||
Comb &c = comb[i];
|
||||
|
||||
int size_limit = c.size - std::rint((float)c.extra_spread_frames * (1.0 - params.extra_spread));
|
||||
for (int j = 0; j < p_frames; j++) {
|
||||
if (c.pos >= size_limit) { //reset this now just in case
|
||||
c.pos = 0;
|
||||
}
|
||||
|
||||
float out = undenormalize(c.buffer[c.pos] * c.feedback);
|
||||
out = out * (1.0 - c.damp) + c.damp_h * c.damp; //lowpass
|
||||
c.damp_h = out;
|
||||
c.buffer[c.pos] = input_buffer[j] + out;
|
||||
p_dst[j] += out;
|
||||
c.pos++;
|
||||
}
|
||||
}
|
||||
|
||||
static const float allpass_feedback = 0.7;
|
||||
/* this one works, but the other version is just nicer....
|
||||
int ap_size_limit[MAX_ALLPASS];
|
||||
|
||||
for (int i=0;i<MAX_ALLPASS;i++) {
|
||||
AllPass &a=allpass[i];
|
||||
ap_size_limit[i]=a.size-std::rint((float)a.extra_spread_frames*(1.0-params.extra_spread));
|
||||
}
|
||||
|
||||
for (int i=0;i<p_frames;i++) {
|
||||
float sample=p_dst[i];
|
||||
float aux,in;
|
||||
float AllPass*ap;
|
||||
|
||||
#define PROCESS_ALLPASS(m_ap) \
|
||||
ap=&allpass[m_ap]; \
|
||||
if (ap->pos>=ap_size_limit[m_ap]) \
|
||||
ap->pos=0; \
|
||||
aux=undenormalize(ap->buffer[ap->pos]); \
|
||||
in=sample; \
|
||||
sample=-in+aux; \
|
||||
ap->pos++;
|
||||
|
||||
|
||||
PROCESS_ALLPASS(0);
|
||||
PROCESS_ALLPASS(1);
|
||||
PROCESS_ALLPASS(2);
|
||||
PROCESS_ALLPASS(3);
|
||||
|
||||
p_dst[i]=sample;
|
||||
}
|
||||
*/
|
||||
|
||||
for (int i = 0; i < MAX_ALLPASS; i++) {
|
||||
AllPass &a = allpass[i];
|
||||
int size_limit = a.size - std::rint((float)a.extra_spread_frames * (1.0 - params.extra_spread));
|
||||
|
||||
for (int j = 0; j < p_frames; j++) {
|
||||
if (a.pos >= size_limit) {
|
||||
a.pos = 0;
|
||||
}
|
||||
|
||||
float aux = a.buffer[a.pos];
|
||||
a.buffer[a.pos] = undenormalize(allpass_feedback * aux + p_dst[j]);
|
||||
p_dst[j] = aux - allpass_feedback * a.buffer[a.pos];
|
||||
a.pos++;
|
||||
}
|
||||
}
|
||||
|
||||
static const float wet_scale = 0.6;
|
||||
|
||||
for (int i = 0; i < p_frames; i++) {
|
||||
p_dst[i] = p_dst[i] * params.wet * wet_scale + p_src[i] * params.dry;
|
||||
}
|
||||
}
|
||||
|
||||
void Reverb::set_room_size(float p_size) {
|
||||
params.room_size = p_size;
|
||||
update_parameters();
|
||||
}
|
||||
|
||||
void Reverb::set_damp(float p_damp) {
|
||||
params.damp = p_damp;
|
||||
update_parameters();
|
||||
}
|
||||
|
||||
void Reverb::set_wet(float p_wet) {
|
||||
params.wet = p_wet;
|
||||
}
|
||||
|
||||
void Reverb::set_dry(float p_dry) {
|
||||
params.dry = p_dry;
|
||||
}
|
||||
|
||||
void Reverb::set_predelay(float p_predelay) {
|
||||
params.predelay = p_predelay;
|
||||
}
|
||||
|
||||
void Reverb::set_predelay_feedback(float p_predelay_fb) {
|
||||
params.predelay_fb = p_predelay_fb;
|
||||
}
|
||||
|
||||
void Reverb::set_highpass(float p_frq) {
|
||||
if (p_frq > 1) {
|
||||
p_frq = 1;
|
||||
}
|
||||
if (p_frq < 0) {
|
||||
p_frq = 0;
|
||||
}
|
||||
params.hpf = p_frq;
|
||||
}
|
||||
|
||||
void Reverb::set_extra_spread(float p_spread) {
|
||||
params.extra_spread = p_spread;
|
||||
}
|
||||
|
||||
void Reverb::set_mix_rate(float p_mix_rate) {
|
||||
params.mix_rate = p_mix_rate;
|
||||
configure_buffers();
|
||||
}
|
||||
|
||||
void Reverb::set_extra_spread_base(float p_sec) {
|
||||
params.extra_spread_base = p_sec;
|
||||
configure_buffers();
|
||||
}
|
||||
|
||||
void Reverb::configure_buffers() {
|
||||
clear_buffers(); //clear if necessary
|
||||
|
||||
for (int i = 0; i < MAX_COMBS; i++) {
|
||||
Comb &c = comb[i];
|
||||
|
||||
c.extra_spread_frames = std::rint(params.extra_spread_base * params.mix_rate);
|
||||
|
||||
int len = std::rint(comb_tunings[i] * params.mix_rate) + c.extra_spread_frames;
|
||||
if (len < 5) {
|
||||
len = 5; //may this happen?
|
||||
}
|
||||
|
||||
c.buffer = memnew_arr(float, len);
|
||||
c.pos = 0;
|
||||
for (int j = 0; j < len; j++) {
|
||||
c.buffer[j] = 0;
|
||||
}
|
||||
c.size = len;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_ALLPASS; i++) {
|
||||
AllPass &a = allpass[i];
|
||||
|
||||
a.extra_spread_frames = std::rint(params.extra_spread_base * params.mix_rate);
|
||||
|
||||
int len = std::rint(allpass_tunings[i] * params.mix_rate) + a.extra_spread_frames;
|
||||
if (len < 5) {
|
||||
len = 5; //may this happen?
|
||||
}
|
||||
|
||||
a.buffer = memnew_arr(float, len);
|
||||
a.pos = 0;
|
||||
for (int j = 0; j < len; j++) {
|
||||
a.buffer[j] = 0;
|
||||
}
|
||||
a.size = len;
|
||||
}
|
||||
|
||||
echo_buffer_size = (int)(((float)MAX_ECHO_MS / 1000.0) * params.mix_rate + 1.0);
|
||||
echo_buffer = memnew_arr(float, echo_buffer_size);
|
||||
for (int i = 0; i < echo_buffer_size; i++) {
|
||||
echo_buffer[i] = 0;
|
||||
}
|
||||
|
||||
echo_buffer_pos = 0;
|
||||
}
|
||||
|
||||
void Reverb::update_parameters() {
|
||||
//more freeverb derived constants
|
||||
static const float room_scale = 0.28f;
|
||||
static const float room_offset = 0.7f;
|
||||
|
||||
for (int i = 0; i < MAX_COMBS; i++) {
|
||||
Comb &c = comb[i];
|
||||
c.feedback = room_offset + params.room_size * room_scale;
|
||||
if (c.feedback < room_offset) {
|
||||
c.feedback = room_offset;
|
||||
} else if (c.feedback > (room_offset + room_scale)) {
|
||||
c.feedback = (room_offset + room_scale);
|
||||
}
|
||||
|
||||
float auxdmp = params.damp / 2.0 + 0.5; //only half the range (0.5 .. 1.0 is enough)
|
||||
auxdmp *= auxdmp;
|
||||
|
||||
c.damp = std::exp(-Math::TAU * auxdmp * 10000 / params.mix_rate); // 0 .. 10khz
|
||||
}
|
||||
}
|
||||
|
||||
void Reverb::clear_buffers() {
|
||||
if (echo_buffer) {
|
||||
memdelete_arr(echo_buffer);
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_COMBS; i++) {
|
||||
if (comb[i].buffer) {
|
||||
memdelete_arr(comb[i].buffer);
|
||||
}
|
||||
|
||||
comb[i].buffer = nullptr;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_ALLPASS; i++) {
|
||||
if (allpass[i].buffer) {
|
||||
memdelete_arr(allpass[i].buffer);
|
||||
}
|
||||
|
||||
allpass[i].buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Reverb::Reverb() {
|
||||
params.room_size = 0.8;
|
||||
params.damp = 0.5;
|
||||
params.dry = 1.0;
|
||||
params.wet = 0.0;
|
||||
params.mix_rate = 44100;
|
||||
params.extra_spread_base = 0;
|
||||
params.extra_spread = 1.0;
|
||||
params.predelay = 150;
|
||||
params.predelay_fb = 0.4;
|
||||
params.hpf = 0;
|
||||
|
||||
input_buffer = memnew_arr(float, INPUT_BUFFER_MAX_SIZE);
|
||||
|
||||
configure_buffers();
|
||||
update_parameters();
|
||||
}
|
||||
|
||||
Reverb::~Reverb() {
|
||||
memdelete_arr(input_buffer);
|
||||
clear_buffers();
|
||||
}
|
115
servers/audio/effects/reverb_filter.h
Normal file
115
servers/audio/effects/reverb_filter.h
Normal file
@@ -0,0 +1,115 @@
|
||||
/**************************************************************************/
|
||||
/* reverb_filter.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
|
||||
|
||||
class Reverb {
|
||||
public:
|
||||
enum {
|
||||
INPUT_BUFFER_MAX_SIZE = 1024,
|
||||
|
||||
};
|
||||
|
||||
private:
|
||||
enum {
|
||||
MAX_COMBS = 8,
|
||||
MAX_ALLPASS = 4,
|
||||
MAX_ECHO_MS = 500
|
||||
|
||||
};
|
||||
|
||||
static const float comb_tunings[MAX_COMBS];
|
||||
static const float allpass_tunings[MAX_ALLPASS];
|
||||
|
||||
struct Comb {
|
||||
int size = 0;
|
||||
float *buffer = nullptr;
|
||||
float feedback = 0;
|
||||
float damp = 0; //lowpass
|
||||
float damp_h = 0; //history
|
||||
int pos = 0;
|
||||
int extra_spread_frames = 0;
|
||||
|
||||
Comb() {}
|
||||
};
|
||||
|
||||
struct AllPass {
|
||||
int size = 0;
|
||||
float *buffer = nullptr;
|
||||
int pos = 0;
|
||||
int extra_spread_frames = 0;
|
||||
AllPass() {}
|
||||
};
|
||||
|
||||
Comb comb[MAX_COMBS];
|
||||
AllPass allpass[MAX_ALLPASS];
|
||||
float *input_buffer = nullptr;
|
||||
float *echo_buffer = nullptr;
|
||||
int echo_buffer_size = 0;
|
||||
int echo_buffer_pos = 0;
|
||||
|
||||
float hpf_h1 = 0.0f;
|
||||
float hpf_h2 = 0.0f;
|
||||
|
||||
struct Parameters {
|
||||
float room_size;
|
||||
float damp;
|
||||
float wet;
|
||||
float dry;
|
||||
float mix_rate;
|
||||
float extra_spread_base;
|
||||
float extra_spread;
|
||||
float predelay;
|
||||
float predelay_fb;
|
||||
float hpf;
|
||||
} params;
|
||||
|
||||
void configure_buffers();
|
||||
void update_parameters();
|
||||
void clear_buffers();
|
||||
|
||||
public:
|
||||
void set_room_size(float p_size);
|
||||
void set_damp(float p_damp);
|
||||
void set_wet(float p_wet);
|
||||
void set_dry(float p_dry);
|
||||
void set_predelay(float p_predelay); // in ms
|
||||
void set_predelay_feedback(float p_predelay_fb); // in ms
|
||||
void set_highpass(float p_frq);
|
||||
void set_mix_rate(float p_mix_rate);
|
||||
void set_extra_spread(float p_spread);
|
||||
void set_extra_spread_base(float p_sec);
|
||||
|
||||
void process(float *p_src, float *p_dst, int p_frames);
|
||||
|
||||
Reverb();
|
||||
|
||||
~Reverb();
|
||||
};
|
Reference in New Issue
Block a user