1/**************************************************************************/
2/* audio_stream_preview.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_stream_preview.h"
32
33/////////////////////
34
35float AudioStreamPreview::get_length() const {
36 return length;
37}
38
39float AudioStreamPreview::get_max(float p_time, float p_time_next) const {
40 if (length == 0) {
41 return 0;
42 }
43
44 int max = preview.size() / 2;
45 if (max == 0) {
46 return 0;
47 }
48
49 int time_from = p_time / length * max;
50 int time_to = p_time_next / length * max;
51 time_from = CLAMP(time_from, 0, max - 1);
52 time_to = CLAMP(time_to, 0, max - 1);
53
54 if (time_to <= time_from) {
55 time_to = time_from + 1;
56 }
57
58 uint8_t vmax = 0;
59
60 for (int i = time_from; i < time_to; i++) {
61 uint8_t v = preview[i * 2 + 1];
62 if (i == 0 || v > vmax) {
63 vmax = v;
64 }
65 }
66
67 return (vmax / 255.0) * 2.0 - 1.0;
68}
69
70float AudioStreamPreview::get_min(float p_time, float p_time_next) const {
71 if (length == 0) {
72 return 0;
73 }
74
75 int max = preview.size() / 2;
76 if (max == 0) {
77 return 0;
78 }
79
80 int time_from = p_time / length * max;
81 int time_to = p_time_next / length * max;
82 time_from = CLAMP(time_from, 0, max - 1);
83 time_to = CLAMP(time_to, 0, max - 1);
84
85 if (time_to <= time_from) {
86 time_to = time_from + 1;
87 }
88
89 uint8_t vmin = 255;
90
91 for (int i = time_from; i < time_to; i++) {
92 uint8_t v = preview[i * 2];
93 if (i == 0 || v < vmin) {
94 vmin = v;
95 }
96 }
97
98 return (vmin / 255.0) * 2.0 - 1.0;
99}
100
101AudioStreamPreview::AudioStreamPreview() {
102 length = 0;
103}
104
105////
106
107void AudioStreamPreviewGenerator::_update_emit(ObjectID p_id) {
108 emit_signal(SNAME("preview_updated"), p_id);
109}
110
111void AudioStreamPreviewGenerator::_preview_thread(void *p_preview) {
112 Preview *preview = static_cast<Preview *>(p_preview);
113
114 float muxbuff_chunk_s = 0.25;
115
116 int mixbuff_chunk_frames = AudioServer::get_singleton()->get_mix_rate() * muxbuff_chunk_s;
117
118 Vector<AudioFrame> mix_chunk;
119 mix_chunk.resize(mixbuff_chunk_frames);
120
121 int frames_total = AudioServer::get_singleton()->get_mix_rate() * preview->preview->length;
122 int frames_todo = frames_total;
123
124 preview->playback->start();
125
126 while (frames_todo) {
127 int ofs_write = uint64_t(frames_total - frames_todo) * uint64_t(preview->preview->preview.size() / 2) / uint64_t(frames_total);
128 int to_read = MIN(frames_todo, mixbuff_chunk_frames);
129 int to_write = uint64_t(to_read) * uint64_t(preview->preview->preview.size() / 2) / uint64_t(frames_total);
130 to_write = MIN(to_write, (preview->preview->preview.size() / 2) - ofs_write);
131
132 preview->playback->mix(mix_chunk.ptrw(), 1.0, to_read);
133
134 for (int i = 0; i < to_write; i++) {
135 float max = -1000;
136 float min = 1000;
137 int from = uint64_t(i) * to_read / to_write;
138 int to = (uint64_t(i) + 1) * to_read / to_write;
139 to = MIN(to, to_read);
140 from = MIN(from, to_read - 1);
141 if (to == from) {
142 to = from + 1;
143 }
144
145 for (int j = from; j < to; j++) {
146 max = MAX(max, mix_chunk[j].l);
147 max = MAX(max, mix_chunk[j].r);
148
149 min = MIN(min, mix_chunk[j].l);
150 min = MIN(min, mix_chunk[j].r);
151 }
152
153 uint8_t pfrom = CLAMP((min * 0.5 + 0.5) * 255, 0, 255);
154 uint8_t pto = CLAMP((max * 0.5 + 0.5) * 255, 0, 255);
155
156 preview->preview->preview.write[(ofs_write + i) * 2 + 0] = pfrom;
157 preview->preview->preview.write[(ofs_write + i) * 2 + 1] = pto;
158 }
159
160 frames_todo -= to_read;
161 singleton->call_deferred(SNAME("_update_emit"), preview->id);
162 }
163
164 preview->preview->version++;
165
166 preview->playback->stop();
167
168 preview->generating.clear();
169}
170
171Ref<AudioStreamPreview> AudioStreamPreviewGenerator::generate_preview(const Ref<AudioStream> &p_stream) {
172 ERR_FAIL_COND_V(p_stream.is_null(), Ref<AudioStreamPreview>());
173
174 if (previews.has(p_stream->get_instance_id())) {
175 return previews[p_stream->get_instance_id()].preview;
176 }
177
178 //no preview exists
179
180 previews[p_stream->get_instance_id()] = Preview();
181
182 Preview *preview = &previews[p_stream->get_instance_id()];
183 preview->base_stream = p_stream;
184 preview->playback = preview->base_stream->instantiate_playback();
185 preview->generating.set();
186 preview->id = p_stream->get_instance_id();
187
188 float len_s = preview->base_stream->get_length();
189 if (len_s == 0) {
190 len_s = 60 * 5; //five minutes
191 }
192
193 int frames = AudioServer::get_singleton()->get_mix_rate() * len_s;
194
195 Vector<uint8_t> maxmin;
196 int pw = frames / 20;
197 maxmin.resize(pw * 2);
198 {
199 uint8_t *ptr = maxmin.ptrw();
200 for (int i = 0; i < pw * 2; i++) {
201 ptr[i] = 127;
202 }
203 }
204
205 preview->preview.instantiate();
206 preview->preview->preview = maxmin;
207 preview->preview->length = len_s;
208
209 if (preview->playback.is_valid()) {
210 preview->thread = memnew(Thread);
211 preview->thread->set_name("AudioStreamPreviewGenerator");
212 preview->thread->start(_preview_thread, preview);
213 }
214
215 return preview->preview;
216}
217
218void AudioStreamPreviewGenerator::_bind_methods() {
219 ClassDB::bind_method("_update_emit", &AudioStreamPreviewGenerator::_update_emit);
220 ClassDB::bind_method(D_METHOD("generate_preview", "stream"), &AudioStreamPreviewGenerator::generate_preview);
221
222 ADD_SIGNAL(MethodInfo("preview_updated", PropertyInfo(Variant::INT, "obj_id")));
223}
224
225AudioStreamPreviewGenerator *AudioStreamPreviewGenerator::singleton = nullptr;
226
227void AudioStreamPreviewGenerator::_notification(int p_what) {
228 switch (p_what) {
229 case NOTIFICATION_PROCESS: {
230 List<ObjectID> to_erase;
231 for (KeyValue<ObjectID, Preview> &E : previews) {
232 if (!E.value.generating.is_set()) {
233 if (E.value.thread) {
234 E.value.thread->wait_to_finish();
235 memdelete(E.value.thread);
236 E.value.thread = nullptr;
237 }
238 if (!ObjectDB::get_instance(E.key)) { //no longer in use, get rid of preview
239 to_erase.push_back(E.key);
240 }
241 }
242 }
243
244 while (to_erase.front()) {
245 previews.erase(to_erase.front()->get());
246 to_erase.pop_front();
247 }
248 } break;
249 }
250}
251
252AudioStreamPreviewGenerator::AudioStreamPreviewGenerator() {
253 singleton = this;
254 set_process(true);
255}
256