Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/interactive_music/audio_stream_playlist.cpp
10277 views
1
/**************************************************************************/
2
/* audio_stream_playlist.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_playlist.h"
32
33
#include "core/math/math_funcs.h"
34
35
Ref<AudioStreamPlayback> AudioStreamPlaylist::instantiate_playback() {
36
Ref<AudioStreamPlaybackPlaylist> playback_playlist;
37
playback_playlist.instantiate();
38
playback_playlist->playlist = Ref<AudioStreamPlaylist>(this);
39
playback_playlist->_update_playback_instances();
40
playbacks.insert(playback_playlist.operator->());
41
return playback_playlist;
42
}
43
44
String AudioStreamPlaylist::get_stream_name() const {
45
return "Playlist";
46
}
47
48
void AudioStreamPlaylist::set_list_stream(int p_stream_index, Ref<AudioStream> p_stream) {
49
ERR_FAIL_COND(p_stream == this);
50
ERR_FAIL_INDEX(p_stream_index, MAX_STREAMS);
51
52
AudioServer::get_singleton()->lock();
53
audio_streams[p_stream_index] = p_stream;
54
for (AudioStreamPlaybackPlaylist *E : playbacks) {
55
E->_update_playback_instances();
56
}
57
AudioServer::get_singleton()->unlock();
58
}
59
60
Ref<AudioStream> AudioStreamPlaylist::get_list_stream(int p_stream_index) const {
61
ERR_FAIL_INDEX_V(p_stream_index, MAX_STREAMS, Ref<AudioStream>());
62
63
return audio_streams[p_stream_index];
64
}
65
66
double AudioStreamPlaylist::get_bpm() const {
67
for (int i = 0; i < stream_count; i++) {
68
if (audio_streams[i].is_valid()) {
69
double bpm = audio_streams[i]->get_bpm();
70
if (bpm != 0.0) {
71
return bpm;
72
}
73
}
74
}
75
return 0.0;
76
}
77
78
double AudioStreamPlaylist::get_length() const {
79
double total_length = 0.0;
80
for (int i = 0; i < stream_count; i++) {
81
if (audio_streams[i].is_valid()) {
82
double bpm = audio_streams[i]->get_bpm();
83
int beat_count = audio_streams[i]->get_beat_count();
84
if (bpm > 0.0 && beat_count > 0) {
85
total_length += beat_count * 60.0 / bpm;
86
} else {
87
total_length += audio_streams[i]->get_length();
88
}
89
}
90
}
91
return total_length;
92
}
93
94
void AudioStreamPlaylist::set_stream_count(int p_count) {
95
ERR_FAIL_COND(p_count < 0 || p_count > MAX_STREAMS);
96
AudioServer::get_singleton()->lock();
97
stream_count = p_count;
98
AudioServer::get_singleton()->unlock();
99
notify_property_list_changed();
100
}
101
102
int AudioStreamPlaylist::get_stream_count() const {
103
return stream_count;
104
}
105
106
void AudioStreamPlaylist::set_fade_time(float p_time) {
107
fade_time = p_time;
108
}
109
110
float AudioStreamPlaylist::get_fade_time() const {
111
return fade_time;
112
}
113
114
void AudioStreamPlaylist::set_shuffle(bool p_shuffle) {
115
shuffle = p_shuffle;
116
}
117
118
bool AudioStreamPlaylist::get_shuffle() const {
119
return shuffle;
120
}
121
122
void AudioStreamPlaylist::set_loop(bool p_loop) {
123
loop = p_loop;
124
}
125
126
bool AudioStreamPlaylist::has_loop() const {
127
return loop;
128
}
129
130
void AudioStreamPlaylist::_validate_property(PropertyInfo &r_property) const {
131
String prop = r_property.name;
132
if (prop != "stream_count" && prop.begins_with("stream_")) {
133
int stream = prop.get_slicec('/', 0).get_slicec('_', 1).to_int();
134
if (stream >= stream_count) {
135
r_property.usage = PROPERTY_USAGE_INTERNAL;
136
}
137
}
138
}
139
140
void AudioStreamPlaylist::_bind_methods() {
141
ClassDB::bind_method(D_METHOD("set_stream_count", "stream_count"), &AudioStreamPlaylist::set_stream_count);
142
ClassDB::bind_method(D_METHOD("get_stream_count"), &AudioStreamPlaylist::get_stream_count);
143
144
ClassDB::bind_method(D_METHOD("get_bpm"), &AudioStreamPlaylist::get_bpm);
145
146
ClassDB::bind_method(D_METHOD("set_list_stream", "stream_index", "audio_stream"), &AudioStreamPlaylist::set_list_stream);
147
ClassDB::bind_method(D_METHOD("get_list_stream", "stream_index"), &AudioStreamPlaylist::get_list_stream);
148
149
ClassDB::bind_method(D_METHOD("set_shuffle", "shuffle"), &AudioStreamPlaylist::set_shuffle);
150
ClassDB::bind_method(D_METHOD("get_shuffle"), &AudioStreamPlaylist::get_shuffle);
151
152
ClassDB::bind_method(D_METHOD("set_fade_time", "dec"), &AudioStreamPlaylist::set_fade_time);
153
ClassDB::bind_method(D_METHOD("get_fade_time"), &AudioStreamPlaylist::get_fade_time);
154
155
ClassDB::bind_method(D_METHOD("set_loop", "loop"), &AudioStreamPlaylist::set_loop);
156
ClassDB::bind_method(D_METHOD("has_loop"), &AudioStreamPlaylist::has_loop);
157
158
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shuffle"), "set_shuffle", "get_shuffle");
159
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop");
160
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fade_time", PROPERTY_HINT_RANGE, "0,1,0.01,suffix:s"), "set_fade_time", "get_fade_time");
161
162
ADD_PROPERTY(PropertyInfo(Variant::INT, "stream_count", PROPERTY_HINT_RANGE, "0," + itos(MAX_STREAMS), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Streams,stream_,unfoldable,page_size=999,add_button_text=" + String(TTRC("Add Stream"))), "set_stream_count", "get_stream_count");
163
164
for (int i = 0; i < MAX_STREAMS; i++) {
165
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "stream_" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, "AudioStream", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_list_stream", "get_list_stream", i);
166
}
167
168
BIND_CONSTANT(MAX_STREAMS);
169
}
170
171
//////////////////////
172
//////////////////////
173
174
AudioStreamPlaybackPlaylist::~AudioStreamPlaybackPlaylist() {
175
if (playlist.is_valid()) {
176
playlist->playbacks.erase(this);
177
}
178
}
179
180
void AudioStreamPlaybackPlaylist::stop() {
181
active = false;
182
for (int i = 0; i < playlist->stream_count; i++) {
183
if (playback[i].is_valid()) {
184
playback[i]->stop();
185
}
186
}
187
}
188
189
void AudioStreamPlaybackPlaylist::_update_order() {
190
for (int i = 0; i < playlist->stream_count; i++) {
191
play_order[i] = i;
192
}
193
194
if (playlist->shuffle) {
195
for (int i = 0; i < playlist->stream_count; i++) {
196
int swap_with = Math::rand() % uint32_t(playlist->stream_count);
197
SWAP(play_order[i], play_order[swap_with]);
198
}
199
}
200
}
201
202
void AudioStreamPlaybackPlaylist::start(double p_from_pos) {
203
if (active) {
204
stop();
205
}
206
207
p_from_pos = MAX(0, p_from_pos);
208
209
float pl_length = playlist->get_length();
210
if (p_from_pos >= pl_length) {
211
if (!playlist->loop) {
212
return; // No loop, end.
213
}
214
p_from_pos = Math::fmod((float)p_from_pos, (float)pl_length);
215
}
216
217
_update_order();
218
219
play_index = -1;
220
221
double play_ofs = p_from_pos;
222
for (int i = 0; i < playlist->stream_count; i++) {
223
int idx = play_order[i];
224
if (playlist->audio_streams[idx].is_valid()) {
225
double bpm = playlist->audio_streams[idx]->get_bpm();
226
int beat_count = playlist->audio_streams[idx]->get_beat_count();
227
double length;
228
if (bpm > 0.0 && beat_count > 0) {
229
length = beat_count * 60.0 / bpm;
230
} else {
231
length = playlist->audio_streams[idx]->get_length();
232
}
233
if (play_ofs < length) {
234
play_index = i;
235
stream_todo = length - play_ofs;
236
break;
237
} else {
238
play_ofs -= length;
239
}
240
}
241
}
242
243
if (play_index == -1) {
244
return;
245
}
246
247
playback[play_order[play_index]]->start(play_ofs);
248
fade_index = -1;
249
loop_count = 0;
250
251
active = true;
252
}
253
254
void AudioStreamPlaybackPlaylist::seek(double p_time) {
255
stop();
256
start(p_time);
257
}
258
259
int AudioStreamPlaybackPlaylist::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
260
if (!active) {
261
return 0;
262
}
263
264
double time_dec = (1.0 / AudioServer::get_singleton()->get_mix_rate());
265
double fade_dec = (1.0 / playlist->fade_time) / AudioServer::get_singleton()->get_mix_rate();
266
267
int todo = p_frames;
268
269
while (todo) {
270
int to_mix = MIN(todo, MIX_BUFFER_SIZE);
271
272
playback[play_order[play_index]]->mix(mix_buffer, 1.0, to_mix);
273
if (fade_index != -1) {
274
playback[fade_index]->mix(fade_buffer, 1.0, to_mix);
275
}
276
277
offset += time_dec * to_mix;
278
279
for (int i = 0; i < to_mix; i++) {
280
*p_buffer = mix_buffer[i];
281
stream_todo -= time_dec;
282
if (stream_todo < 0) {
283
//find next stream.
284
int prev = play_order[play_index];
285
286
for (int j = 0; j < playlist->stream_count; j++) {
287
play_index++;
288
if (play_index >= playlist->stream_count) {
289
// No loop, exit.
290
if (!playlist->loop) {
291
for (int k = i; k < todo - i; k++) {
292
p_buffer[k] = AudioFrame(0, 0);
293
}
294
todo = to_mix;
295
active = false;
296
break;
297
}
298
299
_update_order();
300
play_index = 0;
301
loop_count++;
302
offset = time_dec * (to_mix - i);
303
}
304
if (playback[play_order[play_index]].is_valid()) {
305
break;
306
}
307
}
308
309
if (!active) {
310
break;
311
}
312
313
if (playback[play_order[play_index]].is_null()) {
314
todo = to_mix; // Weird error.
315
active = false;
316
break;
317
}
318
319
bool restart = true;
320
if (prev == play_order[play_index]) {
321
// Went back to the same one, continue loop (if it loops) or restart if it does not.
322
if (playlist->audio_streams[prev]->has_loop()) {
323
restart = false;
324
}
325
fade_index = -1;
326
} else {
327
// Move current mixed data to fade buffer.
328
for (int j = i; j < to_mix; j++) {
329
fade_buffer[j] = mix_buffer[j];
330
}
331
332
fade_index = prev;
333
fade_volume = 1.0;
334
}
335
336
int idx = play_order[play_index];
337
338
if (restart) {
339
playback[idx]->start(0); // No loop, just cold-restart.
340
playback[idx]->mix(mix_buffer + i, 1.0, to_mix - i); // Fill rest of mix buffer
341
}
342
343
// Update fade todo.
344
double bpm = playlist->audio_streams[idx]->get_bpm();
345
int beat_count = playlist->audio_streams[idx]->get_beat_count();
346
347
if (bpm > 0.0 && beat_count > 0) {
348
stream_todo = beat_count * 60.0 / bpm;
349
} else {
350
stream_todo = playlist->audio_streams[idx]->get_length();
351
}
352
}
353
354
if (fade_index != -1) {
355
*p_buffer += fade_buffer[i] * fade_volume;
356
fade_volume -= fade_dec;
357
if (fade_volume <= 0.0) {
358
playback[fade_index]->stop();
359
fade_index = -1;
360
}
361
}
362
363
p_buffer++;
364
}
365
366
todo -= to_mix;
367
}
368
369
return p_frames;
370
}
371
372
void AudioStreamPlaybackPlaylist::tag_used_streams() {
373
if (active) {
374
playlist->audio_streams[play_order[play_index]]->tag_used(playback[play_order[play_index]]->get_playback_position());
375
}
376
377
playlist->tag_used(0);
378
}
379
380
int AudioStreamPlaybackPlaylist::get_loop_count() const {
381
return loop_count;
382
}
383
384
double AudioStreamPlaybackPlaylist::get_playback_position() const {
385
return offset;
386
}
387
388
bool AudioStreamPlaybackPlaylist::is_playing() const {
389
return active;
390
}
391
392
void AudioStreamPlaybackPlaylist::_update_playback_instances() {
393
stop();
394
395
for (int i = 0; i < playlist->stream_count; i++) {
396
if (playlist->audio_streams[i].is_valid()) {
397
playback[i] = playlist->audio_streams[i]->instantiate_playback();
398
} else {
399
playback[i].unref();
400
}
401
}
402
}
403
404