Skip to content

Commit d6d9365

Browse files
PizzaLovers007fire
andcommitted
v2 Implement audio absolute time and scheduled play
This is a rewrite of godotengine#105510 that moves the silence frames logic into a separate AudioStreamPlaybackScheduled class. The rewrite allows both the AudioServer and the player (mostly) to treat it as if it were a generic playback. It also simplifies the addition of new features. Main differences: - play_scheduled returns an AudioStreamPlaybackScheduled instance, which is tied to the player that created it. - The start time can be changed after scheduling. - You can now set an end time for the playback. - The scheduled playback can be cancelled separately from other playbacks on the player. Co-authored-by: K. S. Ernest (iFire) Lee <[email protected]>
1 parent 09ea7bc commit d6d9365

18 files changed

+596
-32
lines changed

doc/classes/AudioServer.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@
3535
Generates an [AudioBusLayout] using the available buses and effects.
3636
</description>
3737
</method>
38+
<method name="get_absolute_time" qualifiers="const" experimental="">
39+
<return type="float" />
40+
<description>
41+
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].
42+
See also [AudioStreamPlaybackScheduled].
43+
[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].
44+
</description>
45+
</method>
3846
<method name="get_bus_channels" qualifiers="const">
3947
<return type="int" />
4048
<param index="0" name="bus_idx" type="int" />
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<class name="AudioStreamPlaybackScheduled" inherits="AudioStreamPlayback" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
3+
<brief_description>
4+
Wrapper around [AudioStreamPlayback] that schedules it to be played in the future.
5+
</brief_description>
6+
<description>
7+
Wrapper around [AudioStreamPlayback] that schedules it to be played in the future.
8+
An [AudioStreamPlaybackScheduled] instance is created from [method AudioStreamPlayer.play_scheduled], [method AudioStreamPlayer2D.play_scheduled], or [method AudioStreamPlayer3D.play_scheduled].
9+
[codeblocks]
10+
[gdscript]
11+
var audio_time = AudioServer.get_absolute_time()
12+
13+
# Play the sound roughly 1 second in the future, then stop it after exactly 3 seconds.
14+
var playback_scheduled = player.play_scheduled(audio_time + 1)
15+
playback_scheduled.scheduled_end_time = audio_time + 4
16+
17+
# Cancel the playback
18+
playback_scheduled.cancel()
19+
[/gdscript]
20+
[csharp]
21+
double audioTime = AudioServer.GetAbsoluteTime();
22+
23+
// Play the sound roughly 1 second in the future, then stop it after exactly 3 seconds.
24+
AudioStreamPlaybackScheduled playbackScheduled = player.PlayScheduled(audioTime + 1);
25+
playbackScheduled.ScheduledEndTime = audioTime + 4;
26+
27+
// Cancel the playback
28+
playbackScheduled.Cancel();
29+
[/csharp]
30+
[/codeblocks]
31+
</description>
32+
<tutorials>
33+
</tutorials>
34+
<methods>
35+
<method name="cancel">
36+
<return type="void" />
37+
<description>
38+
Cancels the currently scheduled playback.
39+
If the sound is already playing, this method does nothing. Use [method stop] instead to both stop the ongoing or cancel the scheduled playback.
40+
</description>
41+
</method>
42+
<method name="is_playing" qualifiers="const">
43+
<return type="bool" />
44+
<description>
45+
Returns [code]true[/code] if the playback is actively playing. Returns [code]false[/code] for scheduled playbacks that have not yet started, or playbacks that have ended.
46+
</description>
47+
</method>
48+
<method name="is_scheduled" qualifiers="const">
49+
<return type="bool" />
50+
<description>
51+
Returns [code]true[/code] if the playback is scheduled and has not yet started.
52+
</description>
53+
</method>
54+
<method name="start">
55+
<return type="void" />
56+
<param index="0" name="from_pos" type="float" />
57+
<description>
58+
Schedules the playback. Once the [AudioServer]'s timeline reaches [member scheduled_start_time], the playback will begin playing from [param from_pos].
59+
</description>
60+
</method>
61+
<method name="stop">
62+
<return type="void" />
63+
<description>
64+
Stops the playback if it has started, or cancels the currently scheduled playback if it has not yet started.
65+
</description>
66+
</method>
67+
</methods>
68+
<members>
69+
<member name="base_playback" type="AudioStreamPlayback" setter="set_base_playback" getter="get_base_playback">
70+
The wrapped [AudioStreamPlayback] to be scheduled.
71+
</member>
72+
<member name="scheduled_start_time" type="float" setter="set_scheduled_start_time" getter="get_scheduled_start_time" default="0.0">
73+
The time the playback is scheduled to start in seconds. This is based on the [AudioServer]'s timeline (see [method AudioServer.get_absolute_time]).
74+
</member>
75+
<member name="scheduled_end_time" type="float" setter="set_scheduled_end_time" getter="get_scheduled_end_time" default="0.0">
76+
The time the playback is scheduled to end in seconds. This is based on the [AudioServer]'s timeline (see [method AudioServer.get_absolute_time]).
77+
</member>
78+
</members>
79+
</class>

doc/classes/AudioStreamPlayer.xml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
<method name="get_stream_playback">
2929
<return type="AudioStreamPlayback" />
3030
<description>
31-
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.
31+
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.
3232
</description>
3333
</method>
3434
<method name="has_stream_playback">
@@ -44,6 +44,18 @@
4444
Plays a sound from the beginning, or the given [param from_position] in seconds.
4545
</description>
4646
</method>
47+
<method name="play_scheduled" experimental="">
48+
<return type="AudioStreamPlaybackScheduled" />
49+
<param index="0" name="absolute_time" type="float" />
50+
<param index="1" name="from_position" type="float" default="0.0" />
51+
<description>
52+
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.
53+
Returns an [AudioStreamPlaybackScheduled] instance representing the scheduled playback of the sound.
54+
Use this method for high precision playbacks, such as a metronome or other rhythm-based sounds.
55+
[b]Note:[/b] Calling this method after [member max_polyphony] is reached will cut off the oldest sound playing on this node.
56+
[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].
57+
</description>
58+
</method>
4759
<method name="seek">
4860
<return type="void" />
4961
<param index="0" name="to_position" type="float" />
@@ -67,7 +79,7 @@
6779
[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].
6880
</member>
6981
<member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1">
70-
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.
82+
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.
7183
</member>
7284
<member name="mix_target" type="int" setter="set_mix_target" getter="get_mix_target" enum="AudioStreamPlayer.MixTarget" default="0">
7385
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]).

doc/classes/AudioStreamPlayer2D.xml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@
3838
Queues the audio to play on the next physics frame, from the given position [param from_position], in seconds.
3939
</description>
4040
</method>
41+
<method name="play_scheduled" experimental="">
42+
<return type="AudioStreamPlaybackScheduled" />
43+
<param index="0" name="absolute_time" type="float" />
44+
<param index="1" name="from_position" type="float" default="0.0" />
45+
<description>
46+
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.
47+
Returns an [AudioStreamPlaybackScheduled] instance representing the scheduled playback of the sound.
48+
Use this method for high precision playbacks, such as a metronome or other rhythm-based sounds.
49+
[b]Note:[/b] Calling this method after [member max_polyphony] is reached will cut off the oldest sound playing on this node.
50+
[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].
51+
</description>
52+
</method>
4153
<method name="seek">
4254
<return type="void" />
4355
<param index="0" name="to_position" type="float" />
@@ -70,7 +82,7 @@
7082
Maximum distance from which audio is still hearable.
7183
</member>
7284
<member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1">
73-
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.
85+
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.
7486
</member>
7587
<member name="panning_strength" type="float" setter="set_panning_strength" getter="get_panning_strength" default="1.0">
7688
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.

doc/classes/AudioStreamPlayer3D.xml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@
3838
Queues the audio to play on the next physics frame, from the given position [param from_position], in seconds.
3939
</description>
4040
</method>
41+
<method name="play_scheduled" experimental="">
42+
<return type="AudioStreamPlaybackScheduled" />
43+
<param index="0" name="absolute_time" type="float" />
44+
<param index="1" name="from_position" type="float" default="0.0" />
45+
<description>
46+
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.
47+
Returns an [AudioStreamPlaybackScheduled] instance representing the scheduled playback of the sound.
48+
Use this method for high precision playbacks, such as a metronome or other rhythm-based sounds.
49+
[b]Note:[/b] Calling this method after [member max_polyphony] is reached will cut off the oldest sound playing on this node.
50+
[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].
51+
</description>
52+
</method>
4153
<method name="seek">
4254
<return type="void" />
4355
<param index="0" name="to_position" type="float" />
@@ -91,7 +103,7 @@
91103
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.
92104
</member>
93105
<member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1">
94-
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.
106+
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.
95107
</member>
96108
<member name="panning_strength" type="float" setter="set_panning_strength" getter="get_panning_strength" default="1.0">
97109
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.

scene/2d/audio_stream_player_2d.cpp

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "scene/2d/audio_listener_2d.h"
3636
#include "scene/audio/audio_stream_player_internal.h"
3737
#include "scene/main/viewport.h"
38+
#include "scene/resources/audio_stream_playback_scheduled.h"
3839
#include "scene/resources/world_2d.h"
3940
#include "servers/audio/audio_stream.h"
4041
#include "servers/audio_server.h"
@@ -231,11 +232,7 @@ float AudioStreamPlayer2D::get_pitch_scale() const {
231232
return internal->pitch_scale;
232233
}
233234

234-
void AudioStreamPlayer2D::play(float p_from_pos) {
235-
Ref<AudioStreamPlayback> stream_playback = internal->play_basic();
236-
if (stream_playback.is_null()) {
237-
return;
238-
}
235+
void AudioStreamPlayer2D::_play_internal(Ref<AudioStreamPlayback> stream_playback, double p_from_pos) {
239236
setplayback = stream_playback;
240237
setplay.set(p_from_pos);
241238

@@ -249,6 +246,25 @@ void AudioStreamPlayer2D::play(float p_from_pos) {
249246
}
250247
}
251248

249+
void AudioStreamPlayer2D::play(float p_from_pos) {
250+
Ref<AudioStreamPlayback> stream_playback = internal->play_basic();
251+
if (stream_playback.is_null()) {
252+
return;
253+
}
254+
_play_internal(stream_playback, p_from_pos);
255+
}
256+
257+
Ref<AudioStreamPlaybackScheduled> AudioStreamPlayer2D::play_scheduled(double p_abs_time, double p_from_pos) {
258+
Ref<AudioStreamPlaybackScheduled> stream_playback_scheduled = internal->play_scheduled_basic();
259+
if (stream_playback_scheduled.is_null()) {
260+
return stream_playback_scheduled;
261+
}
262+
stream_playback_scheduled->set_scheduled_start_time(p_abs_time);
263+
_play_internal(stream_playback_scheduled, p_from_pos);
264+
265+
return stream_playback_scheduled;
266+
}
267+
252268
void AudioStreamPlayer2D::seek(float p_seconds) {
253269
internal->seek(p_seconds);
254270
}
@@ -388,6 +404,7 @@ void AudioStreamPlayer2D::_bind_methods() {
388404
ClassDB::bind_method(D_METHOD("get_pitch_scale"), &AudioStreamPlayer2D::get_pitch_scale);
389405

390406
ClassDB::bind_method(D_METHOD("play", "from_position"), &AudioStreamPlayer2D::play, DEFVAL(0.0));
407+
ClassDB::bind_method(D_METHOD("play_scheduled", "absolute_time", "from_position"), &AudioStreamPlayer2D::play_scheduled, DEFVAL(0.0));
391408
ClassDB::bind_method(D_METHOD("seek", "to_position"), &AudioStreamPlayer2D::seek);
392409
ClassDB::bind_method(D_METHOD("stop"), &AudioStreamPlayer2D::stop);
393410

scene/2d/audio_stream_player_2d.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
struct AudioFrame;
3737
class AudioStream;
3838
class AudioStreamPlayback;
39+
class AudioStreamPlaybackScheduled;
3940
class AudioStreamPlayerInternal;
4041

4142
class AudioStreamPlayer2D : public Node2D {
@@ -72,6 +73,8 @@ class AudioStreamPlayer2D : public Node2D {
7273
StringName _get_actual_bus();
7374
void _update_panning();
7475

76+
void _play_internal(Ref<AudioStreamPlayback> stream_playback, double p_from_pos = 0.0);
77+
7578
static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->force_update_panning = true; }
7679

7780
uint32_t area_mask = 1;
@@ -110,6 +113,7 @@ class AudioStreamPlayer2D : public Node2D {
110113
float get_pitch_scale() const;
111114

112115
void play(float p_from_pos = 0.0);
116+
Ref<AudioStreamPlaybackScheduled> play_scheduled(double p_abs_time, double p_from_pos = 0.0);
113117
void seek(float p_seconds);
114118
void stop();
115119
bool is_playing() const;

scene/3d/audio_stream_player_3d.cpp

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "scene/3d/velocity_tracker_3d.h"
3838
#include "scene/audio/audio_stream_player_internal.h"
3939
#include "scene/main/viewport.h"
40+
#include "scene/resources/audio_stream_playback_scheduled.h"
4041
#include "servers/audio/audio_stream.h"
4142

4243
#ifndef PHYSICS_3D_DISABLED
@@ -591,11 +592,7 @@ float AudioStreamPlayer3D::get_pitch_scale() const {
591592
return internal->pitch_scale;
592593
}
593594

594-
void AudioStreamPlayer3D::play(float p_from_pos) {
595-
Ref<AudioStreamPlayback> stream_playback = internal->play_basic();
596-
if (stream_playback.is_null()) {
597-
return;
598-
}
595+
void AudioStreamPlayer3D::_play_internal(Ref<AudioStreamPlayback> stream_playback, double p_from_pos) {
599596
setplayback = stream_playback;
600597
setplay.set(p_from_pos);
601598

@@ -609,6 +606,25 @@ void AudioStreamPlayer3D::play(float p_from_pos) {
609606
}
610607
}
611608

609+
void AudioStreamPlayer3D::play(float p_from_pos) {
610+
Ref<AudioStreamPlayback> stream_playback = internal->play_basic();
611+
if (stream_playback.is_null()) {
612+
return;
613+
}
614+
_play_internal(stream_playback, p_from_pos);
615+
}
616+
617+
Ref<AudioStreamPlaybackScheduled> AudioStreamPlayer3D::play_scheduled(double p_abs_time, double p_from_pos) {
618+
Ref<AudioStreamPlaybackScheduled> stream_playback_scheduled = internal->play_scheduled_basic();
619+
if (stream_playback_scheduled.is_null()) {
620+
return stream_playback_scheduled;
621+
}
622+
stream_playback_scheduled->set_scheduled_start_time(p_abs_time);
623+
_play_internal(stream_playback_scheduled, p_from_pos);
624+
625+
return stream_playback_scheduled;
626+
}
627+
612628
void AudioStreamPlayer3D::seek(float p_seconds) {
613629
internal->seek(p_seconds);
614630
}
@@ -822,6 +838,7 @@ void AudioStreamPlayer3D::_bind_methods() {
822838
ClassDB::bind_method(D_METHOD("get_pitch_scale"), &AudioStreamPlayer3D::get_pitch_scale);
823839

824840
ClassDB::bind_method(D_METHOD("play", "from_position"), &AudioStreamPlayer3D::play, DEFVAL(0.0));
841+
ClassDB::bind_method(D_METHOD("play_scheduled", "absolute_time", "from_position"), &AudioStreamPlayer3D::play_scheduled, DEFVAL(0.0));
825842
ClassDB::bind_method(D_METHOD("seek", "to_position"), &AudioStreamPlayer3D::seek);
826843
ClassDB::bind_method(D_METHOD("stop"), &AudioStreamPlayer3D::stop);
827844

scene/3d/audio_stream_player_3d.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class Area3D;
3939
struct AudioFrame;
4040
class AudioStream;
4141
class AudioStreamPlayback;
42+
class AudioStreamPlaybackScheduled;
4243
class AudioStreamPlayerInternal;
4344
class VelocityTracker3D;
4445

@@ -97,6 +98,8 @@ class AudioStreamPlayer3D : public Node3D {
9798
#endif // PHYSICS_3D_DISABLED
9899
Vector<AudioFrame> _update_panning();
99100

101+
void _play_internal(Ref<AudioStreamPlayback> stream_playback, double p_from_pos = 0.0);
102+
100103
uint32_t area_mask = 1;
101104

102105
AudioServer::PlaybackType playback_type = AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT;
@@ -155,6 +158,7 @@ class AudioStreamPlayer3D : public Node3D {
155158
float get_pitch_scale() const;
156159

157160
void play(float p_from_pos = 0.0);
161+
Ref<AudioStreamPlaybackScheduled> play_scheduled(double p_abs_time, double p_from_pos = 0.0);
158162
void seek(float p_seconds);
159163
void stop();
160164
bool is_playing() const;

0 commit comments

Comments
 (0)