No sound comes out on .play()

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By jagc

I’ve been trying to work around this, banging my head, for 2 days now. XD

I know this can be solved via dirty, spaghetti code and shove all audio into their respective scripts/scenes. But i’m too stubborn and i want this to work.

Case:

  • I want to play all my audio from a singleton.
  • I want a scene with a library of all my audio.
  • Ideally, i want to call it from any script like this: audioManager.play("confirmButton") well, ideally.

Situation:

  • I’ve read the documentation about singletons over and over and over and …
  • watched all the yt videos about gdscript singletons, inheritance, packaging scenes, scene trees, audio manipulation. But apparently, this isn’t enough.
  • BTW, this would be my 3rd time using a singleton.
  • But this is my 1st time:
    • making a scene (audioPlayer.tscn),
    • creating a script for the root node of that scene (audioManager.gd),
    • making THAT script i just made into a singleton.

Now, i made an awesome dirty spaghetti code but with this pattern still, just to make it work. I’m at the stage where at least it’s recognizing (i guess?) the children nodes of the root node of audioPlayer.tscn

Printing the properties of the node is telling me that it SHOULD BE WORKING. But I’m here, so it’s not. :frowning:

audioPlayer.tscn

  • audioPlayer (Node)
  • gameBgMusic (AudioStreamPlayer)
  • confirmButton (AudioStreamPlayer)
  • spring (AudioStreamPlayer2D)
  • shortJump (AudioStreamPlayer2D)
  • longJump (AudioStreamPlayer2D)

menu.gd (Just to call the function in the singleton)

extends Node

func workNowPlsForCryingOutLoud():
	$"/root/audioManager".play("confirmButton")

audioManager.gd (Singleton)

extends Node

func play(sound = null):
	if sound == null:
		return
	else:
		var s = ResourceLoader.load("res://scenes/audioPlayer.tscn")
		var scene = s.instance()
		###############################################################
		# Honestly? IDK if this is getting gameBgMusic or confirmButton
		var music = scene.get_child(0)
		###############################################################
		music.volume_db = 100
		music.play() # There is no sound coming out.

```
	if music.playing == false:
		print('not playing')
	else:
		print('playing')
		print('paused: ' + str(music.stream_paused))
		print('pitch_scale: ' + str(music.pitch_scale))
		print('mix_target: ' + str(music.mix_target))
		print('bus: ' + str(music.bus))
		print('autoplay: ' + str(music.autoplay))
		print('volume_db: ' + music.volume_db)
```

What’s the output of that beautiful pillar of print, you may ask?

  • playing
  • paused: False
  • pitch_scale: 1
  • mix_target: 0
  • bus: Master
  • autoplay: True
  • 100

Just in case someone asks. Yes, i checked my speakers if they were on and my volume if it was high enough. I even cranked it up to 100 while testing it. I also checked if there is actual audio output via YT, Spotify.

Someone save me from insanity. >.<

Godot 3.1.1

:bust_in_silhouette: Reply From: talupoeg

How I would play sounds is I’d connect a script to whichever level or character needs a sound.

E.g. if I have a main gamestate scene which holds some global info then I’d play a background sound like so:

extends Node2D

var bgMusic = 'res://SFX/level1.wav'

func _ready():
	if not $BGM.playing:
		$BGM.stream = load(bgMusic)
		$BGM.play()

NB!

$BGM is an AudioStreamplayer type of node selected to interact with.
Also when importing any audio file and doing any changes under import tab (loop, mono etc.) then always click on ‘Reimport’ button for the changes to take effect.

Yes, this is very similar to how i did my sounds in my other games.

Thing is, I’m thinking, if i wanna do a large game, this way of doing things won’t fly. I wanted a way to make resources - such as sounds, more manageable. That is what I’m trying to practice here. This is also why I’m putting them all in one scene. Then manage it via a singleton.

I don’t know if this design pattern is wrong/not possible or it is possible but i’m just doing it wrong.

Though, If there is a totally different way of doing this, as long as i can manage all my audio in one place, i’m all ears…or eyes… yyou know what i’m saying. :slight_smile:

jagc | 2019-05-24 14:08

You can still do a completely separate scene for your audio references and workings.
You’d need to add this “audio scene” as a child to your scene with add_child() method. And then you have a quick and easy access to the node and it’s child node’s and methods etc.

Another way is to use signals to trigger certain methods elsewhere within your scene tree.

And also your own classes what you were trying to do I think. But I haven’t tried it in GDscript yet.

talupoeg | 2019-05-24 18:47

And if looking around godot docs then playing around with singletons and instancing a scene you’d still need to add it as a child.
Singletons (AutoLoad) — Godot Engine (3.1) documentation in English

talupoeg | 2019-05-24 22:17

I’m sorry, where in the documentation are you pointing at?

Your other comment is pointing me at a good direction. I like it!! It’s sad though, this is what i was trying to avoid. Instancing the audio scene for every script i need to use it.

In terms of emitting signals to a scene that isn’t in the tree preemptively. I guess i have to experiment more on that.

Thanks for the tips!

jagc | 2019-05-26 12:02

:bust_in_silhouette: Reply From: jagc

After banging my head some more, i finally figured it out.

For reference, this is the design i came up with:

game loads (root) [(currently using)] (medium sized game)
  load audio script (singleton)
    preload audio library of game
  load changeable scene
    load every other nodes of the scene
      call audio (singleton script) via events/signals
        load audio node [scene tree library] (load all audio of game)
          play necessary audio
            queue_free() after sound plays
              removes loaded audio node

menu.gd (Just to call the function in the singleton)

extends Node

func workNowPlsForCryingOutLoud():

```
var volume_db = 10
var pitch_scale = 3
audioManager.play("confirmButton", {volume_db = volume_db, pitch_scale = pitch_scale})
```

audioManager.gd (Singleton)

extends Node

var audioScene = load("res://scenes/audioPlayer.tscn") as PackedScene

# sample way to call it:
# audioManager.play("confirmButton", {volume_db = 100, pitch_scale = 9})
func play(audioSoundName, optionals = {}):
	var audio = audioScene.instance(); add_child(audio)
	var sound = audio.get_node(audioSoundName)

```
_setSoundProperties(sound, optionals)

sound.play()

return sound
```
func _setSoundProperties(sound, optionals):
	var volume_db = (100 if not optionals.has('volume_db') else optionals['volume_db'])
	var pitch_scale = (0 if not optionals.has('pitch_scale') else optionals['pitch_scale'])
	var mix_target = (0 if not optionals.has('mix_target') else optionals['mix_target'])
	var bus = ('Master' if not optionals.has('bus') else optionals['bus'])
	var autoplay = (false if not optionals.has('autoplay') else optionals['autoplay'])
	var stream_paused = (false if not optionals.has('stream_paused') else optionals['stream_paused'])

```
# MIX_TARGET_STEREO   = 0 The audio will be played only on the first channel.
# MIX_TARGET_SURROUND = 1 The audio will be played on all surround channels.
# MIX_TARGET_CENTER   = 2 The audio will be played on the second channel, which is usually the center.
sound.mix_target = mix_target #int value of any from above comment

sound.volume_db = volume_db #tested range: 0-100
sound.pitch_scale = pitch_scale #range from 0-16
sound.bus = bus #string name of bus channel
sound.autoplay = autoplay #bool: true or false
sound.stream_paused = stream_paused #bool: true or false

return sound
```

What the problem was.

  • I was calling the audio scene node from a location it wasn’t loaded in to.
  • Hence, i keep on getting null based errors.

If you missed it, calling the function can be as simple as: audioManager.play("confirmButton")


Some manifestations of the solution.

Returning the instance after playing the sound is so it’s capable of calling built in audio functions.
Implemented like so:

extends Node

var bgSound = audioManager.play("gameBgMusic")

# logic to accept command/signal to change scene

# stop playing the bg sound before changing scenes.
func _changeScene():
  bgSound.stop()

  # change scene code here

I was really banging my head for this to work so i could stick to this kind of design pattern when handling audio - permanently.

In the end,
I realized this wasn’t optimal. So i designed a couple of ways to structure a project - keeping the balance of CPU usage and Memory load in mind.

Disclaimer: The perceived size of the game for a design is a personal assessment. This is not in any way backed up by research.

game loads (root) (medium sized game)  [(Used in this solution)]
  load audio script (singleton)
    preload audio library of game
  load changeable scene
    load every other nodes of the scene
      call audio (singleton script) via events/signals
        load audio node [scene tree library] (load all audio of game)
          play necessary audio
            queue_free() after sound plays
              removes loaded audio node

####################################################################

game loads (root) (very small game) [(What i used to do)]
  load changeable scene
    load every other nodes of the scene
      call audio via events/signals
        play necessary audio
    load audio node

####################################################################

game loads (root) (small-medium sized game)
  load changeable scene
    setup scene root for audio stream export vars
    load every other nodes of the scene
    load audio node [empty stream property]
      call audio via events/signals
        set audio stream property
        play necessary audio

####################################################################

game loads (root) (medium sized game)
  load changeable scene
    load audio node [scene tree library] (load all audio of scene)
    load every other nodes of the scene
      call audio via events/signals
        play necessary audio
          queue_free() after sound plays
            removes loaded audio node

####################################################################

game loads (root) (medium-big sized game)
  load audio script (singleton)
    setup audio libraries to preload
    
  load changeable scene
    load every other nodes of the scene
      call audio (singleton script) via events/signals
        load audio node [scene tree library] (load all audio of scene)
          
          play necessary audio
            queue_free() after sound plays
              removes loaded audio node

for some reason, the browser can’t show all text inside <pre></pre>
It looks like a bug?

So i had to break up the file audioManager.gd into 2 separate preformatted text blocks.

jagc | 2019-05-28 09:05

:bust_in_silhouette: Reply From: rballonline

I was having the same issue which led me to this question but I was calling play() when the parent object was already being removed which also removed the audio so nothing was playing.

Having looked at your code though I feel like what you’ve done seems coupled and more complex than what it needs to be. I think getting your audio playing on the node that should be issuing that sound is the way to go. My psuedo-code would be:

onready var die_sound : AudioStreamPlayer = $DieAudioStreamPlayer
Enemy dies
  Enemy.die()
    die_sound.play()
    ... make sure it has enough time to play so either
    [start animation that is longer than audio]
    or
    yield(die_sound, "finished") # Wait for audio to complete
    or both :)

and that’s it. I’d also suggest moving your background music into the node that should be issuing the sound. In my case it’s a map/level/stage whatever. I just have it attached to the level with an autoplay = true and that’s the end of it. Easy peasy.