babalae/better-genshin-impact · error · FormatException

无法解析音符 {keys} 的时值

Error message

无法解析音符 {keys} 的时值

What it means

A FormatException from ParseYuanQinTokens thrown when the "type" field of a yuanqin note object cannot be parsed as a floating-point number. The "type" value is the beat denominator (e.g. 4 = quarter note) used to compute duration; a missing, null, or non-numeric value leaves the note without a valid duration. The parse uses NumberStyles.Float with the invariant culture, so locale-specific decimals like commas are rejected.

Source

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

            if (string.IsNullOrWhiteSpace(keys))
            {
                throw new FormatException("yuanqin 音符对象缺少 note 字段");
            }

            var special = GetString(note, "spl", "none");
            if (special is "^" or "&")
            {
                tokens.Add(new YuanQinToken(keys, 0, special));
                continue;
            }

            if (!double.TryParse(
                    note["type"]?.ToString(),
                    NumberStyles.Float,
                    CultureInfo.InvariantCulture,
                    out var denominator))
            {
                throw new FormatException($"无法解析音符 {keys} 的时值");
            }

            tokens.Add(new YuanQinToken(keys, denominator, special));
        }

        return tokens;
    }

    private static PerformanceTimeline ParseMidiJsonTimeline(string sheet, double initialBpm, int ticks)
    {
        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(
            '|',

View on GitHub (pinned to a7cb36712d)

Solutions

  1. Ensure each non-special yuanqin note has a numeric "type" (e.g. 1, 2, 4, 8, 0.5) written with a dot decimal separator.
  2. Regenerate the score from its source tool and re-export.
  3. Because ParseAsync catches this, the file becomes an InvalidScore; inspect score.Error to find which file failed.
  4. Add a per-note validator that reports the index of the offending note before parsing.

Example fix

// before
{"note":"Q","type":"quarter"}
// after
{"note":"Q","type":4}
Defensive patterns

Strategy: try-catch

Validate before calling

static bool HasValidYuanQinTypes(JToken notes)
{
    if (notes is not JArray arr) return false;
    foreach (var item in arr)
    {
        if (item is not JObject o) continue;
        var spl = o["spl"]?.ToString();
        if (spl is "^" or "&") continue;
        var t = o["type"]?.ToString();
        if (!double.TryParse(t, NumberStyles.Float, CultureInfo.InvariantCulture, out var d) || d <= 0)
            return false;
    }
    return true;
}

Type guard

static bool IsValidNoteType(JObject o) => double.TryParse(o["type"]?.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out _);

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 yuanqin note object whose "type" field is absent (note["type"]?.ToString() yields null/empty), set to a non-numeric string ("quarter"), or uses a comma decimal separator under a non-invariant convention.

Common situations: Scores edited in a spreadsheet that reformatted the type column to text; converter bug that omitted type on non-special notes; note objects where "type" was mistakenly stored as the note name.

Related errors


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