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);