babalae/better-genshin-impact · error · FormatException

无法解析 MIDI JSON 事件:{rawPart}

Error message

无法解析 MIDI JSON 事件:{rawPart}

What it means

A FormatException from ParseMidiJsonTimeline thrown when a pipe-delimited segment does not start with '*' and does not match MidiJsonEventRegex (the generated source regex that recognizes an event like 'D<keys>:<ticks>' or 'U<keys>:<ticks>'). Every non-tempo segment must be a well-formed event token; anything else aborts parsing.

Source

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

        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;

            if (status == "D"
                && previousWasEvent
                && previousStatus == "U"
                && previousKeys.Any(keys.Contains)
                && delay.TotalMilliseconds < LowestLatencyMilliseconds)
            {
                cursor += TimeSpan.FromMilliseconds(LowestLatencyMilliseconds);
            }
            else if (status == "U"
                     && keys != "@"

View on GitHub (pinned to a7cb36712d)

Solutions

  1. Check the MidiJsonEventRegex definition (MusicScoreParser.cs:713, partial generated regex) and conform each segment to its 'status + keys + ":" + ticks' shape.
  2. Re-export the MIDI-JSON from the original MIDI file.
  3. Locate the offending segment via the {rawPart} value in the message and fix or remove it.
  4. Route the file through ParseAsync so the failure is reported as an InvalidScore.Error.

Example fix

// before (missing colon / unknown status)
"notes":"DQ0|XQ:480"
// after
"notes":"DQ:0|UQ:480"
Defensive patterns

Strategy: try-catch

Validate before calling

static bool MidiJsonEventsValid(string notes, Regex r)
{
    foreach (var part in notes.Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
    {
        if (part.StartsWith('*')) continue;
        if (!r.Match(part).Success) return false;
    }
    return true;
}

Type guard

static bool IsValidEventSegment(string s, Regex r) => !s.StartsWith('*') && r.Match(s).Success;

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: A segment such as 'DQ0' (missing the ticks delimiter), 'XQ:0' (unknown status letter), 'DQ:' (empty ticks), or stray text/punctuation after splitting the notes string on '|'.

Common situations: Hand-edited MIDI-JSON notes string with a typo in an event; exporter version change that altered the event format; copy-paste truncation; an event written with a wrong separator (e.g. 'DQ-0' instead of 'DQ:0').

Related errors


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