You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:将和弦匹配到对应歌词位置的格式转换实现

和弦歌词匹配格式转换解决方案

问题描述

我有若干行歌词与和弦,原格式为和弦行与对应歌词行交替出现(和弦行在上,歌词行在下),需要将和弦插入到歌词的对应位置,转换为「[和弦]歌词片段」的目标格式。目前已能提取和弦行,但无法准确定位和弦在对应歌词行的插入位置。以下是我的Dart代码实现,在此处遇到瓶颈,求C#、JS或Dart的解决方案:

_read({required String text}) {
    var result = text.split('\n');

    RegExp re = RegExp(r\"^[A-G\d#b\s/-/m]*$(?:\r\n)?\", caseSensitive: false, multiLine: true);

    var chords = '';
    for (final i in re.allMatches(text)) {
      chords = '$chords\n${(i[0]) ?? ''}';
    }
    var arrChords = chords.split('\n');

    for (int j = 0; j < arrChords.length; j++) {
      final i = arrChords[j];
      print(i);

      if (i.isNotEmpty) {
        // I am stuck from here
        var lineChords = i.split(' ');

        for (int k = lineChords.length - 1; k > 0; k--) {
          final e = lineChords[k];
          if (e.isNotEmpty) {
            var sRight = result[j + 1].substring(k);
            var sLeft = result[j + 1].substring(0, k);
            print(sLeft + sRight);
            // result[j + 1] = '$sLeft[$e]$sRight';
          }
        }
      }
    }

    // return result;
  }

原代码问题分析

  1. 用split(' ')分割和弦行,会把连续空格拆成空字符串,且无法保留和弦在原行的精确起始位置——和弦是靠空格对齐到歌词上方的,必须通过字符索引来匹配位置。
  2. 直接用和弦数组的下标k作为歌词的分割索引,逻辑错误,因为k和和弦在原行的实际位置没有对应关系。

解决方案

核心思路:通过正则匹配和弦在原行的起始索引,从后往前在歌词行插入和弦标记(避免插入后索引偏移)。

Dart 实现

List<String> formatChordsWithLyrics(String text) {
  final lines = text.split('\n').map((line) => line.trimRight()).toList();
  final result = <String>[];
  // 匹配纯和弦行(允许空格、常见和弦符号)
  final chordPattern = RegExp(r'^[A-G0-9#b/m\- ]+$');

  int i = 0;
  while (i < lines.length) {
    final currentLine = lines[i];
    // 判断当前行是和弦行,且下一行存在对应歌词
    if (chordPattern.hasMatch(currentLine) && i + 1 < lines.length) {
      final chordLine = currentLine;
      final lyricLine = lines[i + 1];
      final lyricBuffer = StringBuffer(lyricLine);

      // 匹配所有合法和弦,同时获取它们在和弦行的起始位置
      final chordMatches = RegExp(r'[A-G][#b]?(?:m|maj|min|dim|aug)?[0-9]?(?:\/[A-G][#b]?)?')
          .allMatches(chordLine);
      // 从后往前插入,避免插入后字符索引偏移
      for (final match in chordMatches.toList().reversed) {
        final chord = match.group(0)!;
        final insertPos = match.start;
        // 防止插入位置超出歌词长度
        if (insertPos <= lyricBuffer.length) {
          lyricBuffer.insert(insertPos, '[$chord]');
        }
      }

      result.add(lyricBuffer.toString());
      i += 2; // 跳过已处理的和弦行+歌词行
    } else {
      // 非和弦行直接加入结果
      result.add(currentLine);
      i += 1;
    }
  }
  return result;
}

// 示例用法
void main() {
  final input = '''C        G        Am
我爱着你 风里雨里 不问归期
F        C        G
往后余生 目光所至 都是你''';
  final formatted = formatChordsWithLyrics(input);
  formatted.forEach(print);
}

JavaScript 实现

function formatChordsWithLyrics(text) {
  const lines = text.split('\n').map(line => line.trimEnd());
  const result = [];
  const chordPattern = /^[A-G0-9#b/m\- ]+$/;
  // 匹配常见和弦格式
  const chordMatchPattern = /[A-G][#b]?(?:m|maj|min|dim|aug)?[0-9]?(?:\/[A-G][#b]?)?/g;

  let i = 0;
  while (i < lines.length) {
    const currentLine = lines[i];
    if (chordPattern.test(currentLine) && i + 1 < lines.length) {
      const chordLine = currentLine;
      let lyricLine = lines[i + 1];
      const matches = [];
      let match;
      // 收集所有和弦的位置与内容
      while ((match = chordMatchPattern.exec(chordLine)) !== null) {
        matches.push({ pos: match.index, chord: match[0] });
      }
      // 从后往前插入
      for (const { pos, chord } of matches.reverse()) {
        if (pos <= lyricLine.length) {
          lyricLine = lyricLine.slice(0, pos) + `[${chord}]` + lyricLine.slice(pos);
        }
      }
      result.push(lyricLine);
      i += 2;
    } else {
      result.push(currentLine);
      i += 1;
    }
  }
  return result;
}

// 示例
const input = `C        G        Am
我爱着你 风里雨里 不问归期
F        C        G
往后余生 目光所至 都是你`;
const formatted = formatChordsWithLyrics(input);
console.log(formatted.join('\n'));

C# 实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

public class ChordFormatter
{
    public static List<string> FormatChordsWithLyrics(string text)
    {
        var lines = text.Split(new[] { '\n' }, StringSplitOptions.None)
                        .Select(line => line.TrimEnd())
                        .ToList();
        var result = new List<string>();
        var chordPattern = new Regex(@"^[A-G0-9#b/m\- ]+$");
        var chordMatchPattern = new Regex(@"[A-G][#b]?(?:m|maj|min|dim|aug)?[0-9]?(?:\/[A-G][#b]?)?");

        int i = 0;
        while (i < lines.Count)
        {
            var currentLine = lines[i];
            if (chordPattern.IsMatch(currentLine) && i + 1 < lines.Count)
            {
                var chordLine = currentLine;
                var lyricLine = lines[i + 1];
                var matches = chordMatchPattern.Matches(chordLine);
                // 从后往前插入,避免索引偏移
                for (int j = matches.Count - 1; j >= 0; j--)
                {
                    var match = matches[j];
                    var chord = match.Value;
                    var insertPos = match.Index;
                    if (insertPos <= lyricLine.Length)
                    {
                        lyricLine = lyricLine.Insert(insertPos, $"[{chord}]");
                    }
                }
                result.Add(lyricLine);
                i += 2;
            }
            else
            {
                result.Add(currentLine);
                i += 1;
            }
        }
        return result;
    }

    // 示例用法
    public static void Main()
    {
        var input = @"C        G        Am
我爱着你 风里雨里 不问归期
F        C        G
往后余生 目光所至 都是你";
        var formatted = FormatChordsWithLyrics(input);
        foreach (var line in formatted)
        {
            Console.WriteLine(line);
        }
    }
}

内容的提问来源于stack exchange,提问作者Fadhly Permata

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 12:19:56