babalae/better-genshin-impact · error · FormatException

{format} 曲谱的 notes 必须是字符串

Error message

{format} 曲谱的 notes 必须是字符串

What it means

A FormatException from GetNotesString thrown when the "notes" token of a MIDI-JSON or keyboard-format score is not a JSON string. For these two formats the parser expects the entire notes payload to be a single delimited string (not an array/object); a different JTokenType is rejected.

Source

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

        return token == null || token.Type == JTokenType.Null
            ? fallback
            : token.ToString();
    }

    private static double GetDouble(JObject json, string propertyName, double fallback)
    {
        var value = GetString(json, propertyName, fallback.ToString(CultureInfo.InvariantCulture));
        return double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)
               && result > 0
            ? result
            : fallback;
    }

    private static string GetNotesString(JToken notes, MusicScoreFormat format)
    {
        return notes.Type == JTokenType.String
            ? notes.Value<string>() ?? string.Empty
            : throw new FormatException($"{format} 曲谱的 notes 必须是字符串");
    }

    private static PerformanceScore CreateInvalidScore(string path, string rootFolder, string error)
    {
        return new PerformanceScore
        {
            FullPath = path,
            RelativePath = Path.GetRelativePath(rootFolder, path),
            Name = Path.GetFileNameWithoutExtension(path),
            Format = Path.GetExtension(path).Equals(".json", StringComparison.OrdinalIgnoreCase)
                ? MusicScoreFormat.YuanQin
                : MusicScoreFormat.MidiFile,
            Error = error
        };
    }

    [GeneratedRegex(@"^(?<status>[DU])(?<keys>[A-Z@]+)(?<ticks>\d+)$", RegexOptions.CultureInvariant)]
    private static partial Regex MidiJsonEventRegex();

View on GitHub (pinned to a7cb36712d)

Solutions

  1. Set the score "type" to match the notes shape: use "yuanqin" for an array, or "midi"/"keyboard" for a string.
  2. If the type is correct, convert the notes value to a single string in the format the parser expects.
  3. Route through ParseAsync to surface InvalidScore.Error with the format name.
  4. Add a type-vs-notes-shape validator before parsing.

Example fix

// before (midi type but array notes)
{"type":"midi","notes":["DQ:0"]}
// after
{"type":"midi","notes":"DQ:0|UQ:480"}
Defensive patterns

Strategy: validation

Validate before calling

static bool NotesShapeMatchesType(string type, JToken notes)
    => type switch
    {
        "yuanqin" => notes is JArray,
        "midi" or "keyboard" => notes.Type == JTokenType.String,
        _ => false
    };

Type guard

static bool IsNotesString(JToken t) => t.Type == JTokenType.String;

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 JSON score with type "midi" or "keyboard" whose "notes" is an array or object (e.g. notes:[...] or notes:{...}) instead of a string. Yuanqin does not hit this path because it uses ParseYuanQinTimeline which expects a JArray.

Common situations: Using a yuanqin-style notes array with type set to "midi"; exporter that emits notes as an array for a format that expects a string; copy-paste of a notes array into a midi/keyboard score.

Related errors


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