Skip to content

Audio: Change playback position type from float to double #105545

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

PizzaLovers007
Copy link

Two reasons to change this:

  • At a default mix rate of 44100, the playback position in seconds can experience rounding errors with a 32-bit type if the value is high enough. 44100 requires 15 free bits in the mantissa to be within 1/2 an audio frame, so the cutoff is 512 seconds before rounding issues occur (512=2^9, and 9+15 > 23 mantissa bits in 32-bit float).
  • Internally, AudioStreamPlayback and AudioStream use a 64-bit type for playback position.

See this discussion: #105510 (comment)

The absolute time is calculated based on the total mixed frames and the mix rate. This means it only updates when a mix step happens.

Specific play_scheduled behaviors:
- If a sound is playing, play_scheduled() will stop that sound. This matches the behavior of play().
- If a sound is scheduled, then paused, then resumed before the schedule happens, the sound still plays at the correct scheduled time.
- If a playing sound is paused, then play_scheduled() is called, the sound will restart from the beginning. This matches the behavior of play().
- With a higher max_polyphony, multiple sounds can be scheduled.
- play_scheduled is unaffected by pitch scale
- play_scheduled does not support samples

Scheduled stop is not implemented due to limited use cases.
@PizzaLovers007 PizzaLovers007 force-pushed the fix-playback-position-types branch from 327b188 to f81aa49 Compare April 18, 2025 22:30
Two reasons to change this:
* At a default mix rate of 44100, the playback position in seconds can experience rounding errors with a 32-bit type if the value is high enough. 44100 requires 15 free bits in the mantissa to be within 1/2 an audio frame, so the cutoff is 512 seconds before rounding issues occur (512=2^9, and 9+15 > 23 mantissa bits in 32-bit float).
* Internally, AudioStreamPlayback and AudioStream use a 64-bit type for playback position.

See this discussion: godotengine#105510 (comment)
@PizzaLovers007 PizzaLovers007 force-pushed the fix-playback-position-types branch 2 times, most recently from f3dd43f to fd9f6f0 Compare April 19, 2025 06:51
@AThousandShips AThousandShips changed the title Change playback position type from float to double Audio: Change playback position type from float to double Apr 19, 2025
@AThousandShips AThousandShips added this to the 4.x milestone Apr 19, 2025
@AThousandShips
Copy link
Member

These still need compatibility binds I'd say, the methods are still used by GDExtension etc.

@PizzaLovers007
Copy link
Author

I actually tried making compatibility binds, but the C# build failed because it defined the same method twice: https://github.com/PizzaLovers007/godot/actions/runs/14546387351/job/40812407480

Error: /home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/AudioStreamPlayer.cs(514,17): error CS0111: Type 'AudioStreamPlayer' already defines a member called 'Play' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
Error: /home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/AudioStreamPlayer.cs(525,17): error CS0111: Type 'AudioStreamPlayer' already defines a member called 'Seek' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
Error: /home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/AudioStreamPlayer.cs(538,19): error CS0111: Type 'AudioStreamPlayer' already defines a member called 'GetPlaybackPosition' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
Error: /home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/AudioStreamPlayer2D.cs(594,17): error CS0111: Type 'AudioStreamPlayer2D' already defines a member called 'Play' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
Error: /home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/AudioStreamPlayer2D.cs(605,17): error CS0111: Type 'AudioStreamPlayer2D' already defines a member called 'Seek' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
Error: /home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/AudioStreamPlayer2D.cs(616,19): error CS0111: Type 'AudioStreamPlayer2D' already defines a member called 'GetPlaybackPosition' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
Error: /home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/AudioStreamPlayer3D.cs(896,17): error CS0111: Type 'AudioStreamPlayer3D' already defines a member called 'Play' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
Error: /home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/AudioStreamPlayer3D.cs(907,17): error CS0111: Type 'AudioStreamPlayer3D' already defines a member called 'Seek' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]
Error: /home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/Generated/GodotObjects/AudioStreamPlayer3D.cs(918,19): error CS0111: Type 'AudioStreamPlayer3D' already defines a member called 'GetPlaybackPosition' with the same parameter types [/home/runner/work/godot/godot/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj]

I'm less familiar with GDExtension API generation, but based on the exception added in #94243 (int32_t to int64_t), I would guess it generates doubles already?

@AThousandShips
Copy link
Member

That's strange, but since it generates the error of changing the return type it should be needed, but problematic with the clash with C#, I'd suggest testing building godot-cpp and seeing what the templates are there

@PizzaLovers007 PizzaLovers007 force-pushed the fix-playback-position-types branch from fd9f6f0 to f3dd43f Compare April 19, 2025 18:08
@PizzaLovers007
Copy link
Author

Here's the generated API with the compatibility methods bound: extension_api.json. The new method entries have a "hash_compatibility" field with the same value as the "hash", not sure if that's intended.

Looking at the gen/include/godot_cpp/classes headers from building godot-cpp, I do see the change to double but don't see anything related to the old float versions.

Is there somewhere else I should be looking to see the template with the compatibility methods?

@AThousandShips
Copy link
Member

The old float versions won't be there when built with this PR, you need to compare without these changes to see, the compatibility methods are used by extensions built with older versions. It's used when looking up the function pointer to use based on the hash

@PizzaLovers007
Copy link
Author

Ok I can confirm there's a duplicate method happening on the C# generated code. Here's AudioStreamPlayer.cs:

    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private static readonly IntPtr MethodBind13 = ClassDB_get_method_with_compatibility(NativeName, MethodName.GetPlaybackPosition, 191475506ul);

    /// <summary>
    /// <para>Returns the position in the <see cref="Godot.AudioStream"/> of the latest sound, in seconds. Returns <c>0.0</c> if no sounds are playing.</para>
    /// <para><b>Note:</b> The position is not always accurate, as the <see cref="Godot.AudioServer"/> does not mix audio every processed frame. To get more accurate results, add <see cref="Godot.AudioServer.GetTimeSinceLastMix()"/> to the returned position.</para>
    /// <para><b>Note:</b> This method always returns <c>0.0</c> if the <see cref="Godot.AudioStreamPlayer.Stream"/> is an <see cref="Godot.AudioStreamInteractive"/>, since it can have multiple clips playing at once.</para>
    /// </summary>
    public double GetPlaybackPosition()
    {
        return NativeCalls.godot_icall_0_144(MethodBind13, GodotObject.GetPtr(this));
    }

...

    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private static readonly IntPtr MethodBind31 = ClassDB_get_method_with_compatibility(NativeName, MethodName.GetPlaybackPosition, 191475506ul);

    /// <summary>
    /// <para>Returns the position in the <see cref="Godot.AudioStream"/> of the latest sound, in seconds. Returns <c>0.0</c> if no sounds are playing.</para>
    /// <para><b>Note:</b> The position is not always accurate, as the <see cref="Godot.AudioServer"/> does not mix audio every processed frame. To get more accurate results, add <see cref="Godot.AudioServer.GetTimeSinceLastMix()"/> to the returned position.</para>
    /// <para><b>Note:</b> This method always returns <c>0.0</c> if the <see cref="Godot.AudioStreamPlayer.Stream"/> is an <see cref="Godot.AudioStreamInteractive"/>, since it can have multiple clips playing at once.</para>
    /// </summary>
    public double GetPlaybackPosition()
    {
        return NativeCalls.godot_icall_0_144(MethodBind31, GodotObject.GetPtr(this));
    }

I assume the 2nd one is the "compatibility" version given that it's next to the other Play and Seek methods. The implementations for all of them look identical to each other.

As for godot-cpp, they are indeed generating with float instead of double. I'm a bit lost on how to proceed from here though, so some more guidance would be much appreciated!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants