diff --git a/doc/classes/AudioServer.xml b/doc/classes/AudioServer.xml
index e33eeb73f119..278470a1f4ee 100644
--- a/doc/classes/AudioServer.xml
+++ b/doc/classes/AudioServer.xml
@@ -35,6 +35,26 @@
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 AudioStreamPlayer.play_scheduled].
+ [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].
+ [b]Example:[/b] Schedule two sounds to be played at the same time, roughly 1 second in the future:
+ [codeblocks]
+ [gdscript]
+ var future_time = AudioServer.get_absolute_time() + 1
+ player1.play_scheduled(future_time)
+ player2.play_scheduled(future_time)
+ [/gdscript]
+ [csharp]
+ double futureTime = AudioServer.GetAbsoluteTime() + 1;
+ player1.PlayScheduled(futureTime);
+ player2.PlayScheduled(futureTime);
+ [/csharp]
+ [/codeblocks]
+
+
diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml
index 142b41b49c0e..feacf8674abd 100644
--- a/doc/classes/AudioStreamPlayer.xml
+++ b/doc/classes/AudioStreamPlayer.xml
@@ -28,7 +28,7 @@
- Returns the latest [AudioStreamPlayback] of this node, usually the most recently created by [method play]. If no sounds are playing, this method fails and returns an empty playback.
+ Returns the latest [AudioStreamPlayback] of this node, usually the most recently created by [method play] or [method play_scheduled]. If no sounds are playing, this method fails and returns an empty playback.
@@ -44,6 +44,17 @@
Plays a sound from the beginning, or the given [param from_position] in seconds.
+
+
+
+
+
+ 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.
+ [b]Note:[/b] Calling this method after [member max_polyphony] is reached will cut off the oldest sound playing on this node.
+ [b]Note:[/b] On the Web platform, [member playback_type] must be set to [constant AudioServer.PLAYBACK_TYPE_STREAM]. Otherwise, this method will behave like [method play].
+
+
@@ -67,7 +78,7 @@
[b]Note:[/b] At runtime, if no bus with the given name exists, all sounds will fall back on [code]"Master"[/code]. See also [method AudioServer.get_bus_name].
- The maximum number of sounds this node can play at the same time. Calling [method play] after this value is reached will cut off the oldest sounds.
+ The maximum number of sounds this node can play and schedule at the same time. Calling [method play] or [method play_scheduled] after this value is reached will cut off the oldest sounds.
The mix target channels, as one of the [enum MixTarget] constants. Has no effect when two speakers or less are detected (see [enum AudioServer.SpeakerMode]).
diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml
index a87be223413b..0995ffdd1f7f 100644
--- a/doc/classes/AudioStreamPlayer2D.xml
+++ b/doc/classes/AudioStreamPlayer2D.xml
@@ -38,6 +38,17 @@
Queues the audio to play on the next physics frame, from the given position [param from_position], in seconds.
+
+
+
+
+
+ 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.
+ [b]Note:[/b] Calling this method after [member max_polyphony] is reached will cut off the oldest sound playing on this node.
+ [b]Note:[/b] On the Web platform, [member playback_type] must be set to [constant AudioServer.PLAYBACK_TYPE_STREAM]. Otherwise, this method will behave like [method play].
+
+
@@ -70,7 +81,7 @@
Maximum distance from which audio is still hearable.
- The maximum number of sounds this node can play at the same time. Playing additional sounds after this value is reached will cut off the oldest sounds.
+ The maximum number of sounds this node can play and schedule at the same time. Calling [method play] or [method play_scheduled] after this value is reached will cut off the oldest sounds.
Scales the panning strength for this node by multiplying the base [member ProjectSettings.audio/general/2d_panning_strength] with this factor. Higher values will pan audio from left to right more dramatically than lower values.
diff --git a/doc/classes/AudioStreamPlayer3D.xml b/doc/classes/AudioStreamPlayer3D.xml
index 718f0e149c5f..68d183b4dc75 100644
--- a/doc/classes/AudioStreamPlayer3D.xml
+++ b/doc/classes/AudioStreamPlayer3D.xml
@@ -38,6 +38,17 @@
Queues the audio to play on the next physics frame, from the given position [param from_position], in seconds.
+
+
+
+
+
+ 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.
+ [b]Note:[/b] Calling this method after [member max_polyphony] is reached will cut off the oldest sound playing on this node.
+ [b]Note:[/b] On the Web platform, [member playback_type] must be set to [constant AudioServer.PLAYBACK_TYPE_STREAM]. Otherwise, this method will behave like [method play].
+
+
@@ -91,7 +102,7 @@
The distance past which the sound can no longer be heard at all. Only has an effect if set to a value greater than [code]0.0[/code]. [member max_distance] works in tandem with [member unit_size]. However, unlike [member unit_size] whose behavior depends on the [member attenuation_model], [member max_distance] always works in a linear fashion. This can be used to prevent the [AudioStreamPlayer3D] from requiring audio mixing when the listener is far away, which saves CPU resources.
- The maximum number of sounds this node can play at the same time. Playing additional sounds after this value is reached will cut off the oldest sounds.
+ The maximum number of sounds this node can play and schedule at the same time. Calling [method play] or [method play_scheduled] after this value is reached will cut off the oldest sounds.
Scales the panning strength for this node by multiplying the base [member ProjectSettings.audio/general/3d_panning_strength] by this factor. If the product is [code]0.0[/code] then stereo panning is disabled and the volume is the same for all channels. If the product is [code]1.0[/code] then one of the channels will be muted when the sound is located exactly to the left (or right) of the listener.
diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp
index ff97ab8cb269..8b25c4345714 100644
--- a/scene/2d/audio_stream_player_2d.cpp
+++ b/scene/2d/audio_stream_player_2d.cpp
@@ -64,7 +64,7 @@ void AudioStreamPlayer2D::_notification(int p_what) {
if (setplayback.is_valid() && setplay.get() >= 0) {
internal->active.set();
- AudioServer::get_singleton()->start_playback_stream(setplayback, _get_actual_bus(), volume_vector, setplay.get(), internal->pitch_scale);
+ AudioServer::get_singleton()->start_playback_stream(setplayback, _get_actual_bus(), volume_vector, setplay.get(), internal->scheduled_time, internal->pitch_scale);
setplayback.unref();
setplay.set(-1);
}
@@ -231,7 +231,7 @@ float AudioStreamPlayer2D::get_pitch_scale() const {
return internal->pitch_scale;
}
-void AudioStreamPlayer2D::play(float p_from_pos) {
+void AudioStreamPlayer2D::_play_internal(double p_from_pos) {
Ref stream_playback = internal->play_basic();
if (stream_playback.is_null()) {
return;
@@ -241,6 +241,10 @@ void AudioStreamPlayer2D::play(float p_from_pos) {
// Sample handling.
if (stream_playback->get_is_sample() && stream_playback->get_sample_playback().is_valid()) {
+ if (internal->scheduled_time > 0) {
+ WARN_PRINT_ED("play_scheduled() does not support samples. Playing immediately.");
+ }
+
Ref sample_playback = stream_playback->get_sample_playback();
sample_playback->offset = p_from_pos;
sample_playback->bus = _get_actual_bus();
@@ -249,6 +253,16 @@ void AudioStreamPlayer2D::play(float p_from_pos) {
}
}
+void AudioStreamPlayer2D::play(float p_from_pos) {
+ internal->scheduled_time = 0;
+ _play_internal(p_from_pos);
+}
+
+void AudioStreamPlayer2D::play_scheduled(double p_abs_time, double p_from_pos) {
+ internal->scheduled_time = p_abs_time;
+ _play_internal(p_from_pos);
+}
+
void AudioStreamPlayer2D::seek(float p_seconds) {
internal->seek(p_seconds);
}
@@ -388,6 +402,7 @@ void AudioStreamPlayer2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_pitch_scale"), &AudioStreamPlayer2D::get_pitch_scale);
ClassDB::bind_method(D_METHOD("play", "from_position"), &AudioStreamPlayer2D::play, DEFVAL(0.0));
+ ClassDB::bind_method(D_METHOD("play_scheduled", "absolute_time", "from_position"), &AudioStreamPlayer2D::play_scheduled, DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("seek", "to_position"), &AudioStreamPlayer2D::seek);
ClassDB::bind_method(D_METHOD("stop"), &AudioStreamPlayer2D::stop);
diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h
index 2d021190e437..5fb59484e638 100644
--- a/scene/2d/audio_stream_player_2d.h
+++ b/scene/2d/audio_stream_player_2d.h
@@ -72,6 +72,8 @@ class AudioStreamPlayer2D : public Node2D {
StringName _get_actual_bus();
void _update_panning();
+ void _play_internal(double p_from_pos = 0.0);
+
static void _listener_changed_cb(void *self) { reinterpret_cast(self)->force_update_panning = true; }
uint32_t area_mask = 1;
@@ -110,6 +112,7 @@ class AudioStreamPlayer2D : public Node2D {
float get_pitch_scale() const;
void play(float p_from_pos = 0.0);
+ void play_scheduled(double p_abs_time, double p_from_pos = 0.0);
void seek(float p_seconds);
void stop();
bool is_playing() const;
diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp
index 5ab8b5523233..78a32ae955d8 100644
--- a/scene/3d/audio_stream_player_3d.cpp
+++ b/scene/3d/audio_stream_player_3d.cpp
@@ -289,7 +289,7 @@ void AudioStreamPlayer3D::_notification(int p_what) {
internal->active.set();
HashMap> bus_map;
bus_map[_get_actual_bus()] = volume_vector;
- AudioServer::get_singleton()->start_playback_stream(setplayback, bus_map, setplay.get(), actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz);
+ AudioServer::get_singleton()->start_playback_stream(setplayback, bus_map, setplay.get(), internal->scheduled_time, actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz);
setplayback.unref();
setplay.set(-1);
}
@@ -591,7 +591,7 @@ float AudioStreamPlayer3D::get_pitch_scale() const {
return internal->pitch_scale;
}
-void AudioStreamPlayer3D::play(float p_from_pos) {
+void AudioStreamPlayer3D::_play_internal(double p_from_pos) {
Ref stream_playback = internal->play_basic();
if (stream_playback.is_null()) {
return;
@@ -601,6 +601,10 @@ void AudioStreamPlayer3D::play(float p_from_pos) {
// Sample handling.
if (stream_playback->get_is_sample() && stream_playback->get_sample_playback().is_valid()) {
+ if (internal->scheduled_time > 0) {
+ WARN_PRINT_ED("play_scheduled() does not support samples. Playing immediately.");
+ }
+
Ref sample_playback = stream_playback->get_sample_playback();
sample_playback->offset = p_from_pos;
sample_playback->bus = _get_actual_bus();
@@ -609,6 +613,16 @@ void AudioStreamPlayer3D::play(float p_from_pos) {
}
}
+void AudioStreamPlayer3D::play(float p_from_pos) {
+ internal->scheduled_time = 0;
+ _play_internal(p_from_pos);
+}
+
+void AudioStreamPlayer3D::play_scheduled(double p_abs_time, double p_from_pos) {
+ internal->scheduled_time = p_abs_time;
+ _play_internal(p_from_pos);
+}
+
void AudioStreamPlayer3D::seek(float p_seconds) {
internal->seek(p_seconds);
}
@@ -822,6 +836,7 @@ void AudioStreamPlayer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_pitch_scale"), &AudioStreamPlayer3D::get_pitch_scale);
ClassDB::bind_method(D_METHOD("play", "from_position"), &AudioStreamPlayer3D::play, DEFVAL(0.0));
+ ClassDB::bind_method(D_METHOD("play_scheduled", "absolute_time", "from_position"), &AudioStreamPlayer3D::play_scheduled, DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("seek", "to_position"), &AudioStreamPlayer3D::seek);
ClassDB::bind_method(D_METHOD("stop"), &AudioStreamPlayer3D::stop);
diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h
index 70108f8422cb..b165c6e9bbe3 100644
--- a/scene/3d/audio_stream_player_3d.h
+++ b/scene/3d/audio_stream_player_3d.h
@@ -97,6 +97,8 @@ class AudioStreamPlayer3D : public Node3D {
#endif // PHYSICS_3D_DISABLED
Vector _update_panning();
+ void _play_internal(double p_from_pos = 0.0);
+
uint32_t area_mask = 1;
AudioServer::PlaybackType playback_type = AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT;
@@ -155,6 +157,7 @@ class AudioStreamPlayer3D : public Node3D {
float get_pitch_scale() const;
void play(float p_from_pos = 0.0);
+ void play_scheduled(double p_abs_time, double p_from_pos = 0.0);
void seek(float p_seconds);
void stop();
bool is_playing() const;
diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp
index 6fbfd25451b5..5af925e9b240 100644
--- a/scene/audio/audio_stream_player.cpp
+++ b/scene/audio/audio_stream_player.cpp
@@ -103,16 +103,20 @@ int AudioStreamPlayer::get_max_polyphony() const {
return internal->max_polyphony;
}
-void AudioStreamPlayer::play(float p_from_pos) {
+void AudioStreamPlayer::_play_internal(double p_from_pos) {
Ref stream_playback = internal->play_basic();
if (stream_playback.is_null()) {
return;
}
- AudioServer::get_singleton()->start_playback_stream(stream_playback, internal->bus, _get_volume_vector(), p_from_pos, internal->pitch_scale);
+ AudioServer::get_singleton()->start_playback_stream(stream_playback, internal->bus, _get_volume_vector(), p_from_pos, internal->scheduled_time, internal->pitch_scale);
internal->ensure_playback_limit();
// Sample handling.
if (stream_playback->get_is_sample() && stream_playback->get_sample_playback().is_valid()) {
+ if (internal->scheduled_time > 0) {
+ WARN_PRINT_ED("play_scheduled() does not support samples. Playing immediately.");
+ }
+
Ref sample_playback = stream_playback->get_sample_playback();
sample_playback->offset = p_from_pos;
sample_playback->volume_vector = _get_volume_vector();
@@ -122,6 +126,16 @@ void AudioStreamPlayer::play(float p_from_pos) {
}
}
+void AudioStreamPlayer::play(float p_from_pos) {
+ internal->scheduled_time = 0;
+ _play_internal(p_from_pos);
+}
+
+void AudioStreamPlayer::play_scheduled(double p_abs_time, double p_from_pos) {
+ internal->scheduled_time = p_abs_time;
+ _play_internal(p_from_pos);
+}
+
void AudioStreamPlayer::seek(float p_seconds) {
internal->seek(p_seconds);
}
@@ -248,6 +262,7 @@ void AudioStreamPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_pitch_scale"), &AudioStreamPlayer::get_pitch_scale);
ClassDB::bind_method(D_METHOD("play", "from_position"), &AudioStreamPlayer::play, DEFVAL(0.0));
+ ClassDB::bind_method(D_METHOD("play_scheduled", "absolute_time", "from_position"), &AudioStreamPlayer::play_scheduled, DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("seek", "to_position"), &AudioStreamPlayer::seek);
ClassDB::bind_method(D_METHOD("stop"), &AudioStreamPlayer::stop);
diff --git a/scene/audio/audio_stream_player.h b/scene/audio/audio_stream_player.h
index 8b19f9032645..ca748d652a29 100644
--- a/scene/audio/audio_stream_player.h
+++ b/scene/audio/audio_stream_player.h
@@ -58,6 +58,8 @@ class AudioStreamPlayer : public Node {
Vector _get_volume_vector();
+ void _play_internal(double p_from_pos = 0.0);
+
protected:
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
@@ -89,6 +91,7 @@ class AudioStreamPlayer : public Node {
int get_max_polyphony() const;
void play(float p_from_pos = 0.0);
+ void play_scheduled(double p_abs_time, double p_from_pos = 0.0);
void seek(float p_seconds);
void stop();
bool is_playing() const;
diff --git a/scene/audio/audio_stream_player_internal.cpp b/scene/audio/audio_stream_player_internal.cpp
index 621aa31fc61b..8d21ad4aa069 100644
--- a/scene/audio/audio_stream_player_internal.cpp
+++ b/scene/audio/audio_stream_player_internal.cpp
@@ -260,7 +260,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);
@@ -286,7 +286,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 40f059593823..527c4522bfeb 100644
--- a/scene/audio/audio_stream_player_internal.h
+++ b/scene/audio/audio_stream_player_internal.h
@@ -76,6 +76,7 @@ class AudioStreamPlayerInternal : public Object {
bool autoplay = false;
StringName bus;
int max_polyphony = 1;
+ double scheduled_time = 0;
void process();
void ensure_playback_limit();
@@ -93,10 +94,10 @@ class AudioStreamPlayerInternal : public Object {
StringName get_bus() const;
Ref play_basic();
- void seek(float p_seconds);
+ 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/servers/audio_server.cpp b/servers/audio_server.cpp
index b5df08211a87..9c815782680d 100644
--- a/servers/audio_server.cpp
+++ b/servers/audio_server.cpp
@@ -404,8 +404,28 @@ void AudioServer::_mix_step() {
buf[i] = playback->lookahead[i];
}
- // Mix the audio stream.
- unsigned int mixed_frames = playback->stream_playback->mix(&buf[LOOKAHEAD_BUFFER_SIZE], playback->pitch_scale.get(), buffer_size);
+ unsigned int mixed_frames = 0;
+
+ // For audio scheduled to start later, fill the buffer with silence frames.
+ // Since the lookahead buffer inserts LOOKAHEAD_BUFFER_SIZE silence frames in the beginning,
+ // we merge those with the total silence frames needed for the schedule to align with the
+ // true absolute time. Concretely, this results in having
+ // MAX(LOOKAHEAD_BUFFER_SIZE, scheduled_start_frame - mix_frames) silence frames before
+ // the actual audio stream is mixed.
+ uint64_t mix_frames_with_lookahead = mix_frames + LOOKAHEAD_BUFFER_SIZE;
+ uint64_t scheduled_start_frame = playback->scheduled_start_frame.get();
+ if (scheduled_start_frame > mix_frames_with_lookahead) {
+ unsigned int silence_frames = (unsigned int)MIN(scheduled_start_frame - mix_frames_with_lookahead, buffer_size);
+ for (unsigned int i = 0; i < silence_frames; i++) {
+ buf[LOOKAHEAD_BUFFER_SIZE + i] = AudioFrame(0, 0);
+ }
+ mixed_frames += silence_frames;
+ }
+
+ // Then mix the actual audio stream with the remaining space.
+ if (mixed_frames < buffer_size) {
+ mixed_frames += playback->stream_playback->mix(&buf[LOOKAHEAD_BUFFER_SIZE + mixed_frames], playback->pitch_scale.get(), buffer_size - mixed_frames);
+ }
if (tag_used_audio_streams && playback->stream_playback->is_playing()) {
playback->stream_playback->tag_used_streams();
@@ -1224,21 +1244,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, double p_scheduled_start_time, 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_scheduled_start_time, 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, double p_scheduled_start_time, 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;
@@ -1263,6 +1283,14 @@ void AudioServer::start_playback_stream(Ref p_playback, con
playback_node->highshelf_gain.set(p_highshelf_gain);
playback_node->attenuation_filter_cutoff_hz.set(p_attenuation_cutoff_hz);
+ uint64_t scheduled_start_frame = uint64_t(p_scheduled_start_time * get_mix_rate());
+ if (scheduled_start_frame > 0 && scheduled_start_frame < mix_frames) {
+ WARN_PRINT_ED(vformat("Sound (%s) was scheduled for absolute time %.4f, which has already passed. Playing immediately.",
+ p_playback->get_instance_id(),
+ p_scheduled_start_time));
+ }
+ playback_node->scheduled_start_frame.set(scheduled_start_frame);
+
memset(playback_node->prev_bus_details->volume, 0, sizeof(playback_node->prev_bus_details->volume));
for (AudioFrame &frame : playback_node->lookahead) {
@@ -1447,7 +1475,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.
@@ -1687,6 +1715,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) {
@@ -2012,6 +2044,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_server.h b/servers/audio_server.h
index e429c83616b2..8ab50d353085 100644
--- a/servers/audio_server.h
+++ b/servers/audio_server.h
@@ -294,6 +294,7 @@ class AudioServer : public Object {
SafeNumeric pitch_scale;
SafeNumeric highshelf_gain;
SafeNumeric attenuation_filter_cutoff_hz; // This isn't used unless highshelf_gain is nonzero.
+ SafeNumeric scheduled_start_frame;
AudioFilterSW::Processor filter_process[8];
// Updating this ref after the list node is created breaks consistency guarantees, don't do it!
Ref stream_playback;
@@ -428,9 +429,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, double p_scheduled_start_time = 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, double p_scheduled_start_time = 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);
@@ -441,7 +442,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;
@@ -472,6 +473,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);