UE5 Blueprint · No Quartz Required

BeatSyncToolkit

Beat/Bar-Synced Adaptive Music Conductor for Unreal Engine 5. Drive your game's music reactively from Blueprint — state transitions, layered stems, zones, and quantized timing. All without a custom GameInstance.

Version v1.1
Engine UE 5.5.x
Type Blueprint-First
Date 2026-02-01

# Before You Start — Prerequisites BEGINNER

This documentation assumes you can navigate Unreal Editor comfortably and make basic Blueprint connections. If you’re brand new, don’t worry — you can still follow along, but these basics will help a lot.

What you should know (minimum)
  • Blueprint basics — variables, functions, events, calling nodes.
  • Actor placement — dragging Actors into a Level and editing their Details panel.
  • Basic audio concepts — what BPM is, and how to import WAV files into UE.

Data Assets (quick explanation)

A Data Asset is an Unreal asset used to store structured configuration data. BeatSyncToolkit uses Data Assets for Music Profiles (playlists, BPM, stems, defaults, etc.). You edit the profile once, then every placed Conductor can reference it.

Tip

If you don’t know what a Data Asset is yet, you can still use the default profile for testing — see the Quick Start.

Engine Compatibility

BeatSyncToolkit is built and tested on UE 5.5.x. It uses standard Blueprint features (AudioComponents, Timers, Overlap Events, Data Assets) with no engine modifications, so it is expected to work on UE 5.4 and UE 5.3 as well. However, only 5.5.x is officially tested and supported. If you encounter issues on older engine versions, please report them.

# 1. What BeatSyncToolkit Does

BeatSyncToolkit lets you drive adaptive music in Unreal Engine using gameplay-friendly Blueprint nodes. It provides beat/bar timing, state-based music selection with per-state playlists, optional stem layers, and a zone system that can override the global music state and add extra layers — all without requiring a custom GameInstance.

Key Features

  • Beat/bar clock (Timer-based) with an OnBar event for quantized changes — no Quartz dependency.
  • State system (E_BST_MusicState): switch music by gameplay state, optionally quantized to the next bar. New states can be added by extending the enum + defining a matching StateConfig in your profile — see Adding a New State.
  • Per-state playlist: each state can contain multiple tracks; auto-advance to the next track when the current ends.
  • Two-base crossfade playback (Base A / Base B) for smooth transitions between tracks and states.
  • Optional stem layers (E_BST_MusicLayer: Drums, Bass, Harmony, Melody, FX). Layers are always started muted and faded in/out — no restart.
  • Event-based layers: add/remove/clear layers via global Blueprint nodes (BFL_BST) with optional next-bar quantization.
  • Multi-layer event calls: add any number of layers in one node (array input), with optional next-bar quantization.
  • Start/Stop Music: optionally start silent and trigger BST_StartMusic / BST_StopMusic from gameplay or demo interactions.
  • Zone system (BP_BST_MusicZone): priority-based TopZone selection, zone state override, extra layers, and optional per-zone profile override.
  • Zone + event layer arbitration: effective layers are the OR combination of zone layers and event layers.
  • Quantized zone layer updates: zone layer changes can be applied immediately or on the next bar.
  • Playlist Track Lock: lock the currently playing track so it restarts instead of advancing — event-based and optional zone-based.
  • Low-friction UX: all gameplay calls go through a Blueprint Function Library. No manual Conductor references required.

What You Need to Place in Your Level

Required Actors

BP_BST_Conductor — exactly one Conductor Actor in the level. This is the only mandatory placement. Optionally add one or more BP_BST_MusicZone actors for zone-based overrides and layer additions.

1.1 Common Use Cases

Below are simple “copy-the-logic” examples of the most common API calls. For more detailed scenarios (zones, track lock, quantization), see Section 12: Practical Examples.

Example 1 · Switch to Combat Music
// In your Combat Manager / AI Director / GameMode:
OnEnemySpawned (or OnCombatStart)
  BST_SetMusicState(WorldContextObject=Self, NewState=Combat)
Example 2 · Add Drums Layer in a Boss Arena (Quantized)
// When player enters boss arena trigger:
OnBossArenaEntered
  BST_AddLayer(WorldContextObject=Self, Layer=Drums, QuantizeToNextBar=true)
Example 3 · Lock a Track During a Boss Phase
// Example: keep the current intense track looping during phase 2
OnBossHealthBelow50Percent
  BST_LockCurrentTrack(WorldContextObject=Self)

OnBossDefeated
  BST_UnlockCurrentTrack(WorldContextObject=Self)
Reminder

If you forget to connect Self (or any valid object) to WorldContextObject, the BFL nodes may fail to find the Conductor and do nothing.

Critical: Always Connect WorldContextObject

When calling any BFL_BST node (BST_SetMusicState, BST_AddLayer(s), BST_LockCurrentTrack, etc.), you must connect a valid object (usually Self) to WorldContextObject. If it’s left empty, the node may silently fail because it cannot locate the current World to find the placed Conductor.

# 2. Quick Start BEGINNER

Get BeatSyncToolkit up and running in about 10 minutes.

Absolute Beginner Quick Test (2 minutes)
  1. In the Content Browser, open Plugins → BeatSyncToolkit → Blueprints.
  2. Drag BP_BST_Conductor into your Level.
  3. Select the Conductor and set:
    • ActiveProfile = DA_BST_MusicProfile_Default
    • AutoStartState = Explore
    • bStartOnBeginPlay = true
  4. Press Play — music should start.

If it doesn’t play, jump to Troubleshooting.

1

Add Content

Add or import the BeatSyncToolkit content folder into your project — keep the folder structure intact. Open or create a test level. Drag BP_BST_Conductor into the level.

2

Assign a Profile & Auto-Start

Select BP_BST_Conductor in the level. If you want music to start automatically, enable bStartOnBeginPlay. For demos, disable it and call BST_StartMusic when the player is ready.

In Details → BST → Profile, set ActiveProfile to DA_BST_MusicProfile_Default and set AutoStartState (e.g. Explore). Press Play — the Conductor starts the chosen state, initializes the beat clock from the track's BPM/BPB, and begins playback.

3

Drive Music from Gameplay (No References)

From any Blueprint, call the global nodes in BFL_BST. The WorldContextObject input is all that's needed — no manual Conductor references.

BFL_BST · Core Nodes
BST_StartMusic(WorldContextObject)
BST_StopMusic(WorldContextObject)
BST_SetMusicState(WorldContextObject, NewState)
BST_AddLayer(WorldContextObject, Layer, QuantizeToNextBar)
BST_AddLayers(WorldContextObject, Layers[], QuantizeToNextBar)
BST_RemoveLayer(WorldContextObject, Layer, QuantizeToNextBar)
BST_ClearLayers(WorldContextObject, QuantizeToNextBar)
BST_LockCurrentTrack(WorldContextObject)
BST_UnlockCurrentTrack(WorldContextObject)
4

Add Zones (Optional)

Drag BP_BST_MusicZone into the level and scale its Box Collision to cover an area. Set ZonePriority (higher wins when zones overlap). Optionally set ZoneState to override the global state, and fill ExtraLayers to add layers while inside. Press Play and walk into the zone to see changes in action.

Troubleshooting — Common Problems & Solutions

Most issues come from a missing Conductor in the Level, an unassigned profile, or a context object that can’t find the World.

Problem 1: Music doesn’t play
  • Is BP_BST_Conductor placed in the Level? (check the Outliner)
  • Is ActiveProfile assigned?
  • Is AutoStartState NOT NONE?
  • Open the console with ` and look for errors.
Problem 2: BST_SetMusicState “does nothing”
  • Is WorldContextObject connected? Usually you should pass Self.
  • Is a Conductor placed in the Level (remember: BeatSyncToolkit does not auto-spawn it)?
  • Enable bDebugPrint on the Conductor to see why a request is ignored.
Problem 3: Layers (stems) are not audible
  • Does the current TrackEntry actually have a Sound assigned for that Layer?
  • Are you calling BST_AddLayer / BST_AddLayers with the correct layer enum?
  • Check layer volumes in the Track Entry (and make sure your stems are exported correctly).
Problem 4: Zones don’t trigger
  • Is the Zone collision set to Generate Overlap Events = true?
  • Does the collision channel overlap your player/pawn?
  • If you have nested zones, is ZonePriority correct (highest wins)?

# 3. Included Assets & Files

Folder Structure

Keep the content folder structure intact so all internal references (profiles, actors, enums, structs) remain valid. The system is Blueprint-first and does not require a plugin build step.

TypeNamePurpose
Blueprint ActorBP_BST_ConductorMain runtime brain. Holds the beat clock, current state/track, playback components, layers, zones, and all arbitration logic.
Blueprint ActorBP_BST_MusicZoneOverlap-triggered zone: override state, add extra layers, override profile, and optionally influence zone quantization / track lock.
Function LibraryBFL_BSTGlobal gameplay-facing API. Finds the Conductor and forwards state / layer / lock requests.
Data AssetDA_BST_MusicProfile_DefaultExample profile containing StateConfigs and Track playlists.
Blueprint (Base)PDA_BST_MusicProfile_BaseBase class type used by the Conductor for profile assets.
EnumE_BST_MusicStateMusic states (e.g. Explore, Combat). Extend by adding new entries — see Adding a New State.
EnumE_BST_MusicLayerFixed layer set: Drums, Bass, Harmony, Melody, FX.
StructF_BST_TrackEntryOne track: BaseSound + BPM / BPB + optional layer defs for each layer.
StructF_BST_StateConfigOne state configuration: the state enum + its playlist of TrackEntry items.
StructF_BST_LayerDefPer-layer definition inside TrackEntry: an optional Sound asset and a target volume.
InternalPending Layer QueuesQuantized layer ops are implemented via pending arrays / flags processed on BST_OnBar. No asset — runtime only.

# 4. Core Concepts BEGINNER

4.1 Conductor Actor

BP_BST_Conductor is the single authority that decides what music is playing. You place it manually in the level — it does not auto-spawn. Gameplay code never stores references to it. Instead, calls go through BFL_BST, which finds the Conductor in the current world and forwards the request.

4.2 Profiles → States → Playlists → Tracks

A Music Profile is a data asset that maps each Music State to a playlist of TrackEntry items.

Data Hierarchy
Profile  (DA_BST_MusicProfile)
  └── StateConfig  (F_BST_StateConfig)
        ├── State:  E_BST_MusicState   ← e.g. Explore, Combat
        └── Playlist:  TrackEntry[]
              └── TrackEntry  (F_BST_TrackEntry)
                    ├── BaseSound      ← main audio (required)
                    ├── BPM            ← beats per minute
                    ├── BeatsPerBar    ← time signature
                    └── Layer Stems (Drums/Bass/Harmony/Melody/FX)    ← optional stems

4.3 Layers (Stems)

Layers are optional stem audio components. If a TrackEntry provides a sound for a layer, the Conductor starts it immediately when the track begins — but at muted standby volume. Add/Remove operations only adjust volume (fade up/down) and do not restart audio.

The fixed layer set is Drums, Bass, Harmony, Melody, FX. If a track has no stem for a layer, that layer is safely ignored.

Gameplay Layer Control (Event-Based)

Use BFL_BST layer nodes to add/remove/clear layers from any Blueprint. Each call includes a QuantizeToNextBar boolean to apply the change on the next bar, or fade right away.

Add Multiple Layers (Single Node)

Use BST_AddLayers with an array input to add 2, 3, or all layers at once. Supply the array using Make Array (E_BST_MusicLayer). Duplicates are ignored.

Tracks can optionally auto-enable certain layers when the track starts (for example: Drums ON by default). This is an advanced feature covered in Section 5.7.

4.4 Beat/Bar Clock (No Quartz)

The Conductor maintains a beat clock using timers. Each beat advances CurrentBeat, and each bar fires BST_OnBar. Quantized operations — state changes and optional layer changes — are applied precisely on BST_OnBar.

No Quartz Dependency

BeatSyncToolkit uses its own timer-based clock instead of Unreal's Quartz system. Zero additional setup — just set BPM and BeatsPerBar in your TrackEntry and the clock handles the rest.

4.5 Zones

BP_BST_MusicZone is an overlap-based, location-driven control layer. Think of it as “automatic music rules based on where the player is”. The Conductor keeps a list of zones the player is currently inside, selects a single TopZone, then applies that zone’s behavior (state override, extra layers, optional profile override, optional track lock, and optional bar-quantized changes).

What Zones Do (At a Glance)

  • Location-based state control: entering an area can automatically switch the music state (e.g. Village → Calm, Forest → Explore).
  • Extra layers on top: a zone can add layers (Drums/Bass/etc.) while you are inside, without changing the state.
  • Optional profile override: a zone can temporarily switch to a different music profile (useful for “Dungeon Music Pack” vs “Overworld Pack”).
  • Nested zones are safe: when multiple zones overlap, the highest priority wins (TopZone).

Zone Priority (Nested Zones)

Each zone has a ZonePriority. If you are inside multiple zones at the same time, the Conductor picks the zone with the highest priority as the active TopZone. This makes nested or overlapping zones predictable: “the most important room wins.”

Zone State Override vs Zone Extra Layers

A zone can influence music in two independent ways:

  • ZoneState (Override): forces a specific state while inside. Example: “BossArena → Combat”.
  • ExtraLayers[] (Additive): adds layers while inside, without forcing a new state. Example: “WindyBridge → FX layer ON”.

If ZoneState is set to NONE, the zone does not force a state — it can still add extra layers (and optionally override the profile only when it is also forcing a state, depending on your Conductor rules).

Simple Example

Village Zone (Priority 0): ZoneState = Calm, ExtraLayers = []
Forest Zone (Priority 10): ZoneState = Explore, ExtraLayers = [Drums]
When the player stands in the overlap region, Forest wins (higher priority) → Explore plays, and Drums layer is enabled while inside.

Why Zones Feel “Smart”

Zones do not fight your gameplay logic. They simply become the current TopZone (by priority) and contribute their rules (state override and/or extra layers) into the same arbitration pipeline as event layers and default layers.

1

Place BP_BST_MusicZone in your level and scale it to cover your area (room, corridor, arena, etc.).

2

Collision / Overlap: make sure the zone’s collision component generates overlaps with the player. Typical checklist:

  • Generate Overlap Events = true
  • Collision set to Overlap the channel your player uses (often Pawn)
  • If using custom collision channels, ensure your player capsule and the zone agree (Overlap ↔ Overlap).
3

Set ZonePriority. Higher wins when zones overlap. A common pattern:

  • Large “region” zones: priority 0–10
  • Small “room / interior / special” zones inside them: priority 50–100+
4

Choose how the zone affects music:

  • State override: set ZoneState to a real E_BST_MusicState value (e.g., Calm / Combat).
  • Layer-only zone: set ZoneState = NONE and use ExtraLayers only.
5

Fill ExtraLayers with any layers you want active while inside the zone (Drums/Bass/Harmony/Melody/FX). These stack on top of the current state.

6

Optional extras:

  • OverrideProfile — used only when the zone is also forcing a state (ZoneStateNONE). This avoids unexpected restarts when the zone is layer-only.
  • bZoneTrackLock — requests track lock while inside (event lock still has priority).
  • QuantizeZoneLayerChangesToBar — if enabled, zone layer sets can snap to the next bar for musical timing.
  • AffectsOnlyLocalPlayer — recommended for multiplayer so zones behave per-client.

How to Set Up a Zone (Step-by-Step)

For deeper rules (profile override edge cases, zone quantization behavior, and zone/event arbitration), see Section 8 and Section 9.

4.6 Intensity System ADVANCED

Intensity is an optional advanced feature: a continuous 0..1 value that can automatically enable “intensity-only” layers based on rules. If you’re new, you can skip Intensity and still get great results with States, Zones, and Event Layers.

Full setup & tuning (advanced)

For the complete workflow (rules, hysteresis, smoothing/ramp, and volume curves), see Section 14 — Intensity System (Advanced).

  • You set intensity from gameplay via BST_SetIntensity (BFL node).
  • Intensity only affects layers that are enabled by intensity rules — it does not override Default / Zone / Event enabled layers.
  • If you don’t call BST_SetIntensity, Intensity stays at its default and does nothing.

# 5. Creating & Editing Music Profiles

5.1 Create a Custom Profile

1

In the Content Browser, duplicate DA_BST_MusicProfile_Default.

2

Rename it — e.g. DA_BST_MusicProfile_MyGame.

3

Open your new profile. For each state you want to support, add or edit a StateConfig entry.

5.2 Fill a StateConfig (Playlist)

In each StateConfig: set the State (E_BST_MusicState), add one or more TrackEntry items to the Tracks array, and provide accurate BPM and BeatsPerBar for each track.

5.3 Fill a TrackEntry

Each TrackEntry is the complete definition for one piece of music in a playlist.

FieldTypeMeaning
BaseSoundSoundWave / SoundCueMain full mix or main stem. Always required.
BPMFloatBeats per minute. Drives the beat/bar clock and all quantization.
BeatsPerBarIntegerTime signature in beats per bar (commonly 4).
Drums / Bass / Harmony / Melody / FXF_BST_LayerDefOptional stems for each layer. Leave empty if you don't have that stem.
Single WAV Use Case

If you only have a single WAV with no stems, set BaseSound only and leave all layer sounds empty. You can still use state switching and playlists. Layer add/remove calls are safely ignored when the current track has no matching stem.

5.3.1 F_BST_LayerDef Fields (Per-Layer Configuration)

Each layer slot (Drums, Bass, Harmony, Melody, FX) inside a TrackEntry uses the F_BST_LayerDef struct. Here are all configurable fields:

FieldTypeDefaultMeaning
SoundSoundWave / SoundCueNoneThe audio asset for this stem. If empty, the layer is ignored for this track.
TargetVolumeFloat1.0Volume when the layer is enabled (0.0 to 1.0). Use this to balance stems against each other.
FadeInDurationFloat0.5Seconds to fade in when the layer is enabled. Shorter = snappier, longer = smoother blend.
FadeOutDurationFloat0.5Seconds to fade out when the layer is disabled. Independent of FadeInDuration.
StandbyVolumeFloat0.0Volume when the layer is muted but still playing in the background. Typically 0.0 (silent).
Layer Fades Are Per-Track, Per-Layer

Each layer in each track can have its own fade durations. For example, Drums can fade in over 0.2s (punchy) while Harmony fades in over 1.5s (smooth). This gives you precise control over how the mix evolves, without any Blueprint logic.

5.3.2 Audio Preparation Guide

Before importing your music into Unreal, follow these guidelines to ensure BeatSyncToolkit works reliably.

Supported Audio Formats

FormatRecommendedNotes
.wavYesBest quality, no compression artifacts. Preferred for all stems and base tracks.
.oggAcceptableSmaller file size. Good for long ambient/exploration tracks where file size matters.

Stem Preparation Rules

Critical: All stems for the same track must be identical in length

BeatSyncToolkit starts all stems simultaneously and fades them in/out. If stems have different lengths, they will drift out of sync. Always export stems from the same session with the same start and end points.

  • Same BPM: All stems for one track must share the exact same BPM. The Conductor uses a single beat clock per track.
  • Same length: Export all stems with identical start/end points. Pad shorter stems with silence if needed.
  • Same sample rate: Use a consistent sample rate across all stems (44100 Hz or 48000 Hz recommended).
  • Mono vs Stereo: Both work. Use stereo for full-mix base tracks and wide stems (pads, ambience). Mono is fine for punchy elements (kick, snare) if you want to save memory.
  • No built-in fades: Do not add fade-in/fade-out to your stem files. BeatSyncToolkit handles all fading at runtime via LayerDef settings.
  • Loop-ready: If you want seamless looping, make sure your audio starts and ends cleanly on a beat boundary. Avoid silence at the start or end of the file.
DAW Export Tip

In your DAW (Ableton, FL Studio, Logic, Reaper, etc.), solo each stem group and "Export All" or "Bounce in Place" with the same range selection. This guarantees all files have identical length and timing.

5.4 Assign Profile to the Conductor

Select BP_BST_Conductor in your level. Set ActiveProfile to your custom profile asset. Set AutoStartState to the initial state. Press Play.

5.5 Adding a New State

You can extend BeatSyncToolkit with your own states. The system uses a loop-based lookup — no hardcoded switch-case — so adding a new enum entry won't break any existing Blueprint graphs or pins.

Enum alone is not enough

Adding a new entry to E_BST_MusicState makes it appear in all BFL nodes immediately — but no music will play for that state until you also define a StateConfig with at least one track in your profile. The system won't crash — it simply won't switch, or will stay on the current track.

1

Add the new entry to the enum

Open E_BST_MusicState and add your new state (e.g. Stealth). Save and compile. It will now appear as a selectable option in all BFL nodes across your project — automatically.

2

Open your Music Profile

Open the profile Data Asset you're using (e.g. DA_BST_MusicProfile_MyGame).

3

Add a new StateConfig entry

In the StateConfigs array, add a new row. Set its State to your new enum value (e.g. Stealth).

4

Add at least one TrackEntry

Inside that StateConfig, add a TrackEntry to the Playlist array. At minimum, set BaseSound, BPM, and BeatsPerBar. Optionally add stem sounds (Drums/Bass/Harmony/Melody/FX) if your audio has them.

5

Save and test

Save the profile. Press Play and call BST_SetMusicState(Stealth) — the Conductor will find the new StateConfig via lookup and switch to it.

Files You Will Edit (State Extension)

EN
E_BST_MusicState
Add the new enum entry
DA
DA_BST_MusicProfile_*
Add a StateConfig row + playlist tracks
ST
F_BST_StateConfig
State + Playlist array
ST
F_BST_TrackEntry
BaseSound + BPM/BPB + optional stems
BP
BP_BST_MusicZone (optional)
Use the new state in ZoneState overrides
Where Your New State Will Show Up Automatically
  • All BFL_BST state nodes (dropdowns update automatically after compile).
  • BP_BST_MusicZone.ZoneState dropdown.
  • Conductor settings such as AutoStartState (if you use it).

If your new state is selectable but nothing changes, 99% of the time the profile is missing a matching StateConfig entry (or its playlist has no tracks).

Why this works without breaking things

The Conductor resolves states by searching through the StateConfig array at runtime — not via a switch-case baked into the Blueprint graph. This means adding a new E_BST_MusicState entry won't cause any pin breaks or compilation errors anywhere. The only requirement is a matching StateConfig in your active profile.

5.6 Adding a Custom Layer

The five built-in layers (Drums, Bass, Harmony, Melody, FX) cover most games. If you truly need a sixth stem layer — for example Percussion — you can extend BeatSyncToolkit.

Optional / Advanced

This is an advanced extension. You are adding a new audio channel, a new stem slot in track data, and wiring it into the Conductor’s layer pipeline (prepare → gating → enable/arbitration → fade routing). If you only want more variety, prefer States, Zones, and State Requests instead of inventing new layers.

What stays the same

  • BFL nodes automatically show the new enum entry in dropdowns (no BFL edits needed).
  • Zone ExtraLayers and Event layers already use E_BST_MusicLayer arrays — your new layer can be requested immediately.
  • The core rule is unchanged: stems start playing in standby (muted), and layer toggles only change volume (no restart).

Step-by-step (recommended order)

1

Extend the Layer Enum

Open E_BST_MusicLayer and add your new entry (example: Percussion). Save + compile. It will now appear in all layer dropdowns automatically.

2

Add a Stem Slot to Track Data

Open F_BST_TrackEntry and add a new field for your layer using the same type as other stems (recommended: F_BST_LayerDef).

Example: add a field named Percussion (type F_BST_LayerDef), so each TrackEntry can optionally provide a stem Sound + Target Volume.

3

Add a Dedicated AudioComponent

In BP_BST_Conductor, add a new AudioComponent for the layer (example: AC_Percussion). Configure it the same way as existing layer components (AC_Drums, AC_Bass, etc.).

This component is the playback channel for the new stem.

4

Prepare the Stem on Track Start

Open BST_EnsureLayerComponentsPlayingMuted and add a block for your new layer:

  • If the current track has a valid stem Sound for the layer → SetSound (if changed), Play, then set volume to StandbyVolume (very low).
  • If the current track has no stem for the layer → fade out to standby and only stop after a short delay (use BST_FadeOutAndStopLayerComponent pattern).
Why this matters

BeatSyncToolkit’s core promise is: stems do not restart when you toggle layers — we only fade volumes. Preparing all available stems in standby on track start is what makes that possible.

5

Gate by Stem Availability

Open BST_CurrentTrackHasLayerSound and add a new case for your layer that returns whether the current track actually contains a stem Sound for it.

This prevents the system from ever trying to enable a layer that does not exist in the current track.

6

Include the Layer in Enable/Arbitration

Update the layer enable logic so your new layer follows the same rules as built-ins:

Enabled = (Default AND NOT Suppressed) OR Zone OR Event, gated by BST_CurrentTrackHasLayerSound.

If your build uses helper functions, extend them:

  • BST_ShouldEnableLayerNow — add your enum case.
  • BST_GetLayerTargetVolumeNow — return the correct target volume for the layer (from your new F_BST_LayerDef field).

If you don’t have those helpers, the same logic lives directly inside BST_ApplyEventLayersNow — extend the corresponding block there.

7

Fade Routing (Apply)

In BST_ApplyEventLayersNow, add a new block that routes ShouldEnable to your new AudioComponent:

  • If enabled → AdjustVolume(FadeSeconds, TargetVolume)
  • If not enabled → AdjustVolume(FadeSeconds, StandbyVolume)

Do not hard-stop stems here (except when the current track has no stem for that layer, handled via the “fade out and stop” helper).

8

Default Layers & (Optional) Intensity

Because default layers and suppression work from enum lists, your new layer is supported automatically — as long as the Conductor includes it in the “all layers” paths:

  • Update BST_RecomputeDefaultLayersForCurrentTrack so AllAvailable can include the new layer.
  • If you use Intensity, you may also need to update any layer-bitmask helpers (example: BST_LayerToBit) and intensity rule evaluation so the new layer can be intensity-driven.

Finally: Fill the stem in your Profile

Open your DA_BST_MusicProfile_* asset. For every TrackEntry that should support the new layer, fill in your new F_BST_LayerDef field (Sound + Target Volume). Tracks where you leave it empty will safely ignore the layer — no errors.

Files You Will Edit (Advanced)

TypeNameWhy
ENE_BST_MusicLayerAdd the enum entry.
STF_BST_TrackEntryAdd the new F_BST_LayerDef field.
BPBP_BST_ConductorAdd the new AudioComponent.
FNBST_EnsureLayerComponentsPlayingMutedPrepare the stem in standby on track start.
FNBST_CurrentTrackHasLayerSoundGate enable by stem availability.
FNBST_ApplyEventLayersNowFade routing for the new AudioComponent.
FNBST_RecomputeDefaultLayersForCurrentTrackSo your layer can be default-enabled (AllAvailable).
DADA_BST_MusicProfile_*Fill the new stem Sound + Volume per track.

Why fixed layers by default
Fixed layers give the best balance of low-friction setup, clean UX (simple nodes), reliable runtime behavior (consistent fades and quantization), and no audio-designer dependency for common cases. Custom layers are supported — but for most games, the five built-in layers are enough.

5.7 Advanced: Default Layer Control (Auto-Start on Track Begin)

By default, BeatSyncToolkit starts all available stems muted (StandbyVolume) and only enables layers when you request them via Events or Zones. Default Layer Control adds an optional third source: the track can auto-enable specific layers as soon as the track begins.

Advanced Feature

If you do not need “auto-on layers” per track, you can ignore this entire section. The toolkit works perfectly with Event Layers + Zone Layers only.

What Problem This Solves

Sometimes you want a track to start with a specific mix automatically, without calling AddLayer in gameplay. Examples:

  • Track A starts with Drums already ON.
  • Track B starts fully muted (no layers ON).
  • Track C starts with Drums + FX ON, but only if those stems exist in that track.

Where You Configure It

Default layers are configured per TrackEntry so each track in the same state playlist can behave differently. In F_BST_TrackEntry, you add:

  • TrackDefaultMode (enum): InheritFromConductor, MutedOnly, AllAvailable, CustomList
  • TrackDefaultLayers (array of E_BST_MusicLayer) used when mode is CustomList

How the Modes Work

ModeMeaningWhen to Use
InheritFromConductor Use the Conductor’s global default-start setting (if you have one). If you don’t use a global setting, treat this like MutedOnly. Keep one consistent default across many tracks, while still allowing per-track override when needed.
MutedOnly Start with no default layers enabled. All stems still play muted at standby volume. Clean “silent layers by default” behavior. Gameplay/Zone decides everything.
AllAvailable Enable every layer that exists in the current track (Drums/Bass/Harmony/Melody/FX, gated by stem validity). Tracks that should start “fully stacked” without extra gameplay calls.
CustomList Enable only the layers in TrackDefaultLayers (also gated by stem validity). Per-track curated starting mix (e.g., Drums+FX only).

Runtime: Recompute Before Applying Layers

On every track start (state switch, StartMusic, playlist auto-advance, and “restart same track while locked”), the Conductor recomputes which default layers are active for that track using:

BP_BST_Conductor
BST_RecomputeDefaultLayersForCurrentTrack()

This function sets ActiveDefaultLayers for the current track, based on TrackDefaultMode and TrackDefaultLayers, and gates every choice through BST_CurrentTrackHasLayerSound so missing stems are never armed.

Why This Must Run Before ApplyEventLayersNow

BST_ApplyEventLayersNow is where volumes actually fade up/down. It needs the current ActiveDefaultLayers to be ready first. The recommended order on track start is:

EnsureLayerComponentsPlayingMutedBST_RecomputeDefaultLayersForCurrentTrackBST_ApplyEventLayersNow

How Default Layers Interact with Zone / Event Layers

Layers are enabled by OR logic across sources. Default layers cannot “turn off” zone or event layers. The effective rule is:

Layer Enable Formula

Enabled = Zone OR Event OR (Default AND NOT SuppressedDefault)

Suppressing Default Layers with Remove / Clear

You requested a special behavior: if the player removes a default-enabled layer during the current track, that layer should stop coming back from Default until the track restarts/changes.

This is handled with SuppressedDefaultLayers:

  • BST_RemoveLayer: removes from Event Layers, and if the layer is in ActiveDefaultLayers, it also adds it into SuppressedDefaultLayers.
  • BST_ClearLayers: clears Event Layers, and sets SuppressedDefaultLayers to the current ActiveDefaultLayers (muting all defaults for the remainder of the track).
  • BST_AddLayer(s) does not remove suppression — it enables via Event. Suppression resets only on track start.
Your Intended Gameplay Workflow

Default layers define the “starting mix”. If you want to change the mix at runtime, you use Event Layers:
• To disable a default layer for this track: call BST_RemoveLayer or BST_ClearLayers
• To enable it again: call BST_AddLayer / BST_AddLayers
• To reset defaults: restart/switch track (state switch, playlist advance, restart locked track)

5.8 Intensity Overrides

Intensity is an optional advanced system. You can safely skip it if you only want beat-synced state changes, zones, layers, and requests. If you do use Intensity, TrackEntries can optionally override which intensity rules/profile are active while that track is playing.

Where to learn it

See Section 14.2 “Intensity System (Advanced)” for the full setup, rules, smoothing, hysteresis, and overrides.

# 6. Gameplay API (BFL_BST)

BFL_BST is the only thing your gameplay Blueprints need to call. It finds BP_BST_Conductor in the current world and forwards the request internally.

State Requests (Priority Overrides)

State Requests are built for temporary, event-driven overrides (AI combat, boss phases, cutscenes, scripted moments) where you want the music to automatically revert when the event ends.

  • Push a request when the event starts.
  • Remove the same request when the event ends.
  • If multiple requests are active, higher Priority wins (requests only compete with other requests).
  • Tip: If you have many instances (multiple AI, repeated triggers), use scoped request keys so each instance cannot overwrite another.
  • Requests override the global baseline. If any State Request is active, the winning request (highest Priority) overrides GlobalRequestedState.
  • Zones win by default. If you are inside a TopZone that is forcing a state (TopZone.ZoneState is not NONE), the zone keeps control unless the winning request is pushed with ForceWhileInZone = true.
  • When requests end, the system falls back automatically. After the last request is removed, the Conductor returns to the zone’s state (if still inside a forced zone), otherwise to GlobalRequestedState.
  • Note: BST_SetMusicState updates GlobalRequestedState but does not cancel active requests.
  • BST_ClearRequests clears every active request at once (useful for respawn, level reset, cutscene skip).
State + Profile are resolved together

ProfileInUse is resolved together with the effective desired state. The zone’s OverrideProfile is used only when the zone is actually providing the desired state (TopZone.ZoneState). If the desired state comes from a State Request or from GlobalRequestedState, then ProfileInUse = ActiveProfile. This prevents “state not found” issues when a zone override profile does not contain a requested state.

Normal Requests vs Scoped Requests

Every state request is stored under a key called RequestId. If two systems use the same key, they are editing the same request.

  • Normal request → You pick a globally-unique RequestId (good for singletons like cutscenes, boss phases, scripted moments).
  • Scoped request → The key is built from ScopeKey + RequestName (good when the same Blueprint can exist many times, like AI enemies or repeated triggers).
Why scoped requests matter (multi-AI / multi-instance safety)

If you spawn 5 enemies of the same AI Blueprint and they all push "AI_Combat", they will fight over one request. The last one overwrites the others, and removing it can end combat music too early. Scoped requests avoid this by giving each instance its own unique request slot.

How to use scoped requests

  • Recommended (if available in your build): Use the request nodes that include a Scope/ScopeKey input. Plug Self for per-actor scope, and keep your request name short (e.g., Combat).
  • Fallback (works in any build): Manually build a unique RequestId by combining a scope value (like the AI actor name) with a short request name (e.g., Enemy_12_Combat). Push and remove using the exact same string.

Example A2 — Multiple AI (Scoped)

// Per AI instance (preferred):
BST_PushStateRequest(Scoped(Self, "Combat"), Danger, 20, false, true)

// When that same AI ends the situation:
BST_RemoveStateRequest(Scoped(Self, "Combat"), true)
Many enemies (100+)? Consider a manager.

For very large crowds, you may prefer a small “Combat Music Manager” that keeps a counter (how many enemies are actively in combat) and pushes a single request while the counter > 0. This avoids storing one request per enemy, but both approaches are valid.

Example A — AI Combat (Auto-Revert)

// When AI spots the player:
BST_PushStateRequest("AI_Combat", Danger, 20, false, true)

// When AI loses the player:
BST_RemoveStateRequest("AI_Combat", true)

Example B — Cutscene (Can Override Forced Zones)

// Cutscene start (override forced zones by using ForceWhileInZone=true):
BST_PushStateRequest("Cutscene", Cinematic, 100, true, true)

// Cutscene end:
BST_RemoveStateRequest("Cutscene", true)

Tip: If you have multiple temporary systems and want to “return to normal” quickly, call BST_ClearRequests instead of tracking every RequestId.

Multiplayer / Replication Notes (Important)

BeatSyncToolkit is client-side audio. In multiplayer, each client plays its own music locally. This means:

  • If you call BFL_BST nodes only on the server, clients will not hear changes. Call the nodes on each client.
  • On a dedicated server, you usually do not need the Conductor at all (no audio). Place/run it on clients.
  • Recommended pattern: replicate a small “music intent” (State, EventLayers, TrackLock) via GameState or a replicated manager, then in OnRep call the matching BFL_BST nodes locally.
  • Zones are evaluated per client. Use AffectsOnlyLocalPlayer on zones to avoid cross-player overlap edge cases.

This toolkit does not force any networking architecture — it simply gives you clean local playback and a tiny gameplay API.

QuantizeToNextBar (per-call)

When a node includes QuantizeToNextBar: true queues the change and applies it on the next bar; false applies immediately using fades. Use quantized calls for tight musical timing; use immediate calls for reactive gameplay.

NodeDescription
BST_GetConductorFinds the placed BP_BST_Conductor in the current world. Returns Found=false if missing.
BST_StartMusicStarts music playback. Use this when bStartOnBeginPlay is disabled — the demo-friendly "start silent" mode.
BST_StopMusicFades out and stops all music, stops the beat clock, resets runtime state. Safe to call anytime.
BST_SetMusicStateSets the Global Requested State (your baseline). Zones and State Requests can still override it. Use this for long-lived gameplay modes (Exploration / Combat / Boss, etc.). For short-lived overrides, prefer BST_PushStateRequest + BST_RemoveStateRequest.
BST_PushStateRequestPushes a temporary state request (RequestId + Priority). Highest Priority wins (between requests). By default it overrides the global baseline; to override a forced zone state, push with ForceWhileInZone = true.
BST_RemoveStateRequestRemoves a previously pushed request by RequestId. After removal, the system falls back to the next winner (zone / another request / global state). Can be quantized to the next bar.
BST_ClearRequestsClears all active state requests at once. Useful when you don’t track IDs (respawn, level reset, cutscene skip). Can be quantized to the next bar.
BST_SetIntensitySets gameplay Intensity (0..1). Intensity auto-enables layers based on rules. Supports optional per-call QuantizeToNextBar and a per-call smooth/instant override (so quantized intensity also applies with the intended feel).
BST_GetCurrentIntensity01Returns the Conductor's current applied intensity (after smoothing/ramp). Useful for UI/debug.
BST_IsIntensityRampingTrue while the ramp timer is actively updating CurrentIntensity01.
BST_GetActiveIntensityLayersReturns the layers currently enabled by Intensity rules (debug/readback).
BST_AddLayerAdds a single event layer (fade in). Enable QuantizeToNextBar to apply on the next bar.
BST_AddLayersAdds multiple event layers in one call (array input). Use Make Array to supply 2/3/all layers.
BST_RemoveLayerRemoves a single event layer (fade out). If the same layer is required by the active zone, it remains enabled.
BST_ClearLayersClears all event layers. Zone layers remain active and unaffected.
BST_LockCurrentTrackLocks the currently playing track — prevents auto-advance, restarts the same track on finish.
BST_UnlockCurrentTrackReleases the lock so the playlist can auto-advance again.
Important Behavior Notes

No manual references neededWorldContextObject is sufficient. If no Conductor exists in the level, all nodes do nothing (Found=false). Layer nodes operate on Event Layers (ActiveEventLayers). Zone Layers are managed separately by zones and are not cleared by BST_ClearLayers.

WorldContextObject — Connect to Self

Every BFL_BST node has a WorldContextObject input. This input must be connected — otherwise BST_GetConductor (which runs internally on every call) cannot locate the world and returns Found=false. The call silently does nothing.

Blueprint · Correct Wiring
← Inside any Blueprint (Actor, Widget, GameMode, etc.)

BST_SetMusicState(
    WorldContextObject: self         ← drag "self" node here
    NewState:           Combat
)

← "self" gives the node a valid World reference.
   BST_GetConductor uses it to find BP_BST_Conductor in that world.
Blueprint · Common Mistake
BST_SetMusicState(
    WorldContextObject: ← left empty / not connected
    NewState:           Combat
)

← WorldContextObject is null.
   BST_GetConductor returns Found=false.
   Nothing happens. No error, no crash — silent fail.

# 7. Conductor Settings & Variables

These are the main user-facing variables shown in the Conductor's Details panel.

VariableMeaning
ActiveProfileProfile asset used to resolve StateConfigs. Zones may temporarily override this via OverrideProfile.
AutoStartStateState to begin automatically when the level starts.
StateChangeFadeDurationCrossfade duration when switching states or tracks.
bQuantizeStateChangesToBarIf true, state change requests are held and applied on the next bar.
bStartOnBeginPlayIf true, music starts automatically on BeginPlay. If false, the level starts silent until BST_StartMusic is called.
BaseVolumeMaster volume multiplier for the base mix.
bAutoAdvancePlaylistIf true, the Conductor advances to the next track in the playlist when the active base finishes.
bLoopIfSingleTrackIf true and the playlist has only 1 track, restart it when it finishes.
bQuantizeZoneLayerChangesToBarIf true, zone layer set updates are queued and applied on the next bar.
DefaultIntensityProfileDefault Intensity Rules asset used when profiles are set to UseConductorDefault (or when no override is provided). (aka DefaultIntensityRules in older builds)
bUseIntensityHysteresisIf true, rules use a separate close threshold (OpenThreshold − HysteresisDelta) to prevent flicker around boundaries.
DefaultIntensityHysteresisDeltaHow far below OpenThreshold a layer must fall before it turns off (when hysteresis is enabled).
bSmoothIntensityIf true, the Conductor ramps CurrentIntensity01 toward TargetIntensity01 over time.
IntensitySmoothingSecondsApproximate time to reach the target intensity (lower = snappier, higher = smoother).
IntensityRampUpdateIntervalHow often the ramp tick runs (smaller = smoother, larger = cheaper).
MinCurveReapplyDeltaMin-change gate for V2.2: prevents re-applying intensity layers on tiny value changes during ramping. (aka IntensityMinDeltaToReapply)
bDebugPrint / bDebugZones / bDebugProfilesPrints debug info to the console for troubleshooting.
Understanding Fade Durations

There are two separate fade systems in BeatSyncToolkit: (1) StateChangeFadeDuration controls the base track crossfade when switching states or tracks (set on the Conductor), and (2) FadeInDuration / FadeOutDuration control individual layer fades (set per-layer in each F_BST_LayerDef inside the TrackEntry). These are independent, so you can have a long 2-second state crossfade with snappy 0.1-second drum layer fades, or vice versa.

Runtime Variables (FYI)

At runtime the Conductor maintains CurrentState, CurrentTrack, beat clock values (BPM / BeatsPerBar / CurrentBeat), pending requests (PendingState, pending layer ops, pending zone layers), zone tracking (ActiveZones, TopZone, ActiveZoneLayers), and track lock flags (bEventTrackLock, bZoneTrackLock, bTrackLockActive).

# 8. Zone System INTERMEDIATE

Zones are overlap-based. When the player enters or exits zones, the Conductor maintains an ActiveZones array and selects the TopZone by highest ZonePriority. Only the TopZone's settings are applied at any time.

8.1 Zone Properties

PropertyMeaning
ZonePriorityHigher value wins when multiple zones overlap.
ZoneStateIf not NONE, becomes the effective desired state while inside the TopZone (zone wins by default; can be overridden only by a forced State Request).
ExtraLayersArray of E_BST_MusicLayer to add while inside the zone.
OverrideProfileOptional: use a different profile when this zone is TopZone and the zone is actually providing the desired state (ZoneState is not NONE and wins the resolver). If a forced State Request overrides the zone, the Conductor stays on ActiveProfile.
bZoneTrackLockRequest Track Lock while inside this zone. Lower priority than Event Track Lock.
State Quantize OverrideOptional tri-state override for state change quantization while this zone is TopZone (Inherit / Force On / Force Off). If forced ON/OFF, it overrides the Conductor default at runtime.
QuantizeZoneLayerChangesToBarToggle that drives the Conductor's zone-layer quantization behavior when this zone becomes TopZone.
AffectsOnlyLocalPlayerIf enabled, the zone only reacts to the local player — useful for single-player testing.

8.2 Overlapping Zones & Priority

When inside multiple zones, the Conductor picks TopZone by the highest ZonePriority. If TopZone changes, the Conductor reapplies zone override (state / profile) and zone layers. Leaving a TopZone triggers reselection of the next best zone, or disables zone override entirely if none remain.

8.3 Zone + Gameplay State Interaction

Gameplay requests via BST_SetMusicState set GlobalRequestedState. If ZoneOverrideActive is true, the TopZone's ZoneState can override what actually plays. When you leave all zones, the Conductor returns to GlobalRequestedState automatically.

Requests vs Zones

If you need to override a zone temporarily (AI combat, scripted moments), use State Requests (BST_PushStateRequest / BST_PushScopedStateRequest). Zones win by default when they force a state. To override a forced zone, push a request with ForceWhileInZone = true. When the request is removed, the system falls back to the zone (or the global state).

# 9. Arbitration Rules ADVANCED

When gameplay, zones, and events request different things at the same time, the Conductor follows deterministic priority rules.

9.1 State Resolution Priority

The Conductor resolves the effective desired state using these deterministic rules:

  1. Forced Zone State — If ZoneOverrideActive is true and TopZone.ZoneState is not NONE, the zone becomes the effective desired state by default.
  2. State Requests — If there is no forced zone state, the winning request (highest Priority among requests) becomes the effective desired state.
  3. GlobalRequestedState — Fallback from BST_SetMusicState.
Overriding a forced zone

If you want a request to override a zone that is forcing a state, push that request with ForceWhileInZone = true. If it is false, the request remains active but the zone stays in control until you exit the forced zone (or the zone state becomes NONE).

9.2 Profile Resolution Priority

ProfileInUse is resolved together with the effective desired state (via BST_ResolveDesiredStateAndProfile_Now). Rule: Use the zone’s OverrideProfile only when the zone is the source of the desired state (TopZone.ZoneState). If the desired state comes from a State Request or from GlobalRequestedState, then ProfileInUse = ActiveProfile. This prevents “state not found” issues when a zone override profile does not contain a requested state.

9.3 Layer Resolution (OR Combination)

A layer is active if any source requests it:

Layer SourceDescription
ActiveDefaultLayersLayers that are enabled by default when a track starts (per-track overrides supported). These can be temporarily muted via SuppressedDefaultLayers.
ActiveZoneLayersLayers from the current TopZone's ExtraLayers (or pending zone layer set if quantized).
ActiveEventLayersLayers explicitly toggled by gameplay through BFL_BST (or pending ops if quantized).
ActiveIntensityLayersLayers enabled automatically by Intensity rules (0..1). This is computed and updated as intensity changes.
OR Combination

Because layers use OR logic, removing a layer from Event Layers will not turn it off if the active zone still requests that same layer.

# 10. Quantization (Next-Bar Changes)

Quantization applies changes exactly on the next bar boundary — keeping transitions musically clean.

10.1 State Quantization

When bQuantizeStateChangesToBar is ON, state requests wait until the next bar boundary. On BST_OnBar, the Conductor re-resolves the effective desired state/profile using the current TopZone (if any) + active State Requests + your latest GlobalRequestedState, then switches only if needed. This prevents “stale” zone states from applying if you enter/exit zones before the bar.

Effective State Quantization (Zones vs Conductor)

There are two related values:

  • bQuantizeStateChangesToBar — your default preference (instance-editable on the Conductor).
  • Quantize State Changes to Bar Effective — the runtime value actually used by the system. This is resolved whenever TopZone changes.
How the effective value is resolved

The Conductor resolves the effective flag via BST_ResolveEffectiveStateQuantize:

  • If the current TopZone forces quantize ON/OFF (tri-state override), that wins.
  • Otherwise, it falls back to bQuantizeStateChangesToBar.
Don’t set the Effective flag directly

Quantize State Changes to Bar Effective is treated as read-only. If you set it manually, it will be overwritten the next time zones are recomputed or BST_ResolveEffectiveStateQuantize runs. For runtime toggles (like demo buttons), set bQuantizeStateChangesToBar instead.

10.2 Event Layer Ops Quantization

Queued layer ops are applied on each bar in a deterministic order:

Pending Layer Ops · Execution Order
1) Clear          ← optional PendingClearEventLayers
2) Remove        ← PendingRemoveLayers[]
3) Add           ← PendingAddLayers[] (only if track has that stem)
4) ApplyNow     ← BST_ApplyEventLayersNow()

10.3 Zone Layer Set Quantization

When zone layer changes are quantized, the Conductor stores PendingZoneLayers and sets bHasPendingZoneLayerSet=true. On bar, BST_ApplyPendingZoneLayers_OnBar copies pending layers into ActiveZoneLayers, clears the flag, and calls BST_ApplyEventLayersNow.

Quantization Summary

BeatSyncToolkit supports quantization for three categories: state changes (bQuantizeStateChangesToBar), event layer operations (per-call QuantizeToNextBar), and zone layer set updates (bQuantizeZoneLayerChangesToBar).

# 11. Playlist, Auto-Advance & Track Lock

11.1 Base Playback & Crossfade

The Conductor uses two base AudioComponents (Base A and Base B). When switching tracks or states, it crossfades from the active base to the other via BST_CrossfadeBaseTo. bUsingBaseA tracks which base is active — OnAudioFinished events from the inactive base are ignored.

11.2 Auto-Advance (Play Next Track)

If bAutoAdvancePlaylist is ON, when the active base finishes, BST_PlayNextTrack_SameState fires. The next track is chosen from the current state's playlist, avoiding immediate repeats when possible. After selection: beat clock restarts, base crossfades, layer components are ensured playing muted, and active layers are re-applied.

11.3 Track Lock

Track Lock prevents auto-advance for the current track. When locked and the active base finishes, the Conductor restarts CurrentTrack instead of advancing. Event-based lock (from BFL_BST) takes priority over zone-based lock (from TopZone).

Track Lock Scope

Track Lock locks the currently playing track at the moment the lock is engaged — not a specific track by name. It locks whatever is playing right now.

11.4 Advance Lock (Anti Double-Advance)

The Conductor briefly sets an internal Advance Lock during transitions to prevent double-advance (e.g. if both base finished events fire near a switch). This lock auto-clears after AdvanceLockSeconds.

# 12. Practical Examples

Example A — Exploration / Combat with Quantized Transitions

Blueprint Pseudocode
// Enable quantization on the Conductor
Conductor.bQuantizeStateChangesToBar = true

// If your game uses per-zone state-quantize overrides, refresh the runtime effective flag:
Conductor.BST_ResolveEffectiveStateQuantize()

// Normal gameplay:
BST_SetMusicState(Explore)

// Combat starts — change applies cleanly on next bar:
BST_SetMusicState(Combat)

// Combat ends:
BST_SetMusicState(Explore)

Example B — Layered Tension (Event Layers)

Blueprint Pseudocode
// Ensure your Combat tracks include a Drums stem
BST_AddLayer(Drums)       ← fade in drums
BST_RemoveLayer(Drums)    ← fade out drums
// No Drums stem on current track → safely ignored

Example C — Zone Overrides + Extra Layers

Place a BP_BST_MusicZone for a "Danger Area". Set ZonePriority high, ZoneState = Danger, and ExtraLayers = [FX].

Walk into the zone: state is overridden and FX layer enables. Walk out: music returns to GlobalRequestedState and FX turns off — unless it's still requested by event layers.

Example D — Overlapping Zones (TopZone Selection)

Place Zone A (Priority 10) and Zone B (Priority 20) overlapping. Inside both: Zone B is TopZone. Leave Zone B while still in Zone A: the Conductor automatically switches back to Zone A's behavior.

Example E — Track Lock during a Boss Fight

Blueprint Pseudocode
// Boss fight begins:
BST_LockCurrentTrack()      ← track restarts on finish

// Boss fight ends:
BST_UnlockCurrentTrack()    ← auto-advance resumes

# 13. Debugging & Troubleshooting

Common Setup Issues

SymptomLikely Cause
Nothing playsForgot to place BP_BST_Conductor, or ActiveProfile / AutoStartState is not set.
Layers do nothingCurrent track has no stem for that layer — LayerDef sound is empty.
Zone changes don't applyPlayer isn't overlapping the zone's collision, or TopZone selection isn't what you expect.
State changes seem delayedbQuantizeStateChangesToBar is on — changes apply on the next bar.
Layer changes seem delayedYou enabled QuantizeToNextBar on your layer call.
Host hears music, clients don’tYou are triggering BFL_BST calls only on the server/host. Call them on each client (or replicate a “music intent” and call nodes in OnRep).

Debug Flags

Enable these on BP_BST_Conductor to get console output:

Debug Flags
bDebugPrint          ← state switches, track picks, bar events
bDebugZones         ← zone enter/exit, TopZone changes, overrides applied
bDebugProfiles      ← profile resolution, state config lookups

Sanity Checklist

  • Exactly one Conductor exists in the level.
  • ActiveProfile contains an entry for the target state.
  • Each TrackEntry has BaseSound set and correct BPM / BPB.
  • Your desired layer's LayerDef sound is valid for that track.
  • Zones have collision enabled and overlap events firing.

# 14. Advanced Features (Optional) #ADVANCED

For power users who want to understand internal responsibilities. Most games only need the BFL nodes and profile / zone setup.

Optional: Conductor Function Map (for power users)
CategoryFunctions
ProfileBST_GetStateConfig, BST_GetStateConfig_FromProfile, BST_PickTrackForState
ClockBST_StartBeatClock, BST_OnBeatTick, BST_OnBar
RuntimeBST_SwitchStateNow, BST_CrossfadeBaseTo
PublicBST_RequestState
State (Internal)BST_RequestState_Internal
LayersBST_EnsureLayerComponentsPlayingMuted, BST_ApplyEventLayersNow, BST_CurrentTrackHasLayerSound, BST_AddEventLayer
Layers (Quantize)BST_QueueAddLayer, BST_QueueRemoveLayer, BST_QueueClearLayers, BST_ApplyPendingLayerOps_OnBar
PlaylistBST_PlayNextTrack_SameState, BST_SetAdvanceLock, BST_ClearAdvanceLock
ZonesBST_NotifyZoneEntered, BST_NotifyZoneExited, BST_RecomputeTopZone, BST_ApplyZoneOverrideNow, BST_ResolveDesiredStateAndProfile_Now, BST_ResolveEffectiveStateQuantize
Zones (Queued)BST_RequestRecomputeTopZone, BST_RecomputeTopZone_Queued, BST_ApplyPendingZoneLayers_OnBar
State RequestsBST_GetTopStateRequest, BST_ResolveDesiredStateAndProfile_Now, BST_PushStateRequest, BST_RemoveStateRequest, BST_ClearRequests
Track LockBST_UpdateTrackLockActive, BST_RestartCurrentTrackLocked
Intensity BST_SetIntensity, BST_BeginIntensityTransition, EVT_IntensityRampTick, BST_ResolveIntensityRulesForCurrentTrack, BST_RecomputeActiveIntensityLayers, BST_ApplyIntensityNow_Internal, BST_ApplyPendingIntensity_OnBar, BST_EvalIntensityVolumeScaleForLayer, BST_GetLayerTargetVolume_Now, BST_LayerToBit

14.2 Intensity System (Advanced)

Advanced feature — optional

You can ship your game using only States/Zones/Layers without Intensity. Use Intensity if you want a single gameplay “tension dial” that drives extra layers automatically.

Intensity is a continuous 0..1 value that can automatically enable or disable layers based on a rule list. This gives you a single gameplay “dial” (exploration → tension → combat) without writing multiple Add/Remove calls.

How Intensity Turns Into Layers

The Conductor evaluates the active intensity rules and builds ActiveIntensityLayers. Each rule maps one layer to an “open” threshold (and optionally a separate “close” threshold).

Rule FieldMeaning
LayerWhich layer this rule controls (Drums/Bass/Harmony/Melody/FX).
ThresholdIntensity at or above this value will enable the layer.
CloseThresholdOptional: intensity at or below this value will disable the layer (used only when hysteresis is enabled). If left negative (e.g. -1), the Conductor derives it automatically.

Hysteresis (Why CloseThreshold Exists)

Without hysteresis, if intensity hovers around a threshold (e.g. 0.49 → 0.51 → 0.50), layers may rapidly toggle ON/OFF. Hysteresis adds a “dead band” so a layer opens at one value, but only closes after intensity drops a bit further.

  • Open: Intensity ≥ Threshold → layer turns ON
  • Close: Intensity ≤ CloseThreshold → layer turns OFF
Practical Example

Drums opens at 0.45. With hysteresis delta 0.10, it closes at 0.35. That means small fluctuations around 0.45 won’t spam toggles.

Where hysteresis is configured

Hysteresis is a Conductor setting (bUseIntensityHysteresis + DefaultIntensityHysteresisDelta). It is not tied to the Music Profile override mode — you can use hysteresis whether your rules come from the Conductor default, a per-track Profile Override, or a per-track Rules Override.

Quantized vs Immediate Intensity Changes

The gameplay node BFL_BST → BST_SetIntensity includes a Quantize to Next Bar input. When enabled, the request is scheduled and applied cleanly on the next bar tick. When disabled, the new target is applied immediately. Any per-call smoothing overrides (smooth vs instant, and optional ramp seconds) are stored alongside the pending intensity change, so the feel stays consistent even when quantized.

Optional Smoothing / Ramp

You can optionally smooth intensity changes so CurrentIntensity01 ramps toward the target instead of snapping instantly. This is useful when gameplay produces noisy or jumpy values (AI counts, threat meters, distance checks).

  • The Conductor has a default smoothing mode (bSmoothIntensity + IntensitySmoothingSeconds).
  • BFL_BST → BST_SetIntensity can override smoothing per call (smooth vs instant). This override is respected even when the intensity change is quantized to the next bar.
  • If smoothing is active for that call, the Conductor ramps toward the target over IntensitySmoothingSeconds (or your optional per-call ramp seconds override, if you use it).
  • Because your thresholds are different per layer, smoothing naturally creates a “build-up” feel: layers can turn on one by one as intensity crosses each threshold.
Smoothing vs Layer Fades

Smoothing affects when a layer becomes enabled (threshold timing). Layer fade durations affect how the audio volume transitions once enabled. You can use either one alone, but the combo feels most musical.

Min-change Apply (Performance Optimization)

During smoothing, intensity may be evaluated many times per second. However, the set of enabled layers typically changes only when intensity crosses a threshold. BeatSyncToolkit includes a small optimization called Min-change Apply to avoid redundant work while intensity ramps.

  • The Conductor computes CurrentIntensityMask (a bitmask) from the rules each time it recomputes ActiveIntensityLayers.
  • If CurrentIntensityMask equals LastAppliedIntensityMask, the Conductor skips re-applying event layers for that tick.
  • When the mask changes (threshold crossed), it applies once and updates LastAppliedIntensityMask.
What This Changes (and What It Doesn't)

This optimization does not change how the music sounds. It only reduces redundant calls to the layer application pipeline. Your audible fades are still controlled by the per-layer fade durations in your Music Profile / TrackEntry layer defs.

How the Bitmask Works (BST_LayerToBit)

To keep the system modular, the Conductor converts a layer enum value into a unique bit using BST_LayerToBit. The default implementation is generic: it returns 2^EnumIndex (equivalent to 1 << EnumIndex). This means you do not need to edit BST_LayerToBit when adding new layers.

If You Extend the Layer Enum

Only append new entries to E_BST_MusicLayer. Do not reorder existing entries. Reordering changes enum indices, which changes bit assignments used by CurrentIntensityMask.

Intensity Curves (Volume Scaling)

By default, Intensity is a binary layer gate (a layer is either enabled or not). With curves, an Intensity-enabled layer can also have its target volume scaled as Intensity changes. This enables “gradual build” layers (e.g., drums getting louder) without needing multiple stems.

  • Each F_BST_IntensityRule can optionally provide a Volume Scalar Curve (CurveFloat). The curve is evaluated with X = CurrentIntensity01 and outputs a scale value.
  • The scale is clamped to 0..1 and applied as: EffectiveTargetVolume = BaseTargetVolume * Scale.
  • Important: the curve scaling is applied only when a layer is enabled ONLY by Intensity (Non‑Intensity ON = false, Intensity ON = true). If a layer is enabled by Default/Zone/Event, the system returns BaseTargetVolume unchanged (your normal layer behavior). Also: Intensity does not disable layers that are enabled by Default/Zone/Event — those sources have higher priority, so the layer remains ON even if Intensity drops.
Min-change for Curves (Performance)
During a smooth ramp, Intensity updates frequently. To avoid spamming volume updates, the Conductor only re-applies curve-scaled volumes when the intensity moved by at least MinCurveReapplyDelta since the last curve re-apply. Whenever something “structural” changes (state switch / track switch / layer enable sources), the Conductor resets LastCurveReapplyIntensity01 to -1 so the next apply forces a curve refresh.

# 15. Known Limitations & Roadmap

15.1 Current Limitations

These are intentional design boundaries or features not yet implemented. None of these prevent shipping a game with BeatSyncToolkit.

LimitationDetailsWorkaround
No editor previewMusic only plays in PIE or Standalone. There is no "audition in editor" mode.Use PIE with bStartOnBeginPlay = true for quick testing.
5 fixed layer slotsThe built-in layers are Drums, Bass, Harmony, Melody, FX. Adding more requires manual extension (see Section 5.6).For most games, 5 layers is more than enough. Use States/Zones for additional variation.
Single Conductor per levelOnly one BP_BST_Conductor should exist at a time. Multiple Conductors are not supported.Use Zones and State Requests for spatial and event-driven variation within the same Conductor.
Timer-based clock (not sample-accurate)The beat clock uses UE Timers, which are frame-dependent. Very high BPM (300+) or very low frame rates may cause slight timing drift.For typical game BPM ranges (60–200) at 30+ FPS, timing is solid. Not intended for rhythm-game-level precision.
No MIDI input/outputBST does not read or generate MIDI data.Use gameplay events (BST_SetMusicState, BST_AddLayer) instead of MIDI.
No dynamic stem loadingAll stems referenced in a profile are loaded when the profile is active. There is no on-demand streaming per stem.Keep stem file sizes reasonable. Use Sound Cues with streaming settings if memory is a concern.

15.2 Roadmap

BeatSyncToolkit is designed to be expandable. Upcoming systems are toggles, they will not break the current workflow.

Planned Additions
  • More convenience BFL nodes (quality-of-life wrappers for common patterns).
  • Optional Intensity add-ons (e.g., temporary suppression, per-zone intensity modifiers).
  • More demo levels and example profiles.
  • Expanded documentation with screenshot walkthroughs.

# 16. Glossary / Key Terms BEGINNER

Quick definitions for common terms used in this documentation.

Stems

Separate audio layers of the same music (drums, bass, melody, etc.). Each stem is its own sound file, so BeatSyncToolkit can fade layers in/out independently.

Quantization

Applying changes on the next bar boundary (instead of instantly). This keeps transitions musically clean.

BPM

Beats Per Minute — how fast the music is. BeatSyncToolkit uses BPM to schedule beat/bar timing.

Bar

A musical measure. Commonly 4 beats = 1 bar (but it depends on your track’s BeatsPerBar).

Quartz

Unreal Engine’s sample-accurate audio timing system. BeatSyncToolkit does not use Quartz — it uses a lightweight Conductor timer/clock approach.

Arbitration

Rules for what “wins” when multiple systems try to control music at once (Zones vs Events vs Defaults vs State requests).

Hysteresis

A “dead zone” between open/close thresholds so a value doesn’t flicker rapidly when hovering near the threshold.

Data Asset

An Unreal asset used to store structured configuration data. BeatSyncToolkit profiles are Data Assets.

Scope / Scoped Requests

A “grouping key” used by State Requests so multiple instances (like many AI actors) don’t overwrite each other. A scoped request key is typically ScopeKey + RequestName. Use per-actor scope for enemy AI, or use a shared scope for group-wide systems.

WorldContextObject

A Blueprint reference used to locate the current World. Most of the time, connect Self. Without a valid World context, global BFL nodes may fail to find the placed Conductor.

Beginner note

If any of these terms are unfamiliar, start with Quick Start and come back here later.

# 17. Frequently Asked Questions (FAQ)

Quick answers to the most common questions.

Do I need C++ knowledge?

No — BeatSyncToolkit is Blueprint-first and ships with global Blueprint nodes for runtime control.

Does this use Quartz?

No. BST uses a lightweight timer-based beat clock and bar quantization. Quartz is not required (and BST can coexist with Quartz if your project uses it elsewhere).

Can I add custom Music States?

Yes. Extend E_BST_MusicState, then add tracks for the new state inside your Music Profile DataAsset.

Can I use my own music?

Yes. Any asset playable by an AudioComponent works (Sound Waves, Sound Cues, and MetaSound Source assets). Import your sounds, then assign them in your profile.

Does this work with MetaSounds?

Yes — if you can assign it to an AudioComponent, BST can play it as a base track or as a layer stem.

Is there multiplayer support?

BST is multiplayer-friendly, but audio is typically driven on each client. Replicate your gameplay events (state requests / layers / intensity) to clients, then call the BST nodes locally. Dedicated servers usually do not need to run audio.

Can I preview music in the editor?

BST is designed for runtime playback (PIE / Standalone). It does not provide an 'editor-only audition' mode without pressing Play.

Performance impact?

Very small. At runtime, BeatSyncToolkit uses 2 base AudioComponents (for crossfade) + 5 layer AudioComponents (one per stem) = 7 total AudioComponents. The beat clock is a single lightweight timer. Zone overlap checks use standard UE collision (no tick cost). State/layer arbitration runs only when something changes (event-driven, not per-tick). In practice, BST's CPU footprint is negligible compared to a single particle system. The only scaling concern is if you have a very large number of concurrent State Requests (100+), which is easily solved with a manager pattern (see Section 6).

Roadmap / updates?

Check the Roadmap section for planned additions and version notes.

How do I remove/uninstall BeatSyncToolkit?

Delete the BeatSyncToolkit content folder from your project. Remove any BP_BST_Conductor and BP_BST_MusicZone actors from your levels. Delete or disconnect any BFL_BST node calls in your Blueprints (they will show as broken nodes after removal). No engine files or config files are modified by BST, so removal is clean.

Does it work on UE 5.3 or 5.4?

BeatSyncToolkit is built and tested on UE 5.5.x. It uses only standard Blueprint features, so it is expected to work on 5.3+ without issues. However, only 5.5.x is officially tested and supported. If you encounter issues on an older version, please report them.

Can I use Sound Cues or MetaSound Sources instead of raw WAV?

Yes. Any asset that can be assigned to a standard UE AudioComponent works as a BaseSound or layer stem. This includes SoundWave (.wav, .ogg imports), SoundCue, and MetaSound Source assets.

What happens if I switch states while a crossfade is in progress?

The Conductor handles rapid state switches gracefully. A new state switch during an active crossfade will start a new crossfade from the current mix, so you never hear a "jump". If quantization is enabled, the new switch waits for the next bar regardless.

Can I have different fade speeds per layer?

Yes. Each layer in each TrackEntry has its own FadeInDuration and FadeOutDuration (configured in F_BST_LayerDef). For example, drums can snap in at 0.1s while harmony fades in over 2s.

How do I report issues?

Contact us at nonfigurestudio@gmail.com and include your engine version, a short repro checklist, and screenshots if possible.

# 18. Support & Contact

Need help or want to report an issue? Reach out to us.

Email Support

For questions, bug reports, or feature requests, contact us at:

nonfigurestudio@gmail.com

When reporting issues, please include:
  • Your Unreal Engine version (e.g., UE 5.5.2)
  • Steps to reproduce the issue
  • Screenshots or error messages if applicable
  • Brief description of your setup (project type, music profile details)