Home / Documentation / Easy Music Manager - Advanced Guide (EMM)

Easy Music Manager - Advanced Guide

Date: 2026-07-20

Project: EEMHatchFoxStudios.uproject

Manager: `/Game/EasyMusicManager/Blueprints/BP_EasyMusicManager`

Trigger: `/Game/EasyMusicManager/Blueprints/BP_EMM_MusicTrigger`

Library: /Game/EasyMusicManager/Blueprints/Data/DT_EMMTracks

General architecture

The latest version centralizes all tracks in DT_EMMTracks.

ST_EMMSettings no longer contains a Tracks array. Its only responsibility is storing the manager's global configuration.

The system uses:

  • DT_EMMTracks as the centralized music source,
  • ST_EMMTrack to describe each track,
  • ST_EMMSettings for global configuration,
  • ST_TrackLibrary as the internal copy loaded when the level begins,
  • two Audio Components to support Crossfade,
  • timers calculated from the actual audio duration,
  • On Audio Finished events to continue or repeat playback.

It does not use Tick.

Each map that uses EMM must contain exactly one BP_EasyMusicManager. BP_EMM_MusicTrigger currently finds it with GetActorOfClass, so multiple manager instances in one map are not supported.

System initialization

Main flow:

BeginPlay
  -> LoadTrackLibrary
  -> Set AC_CurrentAudio
  -> Bind Audio OnAudioFinished
  -> Bind AudioSecondary OnAudioFinished
  -> Set I_TotalTracks
  -> PlayNextAmbientTrack, if B_PlayOnStart is enabled

Audio starts as AC_CurrentAudio. During transitions, Audio and AudioSecondary alternate responsibilities.

Audio components

Audio

The primary Audio Component when the level begins.

AudioSecondary

The second Audio Component, used to play the new song while the previous song is still fading out.

AC_CurrentAudio

Reference to the component currently considered active by the manager.

AC_PreviousAudio

Reference to the component that was active before a transition and must be stopped or faded out.

This separation prevents the system from reusing a single component during a Crossfade.

Main data

ST_EMMTrack

Every row in DT_EMMTracks uses this struct.

  • SB_Music: the SoundBase reference to play.
  • B_LoopThisTrack: determines whether the track starts again when it finishes.
  • B_PlayOnlyWhenRequested: excludes the track from ambient rotation.

ST_EMMSettings

  • B_PlayOnStart: starts music during BeginPlay.
  • B_PlayRandom: selects random or sequential rotation.
  • B_StartWithFirstTrack: forces the first ambient track only during initial playback.
  • Enum_TransitionMode: global transition mode.
  • F_FadeOutTime: global fade-out duration.
  • F_FadeInTime: global fade-in duration.
  • F_GapBetweenTracks: pause used by Fade Out Then Fade In.
  • F_ChangeBeforeTrackEnds: lead time for automatic transitions.
  • SC_Music: Sound Class reserved for music.

Important internal variables

ST_TrackLibrary

Internal array generated from every row in DT_EMMTracks. It should not be configured manually.

B_TracksPlayed

Boolean array parallel to ST_TrackLibrary. Each position indicates whether its corresponding index has already played during the current cycle.

I_UnplayedTrackIndexes

Temporary list of ambient track indexes that are still available for selection.

I_TrackIndexToPlay

Index chosen by the sequential or random selector.

ST_CurrentTrack

Copy of the currently selected track.

B_HasStartedMusic

Distinguishes initial playback from a transition between active songs.

B_HasAmbientTrackToPlay

Indicates whether the latest selection process found at least one valid ambient track.

B_UseRequestedFadeTimes

Temporarily enables the transition mode and timing values supplied by a trigger. After playback begins, it returns to false so the global configuration is used again.

1. Load and play ambient music

Flow:

BeginPlay
  -> LoadTrackLibrary
  -> PlayNextAmbientTrack
  -> PlayInitialAmbientTrack
  -> PlaySelectedTrack
  -> BeginCurrentTrackPlayback

LoadTrackLibrary

  1. Clears ST_TrackLibrary.
  2. Gets the row names from DT_EMMTracks.
  3. Reads each ST_EMMTrack.
  4. Adds every row to the internal library.

PlayNextAmbientTrack

  1. Updates I_TotalTracks.
  2. Resizes B_TracksPlayed to match the library.
  3. If at least one track exists, chooses between initial playback and continuation.

PlayInitialAmbientTrack

Reads B_PlayRandom and B_StartWithFirstTrack to determine the initial selection.

PlaySelectedTrack

  1. Finds the selected index.
  2. Copies the track into ST_CurrentTrack.
  3. Marks the index in B_TracksPlayed.
  4. Stores I_CurrentTrackIndex.
  5. Begins playback directly or calls TransitionToSelectedTrack.

2. Loop an individual track

Flow:

OnAudioFinished
  -> PlayNextAfterCurrentTrack

Important: Do not enable looping directly inside the Unreal SoundWave/audio asset. Easy Music Manager controls looping through its own track configuration, usually from DT_EMMTracks. If looping is enabled directly on the SoundWave, EMM may not correctly detect when the track ends. This can affect sequential and random playback, transitions, requested-only tracks, loop control, triggers, and demo testing.

Correct: Import the audio normally, leave the SoundWave Looping option disabled, and configure looping with B_LoopThisTrack in DT_EMMTracks.

Incorrect: Enabling looping directly inside each SoundWave/audio asset.

PlayNextAfterCurrentTrack checks ST_CurrentTrack.B_LoopThisTrack.

  • Enabled: calls Play again on AC_CurrentAudio.
  • Disabled: selects the next ambient candidate.

Looping does not change the index or mark another track as played.

3. Sequential rotation

Flow:

PlayNextAfterCurrentTrack
  -> SelectFirstUnplayedAmbientTrack
  -> BuildUnplayedAmbientTrackIndexes
  -> PlaySelectedTrack

BuildUnplayedAmbientTrackIndexes creates a list containing tracks that:

  • do not have B_PlayOnlyWhenRequested enabled,
  • have not played during the current cycle.

SelectFirstUnplayedAmbientTrack takes the first available index.

When no candidates remain, the played flags are reset and a new cycle begins.

4. Random rotation without repetitions

Flow:

PlayNextAfterCurrentTrack
  -> SelectRandomUnplayedTrack
  -> BuildUnplayedAmbientTrackIndexes
  -> PlaySelectedTrack

The selector chooses a random index from I_UnplayedTrackIndexes.

Tracks do not repeat until every ambient candidate has played. At the end of the cycle, the flags are cleared and the system selects randomly from all candidates again.

5. Random start or first-track start

Flow:

PlayNextAmbientTrack
  -> PlayInitialAmbientTrack
  • B_PlayRandom = false: selects the first ambient track.
  • B_PlayRandom = true and B_StartWithFirstTrack = false: selects randomly from the beginning.
  • B_PlayRandom = true and B_StartWithFirstTrack = true: starts with the first ambient track and then uses random selection.

B_StartWithFirstTrack does not affect later selections.

6. Requested-only tracks

Exclusion occurs inside:

BuildUnplayedAmbientTrackIndexes

Tracks with B_PlayOnlyWhenRequested:

  • are excluded from sequential selection,
  • are excluded from random selection,
  • do not count toward the end of an ambient cycle,
  • remain available in DT_EMMTracks,
  • can be sent to the manager through PlayRequestedTrack.

The Boolean acts as an ambient filter, not as an access restriction. PlayRequestedTrack can also receive a track that does not have this option enabled.

7. Fade system

Flow:

TransitionToSelectedTrack
  -> E_EMMTransitionMode selection
  -> BeginCurrentTrackPlayback
  -> ScheduleAutomaticTransition

TransitionToSelectedTrack

  1. Stores AC_CurrentAudio as AC_PreviousAudio.
  2. Switches AC_CurrentAudio to the other Audio Component.
  3. Assigns ST_CurrentTrack.SB_Music.
  4. Clears previous timers.
  5. Executes the selected transition mode.

Crossfade

FadeOutPreviousAudio
BeginCurrentTrackPlayback
  -> FadeInCurrentAudio

The previous song fades out while the new song fades in.

Fade Out Then Fade In

FadeOutPreviousAudio
Timer: FadeOutTime + GapBetweenTracks
  -> BeginCurrentTrackPlayback
  -> FadeInCurrentAudio

The new song waits for the fade out and the configured pause to finish.

Cut

Stop AC_PreviousAudio
BeginCurrentTrackPlayback
  -> Play AC_CurrentAudio

This mode does not use fade in or fade out.

Zero-time transition safety

PlayRequestedTrack normalizes the requested transition mode before storing it in Enum_RequestedTransitionMode. This prevents fade modes with no effective duration from calling FadeIn, FadeOut, or a zero-duration transition timer.

  • Crossfade fallback: when RequestedFadeOutTime <= 0 and RequestedFadeInTime <= 0, the effective transition mode becomes Cut.
  • Fade Out Then Fade In fallback: when RequestedFadeOutTime <= 0, RequestedFadeInTime <= 0, and RequestedGapBetweenTracks <= 0, the effective transition mode becomes Cut.
  • Partial timing configurations remain valid. If at least one relevant value is positive, EMM keeps the selected fade mode and follows its normal transition path.

The validation is implemented inside BP_EasyMusicManager.PlayRequestedTrack rather than only in BP_EMM_MusicTrigger. This protects requested-track calls made through that function and keeps Cut as the explicit, deterministic mode for instant entry transitions.

Recommendation: use Cut whenever an instant transition is intended. Use Crossfade or Fade Out Then Fade In only when at least one relevant timing value is greater than 0.

Important limitation: ResumeAmbientMusic and normal ambient transitions do not apply the same all-zero normalization. Select Cut explicitly for instant trigger exits or instant ambient changes.

8. Schedule an automatic transition

Flow:

BeginCurrentTrackPlayback
  -> ScheduleAutomaticTransition
  -> ScheduleTransitionWithLeadTime

ScheduleTransitionWithLeadTime obtains the duration of ST_CurrentTrack.SB_Music and calculates:

Timer duration = Track duration - Lead Time

Lead Time is obtained from the transition mode configuration and F_ChangeBeforeTrackEnds.

Before creating a new timer, the system clears the previous timer associated with PlayNextAfterCurrentTrack.

If B_LoopThisTrack is enabled, no automatic transition is scheduled.

On Audio Finished still handles the natural end of a track. The active Audio Component check prevents the previous component from advancing the playlist when its fade finishes.

9. On Audio Finished events

Two delegates are bound during BeginPlay:

Audio.OnAudioFinished
  -> OnAudioFinished_Event

AudioSecondary.OnAudioFinished
  -> OnAudioSecondaryFinished_Event

Each event compares its component against AC_CurrentAudio.

Only the active component can call PlayNextAfterCurrentTrack. This is necessary because a previous component may finish after a new song has already started.

10. BP_EMM_MusicTrigger

The trigger has two primary flows:

OnComponentBeginOverlap
  -> PlayRequestedTrack

OnComponentEndOverlap
  -> ResumeAmbientMusic

Both events verify that OtherActor is GetPlayerPawn(0).

The trigger contains:

  • an exposed EMM_Manager reference, retained for clarity but not used by the current trigger graph,
  • a Data Table Row Handle for the requested track,
  • fade-out time,
  • fade-in time,
  • gap time,
  • transition mode.

The trigger configuration is independent of ST_EMMSettings, but the same group of values is used both when entering and leaving.

On both overlap events, the current Blueprint locates BP_EasyMusicManager with GetActorOfClass rather than reading EMM_Manager.

Exactly one BP_EasyMusicManager must therefore exist in the map.

11. Request a specific track

Flow:

BeginOverlap
  -> Get Data Table Row from TrackToPlay
  -> PlayRequestedTrack
  -> TransitionToSelectedTrack

The trigger does not play audio by itself.

It reads the selected row from the Data Table specified by TrackToPlay and sends the following values to the manager:

  • ST_EMMTrack,
  • fade-out time,
  • fade-in time,
  • gap time,
  • transition enum.

PlayRequestedTrack:

  1. Enables B_UseRequestedFadeTimes.
  2. Stores the requested values.
  3. Copies the track into ST_CurrentTrack.
  4. Starts a transition or initial playback.

To keep special music playing until the player leaves the trigger, B_LoopThisTrack must be enabled.

12. Return to ambient music

Flow when ambient tracks are available:

EndOverlap
  -> ResumeAmbientMusic
  -> sequential or random selector
  -> PlaySelectedTrack
  -> TransitionToSelectedTrack

ResumeAmbientMusic:

  1. Stores the configuration supplied by the trigger.
  2. Clears playback and transition timers.
  3. Selects an ambient track according to B_PlayRandom.
  4. Respects B_TracksPlayed and B_PlayOnlyWhenRequested.
  5. Transitions to the new track.

It does not resume the interrupted ambient track from its previous position. It continues the rotation.

13. Leave the trigger when no ambient music is available

Flow:

ResumeAmbientMusic
  -> B_HasAmbientTrackToPlay = false
  -> StopCurrentMusic

StopCurrentMusic:

  1. Sets B_HasStartedMusic to false.
  2. Stores the active Audio Component as AC_PreviousAudio.
  3. Switches AC_CurrentAudio to the other component.
  4. Uses FadeOutPreviousAudio for Crossfade and Fade Out Then Fade In.
  5. Uses Stop for Cut.
  6. Disables B_UseRequestedFadeTimes.

Switching components before stopping the music is important. When the previous component finishes, its On Audio Finished events no longer recognize it as active and cannot accidentally restart a looping requested track.

14. Demo map: MapTest

The demo at /Game/EasyMusicManager/Map/MapTest contains one BP_EasyMusicManager and three non-overlapping BP_EMM_MusicTrigger instances. It demonstrates requested music and the transition back to ambient rotation on EndOverlap.

Cut example

TrackToPlay = Battle_2; Fade Out = 0; Fade In = 0; Gap = 0; Transition Mode = Cut.

Crossfade example

TrackToPlay = Battle_1; Fade Out = 5; Fade In = 5; Gap = 0; Transition Mode = Crossfade.

Fade Out Then Fade In example

TrackToPlay = Battle_2; Fade Out = 5; Fade In = 5; Gap = 2; Transition Mode = Fade Out Then Fade In.

The three Box Components use separated volumes. This is intentional because EMM has no trigger-priority stack; overlapping volumes can issue competing enter and exit requests.

Sound Classes

The intended hierarchy is:

SC_Master
  -> SC_Music

SC_Master controls overall audio, while SC_Music controls music only.

Music assets should use SC_Music. The SC_Music field inside ST_EMMSettings is an informational reference that communicates the expected class; it does not automatically route or reassign audio assets. Assign SC_Music directly on each SoundWave, SoundCue, or MetaSound Source used by EMM.

Safety behavior

  • Previous timers are cleared before a new transition is scheduled.
  • Requested-only tracks are filtered before indexes are selected.
  • The played-track list is resized to match the number of loaded rows.
  • The cycle resets after every ambient track has played.
  • Audio events verify the active component.
  • Requested music stops when no ambient tracks are available.
  • Tick is disabled.

Current limitations

Local player

The trigger uses GetPlayerPawn(0). It does not implement player selection, multiplayer behavior, or replication.

Overlapping triggers

There is no priority stack. If two triggers overlap, leaving one may request ambient music even while the player remains inside the other. Keep trigger volumes separated unless the project implements custom priority or arbitration logic.

Data Table order

Sequential rotation depends on the order in which Get Data Table Row Names returns the rows.

If a project requires a guaranteed order independent of the Data Table, an explicit order field or a playlist of Row Handles should be added.

State between maps

The library and played flags are rebuilt during BeginPlay. The manager does not preserve the song, index, or playback position between levels.

Trigger validation

The current trigger expects TrackToPlay to be valid and locates the manager with GetActorOfClass. EMM_Manager remains exposed but is not used by the current graph. Exactly one BP_EasyMusicManager must exist in the map; a future version could add validation messages for incomplete configurations.

Audio duration

Automatic transitions require a valid duration. An audio asset configured to loop internally may never finish and may prevent On Audio Finished from firing.

Playlist looping should be controlled through B_LoopThisTrack.

Complete flow summary

BeginPlay
  -> load DT_EMMTracks
  -> prepare playback history
  -> select an ambient track
  -> play
  -> schedule a timer or wait for On Audio Finished
  -> repeat or select the next track

Trigger BeginOverlap
  -> read a row from DT_EMMTracks
  -> request the special track
  -> apply the trigger transition

Trigger EndOverlap
  -> find the next ambient track
  -> transition back to ambient music
  -> or stop the music if no candidate exists