How do I mute/unmute music on a particular beat?

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

I have several songs running concurrently. When the player presses one button, one song will mute and another will start playing. Right now, this happens immediately when the button is pressed, regardless of whether this is in the middle of a note or not.

Let’s assume that the song is 90 BPM, or 1.5 beats a second, or 2/3 seconds per beat.
How would I go about this so that every mute or unmute happens at an interval of 2/3 seconds rather than at the user input?

My first thought is to have the user input set a flag that gets checked every 2/3 seconds, but I’m not certain how to set that up in Godot yet.

:bust_in_silhouette: Reply From: njamster

Something like this should work:

func _on_Button_pressed() -> void:
	var playback_pos = $AudioStreamPlayer.get_playback_position()
	var wait_time = 2.0/3.0 - fmod(playback_pos, 2.0/3.0)
	yield(get_tree().create_timer(wait_time), "timeout")

However, audio output is usually buffered and prone to several types of delays (e.g. depending on your hardware) - so I wouldn’t bet on this stopping perfectly in-sync with the beat. I’d recommend you slowly fade out the music volume instead.

Given what you said about audio output being buffered, does that mean that if I start 4 songs at the same time, depending on the hardware, they might not all stay in sync with each other unless I do something specific in the code to guarantee they do?

malachi | 2020-05-22 13:08

I’m far from being an audio-expert, but I’d argue even if they don’t stay perfectly in sync, the deviation will be so minor that’s unlikely anyone will notice. However, if you are trying to precisely hit a certain beat, things might look different. I recommend you read through this part of the documentation, if you haven’t already.

njamster | 2020-05-22 14:21

:bust_in_silhouette: Reply From: Magso

It’ll be better using _physics_process as it’s a fixed framerate (I’ve used it to record and play replays with accuracy). You could use a bool to check if the input has been pressed and use a timer node with one_shot false and TimerProcessMode on 0 for physics framerate then use the timeout signal to change the music (hopefully in time).