godotengine / godot-docs

Godot Engine official documentation
https://docs.godotengine.org
Other
3.98k stars 3.24k forks source link

There's no doc about playing a lot of audios at same time. #4622

Open nonunknown opened 3 years ago

nonunknown commented 3 years ago

Your Godot version: 3.2.4 RC1

Issue description: Altought the docs talk about how WAV file format can play thousands of it without any performance issue, the docs doesnt says how this can be archieved, I finally manage to found a tutorial from a user who did it right, but that is not begginer friendly and I couldnt understand it well too.

Maybe there's a better approach? In any case this should be documented, playing a lot of sounds in a little space of time is not simple!

Source Content

https://www.youtube.com/watch?v=DsAIrA9UE4E https://www.youtube.com/watch?v=3CFWlQzMthY

Source Code

extends Node

var dic : Dictionary = {}

func play_sfx(audio_clip : AudioStream, priority : int = 0):
    for child in $sfx.get_children():
        if child.playing == false:
            child.stream = audio_clip
            child.play()
            dic[child.name] = priority
            break

        if child.get_index() == $sfx.get_child_count() - 1:
            var priority_player = check_priority_and_find_oldest(dic, priority) #finds player with same/lowest priority and oldest player
            if priority_player != null:
                $sfx.get_node(priority_player).stream = audio_clip
                $sfx.get_node(priority_player).play()
            else:
                print("priority player is null")
    pass

#playes at most 3 sounds at the same time, in a lot of cases bad, because you get less sound feedback.
#execept when you want to restrict the amount of sounds, for example crashes/destructions/debris
func check_priority(_dic : Dictionary, _priority):
    var prio_list : Array = []

    for key in _dic:
        if _priority > _dic[key]:
            prio_list.append(key)#append key(sfx_player.name) to the array

    #get the lowest priority from prio_list
    var last_prio = null
    for key in prio_list:
        if last_prio == null:
            last_prio = key
            continue
        if _dic[key] < _dic[last_prio]:
            last_prio = key
    return last_prio
    pass

#playes new sounds all the time, bad if you have important sound, wich you liked to play
func find_oldest_player():
    var last_child = null

    for child in $sfx.get_children():
        if last_child == null:
            last_child = child
            continue
        #find player wich played the longest
        if child.get_playback_position() > last_child.get_playback_position():
            last_child = child

    return last_child.name
    pass

#good for all types of situations, important sound get played most of the time and sounds doesn't get get
#swallowed up most of the time
func check_priority_and_find_oldest(_dic, _priority): #1,3,1 == 1
    var prio_list : Array = []
    for key in _dic: 
        if _priority >= _dic[key]:
            prio_list.append(key) #append key(sfx_player.name) with same/lower priority to an array

    #find oldest 
    if prio_list.empty():
        return null
    var oldest_player = prio_list[0]
    for i in range(1, prio_list.size() -1):
        if $sfx.get_node(oldest_player).get_playback_position() < $sfx.get_node(prio_list[i]).get_playback_position():
            oldest_player = prio_list[i]

    return oldest_player
    pass

    ###### Here is a little coding challenge  #######
#func lowest_priority_and_oldest(_dic, _priority):
    #make prio_list
    #append same/lower priority to prio_list
    #get the lowest priority
    #get all player with the same lowest priority
    #if there is more then 1 player
    #get the oldest player from the lowest priority players

func play_music(music_clip : AudioStream):
    $music/music_player.stream = music_clip
    $music/music_player.play()
    pass
Calinou commented 3 years ago

Here's what I use: https://github.com/Calinou/escape-space/blob/c151b264c622fcd823a2c43855b2a6f2d8bbe68f/autoload/sound.gd

To be fair, I think this should be solved at the core level by readding polyphony support.

nonunknown commented 3 years ago

@Calinou This one lets me play a lot of audios in short time? Also makes sense to be a core issue, in all my projects I had problem with audio! but until there a doc to at least give a north to people out there!

Calinou commented 3 years ago

This one lets me play a lot of audios in short time?

Yes :slightly_smiling_face:

nonunknown commented 3 years ago

thank you very much!

nonunknown commented 3 years ago

@Calinou made a C# version of it, worked flawlessly thank you again!

using Godot;
using System;

public static class OSTManager
{

    public static void PlaySFX(Node target, AudioStream stream, Vector2 position, float pitchScale = 1.0f) {
        AudioStreamPlayer2D sfx = new AudioStreamPlayer2D();
        target.GetTree().CurrentScene.AddChild(sfx);
        if (position != null) sfx.GlobalPosition = position;
        sfx.Stream = stream;
        sfx.VolumeDb = 0;
        sfx.PitchScale = pitchScale;
        sfx.Play();
        sfx.Connect("finished",sfx,"queue_free");
    }

    public static void PlaySFXPitched(Node target, AudioStream stream, Vector2 position) {
        RandomNumberGenerator rng = new RandomNumberGenerator();
        PlaySFX(target,stream,position,rng.RandfRange(.9f,1.7f));
    }

}
Calinou commented 2 years ago

4.0alpha has built-in polyphony support, but it can't be backported to 3.x since it relies on different AudioServer internals. I suppose this still needs to be documented on the manual page that mentions playing sounds in general.

https://github.com/Calinou/escape-space/blob/c151b264c622fcd823a2c43855b2a6f2d8bbe68f/autoload/sound.gd is short enough to include inline on the page, but we could also link to a maintained add-on in the asset library.

nonunknown commented 2 years ago

yeah thats the perfect solution!