ppy/osu · error · InvalidDataException

Beat length cannot be NaN in a timing control point

Error message

Beat length cannot be NaN in a timing control point

What it means

Thrown by LegacyBeatmapDecoder when parsing a timing control point (TimingChange=1) whose beatLength field is NaN. The decoder explicitly rejects NaN timing points because they would corrupt the beatmap's timing grid. The value becomes NaN when the .osu file literally contains 'NaN' in the beatLength field, since double.Parse('NaN', InvariantCulture) successfully parses to double.NaN.

Source

Thrown at osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs:533

            bool kiaiMode = false;
            bool omitFirstBarSignature = false;

            if (split.Length >= 8)
            {
                LegacyEffectFlags effectFlags = (LegacyEffectFlags)Parsing.ParseInt(split[7]);
                kiaiMode = effectFlags.HasFlag(LegacyEffectFlags.Kiai);
                omitFirstBarSignature = effectFlags.HasFlag(LegacyEffectFlags.OmitFirstBarLine);
            }

            string stringSampleSet = sampleSet.ToString().ToLowerInvariant();
            if (stringSampleSet == @"none")
                stringSampleSet = HitSampleInfo.BANK_NORMAL;

            if (timingChange)
            {
                if (double.IsNaN(beatLength))
                    throw new InvalidDataException("Beat length cannot be NaN in a timing control point");

                var controlPoint = CreateTimingControlPoint();

                controlPoint.BeatLength = beatLength;
                controlPoint.TimeSignature = timeSignature;
                controlPoint.OmitFirstBarLine = omitFirstBarSignature;

                addControlPoint(time, controlPoint, true);
            }

            int onlineRulesetID = beatmap.BeatmapInfo.Ruleset.OnlineID;

            addControlPoint(time, new DifficultyControlPoint
            {
                GenerateTicks = !double.IsNaN(beatLength),
                SliderVelocity = speedMultiplier,
            }, timingChange);

View on GitHub (pinned to d9c73e12ad)

Solutions

  1. Open the .osu file in a text editor, find the [TimingPoints] section, and locate the offending line (the one with 'NaN' in the beatLength position).
  2. Replace 'NaN' with a valid positive millisecond value (e.g. 60000/BPM — for 120 BPM use 500).
  3. Re-export or re-download the beatmap if the corruption is widespread.
  4. If producing .osu files programmatically, ensure beatLength is always a valid finite double before writing.

Example fix

// before (.osu file [TimingPoints] section):
// 1000,NaN,4,1,0,100,1,0

// after:
// 1000,500,4,1,0,100,1,0
Defensive patterns

Strategy: validation

Validate before calling

// Validate beatLength before adding the timing point
if (timingChange && double.IsNaN(beatLength))
    throw new InvalidDataException($"Timing point at {time}ms has NaN beatLength — fix the .osu file");

Try / catch

try
{
    var beatmap = decoder.Decode(stream);
}
catch (InvalidDataException ex) when (ex.Message.Contains("Beat length cannot be NaN"))
{
    Logger.Log("Beatmap has a NaN timing point — file is corrupt", LoggingTarget.Database);
}

Prevention

When it happens

Trigger: Decoding a .osu file whose [TimingPoints] section contains a timing point (red/uninherited) with 'NaN' as the beatLength value — e.g. a line like '1000,NaN,4,1,0,100,1,0'. The timingChange flag must be true (first value after time is a positive beatLength, not an inherited multiplier).

Common situations: A beatmap produced by a buggy editor that wrote 'NaN' instead of a numeric BPM value; manual editing of a .osu file where a timing point value was deleted or replaced with text; a corrupt beatmap where the beatLength field is garbled.

Related errors


AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13). Data as JSON: /api/errors/5124d0a30e4593c8. Report an issue: GitHub.