Skip to content

Commit

Permalink
Merge pull request godotengine#52237 from ellenhp/polyphony
Browse files Browse the repository at this point in the history
Add optional polyphonic playback to built-in audio player nodes
  • Loading branch information
reduz authored Sep 7, 2021
2 parents 43c896a + 0e3cab4 commit ca11f8a
Show file tree
Hide file tree
Showing 22 changed files with 425 additions and 239 deletions.
11 changes: 11 additions & 0 deletions doc/classes/AudioStream.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,23 @@
<description>
</description>
</method>
<method name="_is_monophonic" qualifiers="virtual const">
<return type="bool" />
<description>
</description>
</method>
<method name="get_length" qualifiers="const">
<return type="float" />
<description>
Returns the length of the audio stream in seconds.
</description>
</method>
<method name="is_monophonic" qualifiers="const">
<return type="bool" />
<description>
Returns true if this audio stream only supports monophonic playback, or false if the audio stream supports polyphony.
</description>
</method>
</methods>
<constants>
</constants>
Expand Down
3 changes: 3 additions & 0 deletions doc/classes/AudioStreamPlayer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
<member name="bus" type="StringName" setter="set_bus" getter="get_bus" default="&amp;&quot;Master&quot;">
Bus on which this audio is playing.
</member>
<member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1">
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.
</member>
<member name="mix_target" type="int" setter="set_mix_target" getter="get_mix_target" enum="AudioStreamPlayer.MixTarget" default="0">
If the audio configuration has more than two speakers, this sets the target channels. See [enum MixTarget] constants.
</member>
Expand Down
3 changes: 3 additions & 0 deletions doc/classes/AudioStreamPlayer2D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
<member name="max_distance" type="float" setter="set_max_distance" getter="get_max_distance" default="2000.0">
Maximum distance from which audio is still hearable.
</member>
<member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1">
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.
</member>
<member name="pitch_scale" type="float" setter="set_pitch_scale" getter="get_pitch_scale" default="1.0">
The pitch and the tempo of the audio, as a multiplier of the audio sample's sample rate.
</member>
Expand Down
3 changes: 3 additions & 0 deletions doc/classes/AudioStreamPlayer3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@
<member name="max_distance" type="float" setter="set_max_distance" getter="get_max_distance" default="0.0">
Sets the distance from which the [member out_of_range_mode] takes effect. Has no effect if set to 0.
</member>
<member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1">
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.
</member>
<member name="out_of_range_mode" type="int" setter="set_out_of_range_mode" getter="get_out_of_range_mode" enum="AudioStreamPlayer3D.OutOfRangeMode" default="0">
Decides if audio should pause when source is outside of [member max_distance] range.
</member>
Expand Down
4 changes: 4 additions & 0 deletions modules/minimp3/audio_stream_mp3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,10 @@ float AudioStreamMP3::get_length() const {
return length;
}

bool AudioStreamMP3::is_monophonic() const {
return false;
}

void AudioStreamMP3::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamMP3::set_data);
ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamMP3::get_data);
Expand Down
2 changes: 2 additions & 0 deletions modules/minimp3/audio_stream_mp3.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ class AudioStreamMP3 : public AudioStream {

virtual float get_length() const override;

virtual bool is_monophonic() const override;

AudioStreamMP3();
virtual ~AudioStreamMP3();
};
Expand Down
4 changes: 4 additions & 0 deletions modules/stb_vorbis/audio_stream_ogg_vorbis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ float AudioStreamOGGVorbis::get_length() const {
return length;
}

bool AudioStreamOGGVorbis::is_monophonic() const {
return false;
}

void AudioStreamOGGVorbis::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamOGGVorbis::set_data);
ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamOGGVorbis::get_data);
Expand Down
2 changes: 2 additions & 0 deletions modules/stb_vorbis/audio_stream_ogg_vorbis.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ class AudioStreamOGGVorbis : public AudioStream {

virtual float get_length() const override; //if supported, otherwise return 0

virtual bool is_monophonic() const override;

AudioStreamOGGVorbis();
virtual ~AudioStreamOGGVorbis();
};
Expand Down
162 changes: 93 additions & 69 deletions scene/2d/audio_stream_player_2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,33 +59,47 @@ void AudioStreamPlayer2D::_notification(int p_what) {

if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
//update anything related to position first, if possible of course
if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) {
_update_panning();
}

if (!stream_playback.is_valid()) {
return;
if (setplay.get() >= 0 && stream.is_valid()) {
active.set();
Ref<AudioStreamPlayback> new_playback = stream->instance_playback();
ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback.");
AudioServer::get_singleton()->start_playback_stream(new_playback, _get_actual_bus(), volume_vector, setplay.get());
stream_playbacks.push_back(new_playback);
setplay.set(-1);
}
if (setplay.get() >= 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) {
_update_panning();
if (setplay.get() >= 0) {
active.set();
AudioServer::get_singleton()->start_playback_stream(stream_playback, _get_actual_bus(), volume_vector, setplay.get());
setplay.set(-1);

if (!stream_playbacks.is_empty() && active.is_set()) {
// Stop playing if no longer active.
Vector<Ref<AudioStreamPlayback>> playbacks_to_remove;
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) {
emit_signal(SNAME("finished"));
playbacks_to_remove.push_back(playback);
}
}
// Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble.
for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) {
stream_playbacks.erase(playback);
}
if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) {
// This node is no longer actively playing audio.
active.clear();
set_physics_process_internal(false);
}
}

// Stop playing if no longer active.
if (active.is_set() && !AudioServer::get_singleton()->is_playback_active(stream_playback)) {
active.clear();
set_physics_process_internal(false);
emit_signal(SNAME("finished"));
while (stream_playbacks.size() > max_polyphony) {
AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]);
stream_playbacks.remove(0);
}
}
}

StringName AudioStreamPlayer2D::_get_actual_bus() {
if (!stream_playback.is_valid()) {
return SNAME("Master");
}

Vector2 global_pos = get_global_position();

//check if any area is diverting sound into a bus
Expand Down Expand Up @@ -113,12 +127,10 @@ StringName AudioStreamPlayer2D::_get_actual_bus() {
}

void AudioStreamPlayer2D::_update_panning() {
if (!stream_playback.is_valid()) {
if (!active.is_set() || stream.is_null()) {
return;
}

last_mix_count = AudioServer::get_singleton()->get_mix_count();

Ref<World2D> world_2d = get_world_2d();
ERR_FAIL_COND(world_2d.is_null());

Expand Down Expand Up @@ -164,28 +176,20 @@ void AudioStreamPlayer2D::_update_panning() {
volume_vector.write[0] = AudioFrame(l, r) * multiplier;
}

AudioServer::get_singleton()->set_playback_bus_exclusive(stream_playback, _get_actual_bus(), volume_vector);
}

void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) {
if (stream_playback.is_valid()) {
stop();
for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) {
AudioServer::get_singleton()->set_playback_bus_exclusive(playback, _get_actual_bus(), volume_vector);
}

stream_playback.unref();
stream.unref();
if (p_stream.is_valid()) {
stream_playback = p_stream->instance_playback();
if (stream_playback.is_valid()) {
stream = p_stream;
} else {
stream.unref();
}
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
AudioServer::get_singleton()->set_playback_pitch_scale(playback, pitch_scale);
}

if (p_stream.is_valid() && stream_playback.is_null()) {
stream.unref();
}
last_mix_count = AudioServer::get_singleton()->get_mix_count();
}

void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) {
stop();
stream = p_stream;
}

Ref<AudioStream> AudioStreamPlayer2D::get_stream() const {
Expand All @@ -203,8 +207,8 @@ float AudioStreamPlayer2D::get_volume_db() const {
void AudioStreamPlayer2D::set_pitch_scale(float p_pitch_scale) {
ERR_FAIL_COND(p_pitch_scale <= 0.0);
pitch_scale = p_pitch_scale;
if (stream_playback.is_valid()) {
AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, p_pitch_scale);
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
AudioServer::get_singleton()->set_playback_pitch_scale(playback, p_pitch_scale);
}
}

Expand All @@ -213,44 +217,50 @@ float AudioStreamPlayer2D::get_pitch_scale() const {
}

void AudioStreamPlayer2D::play(float p_from_pos) {
stop();
if (stream.is_valid()) {
stream_playback = stream->instance_playback();
if (stream.is_null()) {
return;
}
if (stream_playback.is_valid()) {
setplay.set(p_from_pos);
set_physics_process_internal(true);
ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree");
if (stream->is_monophonic() && is_playing()) {
stop();
}

setplay.set(p_from_pos);
active.set();
set_physics_process_internal(true);
}

void AudioStreamPlayer2D::seek(float p_seconds) {
if (stream_playback.is_valid() && active.is_set()) {
if (is_playing()) {
stop();
play(p_seconds);
}
}

void AudioStreamPlayer2D::stop() {
if (stream_playback.is_valid()) {
active.clear();
AudioServer::get_singleton()->stop_playback_stream(stream_playback);
set_physics_process_internal(false);
setplay.set(-1);
setplay.set(-1);
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
AudioServer::get_singleton()->stop_playback_stream(playback);
}
stream_playbacks.clear();
active.clear();
set_physics_process_internal(false);
}

bool AudioStreamPlayer2D::is_playing() const {
if (stream_playback.is_valid()) {
return AudioServer::get_singleton()->is_playback_active(stream_playback);
for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) {
if (AudioServer::get_singleton()->is_playback_active(playback)) {
return true;
}
}

return false;
}

float AudioStreamPlayer2D::get_playback_position() {
if (stream_playback.is_valid()) {
return AudioServer::get_singleton()->get_playback_position(stream_playback);
// 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]);
}

return 0;
}

Expand Down Expand Up @@ -284,11 +294,7 @@ void AudioStreamPlayer2D::_set_playing(bool p_enable) {
}

bool AudioStreamPlayer2D::_is_active() const {
if (stream_playback.is_valid()) {
// TODO make sure this doesn't change any behavior w.r.t. pauses. Is a paused stream active?
return AudioServer::get_singleton()->is_playback_active(stream_playback);
}
return false;
return active.is_set();
}

void AudioStreamPlayer2D::_validate_property(PropertyInfo &property) const {
Expand Down Expand Up @@ -336,21 +342,35 @@ uint32_t AudioStreamPlayer2D::get_area_mask() const {
}

void AudioStreamPlayer2D::set_stream_paused(bool p_pause) {
// TODO this does not have perfect recall, fix that maybe? If the stream isn't set, we can't persist this bool.
if (stream_playback.is_valid()) {
AudioServer::get_singleton()->set_playback_paused(stream_playback, 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<AudioStreamPlayback> &playback : stream_playbacks) {
AudioServer::get_singleton()->set_playback_paused(playback, p_pause);
}
}

bool AudioStreamPlayer2D::get_stream_paused() const {
if (stream_playback.is_valid()) {
return AudioServer::get_singleton()->is_playback_paused(stream_playback);
// There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest.
if (!stream_playbacks.is_empty()) {
return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]);
}
return false;
}

Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() {
return stream_playback;
if (!stream_playbacks.is_empty()) {
return stream_playbacks[stream_playbacks.size() - 1];
}
return nullptr;
}

void AudioStreamPlayer2D::set_max_polyphony(int p_max_polyphony) {
if (p_max_polyphony > 0) {
max_polyphony = p_max_polyphony;
}
}

int AudioStreamPlayer2D::get_max_polyphony() const {
return max_polyphony;
}

void AudioStreamPlayer2D::_bind_methods() {
Expand Down Expand Up @@ -391,6 +411,9 @@ void AudioStreamPlayer2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer2D::set_stream_paused);
ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer2D::get_stream_paused);

ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer2D::set_max_polyphony);
ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer2D::get_max_polyphony);

ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer2D::get_stream_playback);

ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
Expand All @@ -401,6 +424,7 @@ void AudioStreamPlayer2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "1,4096,1,or_greater,exp"), "set_max_distance", "get_max_distance");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_attenuation", "get_attenuation");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus");
ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask");

Expand Down
10 changes: 7 additions & 3 deletions scene/2d/audio_stream_player_2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ class AudioStreamPlayer2D : public Node2D {
Viewport *viewport = nullptr; //pointer only used for reference to previous mix
};

Ref<AudioStreamPlayback> stream_playback;
Vector<Ref<AudioStreamPlayback>> stream_playbacks;
Ref<AudioStream> stream;

SafeFlag active;
SafeFlag active{ false };
SafeNumeric<float> setplay{ -1.0 };

Vector<AudioFrame> volume_vector;
Expand All @@ -64,7 +64,8 @@ class AudioStreamPlayer2D : public Node2D {
float volume_db = 0.0;
float pitch_scale = 1.0;
bool autoplay = false;
StringName default_bus = "Master";
StringName default_bus = SNAME("Master");
int max_polyphony = 1;

void _set_playing(bool p_enable);
bool _is_active() const;
Expand Down Expand Up @@ -119,6 +120,9 @@ class AudioStreamPlayer2D : public Node2D {
void set_stream_paused(bool p_pause);
bool get_stream_paused() const;

void set_max_polyphony(int p_max_polyphony);
int get_max_polyphony() const;

Ref<AudioStreamPlayback> get_stream_playback();

AudioStreamPlayer2D();
Expand Down
Loading

0 comments on commit ca11f8a

Please sign in to comment.