Skip to main content

Using ElysMusicEngine from C++

While the plugin is designed primarily for Blueprint usage, all functionality is fully accessible from C++. This guide shows how to integrate the music system into your C++ code.


Project Setup

1. Add Module Dependency

Edit your project's [YourProject].Build.cs:

PublicDependencyModuleNames.AddRange(new string[] 
{
"Core",
"CoreUObject",
"Engine",
"ElysMusicEngine" // Add this
});

2. Include Headers

#include "ERP_MusicSubsystem.h"
#include "ERP_MusicTypes.h"
#include "ElysMusicEngineBPLibrary.h" // Optional: For static helper functions

Accessing the Music Subsystem

The music system is a UGameInstanceSubsystem. Get it from any world context:

UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();

if (MusicSubsystem)
{
// Use the subsystem
}

Or use the helper function:

UERP_MusicSubsystem* MusicSubsystem = UElysMusicEngineBPLibrary::GetMusicSubsystem(GetWorld());

Basic Layer Control

Push a Music Layer

void AMyGameMode::StartCombatMusic()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();

if (MusicSubsystem)
{
USoundBase* CombatMusic = LoadObject<USoundBase>(nullptr, TEXT("/Game/Audio/Music/Combat_Theme"));

FERP_MusicLayer Layer;
Layer.LayerName = "Combat";
Layer.Sound = CombatMusic;
Layer.Priority = 50;
Layer.LayerMode = EERP_MusicLayerMode::Replace;
Layer.VolumeMultiplier = 1.0f;
Layer.FadeInTime = 2.0f;
Layer.FadeOutTime = 1.0f;
Layer.bLooping = true;
Layer.bPersistAcrossLevels = false;

MusicSubsystem->PushMusicLayer(Layer);
}
}

Pop a Music Layer

void AMyGameMode::StopCombatMusic()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();

if (MusicSubsystem)
{
MusicSubsystem->PopMusicLayer(FName("Combat"));
}
}

Pop All Layers

MusicSubsystem->PopAllMusicLayers();

Music Presets

Push a Preset

void AMyLevel::StartAmbientMusic()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();

if (MusicSubsystem)
{
FGameplayTag PresetTag = FGameplayTag::RequestGameplayTag(FName("Music.Preset.Forest"));
MusicSubsystem->PushMusicPreset(PresetTag);
}
}

Pop Preset

FGameplayTag PresetTag = FGameplayTag::RequestGameplayTag(FName("Music.Preset.Forest"));
MusicSubsystem->PopMusicPreset(PresetTag);

GameplayTag-Based Control

Push Layer by Tag

void StartBossMusic()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();

if (MusicSubsystem)
{
FGameplayTag BossTag = FGameplayTag::RequestGameplayTag(FName("Music.Combat.Boss"));
MusicSubsystem->PushMusicLayerByTag(BossTag);
}
}

Pop Layer by Tag

FGameplayTag BossTag = FGameplayTag::RequestGameplayTag(FName("Music.Combat.Boss"));
MusicSubsystem->PopMusicLayerByTag(BossTag);

Check if Layer is Active

bool bIsBossMusicPlaying = MusicSubsystem->IsLayerActiveByTag(BossTag);
if (bIsBossMusicPlaying)
{
UE_LOG(LogTemp, Warning, TEXT("Boss music is currently playing"));
}

Stingers (One-Shot Audio)

Play a Stinger (Immediate)

void PlayDeathStinger()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();

if (MusicSubsystem)
{
USoundBase* DeathSound = LoadObject<USoundBase>(nullptr, TEXT("/Game/Audio/Stingers/Death_Sting"));

MusicSubsystem->PlayStinger(
DeathSound,
true, // bDuckMusic
0.3f, // DuckVolume
0.2f, // DuckFadeTime
0.5f, // RestoreFadeTime
ERP_EQuantization::None // Play immediately
);
}
}

Play Quantized Stinger

Play a stinger synchronized to the current music beat:

void PlayQuantizedStinger()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();

if (MusicSubsystem)
{
USoundBase* StingerSound = LoadObject<USoundBase>(nullptr, TEXT("/Game/Audio/Stingers/PowerUp"));

MusicSubsystem->PlayStinger(
StingerSound,
true, // bDuckMusic
0.3f, // DuckVolume
0.2f, // DuckFadeTime
0.5f, // RestoreFadeTime
ERP_EQuantization::Beat // Wait for next beat
);
}
}

Play Stinger with Full Parameters

Pass all parameters directly for full control:

void PlayCustomStinger()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();

if (MusicSubsystem)
{
USoundBase* VictoryStinger = LoadObject<USoundBase>(nullptr, TEXT("/Game/Audio/Stingers/Victory"));

MusicSubsystem->PlayStinger(
VictoryStinger,
true, // bDuckMusic
0.2f, // DuckVolume
0.3f, // DuckFadeTime
1.0f, // RestoreFadeTime
ERP_EQuantization::Bar
);
}
}

Play Stinger at Location (3D Spatial Audio)

Play a stinger at a specific world location with spatial attenuation:

void PlayExplosionStinger(FVector ExplosionLocation)
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();

if (MusicSubsystem)
{
USoundBase* ExplosionStinger = LoadObject<USoundBase>(nullptr, TEXT("/Game/Audio/Stingers/Explosion_Musical"));

MusicSubsystem->PlayStingerAtLocation(
ExplosionStinger,
ExplosionLocation,
1.0f, // VolumeMultiplier
1.0f, // PitchMultiplier
500.0f, // AttenuationStartDistance (units)
true, // bDuckMusic
0.3f, // DuckVolume
0.2f, // DuckFadeTime
0.5f, // RestoreFadeTime
ERP_EQuantization::Beat // Quantize to next beat
);
}
}

Use Cases:

  • Combat impact sounds with musical stingers
  • Environmental events tied to 3D space
  • Explosions or magical effects that affect music
  • Localized audio accents in open worlds

Audio Ducking

Temporarily lower music volume for important audio (dialogue, SFX, narration):

void PlayDialogue(UAudioComponent* DialogueAudioComponent)
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();

if (MusicSubsystem)
{
MusicSubsystem->EnableAudioDucking(
DialogueAudioComponent,
0.3f, // Duck to 30% volume
2.0f, // Fade down over 2 seconds
1.5f // Fade back up over 1.5 seconds
);
}
}

Quartz Clock Synchronization

Set BPM

void SetMusicTempo(float NewBPM)
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();

if (MusicSubsystem)
{
MusicSubsystem->SetBPM(NewBPM);
}
}

Trigger Event on Beat

void SetupBeatListener()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();

if (MusicSubsystem)
{
// Subscribe to beat events
FOnQuartzMetronomeEventBP BeatDelegate;
BeatDelegate.BindUFunction(this, FName("OnMusicBeat"));

MusicSubsystem->SubscribeToBeatEvent(
EQuartzCommandQuantization::Beat,
BeatDelegate,
1.0f // Every beat
);
}
}

UFUNCTION()
void OnMusicBeat(FName ClockName, EQuartzCommandQuantization QuantizationType, int32 NumBars, int32 Beat, float BeatFraction)
{
// React to music beat
UE_LOG(LogTemp, Log, TEXT("Music beat: %d"), Beat);
}

Creating Custom Music State Interface

If you want an actor to communicate its music state to MusicVolumes:

1. Implement the Interface

// MyCharacter.h
#include "ERP_MusicStateInterface.h"

UCLASS()
class AMyCharacter : public ACharacter, public IERP_MusicStateInterface
{
GENERATED_BODY()

public:
// IERP_MusicStateInterface implementation
virtual FGameplayTag GetMusicStateTag_Implementation() const override;

private:
UPROPERTY(EditAnywhere, Category = "Music")
FGameplayTag CurrentMusicState;
};

2. Implement the Function

// MyCharacter.cpp
FGameplayTag AMyCharacter::GetMusicStateTag_Implementation() const
{
return CurrentMusicState;
}

3. Update State Dynamically

void AMyCharacter::EnterCombat()
{
CurrentMusicState = FGameplayTag::RequestGameplayTag(FName("Music.Combat"));
}

void AMyCharacter::ExitCombat()
{
CurrentMusicState = FGameplayTag::RequestGameplayTag(FName("Music.Exploration"));
}

Now MusicVolumes will automatically read this actor's state when it overlaps.


Querying System State

Get Active Layers

TArray<FName> ActiveLayers = MusicSubsystem->GetActiveMusicLayers();

for (const FName& LayerName : ActiveLayers)
{
UE_LOG(LogTemp, Log, TEXT("Active layer: %s"), *LayerName.ToString());
}

Get Active Layer Count

int32 LayerCount = MusicSubsystem->GetActiveMusicLayerCount();
UE_LOG(LogTemp, Log, TEXT("Number of active layers: %d"), LayerCount);

Check if Specific Layer is Active

bool bIsCombatActive = MusicSubsystem->IsLayerActive(FName("Combat"));

Volume Control

Set Master Volume

MusicSubsystem->SetMasterVolume(0.7f);  // 70% volume

Get Master Volume

float CurrentVolume = MusicSubsystem->GetMasterVolume();

Example: Complete Combat System Integration

// CombatManager.h
UCLASS()
class ACombatManager : public AActor
{
GENERATED_BODY()

public:
UFUNCTION()
void OnCombatStart();

UFUNCTION()
void OnBossAppears();

UFUNCTION()
void OnCombatEnd();

private:
UPROPERTY()
UERP_MusicSubsystem* MusicSubsystem;
};

// CombatManager.cpp
void ACombatManager::OnCombatStart()
{
MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();

if (MusicSubsystem)
{
// Start combat music
FGameplayTag CombatTag = FGameplayTag::RequestGameplayTag(FName("Music.Combat.Normal"));
MusicSubsystem->PushMusicLayerByTag(CombatTag);

// Set BPM for combat rhythm
MusicSubsystem->SetBPM(130.0f);
}
}

void ACombatManager::OnBossAppears()
{
if (MusicSubsystem)
{
// Play dramatic stinger
USoundBase* BossStinger = LoadObject<USoundBase>(nullptr, TEXT("/Game/Audio/Stingers/Boss_Intro"));
MusicSubsystem->PlayStinger(BossStinger, 1.2f, 1.0f);

// Transition to boss music on next bar
FGameplayTag BossTag = FGameplayTag::RequestGameplayTag(FName("Music.Combat.Boss"));
MusicSubsystem->PushMusicLayerByTag(BossTag);

// Increase tempo
MusicSubsystem->SetBPM(150.0f);
}
}

void ACombatManager::OnCombatEnd()
{
if (MusicSubsystem)
{
// Pop all combat layers
FGameplayTag CombatTag = FGameplayTag::RequestGameplayTag(FName("Music.Combat.Normal"));
FGameplayTag BossTag = FGameplayTag::RequestGameplayTag(FName("Music.Combat.Boss"));

MusicSubsystem->PopMusicLayerByTag(BossTag);
MusicSubsystem->PopMusicLayerByTag(CombatTag);

// Victory stinger
USoundBase* VictoryStinger = LoadObject<USoundBase>(nullptr, TEXT("/Game/Audio/Stingers/Victory"));
MusicSubsystem->PlayStinger(VictoryStinger, 1.0f, 1.0f);
}
}

Best Practices

  1. Cache Subsystem Reference: Get it once in BeginPlay() if using frequently
  2. Always Check Validity: if (MusicSubsystem) before every call
  3. Use GameplayTags for Scalability: Easier to manage than hardcoded layer names
  4. Leverage Presets: Define complex music states in Data Assets, apply them from code
  5. Use Quartz for Rhythm Games: Beat synchronization is critical for musical gameplay
  6. Test in PIE with Multiple Clients: Music subsystem is per-client (no replication)

Multiplayer Considerations

Music playback is client-side only by default. Each client manages its own music independently.

If you need synchronized music events across clients:

// Server RPC to trigger music on all clients
UFUNCTION(Server, Reliable)
void Server_TriggerBossMusic();

void AMyGameMode::Server_TriggerBossMusic_Implementation()
{
Multicast_PlayBossMusic();
}

UFUNCTION(NetMulticast, Reliable)
void Multicast_PlayBossMusic();

void AMyGameMode::Multicast_PlayBossMusic_Implementation()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();

if (MusicSubsystem)
{
FGameplayTag BossTag = FGameplayTag::RequestGameplayTag(FName("Music.Combat.Boss"));
MusicSubsystem->PushMusicLayerByTag(BossTag);
}
}

See Multiplayer Guide for detailed networking patterns.


Further Reading