diff --git a/doc/classes/AudioServer.xml b/doc/classes/AudioServer.xml index 16437fd7d863..addb3070413d 100644 --- a/doc/classes/AudioServer.xml +++ b/doc/classes/AudioServer.xml @@ -35,6 +35,14 @@ Generates an [AudioBusLayout] using the available buses and effects. + + + + Returns the absolute time in seconds of the [AudioServer]'s timeline, based on the number of audio frames mixed. Used to schedule sounds to be played with high precision timing, such as with [method ScheduledAudioStreamPlayer.play_scheduled]. + See also [AudioStreamPlaybackScheduled]. + [b]Note:[/b] This value only updates each time an audio chunk is mixed and should not be relied on as an accurate "current" time of the [AudioServer]. + + diff --git a/doc/classes/AudioStreamPlaybackScheduled.xml b/doc/classes/AudioStreamPlaybackScheduled.xml new file mode 100644 index 000000000000..3bf0d8ca18d8 --- /dev/null +++ b/doc/classes/AudioStreamPlaybackScheduled.xml @@ -0,0 +1,60 @@ + + + + Wrapper around [AudioStreamPlayback] that schedules it to be played in the future. + + + Wrapper around [AudioStreamPlayback] that schedules it to be played in the future. + An [AudioStreamPlaybackScheduled] instance is created from [method ScheduledAudioStreamPlayer.play_scheduled], [method ScheduledAudioStreamPlayer2D.play_scheduled], or [method ScheduledAudioStreamPlayer3D.play_scheduled]. + [codeblocks] + [gdscript] + var audio_time = AudioServer.get_absolute_time() + + # Play the sound roughly 1 second in the future, then stop it after exactly 3 seconds. + var playback_scheduled = player.play_scheduled(audio_time + 1) + playback_scheduled.scheduled_end_time = audio_time + 4 + + # Cancel the playback + playback_scheduled.cancel() + [/gdscript] + [csharp] + double audioTime = AudioServer.GetAbsoluteTime(); + + // Play the sound roughly 1 second in the future, then stop it after exactly 3 seconds. + AudioStreamPlaybackScheduled playbackScheduled = player.PlayScheduled(audioTime + 1); + playbackScheduled.ScheduledEndTime = audioTime + 4; + + // Cancel the playback + playbackScheduled.Cancel(); + [/csharp] + [/codeblocks] + + + + + + + + Cancels the currently scheduled playback. + If the playback is actively playing, this method does nothing. Use [method AudioStreamPlayback.stop] instead to both stop and cancel the scheduled playback. + + + + + + Returns [code]true[/code] if the playback is scheduled and has not yet started. + + + + + + The wrapped [AudioStreamPlayback] to be scheduled. If set, the previously scheduled playback will be cancelled/stopped. + + + The time the playback is scheduled to stop playing in seconds. This is based on the [AudioServer]'s timeline (see [method AudioServer.get_absolute_time]). + + + The time the playback is scheduled to start playing in seconds. This is based on the [AudioServer]'s timeline (see [method AudioServer.get_absolute_time]). + + + diff --git a/doc/classes/ScheduledAudioStreamPlayer.xml b/doc/classes/ScheduledAudioStreamPlayer.xml new file mode 100644 index 000000000000..d13d649d045f --- /dev/null +++ b/doc/classes/ScheduledAudioStreamPlayer.xml @@ -0,0 +1,26 @@ + + + + A node for scheduled audio playback. + + + Behaves similarly to [AudioStreamPlayer] but can schedule audio to play in the future. + If you need to play scheduled audio at a specific position, use [ScheduledAudioStreamPlayer2D] or [ScheduledAudioStreamPlayer3D] instead. + + + + + + + + + + Schedules a sound to be played on the [AudioServer]'s timeline at [param absolute_time] in seconds. If the sound is scheduled to play earlier than the value returned by [method AudioServer.get_absolute_time], it will be played immediately. The sound starts from the given [param from_position] in seconds. + Use this method for high precision playbacks, such as a metronome or other rhythm-based sounds. + Returns an [AudioStreamPlaybackScheduled] instance representing the scheduled playback of the sound. + [b]Note:[/b] Calling this method after [member AudioStreamPlayer.max_polyphony] is reached will cut off the oldest sound playing on this node. + [b]Note:[/b] On the Web platform, [member AudioStreamPlayer.playback_type] must be set to [constant AudioServer.PLAYBACK_TYPE_STREAM]. Otherwise, this method will behave like [method AudioStreamPlayer.play]. + + + + diff --git a/doc/classes/ScheduledAudioStreamPlayer2D.xml b/doc/classes/ScheduledAudioStreamPlayer2D.xml new file mode 100644 index 000000000000..72691f053c6a --- /dev/null +++ b/doc/classes/ScheduledAudioStreamPlayer2D.xml @@ -0,0 +1,26 @@ + + + + A node for scheduled audio playback. + + + Behaves similarly to [AudioStreamPlayer2D] but can schedule audio to play in the future. + See also [ScheduledAudioStreamPlayer] to play a scheduled sound non-positionally. + + + + + + + + + + Schedules a sound to be played on the [AudioServer]'s timeline at [param absolute_time] in seconds. If the sound is scheduled to play earlier than the value returned by [method AudioServer.get_absolute_time], it will be played immediately. The sound starts from the given [param from_position] in seconds. + Use this method for high precision playbacks, such as a metronome or other rhythm-based sounds. + Returns an [AudioStreamPlaybackScheduled] instance representing the scheduled playback of the sound. + [b]Note:[/b] Calling this method after [member AudioStreamPlayer2D.max_polyphony] is reached will cut off the oldest sound playing on this node. + [b]Note:[/b] On the Web platform, [member AudioStreamPlayer2D.playback_type] must be set to [constant AudioServer.PLAYBACK_TYPE_STREAM]. Otherwise, this method will behave like [method AudioStreamPlayer2D.play]. + + + + diff --git a/doc/classes/ScheduledAudioStreamPlayer3D.xml b/doc/classes/ScheduledAudioStreamPlayer3D.xml new file mode 100644 index 000000000000..09c676940176 --- /dev/null +++ b/doc/classes/ScheduledAudioStreamPlayer3D.xml @@ -0,0 +1,26 @@ + + + + A node for scheduled audio playback. + + + Behaves similarly to [AudioStreamPlayer3D] but can schedule audio to play in the future. + See also [ScheduledAudioStreamPlayer] to play a scheduled sound non-positionally. + + + + + + + + + + Schedules a sound to be played on the [AudioServer]'s timeline at [param absolute_time] in seconds. If the sound is scheduled to play earlier than the value returned by [method AudioServer.get_absolute_time], it will be played immediately. The sound starts from the given [param from_position] in seconds. + Use this method for high precision playbacks, such as a metronome or other rhythm-based sounds. + Returns an [AudioStreamPlaybackScheduled] instance representing the scheduled playback of the sound. + [b]Note:[/b] Calling this method after [member AudioStreamPlayer3D.max_polyphony] is reached will cut off the oldest sound playing on this node. + [b]Note:[/b] On the Web platform, [member AudioStreamPlayer3D.playback_type] must be set to [constant AudioServer.PLAYBACK_TYPE_STREAM]. Otherwise, this method will behave like [method AudioStreamPlayer3D.play]. + + + + diff --git a/editor/icons/ScheduledAudioStreamPlayer.svg b/editor/icons/ScheduledAudioStreamPlayer.svg new file mode 100644 index 000000000000..064193640577 --- /dev/null +++ b/editor/icons/ScheduledAudioStreamPlayer.svg @@ -0,0 +1 @@ + diff --git a/editor/icons/ScheduledAudioStreamPlayer2D.svg b/editor/icons/ScheduledAudioStreamPlayer2D.svg new file mode 100644 index 000000000000..3e7684d2674e --- /dev/null +++ b/editor/icons/ScheduledAudioStreamPlayer2D.svg @@ -0,0 +1 @@ + diff --git a/editor/icons/ScheduledAudioStreamPlayer3D.svg b/editor/icons/ScheduledAudioStreamPlayer3D.svg new file mode 100644 index 000000000000..6fe884c991f0 --- /dev/null +++ b/editor/icons/ScheduledAudioStreamPlayer3D.svg @@ -0,0 +1 @@ + diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index e1fa6d5547f7..09b4c459c49c 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -231,11 +231,7 @@ float AudioStreamPlayer2D::get_pitch_scale() const { return internal->pitch_scale; } -void AudioStreamPlayer2D::play(float p_from_pos) { - Ref stream_playback = internal->play_basic(); - if (stream_playback.is_null()) { - return; - } +void AudioStreamPlayer2D::_play_internal(Ref stream_playback, double p_from_pos) { setplayback = stream_playback; setplay.set(p_from_pos); @@ -249,6 +245,14 @@ void AudioStreamPlayer2D::play(float p_from_pos) { } } +void AudioStreamPlayer2D::play(float p_from_pos) { + Ref stream_playback = internal->play_basic(); + if (stream_playback.is_null()) { + return; + } + _play_internal(stream_playback, p_from_pos); +} + void AudioStreamPlayer2D::seek(float p_seconds) { internal->seek(p_seconds); } diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index b3e996922a91..e30c333c9af1 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -41,6 +41,9 @@ class AudioStreamPlayerInternal; class AudioStreamPlayer2D : public Node2D { GDCLASS(AudioStreamPlayer2D, Node2D); +protected: + AudioStreamPlayerInternal *internal = nullptr; + private: enum { MAX_OUTPUTS = 8, @@ -48,8 +51,6 @@ class AudioStreamPlayer2D : public Node2D { }; - AudioStreamPlayerInternal *internal = nullptr; - SafeNumeric setplay{ -1.0 }; Ref setplayback; @@ -85,6 +86,8 @@ class AudioStreamPlayer2D : public Node2D { bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List *p_list) const; + void _play_internal(Ref stream_playback, double p_from_pos = 0.0); + #ifndef DISABLE_DEPRECATED bool _is_autoplay_enabled_bind_compat_86907(); static void _bind_compatibility_methods(); diff --git a/scene/2d/scheduled_audio_stream_player_2d.cpp b/scene/2d/scheduled_audio_stream_player_2d.cpp new file mode 100644 index 000000000000..0de9dd4ea32c --- /dev/null +++ b/scene/2d/scheduled_audio_stream_player_2d.cpp @@ -0,0 +1,49 @@ +/**************************************************************************/ +/* scheduled_audio_stream_player_2d.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 "scheduled_audio_stream_player_2d.h" + +#include "scene/audio/audio_stream_player_internal.h" +#include "scene/resources/audio_stream_playback_scheduled.h" + +Ref ScheduledAudioStreamPlayer2D::play_scheduled(double p_abs_time, double p_from_pos) { + Ref stream_playback_scheduled = internal->play_scheduled_basic(); + if (stream_playback_scheduled.is_null()) { + return stream_playback_scheduled; + } + stream_playback_scheduled->set_scheduled_start_time(p_abs_time); + _play_internal(stream_playback_scheduled, p_from_pos); + + return stream_playback_scheduled; +} + +void ScheduledAudioStreamPlayer2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("play_scheduled", "absolute_time", "from_position"), &ScheduledAudioStreamPlayer2D::play_scheduled, DEFVAL(0.0)); +} diff --git a/scene/2d/scheduled_audio_stream_player_2d.h b/scene/2d/scheduled_audio_stream_player_2d.h new file mode 100644 index 000000000000..d91ac769c61c --- /dev/null +++ b/scene/2d/scheduled_audio_stream_player_2d.h @@ -0,0 +1,45 @@ +/**************************************************************************/ +/* scheduled_audio_stream_player_2d.h */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "audio_stream_player_2d.h" + +class AudioStreamPlaybackScheduled; + +class ScheduledAudioStreamPlayer2D : public AudioStreamPlayer2D { + GDCLASS(ScheduledAudioStreamPlayer2D, AudioStreamPlayer2D); + +protected: + static void _bind_methods(); + +public: + Ref play_scheduled(double p_abs_time, double p_from_pos = 0.0); +}; diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index ea05c6bbbd80..dbea0d78eaf9 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -591,11 +591,7 @@ float AudioStreamPlayer3D::get_pitch_scale() const { return internal->pitch_scale; } -void AudioStreamPlayer3D::play(float p_from_pos) { - Ref stream_playback = internal->play_basic(); - if (stream_playback.is_null()) { - return; - } +void AudioStreamPlayer3D::_play_internal(Ref stream_playback, double p_from_pos) { setplayback = stream_playback; setplay.set(p_from_pos); @@ -609,6 +605,14 @@ void AudioStreamPlayer3D::play(float p_from_pos) { } } +void AudioStreamPlayer3D::play(float p_from_pos) { + Ref stream_playback = internal->play_basic(); + if (stream_playback.is_null()) { + return; + } + _play_internal(stream_playback, p_from_pos); +} + void AudioStreamPlayer3D::seek(float p_seconds) { internal->seek(p_seconds); } diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index 69d41c44fcd6..c82492021f31 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -59,6 +59,9 @@ class AudioStreamPlayer3D : public Node3D { DOPPLER_TRACKING_PHYSICS_STEP }; +protected: + AudioStreamPlayerInternal *internal = nullptr; + private: enum { MAX_OUTPUTS = 8, @@ -66,8 +69,6 @@ class AudioStreamPlayer3D : public Node3D { }; - AudioStreamPlayerInternal *internal = nullptr; - SafeNumeric setplay{ -1.0 }; Ref setplayback; @@ -130,6 +131,8 @@ class AudioStreamPlayer3D : public Node3D { bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List *p_list) const; + void _play_internal(Ref stream_playback, double p_from_pos = 0.0); + #ifndef DISABLE_DEPRECATED bool _is_autoplay_enabled_bind_compat_86907(); static void _bind_compatibility_methods(); diff --git a/scene/3d/scheduled_audio_stream_player_3d.cpp b/scene/3d/scheduled_audio_stream_player_3d.cpp new file mode 100644 index 000000000000..f3b781193e9b --- /dev/null +++ b/scene/3d/scheduled_audio_stream_player_3d.cpp @@ -0,0 +1,49 @@ +/**************************************************************************/ +/* scheduled_audio_stream_player_3d.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 "scheduled_audio_stream_player_3d.h" + +#include "scene/audio/audio_stream_player_internal.h" +#include "scene/resources/audio_stream_playback_scheduled.h" + +Ref ScheduledAudioStreamPlayer3D::play_scheduled(double p_abs_time, double p_from_pos) { + Ref stream_playback_scheduled = internal->play_scheduled_basic(); + if (stream_playback_scheduled.is_null()) { + return stream_playback_scheduled; + } + stream_playback_scheduled->set_scheduled_start_time(p_abs_time); + _play_internal(stream_playback_scheduled, p_from_pos); + + return stream_playback_scheduled; +} + +void ScheduledAudioStreamPlayer3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("play_scheduled", "absolute_time", "from_position"), &ScheduledAudioStreamPlayer3D::play_scheduled, DEFVAL(0.0)); +} diff --git a/scene/3d/scheduled_audio_stream_player_3d.h b/scene/3d/scheduled_audio_stream_player_3d.h new file mode 100644 index 000000000000..21d2989ecba0 --- /dev/null +++ b/scene/3d/scheduled_audio_stream_player_3d.h @@ -0,0 +1,45 @@ +/**************************************************************************/ +/* scheduled_audio_stream_player_3d.h */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "audio_stream_player_3d.h" + +class AudioStreamPlaybackScheduled; + +class ScheduledAudioStreamPlayer3D : public AudioStreamPlayer3D { + GDCLASS(ScheduledAudioStreamPlayer3D, AudioStreamPlayer3D); + +protected: + static void _bind_methods(); + +public: + Ref play_scheduled(double p_abs_time, double p_from_pos = 0.0); +}; diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp index 531b3c0d50a5..471b2e4712ec 100644 --- a/scene/audio/audio_stream_player.cpp +++ b/scene/audio/audio_stream_player.cpp @@ -104,11 +104,7 @@ int AudioStreamPlayer::get_max_polyphony() const { return internal->max_polyphony; } -void AudioStreamPlayer::play(float p_from_pos) { - Ref stream_playback = internal->play_basic(); - if (stream_playback.is_null()) { - return; - } +void AudioStreamPlayer::_play_internal(Ref stream_playback, double p_from_pos) { AudioServer::get_singleton()->start_playback_stream(stream_playback, internal->bus, _get_volume_vector(), p_from_pos, internal->pitch_scale); internal->ensure_playback_limit(); @@ -123,6 +119,14 @@ void AudioStreamPlayer::play(float p_from_pos) { } } +void AudioStreamPlayer::play(float p_from_pos) { + Ref stream_playback = internal->play_basic(); + if (stream_playback.is_null()) { + return; + } + _play_internal(stream_playback, p_from_pos); +} + void AudioStreamPlayer::seek(float p_seconds) { internal->seek(p_seconds); } diff --git a/scene/audio/audio_stream_player.h b/scene/audio/audio_stream_player.h index abaed688d1e4..db6416b963cc 100644 --- a/scene/audio/audio_stream_player.h +++ b/scene/audio/audio_stream_player.h @@ -48,9 +48,10 @@ class AudioStreamPlayer : public Node { MIX_TARGET_CENTER }; -private: +protected: AudioStreamPlayerInternal *internal = nullptr; +private: MixTarget mix_target = MIX_TARGET_STEREO; void _set_playing(bool p_enable); @@ -67,6 +68,8 @@ class AudioStreamPlayer : public Node { bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List *p_list) const; + void _play_internal(Ref stream_playback, double p_from_pos = 0.0); + #ifndef DISABLE_DEPRECATED bool _is_autoplay_enabled_bind_compat_86907(); static void _bind_compatibility_methods(); diff --git a/scene/audio/audio_stream_player_internal.cpp b/scene/audio/audio_stream_player_internal.cpp index f738530f0948..e404742ed629 100644 --- a/scene/audio/audio_stream_player_internal.cpp +++ b/scene/audio/audio_stream_player_internal.cpp @@ -31,6 +31,7 @@ #include "audio_stream_player_internal.h" #include "scene/main/node.h" +#include "scene/resources/audio_stream_playback_scheduled.h" #include "servers/audio/audio_stream.h" void AudioStreamPlayerInternal::_set_process(bool p_enabled) { @@ -133,7 +134,7 @@ void AudioStreamPlayerInternal::notification(int p_what) { } } -Ref AudioStreamPlayerInternal::play_basic() { +Ref AudioStreamPlayerInternal::_create_playback() { Ref stream_playback; if (stream.is_null()) { return stream_playback; @@ -168,12 +169,41 @@ Ref AudioStreamPlayerInternal::play_basic() { } } + return stream_playback; +} + +Ref AudioStreamPlayerInternal::play_basic() { + Ref stream_playback = _create_playback(); + if (stream_playback.is_null()) { + return stream_playback; + } + stream_playbacks.push_back(stream_playback); active.set(); _set_process(true); return stream_playback; } +Ref AudioStreamPlayerInternal::play_scheduled_basic() { + Ref stream_playback = _create_playback(); + if (stream_playback.is_null()) { + return Ref(); + } + + Ref stream_playback_scheduled; + stream_playback_scheduled.instantiate(); + stream_playback_scheduled->set_base_playback(stream_playback); + + if (stream_playback_scheduled->get_is_sample()) { + WARN_PRINT_ED("Scheduled play does not support samples. Playing immediately."); + } + + stream_playbacks.push_back(stream_playback_scheduled); + active.set(); + _set_process(true); + return stream_playback_scheduled; +} + void AudioStreamPlayerInternal::set_stream_paused(bool p_pause) { // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. for (Ref &playback : stream_playbacks) { @@ -263,7 +293,7 @@ void AudioStreamPlayerInternal::set_stream(Ref p_stream) { node->notify_property_list_changed(); } -void AudioStreamPlayerInternal::seek(float p_seconds) { +void AudioStreamPlayerInternal::seek(double p_seconds) { if (is_playing()) { stop_callable.call(); play_callable.call(p_seconds); @@ -289,7 +319,7 @@ bool AudioStreamPlayerInternal::is_playing() const { return false; } -float AudioStreamPlayerInternal::get_playback_position() { +double AudioStreamPlayerInternal::get_playback_position() { // Return the playback position of the most recently started playback stream. if (!stream_playbacks.is_empty()) { return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); diff --git a/scene/audio/audio_stream_player_internal.h b/scene/audio/audio_stream_player_internal.h index bd7075d8514b..f4b0e67256c3 100644 --- a/scene/audio/audio_stream_player_internal.h +++ b/scene/audio/audio_stream_player_internal.h @@ -36,6 +36,7 @@ class AudioStream; class AudioStreamPlayback; +class AudioStreamPlaybackScheduled; class AudioSamplePlayback; class Node; @@ -61,6 +62,8 @@ class AudioStreamPlayerInternal : public Object { void _set_process(bool p_enabled); void _update_stream_parameters(); + Ref _create_playback(); + _FORCE_INLINE_ bool _is_sample() { return (AudioServer::get_singleton()->get_default_playback_type() == AudioServer::PlaybackType::PLAYBACK_TYPE_SAMPLE && get_playback_type() == AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT) || get_playback_type() == AudioServer::PlaybackType::PLAYBACK_TYPE_SAMPLE; } @@ -93,10 +96,11 @@ class AudioStreamPlayerInternal : public Object { StringName get_bus() const; Ref play_basic(); - void seek(float p_seconds); + Ref play_scheduled_basic(); + void seek(double p_seconds); void stop_basic(); bool is_playing() const; - float get_playback_position(); + double get_playback_position(); void set_playing(bool p_enable); bool is_active() const; diff --git a/scene/audio/scheduled_audio_stream_player.cpp b/scene/audio/scheduled_audio_stream_player.cpp new file mode 100644 index 000000000000..00debc672061 --- /dev/null +++ b/scene/audio/scheduled_audio_stream_player.cpp @@ -0,0 +1,49 @@ +/**************************************************************************/ +/* scheduled_audio_stream_player.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 "scheduled_audio_stream_player.h" + +#include "scene/audio/audio_stream_player_internal.h" +#include "scene/resources/audio_stream_playback_scheduled.h" + +Ref ScheduledAudioStreamPlayer::play_scheduled(double p_abs_time, double p_from_pos) { + Ref stream_playback_scheduled = internal->play_scheduled_basic(); + if (stream_playback_scheduled.is_null()) { + return stream_playback_scheduled; + } + stream_playback_scheduled->set_scheduled_start_time(p_abs_time); + _play_internal(stream_playback_scheduled, p_from_pos); + + return stream_playback_scheduled; +} + +void ScheduledAudioStreamPlayer::_bind_methods() { + ClassDB::bind_method(D_METHOD("play_scheduled", "absolute_time", "from_position"), &ScheduledAudioStreamPlayer::play_scheduled, DEFVAL(0.0)); +} diff --git a/scene/audio/scheduled_audio_stream_player.h b/scene/audio/scheduled_audio_stream_player.h new file mode 100644 index 000000000000..a3829fdd80f7 --- /dev/null +++ b/scene/audio/scheduled_audio_stream_player.h @@ -0,0 +1,45 @@ +/**************************************************************************/ +/* scheduled_audio_stream_player.h */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "audio_stream_player.h" + +class AudioStreamPlaybackScheduled; + +class ScheduledAudioStreamPlayer : public AudioStreamPlayer { + GDCLASS(ScheduledAudioStreamPlayer, AudioStreamPlayer); + +protected: + static void _bind_methods(); + +public: + Ref play_scheduled(double p_abs_time, double p_from_pos = 0.0); +}; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index da9d0fbc949a..19fb139dd143 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -43,6 +43,7 @@ #include "scene/animation/animation_tree.h" #include "scene/animation/tween.h" #include "scene/audio/audio_stream_player.h" +#include "scene/audio/scheduled_audio_stream_player.h" #include "scene/debugger/scene_debugger.h" #include "scene/gui/aspect_ratio_container.h" #include "scene/gui/box_container.h" @@ -109,6 +110,7 @@ #include "scene/main/window.h" #include "scene/resources/animation_library.h" #include "scene/resources/atlas_texture.h" +#include "scene/resources/audio_stream_playback_scheduled.h" #include "scene/resources/audio_stream_polyphonic.h" #include "scene/resources/audio_stream_wav.h" #include "scene/resources/bit_map.h" @@ -185,6 +187,7 @@ #include "scene/2d/path_2d.h" #include "scene/2d/polygon_2d.h" #include "scene/2d/remote_transform_2d.h" +#include "scene/2d/scheduled_audio_stream_player_2d.h" #include "scene/2d/skeleton_2d.h" #include "scene/2d/sprite_2d.h" #include "scene/2d/tile_map_layer.h" @@ -252,6 +255,7 @@ #include "scene/3d/reflection_probe.h" #include "scene/3d/remote_transform_3d.h" #include "scene/3d/retarget_modifier_3d.h" +#include "scene/3d/scheduled_audio_stream_player_3d.h" #include "scene/3d/skeleton_3d.h" #include "scene/3d/skeleton_modifier_3d.h" #include "scene/3d/spline_ik_3d.h" @@ -1004,6 +1008,7 @@ void register_scene_types() { GDREGISTER_CLASS(RibbonTrailMesh); GDREGISTER_CLASS(PointMesh); GDREGISTER_ABSTRACT_CLASS(BaseMaterial3D); + GDREGISTER_CLASS(ScheduledAudioStreamPlayer3D); GDREGISTER_CLASS(StandardMaterial3D); GDREGISTER_CLASS(ORMMaterial3D); GDREGISTER_CLASS(ProceduralSkyMaterial); @@ -1121,6 +1126,8 @@ void register_scene_types() { GDREGISTER_CLASS(AudioStreamWAV); GDREGISTER_CLASS(AudioStreamPolyphonic); GDREGISTER_ABSTRACT_CLASS(AudioStreamPlaybackPolyphonic); + GDREGISTER_CLASS(AudioStreamPlaybackScheduled); + GDREGISTER_CLASS(ScheduledAudioStreamPlayer); OS::get_singleton()->yield(); // may take time to init @@ -1128,6 +1135,7 @@ void register_scene_types() { GDREGISTER_CLASS(Curve2D); GDREGISTER_CLASS(Path2D); GDREGISTER_CLASS(PathFollow2D); + GDREGISTER_CLASS(ScheduledAudioStreamPlayer2D); #ifndef PHYSICS_2D_DISABLED GDREGISTER_ABSTRACT_CLASS(Shape2D); diff --git a/scene/resources/audio_stream_playback_scheduled.cpp b/scene/resources/audio_stream_playback_scheduled.cpp new file mode 100644 index 000000000000..cf8b119fc161 --- /dev/null +++ b/scene/resources/audio_stream_playback_scheduled.cpp @@ -0,0 +1,249 @@ +/**************************************************************************/ +/* audio_stream_playback_scheduled.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_playback_scheduled.h" + +#include "servers/audio/audio_server.h" + +void AudioStreamPlaybackScheduled::start(double p_from_pos) { + ERR_FAIL_COND_MSG(base_playback.is_null(), "base_playback must be set."); + + active = true; + base_playback->start(p_from_pos); + + uint64_t mix_start = AudioServer::get_singleton()->get_mixed_frames(); + if (mix_start > scheduled_start_frame) { + WARN_PRINT_ED(vformat("Sound (%s) was scheduled for absolute time %.4f, which has already passed. Playing immediately.", + get_instance_id(), + get_scheduled_start_time())); + } +} + +void AudioStreamPlaybackScheduled::stop() { + active = false; + if (base_playback.is_null()) { + return; + } + base_playback->stop(); +} + +bool AudioStreamPlaybackScheduled::is_playing() const { + if (base_playback.is_null()) { + return false; + } + uint64_t mix_start = AudioServer::get_singleton()->get_mixed_frames(); + if (mix_start < scheduled_start_frame) { + return false; + } + return base_playback->is_playing(); +} + +int AudioStreamPlaybackScheduled::get_loop_count() const { + if (base_playback.is_null()) { + return 0; + } + return base_playback->get_loop_count(); +} + +double AudioStreamPlaybackScheduled::get_playback_position() const { + if (base_playback.is_null()) { + return 0.0; + } + return base_playback->get_playback_position(); +} + +void AudioStreamPlaybackScheduled::seek(double p_time) { + if (base_playback.is_null()) { + return; + } + base_playback->seek(p_time); +} + +void AudioStreamPlaybackScheduled::tag_used_streams() { + if (base_playback.is_null()) { + return; + } + base_playback->tag_used_streams(); +} + +void AudioStreamPlaybackScheduled::set_parameter(const StringName &p_name, const Variant &p_value) { + if (base_playback.is_null()) { + return; + } + base_playback->set_parameter(p_name, p_value); +} + +Variant AudioStreamPlaybackScheduled::get_parameter(const StringName &p_name) const { + if (base_playback.is_null()) { + return Variant(); + } + return base_playback->get_parameter(p_name); +} + +int AudioStreamPlaybackScheduled::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { + // Pre-clear buffer. + for (int i = 0; i < p_frames; i++) { + p_buffer[i] = AudioFrame(0, 0); + } + + if (!active) { + return 0; + } + if (base_playback.is_null()) { + return 0; + } + ERR_FAIL_COND_V_MSG(scheduled_end_frame > 0 && scheduled_end_frame < scheduled_start_frame, 0, "Scheduled end time is before scheduled start time."); + + AudioFrame *buf = p_buffer; + unsigned int todo = p_frames; + uint64_t mix_start = AudioServer::get_singleton()->get_mixed_frames(); + uint64_t mix_end = mix_start + p_frames; + + // Fill buffer with silence frames if sound isn't scheduled to start yet. + if (mix_start < scheduled_start_frame) { + unsigned int silence_frames = (unsigned int)MIN(scheduled_start_frame - mix_start, todo); + for (unsigned int i = 0; i < silence_frames; i++) { + buf[i] = AudioFrame(0, 0); + } + todo -= silence_frames; + buf += silence_frames; + + if (todo == 0) { + return p_frames; + } + } + + // Limit the amount of mixing if the sound is scheduled to end within this + // chunk. + if (scheduled_end_frame > 0 && scheduled_end_frame < mix_end) { + unsigned int frames_to_skip = (unsigned int)MIN(mix_end - scheduled_end_frame, todo); + todo -= base_playback->mix(buf, p_rate_scale, todo - frames_to_skip); + stop(); + return p_frames - todo; + } + + // Normal mixing. + todo -= base_playback->mix(buf, p_rate_scale, todo); + + return p_frames - todo; +} + +void AudioStreamPlaybackScheduled::set_is_sample(bool p_is_sample) { + if (base_playback.is_null()) { + return; + } + base_playback->set_is_sample(p_is_sample); +} + +bool AudioStreamPlaybackScheduled::get_is_sample() const { + if (base_playback.is_null()) { + return false; + } + return base_playback->get_is_sample(); +} + +void AudioStreamPlaybackScheduled::set_sample_playback(const Ref &p_playback) { + if (base_playback.is_null()) { + return; + } + base_playback->set_sample_playback(p_playback); +} + +Ref AudioStreamPlaybackScheduled::get_sample_playback() const { + if (base_playback.is_null()) { + return Ref(); + } + return base_playback->get_sample_playback(); +} + +void AudioStreamPlaybackScheduled::cancel() { + uint64_t mix_start = AudioServer::get_singleton()->get_mixed_frames(); + if (mix_start < scheduled_start_frame) { + stop(); + } +} + +bool AudioStreamPlaybackScheduled::is_scheduled() const { + uint64_t mix_start = AudioServer::get_singleton()->get_mixed_frames(); + return active && mix_start < scheduled_start_frame; +} + +void AudioStreamPlaybackScheduled::set_scheduled_start_time(double p_start_time) { + scheduled_start_frame = int64_t(p_start_time * AudioServer::get_singleton()->get_mix_rate()); + + if (active) { + uint64_t mix_start = AudioServer::get_singleton()->get_mixed_frames(); + if (mix_start > scheduled_start_frame) { + WARN_PRINT_ED(vformat("Sound (%s) was scheduled for absolute time %.4f, which has already passed. Playing immediately.", + get_instance_id(), + get_scheduled_start_time())); + } + } +} + +double AudioStreamPlaybackScheduled::get_scheduled_start_time() const { + return scheduled_start_frame / AudioServer::get_singleton()->get_mix_rate(); +} + +void AudioStreamPlaybackScheduled::set_scheduled_end_time(double p_end_time) { + scheduled_end_frame = int64_t(p_end_time * AudioServer::get_singleton()->get_mix_rate()); +} + +double AudioStreamPlaybackScheduled::get_scheduled_end_time() const { + return scheduled_end_frame / AudioServer::get_singleton()->get_mix_rate(); +} + +void AudioStreamPlaybackScheduled::set_base_playback(const Ref &p_playback) { + ERR_FAIL_COND_MSG(p_playback == this, "Cannot assign base_playback to self."); + stop(); + base_playback = p_playback; +} + +Ref AudioStreamPlaybackScheduled::get_base_playback() const { + return base_playback; +} + +void AudioStreamPlaybackScheduled::_bind_methods() { + ClassDB::bind_method(D_METHOD("cancel"), &AudioStreamPlaybackScheduled::cancel); + ClassDB::bind_method(D_METHOD("is_scheduled"), &AudioStreamPlaybackScheduled::is_scheduled); + + ClassDB::bind_method(D_METHOD("set_base_playback", "base_playback"), &AudioStreamPlaybackScheduled::set_base_playback); + ClassDB::bind_method(D_METHOD("get_base_playback"), &AudioStreamPlaybackScheduled::get_base_playback); + + ClassDB::bind_method(D_METHOD("set_scheduled_start_time", "start_time"), &AudioStreamPlaybackScheduled::set_scheduled_start_time); + ClassDB::bind_method(D_METHOD("get_scheduled_start_time"), &AudioStreamPlaybackScheduled::get_scheduled_start_time); + + ClassDB::bind_method(D_METHOD("set_scheduled_end_time", "end_time"), &AudioStreamPlaybackScheduled::set_scheduled_end_time); + ClassDB::bind_method(D_METHOD("get_scheduled_end_time"), &AudioStreamPlaybackScheduled::get_scheduled_end_time); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "base_playback", PROPERTY_HINT_RESOURCE_TYPE, "AudioStreamPlayback"), "set_base_playback", "get_base_playback"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scheduled_start_time"), "set_scheduled_start_time", "get_scheduled_start_time"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scheduled_end_time"), "set_scheduled_end_time", "get_scheduled_end_time"); +} diff --git a/scene/resources/audio_stream_playback_scheduled.h b/scene/resources/audio_stream_playback_scheduled.h new file mode 100644 index 000000000000..83f4747b43fe --- /dev/null +++ b/scene/resources/audio_stream_playback_scheduled.h @@ -0,0 +1,83 @@ +/**************************************************************************/ +/* audio_stream_playback_scheduled.h */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "servers/audio/audio_stream.h" + +class AudioStreamPlaybackScheduled : public AudioStreamPlayback { + GDCLASS(AudioStreamPlaybackScheduled, AudioStreamPlayback); + + Ref base_playback; + + uint64_t scheduled_start_frame = 0; + uint64_t scheduled_end_frame = 0; + + bool active = false; + +protected: + static void _bind_methods(); + +public: + virtual void start(double p_from_pos = 0.0) override; + virtual void stop() override; + virtual bool is_playing() const override; + + virtual int get_loop_count() const override; //times it looped + + virtual double get_playback_position() const override; + virtual void seek(double p_time) override; + + virtual void tag_used_streams() override; + + virtual void set_parameter(const StringName &p_name, const Variant &p_value) override; + virtual Variant get_parameter(const StringName &p_name) const override; + + virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override; + + virtual void set_is_sample(bool p_is_sample) override; + virtual bool get_is_sample() const override; + virtual void set_sample_playback(const Ref &p_playback) override; + virtual Ref get_sample_playback() const override; + + void cancel(); + bool is_scheduled() const; + + void set_base_playback(const Ref &p_playback); + Ref get_base_playback() const; + + void set_scheduled_start_time(double p_start_time); + double get_scheduled_start_time() const; + + void set_scheduled_end_time(double p_end_time); + double get_scheduled_end_time() const; + + AudioStreamPlaybackScheduled() {} +}; diff --git a/servers/audio/audio_server.cpp b/servers/audio/audio_server.cpp index fa6715dec9ec..a676fa9be41d 100644 --- a/servers/audio/audio_server.cpp +++ b/servers/audio/audio_server.cpp @@ -1225,21 +1225,21 @@ float AudioServer::get_playback_speed_scale() const { return playback_speed_scale; } -void AudioServer::start_playback_stream(Ref p_playback, const StringName &p_bus, Vector p_volume_db_vector, float p_start_time, float p_pitch_scale) { +void AudioServer::start_playback_stream(Ref p_playback, const StringName &p_bus, Vector p_volume_db_vector, double p_from_pos, float p_pitch_scale) { ERR_FAIL_COND(p_playback.is_null()); HashMap> map; map[p_bus] = p_volume_db_vector; - start_playback_stream(p_playback, map, p_start_time, p_pitch_scale); + start_playback_stream(p_playback, map, p_from_pos, p_pitch_scale); } -void AudioServer::start_playback_stream(Ref p_playback, const HashMap> &p_bus_volumes, float p_start_time, float p_pitch_scale, float p_highshelf_gain, float p_attenuation_cutoff_hz) { +void AudioServer::start_playback_stream(Ref p_playback, const HashMap> &p_bus_volumes, double p_from_pos, float p_pitch_scale, float p_highshelf_gain, float p_attenuation_cutoff_hz) { ERR_FAIL_COND(p_playback.is_null()); AudioStreamPlaybackListNode *playback_node = new AudioStreamPlaybackListNode(); playback_node->stream_playback = p_playback; - playback_node->stream_playback->start(p_start_time); + playback_node->stream_playback->start(p_from_pos); AudioStreamPlaybackBusDetails *new_bus_details = new AudioStreamPlaybackBusDetails(); int idx = 0; @@ -1448,7 +1448,7 @@ bool AudioServer::is_playback_active(Ref p_playback) { return playback_node->state.load() == AudioStreamPlaybackListNode::PLAYING; } -float AudioServer::get_playback_position(Ref p_playback) { +double AudioServer::get_playback_position(Ref p_playback) { ERR_FAIL_COND_V(p_playback.is_null(), 0); // Samples. @@ -1688,6 +1688,10 @@ double AudioServer::get_time_since_last_mix() const { return AudioDriver::get_singleton()->get_time_since_last_mix(); } +double AudioServer::get_absolute_time() const { + return mix_frames / double(get_mix_rate()); +} + AudioServer *AudioServer::singleton = nullptr; void AudioServer::add_update_callback(AudioCallback p_callback, void *p_userdata) { @@ -2079,6 +2083,7 @@ void AudioServer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_time_to_next_mix"), &AudioServer::get_time_to_next_mix); ClassDB::bind_method(D_METHOD("get_time_since_last_mix"), &AudioServer::get_time_since_last_mix); + ClassDB::bind_method(D_METHOD("get_absolute_time"), &AudioServer::get_absolute_time); ClassDB::bind_method(D_METHOD("get_output_latency"), &AudioServer::get_output_latency); ClassDB::bind_method(D_METHOD("get_input_device_list"), &AudioServer::get_input_device_list); diff --git a/servers/audio/audio_server.h b/servers/audio/audio_server.h index a7a4decf8357..721c689f0fd3 100644 --- a/servers/audio/audio_server.h +++ b/servers/audio/audio_server.h @@ -431,9 +431,9 @@ class AudioServer : public Object { float get_playback_speed_scale() const; // Convenience method. - void start_playback_stream(Ref p_playback, const StringName &p_bus, Vector p_volume_db_vector, float p_start_time = 0, float p_pitch_scale = 1); + void start_playback_stream(Ref p_playback, const StringName &p_bus, Vector p_volume_db_vector, double p_from_pos = 0, float p_pitch_scale = 1); // Expose all parameters. - void start_playback_stream(Ref p_playback, const HashMap> &p_bus_volumes, float p_start_time = 0, float p_pitch_scale = 1, float p_highshelf_gain = 0, float p_attenuation_cutoff_hz = 0); + void start_playback_stream(Ref p_playback, const HashMap> &p_bus_volumes, double p_from_pos = 0, float p_pitch_scale = 1, float p_highshelf_gain = 0, float p_attenuation_cutoff_hz = 0); void stop_playback_stream(Ref p_playback); void set_playback_bus_exclusive(Ref p_playback, const StringName &p_bus, Vector p_volumes); @@ -444,7 +444,7 @@ class AudioServer : public Object { void set_playback_highshelf_params(Ref p_playback, float p_gain, float p_attenuation_cutoff_hz); bool is_playback_active(Ref p_playback); - float get_playback_position(Ref p_playback); + double get_playback_position(Ref p_playback); bool is_playback_paused(Ref p_playback); uint64_t get_mix_count() const; @@ -475,6 +475,7 @@ class AudioServer : public Object { virtual double get_output_latency() const; virtual double get_time_to_next_mix() const; virtual double get_time_since_last_mix() const; + virtual double get_absolute_time() const; void add_listener_changed_callback(AudioCallback p_callback, void *p_userdata); void remove_listener_changed_callback(AudioCallback p_callback, void *p_userdata);