Skip to main content
The Conductor class is a core singleton that manages musical timing for both gameplay and menus. It handles BPM changes, time signatures, beat/step detection, and provides time conversion utilities.

Overview

The Conductor maintains the current song position and automatically dispatches signals when musical events occur (measures, beats, steps). It supports complex timing scenarios including:
  • Multiple BPM changes within a song
  • Arbitrary time signatures (4/4, 3/4, 7/8, etc.)
  • Audio/visual offsets and latency compensation
  • Precise time-to-beat/step conversions

Accessing the Conductor

// Get the singleton instance
var conductor = Conductor.instance;

// Access current timing information
trace(conductor.bpm);              // Current BPM
trace(conductor.currentBeat);      // Current beat (integer)
trace(conductor.currentBeatTime);  // Current beat (float with decimals)
trace(conductor.songPosition);     // Current position in milliseconds

Properties

Timing Properties

bpm
Float
Current beats per minute at the current song position. Automatically adjusts when time changes occur.
songPosition
Float
Current position in the song in milliseconds. Updated every frame via update().
currentBeat
Int
Current position in the song as an integer beat number.
currentBeatTime
Float
Current position in the song in beats, including fractional beats.
currentStep
Int
Current position in the song as an integer step number. There are 4 steps per beat.
currentStepTime
Float
Current position in the song in steps, including fractional steps.
currentMeasure
Int
Current position in the song as an integer measure number.
currentMeasureTime
Float
Current position in the song in measures, including fractional measures.

Duration Properties

beatLengthMs
Float
Duration of a beat in milliseconds, calculated from the current BPM and time signature.
stepLengthMs
Float
Duration of a step in milliseconds. Always 1/4 of beatLengthMs.
measureLengthMs
Float
Duration of a measure in milliseconds, calculated from beat length and time signature.

Time Signature Properties

timeSignatureNumerator
Int
The numerator of the current time signature (the 3 in 3/4).
timeSignatureDenominator
Int
The denominator of the current time signature (the 4 in 3/4).
beatsPerMeasure
Float
Number of beats in a measure. Equal to timeSignatureNumerator.
stepsPerMeasure
Int
Number of steps in a measure. Equal to timeSignatureNumerator * 4.

Offset Properties

instrumentalOffset
Float
Chart-specific offset in milliseconds to compensate for instrumental delays.
formatOffset
Float
Audio format offset (e.g., MP3 encoding delay).
globalOffset
Int
User-configured offset to compensate for input lag, loaded from save data.
audioVisualOffset
Int
User-configured offset to compensate for audio/visual lag, loaded from save data.
combinedOffset
Float
Sum of instrumentalOffset + formatOffset + globalOffset.

Methods

update()

Updates the conductor with the current song position and recalculates all timing properties.
public function update(?songPos:Float, applyOffsets:Bool = true, forceDispatch:Bool = false):Void
songPos
Float
The current position in the song in milliseconds. If omitted, uses FlxG.sound.music.time.
applyOffsets
Bool
default:"true"
Whether to apply combinedOffset to the song position.
forceDispatch
Bool
default:"false"
Force signal dispatch even if the current step/beat/measure hasn’t changed.
Example:
override function update(elapsed:Float):Void
{
  super.update(elapsed);
  
  // Update conductor every frame in gameplay
  Conductor.instance.update();
}

mapTimeChanges()

Applies song time changes (BPM/time signature changes) to the conductor.
public function mapTimeChanges(songTimeChanges:Array<SongTimeChange>):Void
songTimeChanges
Array<SongTimeChange>
required
Array of time change data from song metadata.
Example:
// Load time changes from song data
var song = SongRegistry.instance.fetchEntry("roses");
var chart = song.getDifficulty("hard");
Conductor.instance.mapTimeChanges(chart.getTimeChanges());

forceBPM()

Forces the conductor to use a specific BPM, ignoring time changes.
public function forceBPM(?bpm:Float):Void
bpm
Float
The BPM to force. Pass null to reset to time change-based BPM.
Avoid using this for setting BPM of menu music. Use metadata files instead. This is primarily for tools like the chart editor.
Example:
// Force to 150 BPM
Conductor.instance.forceBPM(150);

// Reset to normal time change behavior
Conductor.instance.forceBPM(null);

Time Conversion Methods

The Conductor provides several methods for converting between different time units.

getTimeInSteps()

Converts milliseconds to steps.
public function getTimeInSteps(ms:Float):Float
ms
Float
required
Time in milliseconds.
Returns: Time in steps (float).

getStepTimeInMs()

Converts steps to milliseconds.
public function getStepTimeInMs(stepTime:Float):Float
stepTime
Float
required
Time in steps.
Returns: Time in milliseconds.

getBeatTimeInMs()

Converts beats to milliseconds.
public function getBeatTimeInMs(beatTime:Float):Float
beatTime
Float
required
Time in beats.
Returns: Time in milliseconds.

getTimeInMeasures()

Converts milliseconds to measures.
public function getTimeInMeasures(ms:Float):Float
ms
Float
required
Time in milliseconds.
Returns: Time in measures (float).

getMeasureTimeInMs()

Converts measures to milliseconds.
public function getMeasureTimeInMs(measureTime:Float):Float
measureTime
Float
required
Time in measures.
Returns: Time in milliseconds. Example:
// Jump to beat 16
var targetMs = Conductor.instance.getBeatTimeInMs(16);
FlxG.sound.music.time = targetMs;

// Get current position in steps
var currentStep = Conductor.instance.getTimeInSteps(FlxG.sound.music.time);

getTimeWithDelta()

Returns a more accurate music time for higher framerates by including interpolated delta time.
public function getTimeWithDelta():Float
Returns: Song position with delta applied for smoother timing.

Signals

The Conductor dispatches signals when timing events occur. Use these to sync animations and gameplay to the music.
stepHit
FlxSignal
Fired when the conductor advances to a new step (16th note in 4/4 time).
beatHit
FlxSignal
Fired when the conductor advances to a new beat (quarter note in 4/4 time).
measureHit
FlxSignal
Fired when the conductor advances to a new measure.
Example:
override function create():Void
{
  super.create();
  
  // Listen for beat hits
  Conductor.beatHit.add(onBeatHit);
}

function onBeatHit():Void
{
  // Bump characters on every beat
  boyfriend.dance();
  dad.dance();
  
  // Camera zoom every 4 beats
  if (Conductor.instance.currentBeat % 4 == 0)
  {
    FlxG.camera.zoom += 0.015;
  }
}

override function destroy():Void
{
  Conductor.beatHit.remove(onBeatHit);
  super.destroy();
}

Static Methods

reset()

Resets the conductor by creating a new instance.
public static function reset():Void
Example:
// Reset conductor state when changing songs
Conductor.reset();

watchQuick()

Adds conductor properties to the Flixel debugger watch window.
public static function watchQuick(?target:Conductor):Void
target
Conductor
The conductor instance to watch. Defaults to Conductor.instance.
Example:
#if debug
Conductor.watchQuick();
#end

Understanding Steps, Beats, and Measures

The Conductor uses musical notation concepts:
  • Step: A subdivision of a beat. In 4/4 time with 4 steps per beat, a step equals a 16th note.
  • Beat: The basic unit of time in music. In 4/4 time, a beat equals a quarter note.
  • Measure: A grouping of beats. In 4/4 time, a measure contains 4 beats.

Time Signature Effects

  • 4/4 time: 4 beats per measure, 16 steps per measure
    • 120 BPM = 2 beats/second, 8 steps/second
  • 3/4 time: 3 beats per measure, 12 steps per measure
    • 120 BPM = 2 beats/second, 8 steps/second
  • 7/8 time: 7 beats per measure (eighth notes!), 28 steps per measure
    • 120 BPM = 4 beats/second, 16 steps/second

Example: Syncing to Music

class MyMusicState extends MusicBeatState
{
  var particles:FlxEmitter;
  
  override function create():Void
  {
    super.create();
    
    // Load song and set up conductor
    FlxG.sound.playMusic("assets/music/freakyMenu.ogg");
    Conductor.instance.forceBPM(102);
    
    // Create particle emitter
    particles = new FlxEmitter();
    add(particles);
    
    // Listen for beat hits
    Conductor.beatHit.add(onBeatHit);
  }
  
  override function update(elapsed:Float):Void
  {
    super.update(elapsed);
    
    // Update conductor every frame
    Conductor.instance.update();
  }
  
  function onBeatHit():Void
  {
    // Emit particles on every beat
    particles.start();
    
    // Do something special every 4 beats (every measure in 4/4 time)
    if (Conductor.instance.currentBeat % 4 == 0)
    {
      trace("Measure " + Conductor.instance.currentMeasure);
    }
  }
  
  override function destroy():Void
  {
    Conductor.beatHit.remove(onBeatHit);
    super.destroy();
  }
}

See Also

  • PlayState - Main gameplay state that uses Conductor extensively
  • Song Data - Song data structure that provides time changes to the Conductor

Build docs developers (and LLMs) love