Skip to content

avm1: Improve behavior of Sound when loadSound() has been called#23975

Draft
ChrisCPI wants to merge 1 commit into
ruffle-rs:masterfrom
ChrisCPI:avm1-loadsound-fixes
Draft

avm1: Improve behavior of Sound when loadSound() has been called#23975
ChrisCPI wants to merge 1 commit into
ruffle-rs:masterfrom
ChrisCPI:avm1-loadsound-fixes

Conversation

@ChrisCPI

Copy link
Copy Markdown
Collaborator

Description

Fixes #5019
Fixes #22572

This fixes several of the behaviors of the Sound object when playing external sound via loadSound().

When loadSound is called on a Sound, it appears that it enters a state of being "separated" from its original owner (even when controlling global sound). This means it can no longer control any prior sounds that were added to it via attachSound (and can no longer attach new sounds in this way), and it inherits its own unique sound transform, not attached to any particular display object. This has now been implemented.

Some of the other changes include:

  • Added a is_stopped flag to the native object, which is true when stop() has been called. Used to prevent external sound from playing when it has finished loading.
  • When calling start(), per the docs, the second argument (loops) is now ignored when the sound is streaming.
  • When loadSound is called again on the object, any external sounds it was playing are stopped, streaming or not.

Testing

Several tests added to cover this.

Checklist

  • I, a human, have self-reviewed this PR and fully understand the changes within.
  • I have made or updated tests where possible.
  • All of my commits are properly scoped, compile successfully, and pass all tests.
  • This PR does not make sense to split up into smaller PRs.
  • An LLM was involved in the authoring of this code. (Used in authoring the added set_sound_transform_with_handle function)

@ChrisCPI ChrisCPI force-pushed the avm1-loadsound-fixes branch from ff54238 to 442d3c3 Compare June 13, 2026 20:27
Corrects several behaviors of the Sound object when loading external sound. Some of the important changes:
- Sound objects seem to inherit their own sound transform when loading external sound, and become "separated" from their original owner, so this has been implemented.
- Added a `is_stopped` flag to the native object, which is true when `stop()` has been called. Used to prevent external sound from playing when it has finished loading.
- When calling `start()`, per the docs, the second argument (loops) is now ignored when the sound is streaming.
@ChrisCPI ChrisCPI force-pushed the avm1-loadsound-fixes branch from 442d3c3 to 94276fb Compare June 13, 2026 20:54
/// and can then only ever control externally loaded sounds.
/// Because of this, this can be used to check if this Sound is set
/// to play external sounds.
transform: Cell<Option<SoundTransform>>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of giving multiple meanings to this field, can't you reuse the state enum?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how I would do that exactly?

Earlier I had a separate is_external field in the native object, but it seemed redundant since it should always be true when the transform is Some, and always false when transform is None.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could have the transform in the state enum, e.g. a separate enum entry for when the sound is external. But that depends on how it plays with the loading/loaded states.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But doesn't it not make sense to have it be a part of the state enum? Because e.g. if a sound was still being loaded (in the Loading state), wouldn't that mean it can't access the transform being stored in a separate state, because it's not in that state at that time?

Comment thread core/src/avm1/globals/sound.rs
@Lord-McSweeney

Copy link
Copy Markdown
Collaborator

If both is_stopped and transform are only valid for external sounds, I would suggest adding a new struct called ExternalSound or ExternalSoundInfo that holds both pieces of information, and adding a single Option<ExternalSoundInfo> field to SoundData rather than the two new fields.

@Lord-McSweeney Lord-McSweeney added A-avm1 Area: AVM1 (ActionScript 1 & 2) newsworthy T-fix Type: Bug fix (in something that's supposed to work already) labels Jun 14, 2026
@ChrisCPI

Copy link
Copy Markdown
Collaborator Author

I can look into doing that if no one disagrees with the idea.

@ChrisCPI

Copy link
Copy Markdown
Collaborator Author

If both is_stopped and transform are only valid for external sounds, I would suggest adding a new struct called ExternalSound or ExternalSoundInfo that holds both pieces of information, and adding a single Option<ExternalSoundInfo> field to SoundData rather than the two new fields.

@kjarosh Thoughts on doing this idea? I think it could be helpful in using that to check whether the sound is external, rather than checking the transform.

@kjarosh

kjarosh commented Jun 14, 2026

Copy link
Copy Markdown
Member

It depends on how this plays with the loading state, if it's unrelated to the state then yes.

@ChrisCPI

ChrisCPI commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

Putting this in draft for now - Need to do some refactoring, as it looks like the idea of the existence of a playback queue for loaded sounds is incorrect, and so I will likely remove it (effectively undoing most of #21163).

What actually appears to be happening is that in FP, local sounds are loaded synchronously, so that when loadSound() is called, it doesn't continue executing until the sound has been loaded. This is why the start() calls appeared to be queued, when they actually are not.

An easy way to observe this is to see that the onLoad listener can be fired in-between two traces before/after a loadSound() call. e.g.:

var s = new Sound();
s.onLoad = function() { trace("onLoad"); }
trace("before");
s.loadSound("sound.mp3");
trace("after");

In the local projector, this outputs:

before
onLoad
after

But when loading a remote file, it becomes asynchronous, and start() calls have no effect before the sound is fully loaded.

@ChrisCPI ChrisCPI marked this pull request as draft June 16, 2026 01:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-avm1 Area: AVM1 (ActionScript 1 & 2) newsworthy T-fix Type: Bug fix (in something that's supposed to work already)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sound.setVolume should not affect the whole movie if loadSound has been called bug with sound.position issue on imported .mp3s: Conductor Volume game

3 participants