Skip to content

Commit 39034ac

Browse files
Implement audio absolute time and scheduled play
The absolute time is calculated based on the total mixed frames and the mix rate. This means it only updates when a mix step happens. Specific play_scheduled behaviors: - If a sound is playing, play_scheduled() will stop that sound. This matches the behavior of play(). - If a sound is scheduled, then paused, then resumed before the schedule happens, the sound still plays at the correct scheduled time. - If a playing sound is paused, then play_scheduled() is called, the sound will restart from the beginning. This matches the behavior of play(). - With a higher max_polyphony, multiple sounds can be scheduled. - play_scheduled is unaffected by pitch scale - play_scheduled does not support samples Scheduled stop is not implemented due to limited use cases.
1 parent 09ea7bc commit 39034ac

9 files changed

+98
-14
lines changed

scene/2d/audio_stream_player_2d.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ void AudioStreamPlayer2D::_notification(int p_what) {
6464

6565
if (setplayback.is_valid() && setplay.get() >= 0) {
6666
internal->active.set();
67-
AudioServer::get_singleton()->start_playback_stream(setplayback, _get_actual_bus(), volume_vector, setplay.get(), internal->pitch_scale);
67+
AudioServer::get_singleton()->start_playback_stream(setplayback, _get_actual_bus(), volume_vector, setplay.get(), internal->scheduled_time, internal->pitch_scale);
6868
setplayback.unref();
6969
setplay.set(-1);
7070
}
@@ -231,7 +231,7 @@ float AudioStreamPlayer2D::get_pitch_scale() const {
231231
return internal->pitch_scale;
232232
}
233233

234-
void AudioStreamPlayer2D::play(float p_from_pos) {
234+
void AudioStreamPlayer2D::_play_internal(float p_from_pos) {
235235
Ref<AudioStreamPlayback> stream_playback = internal->play_basic();
236236
if (stream_playback.is_null()) {
237237
return;
@@ -241,6 +241,10 @@ void AudioStreamPlayer2D::play(float p_from_pos) {
241241

242242
// Sample handling.
243243
if (stream_playback->get_is_sample() && stream_playback->get_sample_playback().is_valid()) {
244+
if (internal->scheduled_time > 0) {
245+
WARN_PRINT_ED("play_scheduled() does not support samples. Playing immediately.");
246+
}
247+
244248
Ref<AudioSamplePlayback> sample_playback = stream_playback->get_sample_playback();
245249
sample_playback->offset = p_from_pos;
246250
sample_playback->bus = _get_actual_bus();
@@ -249,6 +253,16 @@ void AudioStreamPlayer2D::play(float p_from_pos) {
249253
}
250254
}
251255

256+
void AudioStreamPlayer2D::play(float p_from_pos) {
257+
internal->scheduled_time = 0;
258+
_play_internal(p_from_pos);
259+
}
260+
261+
void AudioStreamPlayer2D::play_scheduled(float p_abs_time, float p_from_pos) {
262+
internal->scheduled_time = p_abs_time;
263+
_play_internal(p_from_pos);
264+
}
265+
252266
void AudioStreamPlayer2D::seek(float p_seconds) {
253267
internal->seek(p_seconds);
254268
}
@@ -388,6 +402,7 @@ void AudioStreamPlayer2D::_bind_methods() {
388402
ClassDB::bind_method(D_METHOD("get_pitch_scale"), &AudioStreamPlayer2D::get_pitch_scale);
389403

390404
ClassDB::bind_method(D_METHOD("play", "from_position"), &AudioStreamPlayer2D::play, DEFVAL(0.0));
405+
ClassDB::bind_method(D_METHOD("play_scheduled", "absolute_time", "from_position"), &AudioStreamPlayer2D::play_scheduled, DEFVAL(0.0));
391406
ClassDB::bind_method(D_METHOD("seek", "to_position"), &AudioStreamPlayer2D::seek);
392407
ClassDB::bind_method(D_METHOD("stop"), &AudioStreamPlayer2D::stop);
393408

scene/2d/audio_stream_player_2d.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ class AudioStreamPlayer2D : public Node2D {
7272
StringName _get_actual_bus();
7373
void _update_panning();
7474

75+
void _play_internal(float p_from_pos = 0.0);
76+
7577
static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->force_update_panning = true; }
7678

7779
uint32_t area_mask = 1;
@@ -110,6 +112,7 @@ class AudioStreamPlayer2D : public Node2D {
110112
float get_pitch_scale() const;
111113

112114
void play(float p_from_pos = 0.0);
115+
void play_scheduled(float p_abs_time, float p_from_pos = 0.0);
113116
void seek(float p_seconds);
114117
void stop();
115118
bool is_playing() const;

scene/3d/audio_stream_player_3d.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ void AudioStreamPlayer3D::_notification(int p_what) {
289289
internal->active.set();
290290
HashMap<StringName, Vector<AudioFrame>> bus_map;
291291
bus_map[_get_actual_bus()] = volume_vector;
292-
AudioServer::get_singleton()->start_playback_stream(setplayback, bus_map, setplay.get(), actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz);
292+
AudioServer::get_singleton()->start_playback_stream(setplayback, bus_map, setplay.get(), internal->scheduled_time, actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz);
293293
setplayback.unref();
294294
setplay.set(-1);
295295
}
@@ -591,7 +591,7 @@ float AudioStreamPlayer3D::get_pitch_scale() const {
591591
return internal->pitch_scale;
592592
}
593593

594-
void AudioStreamPlayer3D::play(float p_from_pos) {
594+
void AudioStreamPlayer3D::_play_internal(float p_from_pos) {
595595
Ref<AudioStreamPlayback> stream_playback = internal->play_basic();
596596
if (stream_playback.is_null()) {
597597
return;
@@ -601,6 +601,10 @@ void AudioStreamPlayer3D::play(float p_from_pos) {
601601

602602
// Sample handling.
603603
if (stream_playback->get_is_sample() && stream_playback->get_sample_playback().is_valid()) {
604+
if (internal->scheduled_time > 0) {
605+
WARN_PRINT_ED("play_scheduled() does not support samples. Playing immediately.");
606+
}
607+
604608
Ref<AudioSamplePlayback> sample_playback = stream_playback->get_sample_playback();
605609
sample_playback->offset = p_from_pos;
606610
sample_playback->bus = _get_actual_bus();
@@ -609,6 +613,16 @@ void AudioStreamPlayer3D::play(float p_from_pos) {
609613
}
610614
}
611615

616+
void AudioStreamPlayer3D::play(float p_from_pos) {
617+
internal->scheduled_time = 0;
618+
_play_internal(p_from_pos);
619+
}
620+
621+
void AudioStreamPlayer3D::play_scheduled(float p_abs_time, float p_from_pos) {
622+
internal->scheduled_time = p_abs_time;
623+
_play_internal(p_from_pos);
624+
}
625+
612626
void AudioStreamPlayer3D::seek(float p_seconds) {
613627
internal->seek(p_seconds);
614628
}
@@ -822,6 +836,7 @@ void AudioStreamPlayer3D::_bind_methods() {
822836
ClassDB::bind_method(D_METHOD("get_pitch_scale"), &AudioStreamPlayer3D::get_pitch_scale);
823837

824838
ClassDB::bind_method(D_METHOD("play", "from_position"), &AudioStreamPlayer3D::play, DEFVAL(0.0));
839+
ClassDB::bind_method(D_METHOD("play_scheduled", "absolute_time", "from_position"), &AudioStreamPlayer3D::play_scheduled, DEFVAL(0.0));
825840
ClassDB::bind_method(D_METHOD("seek", "to_position"), &AudioStreamPlayer3D::seek);
826841
ClassDB::bind_method(D_METHOD("stop"), &AudioStreamPlayer3D::stop);
827842

scene/3d/audio_stream_player_3d.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ class AudioStreamPlayer3D : public Node3D {
9797
#endif // PHYSICS_3D_DISABLED
9898
Vector<AudioFrame> _update_panning();
9999

100+
void _play_internal(float p_from_pos = 0.0);
101+
100102
uint32_t area_mask = 1;
101103

102104
AudioServer::PlaybackType playback_type = AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT;
@@ -155,6 +157,7 @@ class AudioStreamPlayer3D : public Node3D {
155157
float get_pitch_scale() const;
156158

157159
void play(float p_from_pos = 0.0);
160+
void play_scheduled(float p_abs_time, float p_from_pos = 0.0);
158161
void seek(float p_seconds);
159162
void stop();
160163
bool is_playing() const;

scene/audio/audio_stream_player.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,20 @@ int AudioStreamPlayer::get_max_polyphony() const {
103103
return internal->max_polyphony;
104104
}
105105

106-
void AudioStreamPlayer::play(float p_from_pos) {
106+
void AudioStreamPlayer::_play_internal(float p_from_pos) {
107107
Ref<AudioStreamPlayback> stream_playback = internal->play_basic();
108108
if (stream_playback.is_null()) {
109109
return;
110110
}
111-
AudioServer::get_singleton()->start_playback_stream(stream_playback, internal->bus, _get_volume_vector(), p_from_pos, internal->pitch_scale);
111+
AudioServer::get_singleton()->start_playback_stream(stream_playback, internal->bus, _get_volume_vector(), p_from_pos, internal->scheduled_time, internal->pitch_scale);
112112
internal->ensure_playback_limit();
113113

114114
// Sample handling.
115115
if (stream_playback->get_is_sample() && stream_playback->get_sample_playback().is_valid()) {
116+
if (internal->scheduled_time > 0) {
117+
WARN_PRINT_ED("play_scheduled() does not support samples. Playing immediately.");
118+
}
119+
116120
Ref<AudioSamplePlayback> sample_playback = stream_playback->get_sample_playback();
117121
sample_playback->offset = p_from_pos;
118122
sample_playback->volume_vector = _get_volume_vector();
@@ -122,6 +126,16 @@ void AudioStreamPlayer::play(float p_from_pos) {
122126
}
123127
}
124128

129+
void AudioStreamPlayer::play(float p_from_pos) {
130+
internal->scheduled_time = 0;
131+
_play_internal(p_from_pos);
132+
}
133+
134+
void AudioStreamPlayer::play_scheduled(float p_abs_time, float p_from_pos) {
135+
internal->scheduled_time = p_abs_time;
136+
_play_internal(p_from_pos);
137+
}
138+
125139
void AudioStreamPlayer::seek(float p_seconds) {
126140
internal->seek(p_seconds);
127141
}
@@ -248,6 +262,7 @@ void AudioStreamPlayer::_bind_methods() {
248262
ClassDB::bind_method(D_METHOD("get_pitch_scale"), &AudioStreamPlayer::get_pitch_scale);
249263

250264
ClassDB::bind_method(D_METHOD("play", "from_position"), &AudioStreamPlayer::play, DEFVAL(0.0));
265+
ClassDB::bind_method(D_METHOD("play_scheduled", "absolute_time", "from_position"), &AudioStreamPlayer::play_scheduled, DEFVAL(0.0));
251266
ClassDB::bind_method(D_METHOD("seek", "to_position"), &AudioStreamPlayer::seek);
252267
ClassDB::bind_method(D_METHOD("stop"), &AudioStreamPlayer::stop);
253268

scene/audio/audio_stream_player.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class AudioStreamPlayer : public Node {
5858

5959
Vector<AudioFrame> _get_volume_vector();
6060

61+
void _play_internal(float p_from_pos = 0.0);
62+
6163
protected:
6264
void _validate_property(PropertyInfo &p_property) const;
6365
void _notification(int p_what);
@@ -89,6 +91,7 @@ class AudioStreamPlayer : public Node {
8991
int get_max_polyphony() const;
9092

9193
void play(float p_from_pos = 0.0);
94+
void play_scheduled(float p_abs_time, float p_from_pos = 0.0);
9295
void seek(float p_seconds);
9396
void stop();
9497
bool is_playing() const;

scene/audio/audio_stream_player_internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class AudioStreamPlayerInternal : public Object {
7676
bool autoplay = false;
7777
StringName bus;
7878
int max_polyphony = 1;
79+
float scheduled_time = 0;
7980

8081
void process();
8182
void ensure_playback_limit();

servers/audio_server.cpp

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,22 @@ void AudioServer::_mix_step() {
404404
buf[i] = playback->lookahead[i];
405405
}
406406

407-
// Mix the audio stream.
408-
unsigned int mixed_frames = playback->stream_playback->mix(&buf[LOOKAHEAD_BUFFER_SIZE], playback->pitch_scale.get(), buffer_size);
407+
unsigned int mixed_frames = 0;
408+
409+
// Mix in audio stream delay if applicable.
410+
uint64_t scheduled_start_frame = playback->scheduled_start_frame.get();
411+
if (scheduled_start_frame > mix_frames) {
412+
unsigned int silence_frames = MIN(scheduled_start_frame - mix_frames, buffer_size);
413+
for (int i = 0; i < silence_frames; i++) {
414+
buf[LOOKAHEAD_BUFFER_SIZE + i] = AudioFrame(0, 0);
415+
}
416+
mixed_frames += silence_frames;
417+
}
418+
419+
// Then mix the actual audio stream.
420+
if (mixed_frames < buffer_size) {
421+
mixed_frames += playback->stream_playback->mix(&buf[LOOKAHEAD_BUFFER_SIZE + mixed_frames], playback->pitch_scale.get(), buffer_size - mixed_frames);
422+
}
409423

410424
if (tag_used_audio_streams && playback->stream_playback->is_playing()) {
411425
playback->stream_playback->tag_used_streams();
@@ -1224,21 +1238,21 @@ float AudioServer::get_playback_speed_scale() const {
12241238
return playback_speed_scale;
12251239
}
12261240

1227-
void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, const StringName &p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time, float p_pitch_scale) {
1241+
void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, const StringName &p_bus, Vector<AudioFrame> p_volume_db_vector, float p_from_pos, float p_scheduled_start_time, float p_pitch_scale) {
12281242
ERR_FAIL_COND(p_playback.is_null());
12291243

12301244
HashMap<StringName, Vector<AudioFrame>> map;
12311245
map[p_bus] = p_volume_db_vector;
12321246

1233-
start_playback_stream(p_playback, map, p_start_time, p_pitch_scale);
1247+
start_playback_stream(p_playback, map, p_from_pos, p_scheduled_start_time, p_pitch_scale);
12341248
}
12351249

1236-
void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, const HashMap<StringName, Vector<AudioFrame>> &p_bus_volumes, float p_start_time, float p_pitch_scale, float p_highshelf_gain, float p_attenuation_cutoff_hz) {
1250+
void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, const HashMap<StringName, Vector<AudioFrame>> &p_bus_volumes, float p_from_pos, float p_scheduled_start_time, float p_pitch_scale, float p_highshelf_gain, float p_attenuation_cutoff_hz) {
12371251
ERR_FAIL_COND(p_playback.is_null());
12381252

12391253
AudioStreamPlaybackListNode *playback_node = new AudioStreamPlaybackListNode();
12401254
playback_node->stream_playback = p_playback;
1241-
playback_node->stream_playback->start(p_start_time);
1255+
playback_node->stream_playback->start(p_from_pos);
12421256

12431257
AudioStreamPlaybackBusDetails *new_bus_details = new AudioStreamPlaybackBusDetails();
12441258
int idx = 0;
@@ -1263,6 +1277,14 @@ void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, con
12631277
playback_node->highshelf_gain.set(p_highshelf_gain);
12641278
playback_node->attenuation_filter_cutoff_hz.set(p_attenuation_cutoff_hz);
12651279

1280+
uint64_t scheduled_start_frame = uint64_t(p_scheduled_start_time * get_mix_rate());
1281+
if (scheduled_start_frame > 0 && scheduled_start_frame < mix_frames) {
1282+
WARN_PRINT_ED(vformat("Sound (%s) was scheduled for absolute time %.4f, which has already passed. Playing immediately.",
1283+
p_playback->get_instance_id(),
1284+
p_scheduled_start_time));
1285+
}
1286+
playback_node->scheduled_start_frame.set(scheduled_start_frame);
1287+
12661288
memset(playback_node->prev_bus_details->volume, 0, sizeof(playback_node->prev_bus_details->volume));
12671289

12681290
for (AudioFrame &frame : playback_node->lookahead) {
@@ -1687,6 +1709,10 @@ double AudioServer::get_time_since_last_mix() const {
16871709
return AudioDriver::get_singleton()->get_time_since_last_mix();
16881710
}
16891711

1712+
double AudioServer::get_absolute_time() const {
1713+
return mix_frames / (double)get_mix_rate();
1714+
}
1715+
16901716
AudioServer *AudioServer::singleton = nullptr;
16911717

16921718
void AudioServer::add_update_callback(AudioCallback p_callback, void *p_userdata) {
@@ -2012,6 +2038,7 @@ void AudioServer::_bind_methods() {
20122038

20132039
ClassDB::bind_method(D_METHOD("get_time_to_next_mix"), &AudioServer::get_time_to_next_mix);
20142040
ClassDB::bind_method(D_METHOD("get_time_since_last_mix"), &AudioServer::get_time_since_last_mix);
2041+
ClassDB::bind_method(D_METHOD("get_absolute_time"), &AudioServer::get_absolute_time);
20152042
ClassDB::bind_method(D_METHOD("get_output_latency"), &AudioServer::get_output_latency);
20162043

20172044
ClassDB::bind_method(D_METHOD("get_input_device_list"), &AudioServer::get_input_device_list);

servers/audio_server.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ class AudioServer : public Object {
294294
SafeNumeric<float> pitch_scale;
295295
SafeNumeric<float> highshelf_gain;
296296
SafeNumeric<float> attenuation_filter_cutoff_hz; // This isn't used unless highshelf_gain is nonzero.
297+
SafeNumeric<uint64_t> scheduled_start_frame;
297298
AudioFilterSW::Processor filter_process[8];
298299
// Updating this ref after the list node is created breaks consistency guarantees, don't do it!
299300
Ref<AudioStreamPlayback> stream_playback;
@@ -428,9 +429,9 @@ class AudioServer : public Object {
428429
float get_playback_speed_scale() const;
429430

430431
// Convenience method.
431-
void start_playback_stream(Ref<AudioStreamPlayback> p_playback, const StringName &p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time = 0, float p_pitch_scale = 1);
432+
void start_playback_stream(Ref<AudioStreamPlayback> p_playback, const StringName &p_bus, Vector<AudioFrame> p_volume_db_vector, float p_from_pos = 0, float p_scheduled_start_time = 0, float p_pitch_scale = 1);
432433
// Expose all parameters.
433-
void start_playback_stream(Ref<AudioStreamPlayback> p_playback, const HashMap<StringName, Vector<AudioFrame>> &p_bus_volumes, float p_start_time = 0, float p_pitch_scale = 1, float p_highshelf_gain = 0, float p_attenuation_cutoff_hz = 0);
434+
void start_playback_stream(Ref<AudioStreamPlayback> p_playback, const HashMap<StringName, Vector<AudioFrame>> &p_bus_volumes, float p_from_pos = 0, float p_scheduled_start_time = 0, float p_pitch_scale = 1, float p_highshelf_gain = 0, float p_attenuation_cutoff_hz = 0);
434435
void stop_playback_stream(Ref<AudioStreamPlayback> p_playback);
435436

436437
void set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback, const StringName &p_bus, Vector<AudioFrame> p_volumes);
@@ -472,6 +473,7 @@ class AudioServer : public Object {
472473
virtual double get_output_latency() const;
473474
virtual double get_time_to_next_mix() const;
474475
virtual double get_time_since_last_mix() const;
476+
virtual double get_absolute_time() const;
475477

476478
void add_listener_changed_callback(AudioCallback p_callback, void *p_userdata);
477479
void remove_listener_changed_callback(AudioCallback p_callback, void *p_userdata);

0 commit comments

Comments
 (0)