babalae/better-genshin-impact · error · FormatException

BPM 和音符时值必须大于 0

Error message

BPM 和音符时值必须大于 0

What it means

A FormatException from GetNoteDuration guarding against non-positive BPM or note denominator. The duration formula 60000/bpm*beatDenominator/noteDenominator requires both inputs strictly positive to avoid division by zero or negative durations. This is a downstream precondition failure, usually caused by bad upstream values rather than user input directly.

Source

Thrown at BetterGenshinImpact/GameTask/Music/Service/MusicScoreParser.cs:655

                previousDown[item.Key] = item.Time;
            }
            else
            {
                previousUpIndex[item.Key] = i;
            }
        }

        return events
            .OrderBy(x => x.Time)
            .ThenBy(x => x.Type == PerformanceEventType.KeyUp ? 0 : 1)
            .ToList();
    }

    private static TimeSpan GetNoteDuration(double bpm, int beatDenominator, double noteDenominator)
    {
        if (bpm <= 0 || noteDenominator <= 0)
        {
            throw new FormatException("BPM 和音符时值必须大于 0");
        }

        return TimeSpan.FromMilliseconds(60000d / bpm * beatDenominator / noteDenominator);
    }

    private static int ParseBeatDenominator(string timeSignature)
    {
        var parts = timeSignature.Split('/');
        return parts.Length == 2 && int.TryParse(parts[1], out var denominator) && denominator > 0
            ? denominator
            : 4;
    }

    private static TimeSpan ToTimeSpan(MetricTimeSpan time)
    {
        return TimeSpan.FromMicroseconds(time.TotalMicroseconds);
    }

View on GitHub (pinned to a7cb36712d)

Solutions

  1. Ensure the score's "bpm" field is a positive number (e.g. 120).
  2. Ensure every note denominator is positive.
  3. Validate bpm and denominators at the ParseJsonAsync entry before dispatching to timeline builders.
  4. Route through ParseAsync so the file is reported as InvalidScore.Error.

Example fix

// before
{"bpm":0,"notes":[...]}
// after
{"bpm":120,"notes":[...]}
Defensive patterns

Strategy: validation

Validate before calling

static bool PositiveBpmAndNotes(double bpm, IEnumerable<double> denominators)
    => bpm > 0 && denominators.All(d => d > 0);

Type guard

static bool IsValidBpm(double bpm) => bpm > 0;

Try / catch

var score = await parser.ParseAsync(path, root, ct);
if (!string.IsNullOrEmpty(score.Error)) { Log.Warning(score.Error); continue; }

Prevention

When it happens

Trigger: GetNoteDuration is called with bpm <= 0 or noteDenominator <= 0. This happens when a score's bpm field is missing/zero/non-positive, or a note's denominator parsed as <= 0.

Common situations: A yuanqin score with bpm set to 0 or a negative value; a note with a fractional type that rounds to <= 0; upstream GetDouble returning its fallback only when the value is non-positive, but a direct caller bypassing that guard.

Related errors


AI-assisted analysis of babalae/better-genshin-impact@a7cb36712d (2026-08-13). Data as JSON: /api/errors/b7d0ec8633a763a6. Report an issue: GitHub.