Skip to content
New issue

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

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

Already on GitHub? # to your account

Paused Cues are Stopped and Paused when a second Cue is played #368

Closed
DanielJonesEB opened this issue Dec 9, 2024 · 12 comments · Fixed by #370
Closed

Paused Cues are Stopped and Paused when a second Cue is played #368

DanielJonesEB opened this issue Dec 9, 2024 · 12 comments · Fixed by #370
Assignees

Comments

@DanielJonesEB
Copy link

Further to the thread on Discord, it seems that playing a Cue after previously pausing another one makes the paused cue simultaneously enter both a paused and stopped state. While I can't reproduce the expected behaviour any more due to a lack of access to XNA, the game I wrote back in 2010 depended on this behaviour to prevent players going insane from listening to the first few bars of the overworld theme repeatedly 😁

Versions

  • FNA 24.12, d12e1b0
  • MacOS Sequoia 15.1.1
  • .NET 8.0.111

Observed behaviour

  • Consider two Cue instances, a and b
  • a.Play()
  • a.Pause()
  • b.Play()
  • a.IsPaused && a.IsStopped == true

Expected behaviour

  • a.Play()
  • a.Pause()
  • b.Play()
  • a.IsPaused && !a.IsStopped

...then for bonus points:

  • b.Pause()
  • a.Resume()
  • a starts playing from the point at which it was previously paused

Repro steps

I made a repro game that depends on Git LFS, a local checkout of FNA, and FNA libraries to be manually copied in that throws an exception if the (suspected) bug is present.

Other possible explanations

  • It's entirely possible that something changed in XACT between XNA 3.0 and XNA 4 that meant that I've forgotten to change a setting. I've been updating my game project from XNA 3.0 after having ignored it for 14 years, so there's lots of context I'll have missed. So it could be that the library is fine, but the problem is in my content.
  • Could be something to do with MacOS specifically?
@DanielJonesEB
Copy link
Author

Having looked at FACT.c I couldn't see anywhere that directly sets the stopped bit; I think it might be the case that when the notification is sent that the second cue starts playing, something consumes that event and maybe then thinks the first cue should be destroyed, and it's in the destroyed step that the stopped bit is set and the paused one is not cleared. I'm totally guessing though.

@flibitijibibo
Copy link
Member

That actually might make some sense - we do have notifications and FNA does try to handle them:

https://github.com/FNA-XNA/FNA/blob/master/src/Audio/AudioEngine.cs#L374-L422

But it'd be weird to see different objects colliding on these events... it's hard to say.

When I sit down to test this the first thing I'll probably do is just breakpoint anywhere that the stop flag is set and see what might have triggered that, after that I'll monitor the events to see if something underneath is requesting a stop the application didn't ask for (Cue instance limits maybe?), from there hopefully I'll have enough to figure out what's up.

@flibitijibibo flibitijibibo self-assigned this Dec 9, 2024
@flibitijibibo
Copy link
Member

flibitijibibo commented Dec 12, 2024

I think I found the issue - what happens on XNA when you call AudioEngine.Update() after calling b.Play()?

If you're able to change the XACT data, does adding a fade out time for the sound category fix it for FNA?

@DanielJonesEB
Copy link
Author

DanielJonesEB commented Dec 12, 2024

I'll need to try getting my XNA build setup working again; I think my VS licence is no longer valid. If you're able to point me towards any way of getting Ye Olde XNA Game Studio working on Windows 11, that'd be really helpful.

EDIT - oh, and thanks for looking into this!

@flibitijibibo
Copy link
Member

Meant to look into this sooner but other stuff happened, will dump what I was able to find in the meantime:

The issue is the fadeout function I wrote, which assumes a tightly-packed stop event when a sound is destroyed (which is what happens when a Cue is destroyed for category limits and has a 0ms fade time):

Part of me wonders if this could be fixed by simply removing the 0ms handler and letting it be in STOPPING for exactly one frame, though I seem to have run into this problem before...

https://github.com/FNA-XNA/FAudio/blob/master/src/FACT_internal.c#L1691

... so it's one of those areas where it's a 1-frame inaccuracy, but I don't know if the frame step is in AudioEngine or in the XACT thread (both of which have their own update routines... sigh).

flibitijibibo added a commit that referenced this issue Dec 18, 2024
There's a 1-frame timing issue where two sounds in the same category being played consecutively should still have both sounds _technically_ playing, even if the fade time is 0ms.

See #368 for details.
@DanielJonesEB
Copy link
Author

Thanks for your continued work, and sorry for not being able to keep up my end of the bargain my testing the XNA implementation. It's been a busy week with Christmas looming, and needing to deal with a tree that blew down! 😆

flibitijibibo added a commit that referenced this issue Jan 6, 2025
There's a 1-frame timing issue where two sounds in the same category being played consecutively should still have both sounds _technically_ playing, even if the fade time is 0ms.

See #368 for details.
@DanielJonesEB
Copy link
Author

DanielJonesEB commented Jan 7, 2025

Argh - this all got rather complicated! The new change introduces a possible new issue (Stopping staying true indefinitely), fixes the original issue of a Cue ending up both Stopped and Paused, but neither of these occur if instance limits aren't exceeded.

I managed to open up the XACT project, and limit instances for the relevant category was ticked, with a limit of 1. ChatGPT thinks that in XNA 3.0 paused instances did not count towards the instance limit and that this changed in XNA 4.0 so that paused instances do count towards the limit. It couldn't provide a source though, so I don't know whether it's hallucinating.

If ChatGPT is correct the FAudio was nearly showing the correct behaviour originally - a paused Cue should contribute towards the limit, so presumably should be stopped and/or destroyed when another Cue from the same category is played. It's arguably a bug (see later!) prior to your PR that it remained in both IsPaused and IsStopped state. Resuming shouldn't work, because the Cue should have disappeared off into the ether once a second Cue is played.

FAudio appears to behave correctly both before and after the PR if instance limits are not exceeded.

FAudio appears to leave the IsStopping bit set indefinitely in the PR if instance limits are exceeded.

I've tried to summarise my findings below. The states are the values after the second Cue is played, and whether resumption works was tested manually (aurally?).

FAudio Instance limits? IsStopped IsStopping IsPaused Resume works?
Old ❌ (silence)
Old
New
New ❌ (silence)

So, in summary:

  • The pre-PR behaviour when instance limits are not exceeded is correct.
  • The pre-PR behaviour of not-resuming when instance limits are exceeded is probably correct, but the Cue being both Paused and Stopped probably is not.
  • The post-PR behaviour when instance limits are not exceeded is correct.
  • The post-PR behaviour of not-resuming when instance limits are exceeded is probably correct, but the Cue remaining in an Stopping state indefinitely is probably not.

I hope that makes sense? One cup of tea was not enough to get through this 😁 I've pushed a commit to the repro-repo that might make investigating the indefinite IsStopping a little easier.

Epilogue

I feel a bit bad for taking up your time on what's basically an edge case - how FAudio should behave when a fool forgets that he's got instance limits set and chooses to exceed them. I've got my XNA 4.0 build setup working again now, so if it would be helpful for the project I could put some time into figuring out the reference implementation's behaviour in various scenarios?

@flibitijibibo
Copy link
Member

Found the issue with the post-PR behavior, the engine wasn't updating the Cue because it was Paused, which is normally fine but the Stopping state should override that. Updated binaries will be here:

https://github.com/FNA-XNA/FAudio/actions/runs/12653809981

@DanielJonesEB
Copy link
Author

Thanks! I just tried the latest build, and assuming I've not made any silly mistakes, this goes back to the original issue of the Cue ending up both Paused and Stopped.

@flibitijibibo
Copy link
Member

Was missing a bitflag in DestroySound, next batch of builds:

https://github.com/FNA-XNA/FAudio/actions/runs/12655539462

@DanielJonesEB
Copy link
Author

Splendid - with that latest build when the Cue is paused IsPaused and IsPlaying are true, and then as soon as the second Cue is played the first Cue transitions to IsStopped being true and IsPaused and IsPlaying being false. That sounds about right, so I think we can consider the issue resolved once that change is merged.

Please let me know if I can provide any value by exploring the XNA 4.0 behaviour for you.

@flibitijibibo
Copy link
Member

That was good timing actually, I dug up my VS2010 VM and can confirm that the behavior matches now!

flibitijibibo added a commit that referenced this issue Jan 7, 2025
There's a 1-frame timing issue where two sounds in the same category being played consecutively should still have both sounds _technically_ playing, even if the fade time is 0ms.

See #368 for details.
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants