1 | /**************************************************************************/ |
2 | /* audio_stream_polyphonic.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_polyphonic.h" |
32 | #include "scene/main/scene_tree.h" |
33 | |
34 | Ref<AudioStreamPlayback> AudioStreamPolyphonic::instantiate_playback() { |
35 | Ref<AudioStreamPlaybackPolyphonic> playback; |
36 | playback.instantiate(); |
37 | playback->streams.resize(polyphony); |
38 | return playback; |
39 | } |
40 | |
41 | String AudioStreamPolyphonic::get_stream_name() const { |
42 | return "AudioStreamPolyphonic" ; |
43 | } |
44 | |
45 | bool AudioStreamPolyphonic::is_monophonic() const { |
46 | return true; // This avoids stream players to instantiate more than one of these. |
47 | } |
48 | |
49 | void AudioStreamPolyphonic::set_polyphony(int p_voices) { |
50 | ERR_FAIL_COND(p_voices < 0 || p_voices > 128); |
51 | polyphony = p_voices; |
52 | } |
53 | int AudioStreamPolyphonic::get_polyphony() const { |
54 | return polyphony; |
55 | } |
56 | |
57 | void AudioStreamPolyphonic::_bind_methods() { |
58 | ClassDB::bind_method(D_METHOD("set_polyphony" , "voices" ), &AudioStreamPolyphonic::set_polyphony); |
59 | ClassDB::bind_method(D_METHOD("get_polyphony" ), &AudioStreamPolyphonic::get_polyphony); |
60 | |
61 | ADD_PROPERTY(PropertyInfo(Variant::INT, "polyphony" , PROPERTY_HINT_RANGE, "1,128,1" ), "set_polyphony" , "get_polyphony" ); |
62 | } |
63 | |
64 | AudioStreamPolyphonic::AudioStreamPolyphonic() { |
65 | } |
66 | |
67 | //////////////////////// |
68 | |
69 | void AudioStreamPlaybackPolyphonic::start(double p_from_pos) { |
70 | if (active) { |
71 | stop(); |
72 | } |
73 | |
74 | active = true; |
75 | } |
76 | |
77 | void AudioStreamPlaybackPolyphonic::stop() { |
78 | if (!active) { |
79 | return; |
80 | } |
81 | |
82 | bool locked = false; |
83 | for (Stream &s : streams) { |
84 | if (s.active.is_set()) { |
85 | // Need locking because something may still be mixing. |
86 | locked = true; |
87 | AudioServer::get_singleton()->lock(); |
88 | } |
89 | s.active.clear(); |
90 | s.finish_request.clear(); |
91 | s.stream_playback.unref(); |
92 | s.stream.unref(); |
93 | } |
94 | if (locked) { |
95 | AudioServer::get_singleton()->unlock(); |
96 | } |
97 | |
98 | active = false; |
99 | } |
100 | |
101 | bool AudioStreamPlaybackPolyphonic::is_playing() const { |
102 | return active; |
103 | } |
104 | |
105 | int AudioStreamPlaybackPolyphonic::get_loop_count() const { |
106 | return 0; |
107 | } |
108 | |
109 | double AudioStreamPlaybackPolyphonic::get_playback_position() const { |
110 | return 0; |
111 | } |
112 | void AudioStreamPlaybackPolyphonic::seek(double p_time) { |
113 | // Ignored. |
114 | } |
115 | |
116 | void AudioStreamPlaybackPolyphonic::tag_used_streams() { |
117 | for (Stream &s : streams) { |
118 | if (s.active.is_set()) { |
119 | s.stream_playback->tag_used_streams(); |
120 | } |
121 | } |
122 | } |
123 | |
124 | int AudioStreamPlaybackPolyphonic::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { |
125 | if (!active) { |
126 | return 0; |
127 | } |
128 | |
129 | // Pre-clear buffer. |
130 | for (int i = 0; i < p_frames; i++) { |
131 | p_buffer[i] = AudioFrame(0, 0); |
132 | } |
133 | |
134 | for (Stream &s : streams) { |
135 | if (!s.active.is_set()) { |
136 | continue; |
137 | } |
138 | |
139 | float volume_db = s.volume_db; // Copy because it can be overridden at any time. |
140 | float next_volume = Math::db_to_linear(volume_db); |
141 | s.prev_volume_db = volume_db; |
142 | |
143 | if (s.finish_request.is_set()) { |
144 | if (s.pending_play.is_set()) { |
145 | // Did not get the chance to play, was finalized too soon. |
146 | s.active.clear(); |
147 | continue; |
148 | } |
149 | next_volume = 0; |
150 | } |
151 | |
152 | if (s.pending_play.is_set()) { |
153 | s.stream_playback->start(s.play_offset); |
154 | s.pending_play.clear(); |
155 | } |
156 | float prev_volume = Math::db_to_linear(s.prev_volume_db); |
157 | |
158 | float volume_inc = (next_volume - prev_volume) / float(p_frames); |
159 | |
160 | int todo = p_frames; |
161 | int offset = 0; |
162 | float volume = prev_volume; |
163 | |
164 | bool stream_done = false; |
165 | |
166 | while (todo) { |
167 | int to_mix = MIN(todo, int(INTERNAL_BUFFER_LEN)); |
168 | int mixed = s.stream_playback->mix(internal_buffer, s.pitch_scale, to_mix); |
169 | |
170 | for (int i = 0; i < to_mix; i++) { |
171 | p_buffer[offset + i] += internal_buffer[i] * volume; |
172 | volume += volume_inc; |
173 | } |
174 | |
175 | if (mixed < to_mix) { |
176 | // Stream is done. |
177 | s.active.clear(); |
178 | stream_done = true; |
179 | break; |
180 | } |
181 | |
182 | todo -= to_mix; |
183 | offset += to_mix; |
184 | } |
185 | |
186 | if (stream_done) { |
187 | continue; |
188 | } |
189 | |
190 | if (s.finish_request.is_set()) { |
191 | s.active.clear(); |
192 | } |
193 | } |
194 | |
195 | return p_frames; |
196 | } |
197 | |
198 | AudioStreamPlaybackPolyphonic::ID AudioStreamPlaybackPolyphonic::play_stream(const Ref<AudioStream> &p_stream, float p_from_offset, float p_volume_db, float p_pitch_scale) { |
199 | ERR_FAIL_COND_V(p_stream.is_null(), INVALID_ID); |
200 | for (uint32_t i = 0; i < streams.size(); i++) { |
201 | if (!streams[i].active.is_set()) { |
202 | // Can use this stream, as it's not active. |
203 | streams[i].stream = p_stream; |
204 | streams[i].stream_playback = streams[i].stream->instantiate_playback(); |
205 | streams[i].play_offset = p_from_offset; |
206 | streams[i].volume_db = p_volume_db; |
207 | streams[i].prev_volume_db = p_volume_db; |
208 | streams[i].pitch_scale = p_pitch_scale; |
209 | streams[i].id = id_counter++; |
210 | streams[i].finish_request.clear(); |
211 | streams[i].pending_play.set(); |
212 | streams[i].active.set(); |
213 | return (ID(i) << INDEX_SHIFT) | ID(streams[i].id); |
214 | } |
215 | } |
216 | |
217 | return INVALID_ID; |
218 | } |
219 | |
220 | AudioStreamPlaybackPolyphonic::Stream *AudioStreamPlaybackPolyphonic::_find_stream(int64_t p_id) { |
221 | uint32_t index = p_id >> INDEX_SHIFT; |
222 | if (index >= streams.size()) { |
223 | return nullptr; |
224 | } |
225 | if (!streams[index].active.is_set()) { |
226 | return nullptr; // Not active, no longer exists. |
227 | } |
228 | int64_t id = p_id & ID_MASK; |
229 | if (streams[index].id != id) { |
230 | return nullptr; |
231 | } |
232 | return &streams[index]; |
233 | } |
234 | |
235 | void AudioStreamPlaybackPolyphonic::set_stream_volume(ID p_stream_id, float p_volume_db) { |
236 | Stream *s = _find_stream(p_stream_id); |
237 | if (!s) { |
238 | return; |
239 | } |
240 | s->volume_db = p_volume_db; |
241 | } |
242 | |
243 | void AudioStreamPlaybackPolyphonic::set_stream_pitch_scale(ID p_stream_id, float p_pitch_scale) { |
244 | Stream *s = _find_stream(p_stream_id); |
245 | if (!s) { |
246 | return; |
247 | } |
248 | s->pitch_scale = p_pitch_scale; |
249 | } |
250 | |
251 | bool AudioStreamPlaybackPolyphonic::is_stream_playing(ID p_stream_id) const { |
252 | return const_cast<AudioStreamPlaybackPolyphonic *>(this)->_find_stream(p_stream_id) != nullptr; |
253 | } |
254 | |
255 | void AudioStreamPlaybackPolyphonic::stop_stream(ID p_stream_id) { |
256 | Stream *s = _find_stream(p_stream_id); |
257 | if (!s) { |
258 | return; |
259 | } |
260 | s->finish_request.set(); |
261 | } |
262 | |
263 | void AudioStreamPlaybackPolyphonic::_bind_methods() { |
264 | ClassDB::bind_method(D_METHOD("play_stream" , "stream" , "from_offset" , "volume_db" , "pitch_scale" ), &AudioStreamPlaybackPolyphonic::play_stream, DEFVAL(0), DEFVAL(0), DEFVAL(1.0)); |
265 | ClassDB::bind_method(D_METHOD("set_stream_volume" , "stream" , "volume_db" ), &AudioStreamPlaybackPolyphonic::set_stream_volume); |
266 | ClassDB::bind_method(D_METHOD("set_stream_pitch_scale" , "stream" , "pitch_scale" ), &AudioStreamPlaybackPolyphonic::set_stream_pitch_scale); |
267 | ClassDB::bind_method(D_METHOD("is_stream_playing" , "stream" ), &AudioStreamPlaybackPolyphonic::is_stream_playing); |
268 | ClassDB::bind_method(D_METHOD("stop_stream" , "stream" ), &AudioStreamPlaybackPolyphonic::stop_stream); |
269 | |
270 | BIND_CONSTANT(INVALID_ID); |
271 | } |
272 | |
273 | AudioStreamPlaybackPolyphonic::AudioStreamPlaybackPolyphonic() { |
274 | } |
275 | |