Skip to content

bugfix(milesaudiomanager): Fix premature 2d and 3d sound cancellations from MilesAudioManager::stopAudioEvent() - #3254

Open
xezon wants to merge 5 commits into
TheSuperHackers:mainfrom
xezon:xezon/fix-audio-bugs-4
Open

bugfix(milesaudiomanager): Fix premature 2d and 3d sound cancellations from MilesAudioManager::stopAudioEvent()#3254
xezon wants to merge 5 commits into
TheSuperHackers:mainfrom
xezon:xezon/fix-audio-bugs-4

Conversation

@xezon

@xezon xezon commented Sep 4, 2026

Copy link
Copy Markdown

Merge with Rebase

This change has 5 commits.

1: MilesAudioManager now uses consistent variable names for PlayingAudio pointers. Previously was a mix of audio, aud, release, playing, looping. For ease of understanding the code.

2: Simplifies function MilesAudioManager::notifyOfAudioCompletion(). For ease of understanding the code.

3: Simplifies function MilesAudioManager::findLowestPrioritySound(). For ease of understanding the code.

4: Fixes premature 2d and 3d sound cancellations from MilesAudioManager::stopAudioEvent(). Fixes #3232

5: No longer uses stopped audio in queries and updates to prevent 1 frame gaps when a stopping audio is about to be released.

AI Use

All code changes were applied by hand and Claude Opus was used for several review rounds.

TODO

  • Add pull id to commit titles

@xezon xezon added Audio Is audio related Bug Something is not working right, typically is user facing Major Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Refactor Edits the code with insignificant behavior changes, is never user facing ThisProject The issue was introduced by this project, or this task is specific to this project labels Sep 4, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Prevent premature 2D and 3D audio cancellation in MilesAudioManager

🐞 Bug fix 🕐 20-40 Minutes

Grey Divider

AI Description

• Lets stopped 2D and 3D effects finish their current sample before ending loops.
• Preserves graceful-stop intent across queued and re-requested audio playback.
• Excludes stopped audio from playback queries, limits, priorities, and runtime updates.
Diagram

graph TD
  A["Stop request"] --> B{"Handle location"}
  B -->|Queued effect| C["Flag audio request"] --> E["Current sample"] --> F{"Loop permitted"}
  B -->|Playing effect| D["Flag playback"] --> E
  F -->|Stop requested| G["Complete and release"]
  F -->|Allowed| H["Start next loop"] --> E
Loading
High-Level Assessment

The graceful-stop flag is the appropriate approach because it preserves the existing stop API while distinguishing normal sound-effect cancellation from immediate termination. It also carries intent through queued and re-requested playback without duplicating audio events; immediate cancellation remains available through the dedicated kill path.

Files changed (3) +245 / -132

Bug fix (1) +2 / -0
AudioRequest.hCarry graceful-stop intent on queued audio requests +2/-0

Carry graceful-stop intent on queued audio requests

• Adds a default-disabled stop-request flag to AudioRequest. This allows a stop received before playback starts to follow the event into the Miles playback lifecycle.

Core/GameEngine/Include/Common/AudioRequest.h

Other (2) +243 / -132
MilesAudioManager.hTrack stoppable playback and active lifecycle state +10/-3

Track stoppable playback and active lifecycle state

• Adds graceful-stop state to PlayingAudio and a helper that recognizes active or pending re-requested playback. It also standardizes PlayingAudio parameter naming in lifecycle method declarations.

Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h

MilesAudioManager.cppFinish active effects before stopping their loops +233/-129

Finish active effects before stopping their loops

• Changes regular stop handling so queued and active 2D/3D effects finish their current sample but cannot begin another loop, while streams retain immediate-stop behavior. It propagates stop intent through re-requests, excludes stopped entries from queries and updates, simplifies completion and priority selection logic, corrects handle assertions, and standardizes PlayingAudio variable names.

Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (1) 📜 Skill insights (0)

Grey Divider


Action required

1. release is undefined 📎 Requirement gap ≡ Correctness
Description
stopPlayingAudio() references the removed, undeclared parameter name release instead of
playing, causing MilesAudioManager.cpp to fail compilation. The broken build prevents the audio
regression fix from restoring playback in the required gameplay scenarios.
Code

Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp[1078]

+	release->m_rerequestOnNextUpdate = false;
Evidence
PR Compliance ID 1 requires audio playback to be restored in the affected gameplay scenarios, but
the function declares only PlayingAudio *playing and otherwise uses that parameter to release the
Miles handles. The newly added assignment on line 1078 dereferences the obsolete, undefined name
release, proving that the PR cannot compile and deliver the required fix.

Restore audio playback in affected gameplay scenarios
Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp[1068-1078]
Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h[69-72]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`MilesAudioManager::stopPlayingAudio()` references the removed and undefined parameter name `release` instead of `playing`, causing a compilation failure.

## Issue Context
The function parameter was renamed to `playing`, and every other access in the function uses that name. This compilation failure prevents the audio playback fix from being built and therefore prevents compliance with the required gameplay scenarios.

## Fix Focus Areas
- Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp[1068-1078]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Delayed retriggers get rejected 🐞 Bug ≡ Correctness
Description
isPlayingAlready() skips delayed loops in PS_Stopping even when m_rerequestOnNextUpdate means
they remain logically requested. When channels are unavailable, a same-event interrupting retrigger
can consequently return false here and be culled instead of queued for the channel about to be
released.
Code

Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp[R1922-1924]

+			if ((*it)->m_status != PS_Playing) {
+				continue;
+			}
Evidence
A delayed loop changes to PS_Stopping while setting m_rerequestOnNextUpdate, and the new helper
explicitly classifies that state as playing or requested. The changed strict status check omits it;
SoundManager uses a false isPlayingAlready() result to reject an interrupting event when no
channel is available.

Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp[2693-2713]
Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h[86-89]
Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp[1916-1937]
Core/GameEngine/Source/Common/Audio/GameSounds.cpp[248-266]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`isPlayingAlready()` treats delayed loop rerequests as inactive, causing valid interrupting retriggers to be rejected while their channel is being released.

## Issue Context
`startNextLoop()` represents a delayed loop as `PS_Stopping` plus `m_rerequestOnNextUpdate`. The new `isPlayingOrRequested()` helper already captures the required distinction between an ordinary stopped sound and one awaiting rerequest.

## Fix Focus Areas
- Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp[1916-1937]
- Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h[86-89]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Delayed voices permit overlap 🐞 Bug ≡ Correctness
Description
isObjectPlayingVoice() ignores a looping voice while it is in the delayed-rerequest state because
that state is PS_Stopping, despite still being logically active. A different non-interrupting
voice for the same object can therefore pass the voice constraint and overlap when the original loop
resumes.
Code

Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp[R1954-1956]

+		if ((*it)->m_status != PS_Playing) {
+			continue;
+		}
Evidence
startNextLoop() marks delayed loops as PS_Stopping and schedules a rerequest, while
isPlayingOrRequested() explicitly recognizes that combination. The new strict status filters
exclude such voices, and SoundManager::violatesVoice() relies on this query to reject another
non-interrupting voice for the same object.

Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp[2693-2713]
Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h[86-89]
Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp[1951-1969]
Core/GameEngine/Source/Common/Audio/GameSounds.cpp[200-213]
Core/GameEngine/Source/Common/Audio/GameSounds.cpp[275-279]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`isObjectPlayingVoice()` excludes delayed loop rerequests, allowing another voice for the same object to be admitted before the original voice resumes.

## Issue Context
Delayed loops are represented by `PS_Stopping` with `m_rerequestOnNextUpdate` set. Use the playing-or-requested state for this logical voice-presence query while continuing to exclude genuinely stopped audio.

## Fix Focus Areas
- Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp[1951-1969]
- Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h[86-89]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources

Grey Divider

Tip of the day
💡 Did you know, you can route each action level your way: inline, summary, both, or drop

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp Outdated
Comment thread Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp
Comment thread Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp
@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR corrects stop handling across the Miles audio request and playback lifecycle.

  • Adds stop-request state to queued requests and active sample playback.
  • Allows 2D and 3D samples to finish their current portion while preventing subsequent loops.
  • Cancels queued stream and music playback when stopped before starting.
  • Excludes stopped audio from playback queries, updates, limits, and priority decisions.
  • Simplifies completion and priority-selection logic and standardizes PlayingAudio variable names.
  • The two previously reported defects are no longer outstanding: the stale identifier was corrected, and queued streaming requests are now canceled.

Confidence Score: 5/5

The PR appears safe to merge; no outstanding correctness, security, or repository-rule violations were identified.

The current code consistently propagates stop intent for delayed 2D and 3D samples, prevents additional loops after a stop, and removes queued stream or music requests before playback. Both previous threads are resolved, and the changes made since the previous review introduce no actionable regression.

Important Files Changed

Filename Overview
Core/GameEngine/Include/Common/AudioRequest.h Adds initialized stop-request state so delayed sample requests can retain loop-cancellation intent.
Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h Adds active-playback stop state and a helper distinguishing playable or rerequested audio from stopped entries.
Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp Propagates stop intent through delayed playback, cancels queued streams, filters stopped entries, and simplifies completion and priority logic.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    A[Queued audio request] --> B{Stop requested?}
    B -->|No| C[Start playback]
    B -->|Queued sound effect| D[Carry stop flag into PlayingAudio]
    B -->|Queued stream or music| E[Remove queued request]
    C --> F[Current sample portion completes]
    D --> F
    F --> G{Stop flag set?}
    G -->|Yes| H[Do not start another loop]
    G -->|No| I[Continue or rerequest next loop]
    H --> J[Release during update]
    E --> J
Loading

Reviews (3): Last reviewed commit: "bugfix(milesaudiomanager): No longer use..." | Re-trigger Greptile

@OmarAglan

Copy link
Copy Markdown

@codex

Comment thread Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp Outdated
Comment thread Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp
@xezon
xezon force-pushed the xezon/fix-audio-bugs-4 branch from 05603ad to 2ec9a2d Compare September 4, 2026 14:39

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 05603adc80

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2132 to +2134
if (playing->m_status != PS_Playing) {
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve volume changes during delayed loop handoffs

When a looping sample has an inter-loop delay, startNextLoop() sets its status to PS_Stopping while m_rerequestOnNextUpdate remains true. If setAudioEventVolumeOverride() runs during that handoff, this new guard skips the event, so its shared DynamicAudioEventRTS retains the old volume; the subsequent rerequest does not reapply m_adjustedVolumes, and the loop resumes indefinitely at the stale volume. Treat isPlayingOrRequested() entries as adjustable (at least update their event state without calling Miles on a stopped sample).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, but it is also not looking into requested audio so it is consistent from that angle.

I will make a follow up fix and make a pass over all functions to also cover audio requests.

Comment on lines +1954 to +1956
if ((*it)->m_status != PS_Playing) {
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep delayed looping voices active for voice arbitration

For a looping ST_VOICE event with an inter-loop delay, startNextLoop() temporarily sets PS_Stopping together with m_rerequestOnNextUpdate, and processPlayingList() later queues the same event again. This guard reports that the object's voice is inactive during that handoff, so another voice triggered for the same object can pass violatesVoice() and overlap the original when it resumes. Use isPlayingOrRequested() here so only genuinely completed voices are excluded.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes it is suspicious but beyond the scope for now because this function is also not looking into the request list.

Comment thread Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp
@xezon
xezon force-pushed the xezon/fix-audio-bugs-4 branch from 2ec9a2d to cd11e0e Compare September 4, 2026 15:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Audio Is audio related Bug Something is not working right, typically is user facing Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker Refactor Edits the code with insignificant behavior changes, is never user facing ThisProject The issue was introduced by this project, or this task is specific to this project ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Audio fails to play or is prematurely cut off in various circumstances

2 participants