looping buffer in AudioStreamPlayer

Godot Version

4.3

Question

Hello, I’m currently making a synthesizer in Godot and unfortunately I can’t loop a buffer without hearing a “pop” after the buffer is over
can anyone help me please?
my code:

extends Button

@onready var freq = $"/root/Global".notesdict[name]["freq"]
@onready var wave = "sine"#$"/root/Global".wave
@onready var sample_hz = $AudioStreamPlayer.stream.mix_rate
var playback : AudioStreamGeneratorPlayback

var buffer = []
var buffer_length = 0

var touchmouse : bool = false

func _on_mouse_entered():
	print("Mouse entered")
	touchmouse = true

func _on_mouse_exited():
	print("Mouse exited")
	touchmouse = false

func generate_buffer():
	var increment = freq / sample_hz
	var phase = 0.0
	buffer = []
	var frames_available = sample_hz * 2  # Erhöhte Buffer-Größe
	for i in range(frames_available):
		var value = 0.0
		match wave:
			"sine":
				value = sin(phase * TAU)
			"square":
				value = 1.0 if phase % 1.0 < 0.5 else -1.0
			"triangle":
				value = 2.0 * abs(2.0 * (phase - floor(phase + 0.5))) - 1.0
			"sawtooth":
				value = 2.0 * (phase - floor(phase + 0.5))
		buffer.append(Vector2.ONE * value)
		phase = fmod(phase + increment, 1.0)
	buffer_length = len(buffer)

func _ready() -> void:
	generate_buffer()

func fill_buffer():
	while playback.get_frames_available() > 0:
		playback.push_frame(buffer[playback.get_frames_available() % buffer_length])

func _process(delta: float) -> void:
	if Input.is_action_pressed("mousepressed") and touchmouse:
		if not $AudioStreamPlayer.is_playing():
			print("playing")
			$AudioStreamPlayer.play()
			playback = $AudioStreamPlayer.get_stream_playback()
		fill_buffer()
	else:
		$AudioStreamPlayer.stop()

Your fill buffer needs to keep a pointer to the buffer position. Because available frames may be a fixed amount that is smaller or larger then your buffer. And you start from the beginning each time you call the function.

What is the best way to implement and use a pointer for the buffer?

For Godot, it is an index to point at which element of the buffer array should be pushed next. (An int)

var buffer_idx : int = 0
func fill_buffer():
  var avail_frames = playback.get_frames_available()
  while avail_frames > 0:
    playback.push_frame(buffer[buffer_idx])
    buffer_idx = (buffer_idx + 1) % buffer_length
    avail_frames -= 1
1 Like

sorry for the late reply.
your code works partially, I still get a few clicks at some frequencies.

What am I doing wrong?

Could it be due to the generate buffer function?

If the pops are periodic at a constant rate, yes. If they are random probably not.

I haven’t looked at your waveform code but the waveform could have a discontinuity, and will cause a pop every buffer period.

A good way to debug this is to do an internal digital capture, recording Godot’s output and looking at the waveform in your favorite DAW. Audacity is free.