Latency / unpredictable behavior when using AudioStreamPlayer ?

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

Hi everyone,

I’m trying to make a small music syncing engine to include in projects that need to have stuff react to audio.
While playing around with AudioStreamPlayer and encapsulating it in my methods, I noticed some weird behavior when printing values on screen.

Code :

extends Node

#
# Inspector variables
#
export(AudioStream) var music # Audio file to be played
export(int) var bpm = 120 # BPM of the song
export(int) var offset = 0 # Offset in seconds

#
# Private variables
#
var _player : AudioStreamPlayer # Music player
var _is_playing : bool # Current play state (not using the player's internal variable because of inconsistent results)
var _length : float # Length of song in seconds
var _position : float # Position in the song in seconds
var _position_beats : float # Position in the song in beats
var _beat_duration : float # Duration of a beat (quarter note) in seconds

#
# Initialization
#
func _ready():
    _player = AudioStreamPlayer.new()
    _is_playing = false
    _length = 0.0
    _position = 0.0
    _position_beats = 0.0
    _beat_duration = 0.0
    
    # For debug, should normally be set in the inspector
    MusicSync.music = load("res://test.ogg")
    MusicSync.bpm = 210
    MusicSync.offset = 0.123
    
    add_child(_player)
    
    if music != null:
        load_music(music)

#
# Main loop
#
func _process(delta):
    if _is_playing:
        _position = _player.get_playback_position() - offset
        _position_beats = _position / _beat_duration

#
# Public methods
#
func load_music(file : AudioStream):
    _length = file.get_length()
    _position = 0.0
    _position_beats = 0.0
    _beat_duration = 60.0 / bpm
    _player.set_stream(file)

func play(time : float = _position):
    _position = time
    _position_beats = _position / _beat_duration
    _player.play(_position + offset)
    _is_playing = true

func pause():
    _player.stop()
    _is_playing = false

func stop():
    _position = 0.0
    _position_beats = 0.0
    pause()

func seek(time : float):
    _position = time
    _position_beats = _position / _beat_duration
    _player.seek(_position + offset)

func is_playing() -> bool:
    return _is_playing

func get_length() -> float:
    return _length

func get_position() -> float:
    return _position

func get_position_beats() -> float:
    return _position_beats

At first, calling stop() would not always reset the Player’s position, so I ditched the internal playing variable for my own that I’m setting manually (_is_playing).
But I’m still facing weird issues where all changes I do to the Player’s state have some kind of latency with “interpolation”.

Let me explain :
I’m printing the values of _position (that I’m manually modifying) and _player.get_playback_position() (the actual Player position) in a 2D scene and I assigned play(), pause(), etc… to keyboard keys.
If I seek() forward in a song while it’s playing, then stop(), my position will be reset to 0 as it’s supposed to, and the Player’s position just freezes until play() is called again (I did it this way because for some reason Godot’s Player doesn’t allow seeking when stopped).
However, when hitting play() again my position will briefly flash to a high value, somewhere in-between 0 and the previous position, even though I’m not modifying it.
What seems to happen is that after seeking to 0 into the Player, I go back to the _process() loop where I read and copy its position, and it somehow didn’t reach 0 yet. This sounds very odd because I don’t see how it could just “slide” to a value, as if it was interpolating.

Aside from this specific behavior, I also get an overall jittery feeling when playing around with the Player, and I’m wondering if I’m just using it wrong.
I hope I explained the situation well enough, and that someone will have a solution !