/**************************************************************************/ /* audio_stream_polyphonic.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ #include "audio_stream_polyphonic.h" #include "scene/main/scene_tree.h" Ref AudioStreamPolyphonic::instantiate_playback() { Ref playback; playback.instantiate(); playback->streams.resize(polyphony); return playback; } String AudioStreamPolyphonic::get_stream_name() const { return "AudioStreamPolyphonic"; } bool AudioStreamPolyphonic::is_monophonic() const { return true; // This avoids stream players to instantiate more than one of these. } void AudioStreamPolyphonic::set_polyphony(int p_voices) { ERR_FAIL_COND(p_voices < 0 || p_voices > 128); polyphony = p_voices; } int AudioStreamPolyphonic::get_polyphony() const { return polyphony; } void AudioStreamPolyphonic::_bind_methods() { ClassDB::bind_method(D_METHOD("set_polyphony", "voices"), &AudioStreamPolyphonic::set_polyphony); ClassDB::bind_method(D_METHOD("get_polyphony"), &AudioStreamPolyphonic::get_polyphony); ADD_PROPERTY(PropertyInfo(Variant::INT, "polyphony", PROPERTY_HINT_RANGE, "1,128,1"), "set_polyphony", "get_polyphony"); } AudioStreamPolyphonic::AudioStreamPolyphonic() { } //////////////////////// void AudioStreamPlaybackPolyphonic::start(double p_from_pos) { if (active) { stop(); } active = true; } void AudioStreamPlaybackPolyphonic::stop() { if (!active) { return; } bool locked = false; for (Stream &s : streams) { if (s.active.is_set()) { // Need locking because something may still be mixing. locked = true; AudioServer::get_singleton()->lock(); } s.active.clear(); s.finalizing.clear(); s.finish_request.clear(); s.stream_playback.unref(); s.stream.unref(); } if (locked) { AudioServer::get_singleton()->unlock(); } active = false; } bool AudioStreamPlaybackPolyphonic::is_playing() const { return active; } int AudioStreamPlaybackPolyphonic::get_loop_count() const { return 0; } double AudioStreamPlaybackPolyphonic::get_playback_position() const { return 0; } void AudioStreamPlaybackPolyphonic::seek(double p_time) { // Ignored. } void AudioStreamPlaybackPolyphonic::tag_used_streams() { for (Stream &s : streams) { if (s.active.is_set()) { s.stream_playback->tag_used_streams(); } } } int AudioStreamPlaybackPolyphonic::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { if (!active) { return 0; } // Pre-clear buffer. for (int i = 0; i < p_frames; i++) { p_buffer[i] = AudioFrame(0, 0); } for (Stream &s : streams) { if (!s.active.is_set()) { continue; } float volume_db = s.volume_db; // Copy because it can be overriden at any time. float next_volume = Math::db_to_linear(volume_db); s.prev_volume_db = volume_db; if (s.finish_request.is_set()) { if (s.pending_play.is_set()) { // Did not get the chance to play, was finalized too soon. s.active.clear(); s.finalizing.set(); continue; } next_volume = 0; } if (s.pending_play.is_set()) { s.stream_playback->start(s.play_offset); s.pending_play.clear(); } float prev_volume = Math::db_to_linear(s.prev_volume_db); float volume_inc = (next_volume - prev_volume) / float(p_frames); int todo = p_frames; int offset = 0; float volume = prev_volume; while (todo) { int to_mix = MIN(todo, int(INTERNAL_BUFFER_LEN)); int mixed = s.stream_playback->mix(internal_buffer, s.pitch_scale, to_mix); for (int i = 0; i < to_mix; i++) { p_buffer[offset + i] += internal_buffer[i] * volume; volume += volume_inc; } if (mixed < to_mix) { // Stream is done. s.active.clear(); s.finalizing.set(); break; } todo -= to_mix; offset += to_mix; } if (s.finish_request.is_set()) { s.active.clear(); s.finalizing.set(); } } return p_frames; } void AudioStreamPlaybackPolyphonic::_check_finalized_streams() { if (!active) { return; } for (Stream &s : streams) { if (!s.active.is_set() && s.finalizing.is_set()) { s.stream_playback.unref(); s.stream.unref(); s.finalizing.clear(); s.finish_request.clear(); } } } AudioStreamPlaybackPolyphonic::ID AudioStreamPlaybackPolyphonic::play_stream(const Ref &p_stream, float p_from_offset, float p_volume_db, float p_pitch_scale) { ERR_FAIL_COND_V(p_stream.is_null(), INVALID_ID); for (uint32_t i = 0; i < streams.size(); i++) { if (!streams[i].active.is_set() && !streams[i].finish_request.is_set() && !streams[i].finalizing.is_set()) { // Can use this stream, as it's not active. streams[i].stream = p_stream; streams[i].stream_playback = streams[i].stream->instantiate_playback(); streams[i].play_offset = p_from_offset; streams[i].volume_db = p_volume_db; streams[i].prev_volume_db = p_volume_db; streams[i].pitch_scale = p_pitch_scale; streams[i].id = id_counter++; streams[i].pending_play.set(); streams[i].active.set(); return (ID(i) << INDEX_SHIFT) | ID(streams[i].id); } } return INVALID_ID; } AudioStreamPlaybackPolyphonic::Stream *AudioStreamPlaybackPolyphonic::_find_stream(int64_t p_id) { uint32_t index = p_id >> INDEX_SHIFT; if (index >= streams.size()) { return nullptr; } if (!streams[index].active.is_set()) { return nullptr; // Not active, no longer exists. } int64_t id = p_id & ID_MASK; if (streams[index].id != id) { return nullptr; } return &streams[index]; } void AudioStreamPlaybackPolyphonic::set_stream_volume(ID p_stream_id, float p_volume_db) { Stream *s = _find_stream(p_stream_id); if (!s) { return; } s->volume_db = p_volume_db; } void AudioStreamPlaybackPolyphonic::set_stream_pitch_scale(ID p_stream_id, float p_pitch_scale) { Stream *s = _find_stream(p_stream_id); if (!s) { return; } s->pitch_scale = p_pitch_scale; } bool AudioStreamPlaybackPolyphonic::is_stream_playing(ID p_stream_id) const { return const_cast(this)->_find_stream(p_stream_id) != nullptr; } void AudioStreamPlaybackPolyphonic::stop_stream(ID p_stream_id) { Stream *s = _find_stream(p_stream_id); if (!s) { return; } s->finish_request.set(); } void AudioStreamPlaybackPolyphonic::_bind_methods() { ClassDB::bind_method(D_METHOD("play_stream", "stream", "from_offset", "volume_db", "pitch_scale"), &AudioStreamPlaybackPolyphonic::play_stream, DEFVAL(0), DEFVAL(0), DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("set_stream_volume", "stream", "volume_db"), &AudioStreamPlaybackPolyphonic::set_stream_volume); ClassDB::bind_method(D_METHOD("set_stream_pitch_scale", "stream", "pitch_scale"), &AudioStreamPlaybackPolyphonic::set_stream_pitch_scale); ClassDB::bind_method(D_METHOD("is_stream_playing", "stream"), &AudioStreamPlaybackPolyphonic::is_stream_playing); ClassDB::bind_method(D_METHOD("stop_stream", "stream"), &AudioStreamPlaybackPolyphonic::stop_stream); BIND_CONSTANT(INVALID_ID); } AudioStreamPlaybackPolyphonic::AudioStreamPlaybackPolyphonic() { SceneTree::get_singleton()->connect(SNAME("process_frame"), callable_mp(this, &AudioStreamPlaybackPolyphonic::_check_finalized_streams)); }