How to create a variable which is an unique, changed copy of a Curve?

Godot Version

4.7.1

A bit about the Project

ASREnvelope Node type to animate the Loudness of a continuous Sound, and other Things.
I started with an ADSR Envelope (Attack, Decay, Sustain, Release) but i have since removed Decay because the Attack Curve can easily represent both Attack and Decay.

Question

My work-in-progress Node of type ASREnvelope stores 2 versions of the Release Curve:

  • Inspector Property release_curve
  • Private variable _scaled_release_curve

The private copy is derived from the public original, using this function:

var _update_scaled_release_curve: Callable = func() -> void:
	_scaled_release_curve = Curve.new()
	_scaled_release_curve.set_min_domain(release_curve.get_min_domain())
	_scaled_release_curve.set_max_domain(release_curve.get_max_domain())
	_scaled_release_curve.set_min_value(release_curve.get_min_value())
	_scaled_release_curve.set_max_value(release_curve.get_max_value())
	var _scale_value: Callable = func(value: float) -> float:
		var _end_value_of_release_curve: float = \
				release_curve.get_point_position(
					release_curve.get_point_count() - 1
				).y
		return remap(
			value,
			_end_value_of_release_curve,
			release_curve.get_point_position(0).y,
			_end_value_of_release_curve,
			sample,
		)
	for _i in release_curve.get_point_count():
		_scaled_release_curve.add_point(
			Vector2(
				_scale_value.call(
					release_curve.get_point_position(_i).x
				),
				_scale_value.call(
					release_curve.get_point_position(_i).y
				),
			),
			_scale_value.call(
				release_curve.get_point_left_tangent(_i)
			),
			_scale_value.call(
				release_curve.get_point_right_tangent(_i)
			),
			release_curve.get_point_left_mode(_i),
			release_curve.get_point_right_mode(_i),
		)

Note how this function copies each value from the original Curve before it scales the result.
I want to duplicate the whole Curve instead of its singular values
but i don’t know how to do that without changing the original Curve too.

Curve inherits duplicate() from Resource, so release_curve.duplicate() gives you an independent copy with the points, tangents, tangent modes, and the domain and value ranges already carried across. Editing the copy leaves the original alone.

That collapses the function down to a duplicate plus one pass over the points:

_scaled_release_curve = release_curve.duplicate()
for i in _scaled_release_curve.get_point_count():
	var y := _scaled_release_curve.get_point_position(i).y
	_scaled_release_curve.set_point_value(i, _scale_value.call(y))

set_point_value() only writes the Y, so the X offsets stay put without you rebuilding each point.

Thank you, @Baz :+1:

Building on top of your answer, my goal was to refactor the function so that i could use it for both Curves (Attack and Decay).

It’s typewritten like this now:

var _scale_curve: Callable = func(curve: Curve) -> Curve:
	var _scale_point_value: Callable = func(value: float) -> float:
		return remap(
			value,
			curve.sample(curve.get_max_domain()),
			curve.sample(curve.get_min_domain()),
			curve.sample(curve.get_max_domain()),
			sample,
		)
	
	var _output_curve: Curve = curve.duplicate()
	for _point in curve.get_point_count():
		var _y : float = curve.get_point_position(_point).y
		_output_curve.set_point_value(
			_point, _scale_point_value.call(_y)
		)
	return _output_curve

Notes:

  • The function no longer changes the _scaled_release_curveby itself.
    It now returns a scaled version of the input curve,
    so this line outside the function sets _scaled_release_curve:

    _scaled_release_curve = _scale_curve.call(release_curve)
    
  • I remembered that i started coding this Add‑on before Godot 4.4.
    Godot 4.4 added the min_domain and max_domain properties to Curve,
    so i changed the Lookup of Curves’ first Position and last Position to a simpler Way,
    how i would have done it if i started the Project today.

    LookupBeforeAfter
    First
    Position
    release_curve.get_point_position(0).y
    
    curve.sample(curve.get_min_domain())
    
    Last
    Position
    release_curve.get_point_position(
    	release_curve.get_point_count() - 1
    ).y
    
    curve.sample(curve.get_max_domain())
    

    I chose sample instead of sample_baked because i expect developers using my Add‑on to change the Curves at Runtime.