babalae/better-genshin-impact · error · FormatException

无法解析 MIDI JSON 变速标记:{rawPart}

Error message

无法解析 MIDI JSON 变速标记:{rawPart}

What it means

A FormatException from ParseMidiJsonTimeline raised when a pipe-delimited segment of a MIDI-JSON "notes" string starts with '*' (a tempo-change marker) but the remainder is not a strictly positive floating-point number. The '*' prefix denotes a BPM change applied from that point; an unparseable or non-positive value aborts the whole timeline build. Parse uses NumberStyles.Float with the invariant culture.

Source

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

        var events = new List<PerformanceEvent>();
        var cursor = TimeSpan.Zero;
        var bpm = initialBpm;
        string previousStatus = string.Empty;
        string previousKeys = string.Empty;
        var previousWasEvent = false;
        var rawParts = sheet.Split(
            '|',
            StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

        for (var index = 0; index < rawParts.Length; index++)
        {
            var rawPart = rawParts[index];
            if (rawPart.StartsWith('*'))
            {
                if (!double.TryParse(rawPart[1..], NumberStyles.Float, CultureInfo.InvariantCulture, out bpm)
                    || bpm <= 0)
                {
                    throw new FormatException($"无法解析 MIDI JSON 变速标记:{rawPart}");
                }

                previousWasEvent = false;
                continue;
            }

            var match = MidiJsonEventRegex().Match(rawPart);
            if (!match.Success)
            {
                throw new FormatException($"无法解析 MIDI JSON 事件:{rawPart}");
            }

            var status = match.Groups["status"].Value;
            var keys = match.Groups["keys"].Value;
            var deltaTicks = long.Parse(match.Groups["ticks"].Value, CultureInfo.InvariantCulture);
            var delay = TimeSpan.FromMilliseconds(deltaTicks * 60000d / (bpm * ticks));
            cursor += delay;

View on GitHub (pinned to a7cb36712d)

Solutions

  1. Inspect the notes string and fix every '*...' segment to be a positive number with a dot decimal (e.g. '*120').
  2. Regenerate the MIDI-JSON file from the original MIDI with the exporter.
  3. Strip or correct any stray '*' that is not a real tempo change.
  4. Wrap file loading through ParseAsync so a bad tempo yields an InvalidScore with a readable Error instead of crashing the UI.

Example fix

// before
"notes":"*X|DQ:0|UQ:480"
// after
"notes":"*120|DQ:0|UQ:480"
Defensive patterns

Strategy: try-catch

Validate before calling

static bool MidiJsonTemposValid(string notes)
{
    foreach (var part in notes.Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
    {
        if (!part.StartsWith('*')) continue;
        if (!double.TryParse(part[1..], NumberStyles.Float, CultureInfo.InvariantCulture, out var bpm) || bpm <= 0)
            return false;
    }
    return true;
}

Type guard

static bool IsValidTempoSegment(string s) => s.StartsWith('*') && double.TryParse(s[1..], NumberStyles.Float, CultureInfo.InvariantCulture, out var b) && b > 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: The MIDI-JSON notes string contains a segment like '*X', '**120', '*0', '*-5', or '*120,5' (comma decimal) after splitting on '|'.

Common situations: A MIDI-to-JSON exporter emitted a malformed tempo token; manual editing inserted a stray '*'; a locale conversion turned '*120.0' into '*120,0'; a 'multiply' or comment character was mistaken for a tempo marker.

Related errors


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