Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/servers/audio/effects/audio_effect_hard_limiter.cpp
10278 views
1
/**************************************************************************/
2
/* audio_effect_hard_limiter.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "audio_effect_hard_limiter.h"
32
33
#include "servers/audio_server.h"
34
35
void AudioEffectHardLimiterInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
36
float sample_rate = AudioServer::get_singleton()->get_mix_rate();
37
38
float ceiling = Math::db_to_linear(base->ceiling);
39
float release = base->release;
40
float attack = base->attack;
41
float pre_gain = Math::db_to_linear(base->pre_gain);
42
43
for (int i = 0; i < p_frame_count; i++) {
44
float sample_left = p_src_frames[i].left;
45
float sample_right = p_src_frames[i].right;
46
47
sample_left *= pre_gain;
48
sample_right *= pre_gain;
49
50
float largest_sample = MAX(Math::abs(sample_left), Math::abs(sample_right));
51
52
release_factor = MAX(0.0, release_factor - 1.0 / sample_rate);
53
release_factor = MIN(release_factor, release);
54
55
if (release_factor > 0.0) {
56
gain = Math::lerp(gain_target, 1.0f, 1.0f - release_factor / release);
57
}
58
59
if (largest_sample * gain > ceiling) {
60
gain_target = ceiling / largest_sample;
61
release_factor = release;
62
attack_factor = attack;
63
}
64
65
// Lerp gain over attack time to avoid distortion.
66
attack_factor = MAX(0.0f, attack_factor - 1.0f / sample_rate);
67
if (attack_factor > 0.0) {
68
gain = Math::lerp(gain_target, gain, 1.0f - attack_factor / attack);
69
}
70
71
int bucket_id = gain_bucket_cursor / gain_bucket_size;
72
73
// If first item within the current bucket, reset the bucket.
74
if (gain_bucket_cursor % gain_bucket_size == 0) {
75
gain_buckets[bucket_id] = 1.0f;
76
}
77
78
gain_buckets[bucket_id] = MIN(gain_buckets[bucket_id], gain);
79
80
gain_bucket_cursor = (gain_bucket_cursor + 1) % gain_samples_to_store;
81
82
for (int j = 0; j < (int)gain_buckets.size(); j++) {
83
gain = MIN(gain, gain_buckets[j]);
84
}
85
86
// Introduce latency by grabbing the AudioFrame stored previously,
87
// then overwrite it with current audioframe, then update circular
88
// buffer cursor.
89
float dst_buffer_left = sample_buffer_left[sample_cursor];
90
float dst_buffer_right = sample_buffer_right[sample_cursor];
91
92
sample_buffer_left[sample_cursor] = sample_left;
93
sample_buffer_right[sample_cursor] = sample_right;
94
95
sample_cursor = (sample_cursor + 1) % sample_buffer_left.size();
96
97
p_dst_frames[i].left = dst_buffer_left * gain;
98
p_dst_frames[i].right = dst_buffer_right * gain;
99
}
100
}
101
102
Ref<AudioEffectInstance> AudioEffectHardLimiter::instantiate() {
103
Ref<AudioEffectHardLimiterInstance> ins;
104
ins.instantiate();
105
ins->base = Ref<AudioEffectHardLimiter>(this);
106
107
float mix_rate = AudioServer::get_singleton()->get_mix_rate();
108
109
for (int i = 0; i < (int)Math::ceil(mix_rate * attack) + 1; i++) {
110
ins->sample_buffer_left.push_back(0.0f);
111
ins->sample_buffer_right.push_back(0.0f);
112
}
113
114
ins->gain_samples_to_store = (int)Math::ceil(mix_rate * (attack + sustain) + 1);
115
ins->gain_bucket_size = (int)(mix_rate * attack);
116
117
for (int i = 0; i < ins->gain_samples_to_store; i += ins->gain_bucket_size) {
118
ins->gain_buckets.push_back(1.0f);
119
}
120
121
return ins;
122
}
123
124
void AudioEffectHardLimiter::set_ceiling_db(float p_ceiling) {
125
ceiling = p_ceiling;
126
}
127
128
float AudioEffectHardLimiter::get_ceiling_db() const {
129
return ceiling;
130
}
131
132
float AudioEffectHardLimiter::get_pre_gain_db() const {
133
return pre_gain;
134
}
135
136
void AudioEffectHardLimiter::set_pre_gain_db(const float p_pre_gain) {
137
pre_gain = p_pre_gain;
138
}
139
140
float AudioEffectHardLimiter::get_release() const {
141
return release;
142
}
143
144
void AudioEffectHardLimiter::set_release(const float p_release) {
145
release = p_release;
146
}
147
148
void AudioEffectHardLimiter::_bind_methods() {
149
ClassDB::bind_method(D_METHOD("set_ceiling_db", "ceiling"), &AudioEffectHardLimiter::set_ceiling_db);
150
ClassDB::bind_method(D_METHOD("get_ceiling_db"), &AudioEffectHardLimiter::get_ceiling_db);
151
152
ClassDB::bind_method(D_METHOD("set_pre_gain_db", "p_pre_gain"), &AudioEffectHardLimiter::set_pre_gain_db);
153
ClassDB::bind_method(D_METHOD("get_pre_gain_db"), &AudioEffectHardLimiter::get_pre_gain_db);
154
155
ClassDB::bind_method(D_METHOD("set_release", "p_release"), &AudioEffectHardLimiter::set_release);
156
ClassDB::bind_method(D_METHOD("get_release"), &AudioEffectHardLimiter::get_release);
157
158
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");
159
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ceiling_db", PROPERTY_HINT_RANGE, "-24,0.0,0.01,suffix:dB"), "set_ceiling_db", "get_ceiling_db");
160
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "release", PROPERTY_HINT_RANGE, "0.01,3,0.01"), "set_release", "get_release");
161
}
162
163