Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/audio/audio_stream_preview.cpp
9897 views
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
35
float AudioStreamPreview::get_length() const {
36
return length;
37
}
38
39
float 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
70
float 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
101
AudioStreamPreview::AudioStreamPreview() {
102
length = 0;
103
}
104
105
////
106
107
void AudioStreamPreviewGenerator::_update_emit(ObjectID p_id) {
108
emit_signal(SNAME("preview_updated"), p_id);
109
}
110
111
void AudioStreamPreviewGenerator::_preview_thread(void *p_preview) {
112
Thread::set_name("AudioStreamPreviewGenerator");
113
114
Preview *preview = static_cast<Preview *>(p_preview);
115
116
float muxbuff_chunk_s = 0.25;
117
118
int mixbuff_chunk_frames = AudioServer::get_singleton()->get_mix_rate() * muxbuff_chunk_s;
119
120
Vector<AudioFrame> mix_chunk;
121
mix_chunk.resize(mixbuff_chunk_frames);
122
123
int frames_total = AudioServer::get_singleton()->get_mix_rate() * preview->preview->length;
124
int frames_todo = frames_total;
125
126
preview->playback->start();
127
128
while (frames_todo) {
129
int ofs_write = uint64_t(frames_total - frames_todo) * uint64_t(preview->preview->preview.size() / 2) / uint64_t(frames_total);
130
int to_read = MIN(frames_todo, mixbuff_chunk_frames);
131
int to_write = uint64_t(to_read) * uint64_t(preview->preview->preview.size() / 2) / uint64_t(frames_total);
132
to_write = MIN(to_write, (preview->preview->preview.size() / 2) - ofs_write);
133
134
preview->playback->mix(mix_chunk.ptrw(), 1.0, to_read);
135
136
for (int i = 0; i < to_write; i++) {
137
float max = -1000;
138
float min = 1000;
139
int from = uint64_t(i) * to_read / to_write;
140
int to = (uint64_t(i) + 1) * to_read / to_write;
141
to = MIN(to, to_read);
142
from = MIN(from, to_read - 1);
143
if (to == from) {
144
to = from + 1;
145
}
146
147
for (int j = from; j < to; j++) {
148
max = MAX(max, mix_chunk[j].left);
149
max = MAX(max, mix_chunk[j].right);
150
151
min = MIN(min, mix_chunk[j].left);
152
min = MIN(min, mix_chunk[j].right);
153
}
154
155
uint8_t pfrom = CLAMP((min * 0.5 + 0.5) * 255, 0, 255);
156
uint8_t pto = CLAMP((max * 0.5 + 0.5) * 255, 0, 255);
157
158
preview->preview->preview.write[(ofs_write + i) * 2 + 0] = pfrom;
159
preview->preview->preview.write[(ofs_write + i) * 2 + 1] = pto;
160
}
161
162
frames_todo -= to_read;
163
callable_mp(singleton, &AudioStreamPreviewGenerator::_update_emit).call_deferred(preview->id);
164
}
165
166
preview->preview->version++;
167
168
preview->playback->stop();
169
170
preview->generating.clear();
171
}
172
173
Ref<AudioStreamPreview> AudioStreamPreviewGenerator::generate_preview(const Ref<AudioStream> &p_stream) {
174
ERR_FAIL_COND_V(p_stream.is_null(), Ref<AudioStreamPreview>());
175
176
if (previews.has(p_stream->get_instance_id())) {
177
return previews[p_stream->get_instance_id()].preview;
178
}
179
180
//no preview exists
181
182
previews[p_stream->get_instance_id()] = Preview();
183
184
Preview *preview = &previews[p_stream->get_instance_id()];
185
preview->base_stream = p_stream;
186
preview->playback = preview->base_stream->instantiate_playback();
187
preview->generating.set();
188
preview->id = p_stream->get_instance_id();
189
190
float len_s = preview->base_stream->get_length();
191
if (len_s == 0) {
192
len_s = 60 * 5; //five minutes
193
}
194
195
int frames = AudioServer::get_singleton()->get_mix_rate() * len_s;
196
197
Vector<uint8_t> maxmin;
198
int pw = frames / 20;
199
maxmin.resize(pw * 2);
200
{
201
uint8_t *ptr = maxmin.ptrw();
202
for (int i = 0; i < pw * 2; i++) {
203
ptr[i] = 127;
204
}
205
}
206
207
preview->preview.instantiate();
208
preview->preview->preview = maxmin;
209
preview->preview->length = len_s;
210
211
if (preview->playback.is_valid()) {
212
preview->thread = memnew(Thread);
213
preview->thread->start(_preview_thread, preview);
214
}
215
216
return preview->preview;
217
}
218
219
void AudioStreamPreviewGenerator::_bind_methods() {
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
225
AudioStreamPreviewGenerator *AudioStreamPreviewGenerator::singleton = nullptr;
226
227
void 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
252
AudioStreamPreviewGenerator::AudioStreamPreviewGenerator() {
253
singleton = this;
254
set_process(true);
255
}
256
257