Skip to content

Commit 1dc7170

Browse files
committed
feat(parser): 更新 ABC 解析器以支持声部元数据的完整解析
- 在 SNVoiceMeta 接口中添加 voiceNumber 和 name 字段,以增强声部的元数据定义。 - 优化 AbcParser 中的声部信息解析逻辑,确保从 V: 定义中提取的名称、谱号和移调信息能够正确合并。 - 更新默认声部创建逻辑,确保在缺失信息时使用 props.voices 中的定义进行补充。 该变更提升了 ABC 解析器对声部元数据的处理能力,确保乐谱解析的准确性和灵活性。
1 parent 21a2761 commit 1dc7170

2 files changed

Lines changed: 99 additions & 19 deletions

File tree

packages/simple-notation/src/data/model/parser.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,16 @@ export interface SNParserMeta {
5858
}
5959

6060
export interface SNVoiceMeta {
61-
clef: SNVoiceMetaClef; // 谱号(高音/低音/中音/次中音)
62-
transpose?: number; // 移调半音数(如 2=升大二度)
63-
keySignature?: SNKeySignature; // 声部专属调号(覆盖上层,极少用)
61+
/** 声部编号(如 "1", "2") */
62+
voiceNumber: string;
63+
/** 声部名称(如 "Melody", "Harmony") */
64+
name?: string;
65+
/** 谱号(高音/低音/中音/次中音) */
66+
clef: SNVoiceMetaClef;
67+
/** 移调半音数(如 2=升大二度) */
68+
transpose?: number;
69+
/** 声部专属调号(覆盖上层,极少用) */
70+
keySignature?: SNKeySignature;
6471
[key: string]: unknown;
6572
}
6673

packages/simple-notation/src/data/parser/abc-parser.ts

Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -516,21 +516,52 @@ export class AbcParser extends BaseParser<SNAbcInput> {
516516
});
517517

518518
// 创建声部节点(只包含元数据,不包含乐谱内容)
519-
const name =
520-
metaLine.match(/name="([^"]+)"/)?.[1] || `Voice ${voiceNumber}`;
521-
const clefMatch = metaLine.match(/clef=([a-z]+)/);
522-
const clef: SNVoiceMetaClef =
523-
(clefMatch?.[1] as SNVoiceMetaClef) || 'treble';
519+
// 从 V: 定义中解析所有信息
520+
// 注意:metaLine 可能包含前导空格,需要 trim
521+
const trimmedMetaLine = (metaLine || '').trim();
522+
523+
// 解析 name(支持 name="..." 格式)
524+
const nameMatch = trimmedMetaLine.match(/name\s*=\s*"([^"]+)"/);
525+
const name = nameMatch?.[1] || undefined;
526+
527+
// 解析 clef(支持 clef=... 格式)
528+
const clefMatch = trimmedMetaLine.match(/clef\s*=\s*([a-z]+)/);
529+
const clef: SNVoiceMetaClef | undefined =
530+
(clefMatch?.[1] as SNVoiceMetaClef) || undefined;
531+
532+
// 解析 transpose(支持 transpose=... 格式)
533+
const transposeMatch = trimmedMetaLine.match(
534+
/transpose\s*=\s*([+-]?\d+)/,
535+
);
536+
const transpose = transposeMatch
537+
? parseInt(transposeMatch[1], 10)
538+
: undefined;
539+
540+
// 尝试从 props.voices 中获取声部定义(向上覆盖)
541+
// 如果 props.voices 中有该声部的定义,使用它来覆盖或补充信息
542+
const voiceDefinition = props.voices?.find(
543+
(v) => v.voiceNumber === voiceNumber,
544+
);
524545

525-
const voiceId = `voice-${voiceNumber}-${name.toLowerCase().replace(/\W+/g, '-')}`;
546+
// 合并信息:V: 定义优先,props.voices 作为补充
547+
// V: 定义中的值优先,如果 V: 定义中没有,则使用 props.voices 中的值
548+
const finalName =
549+
name || voiceDefinition?.name || `Voice ${voiceNumber}`;
550+
const finalClef = clef || voiceDefinition?.clef || 'treble';
551+
const finalTranspose = transpose ?? voiceDefinition?.transpose;
552+
553+
const voiceId = `voice-${voiceNumber}-${finalName.toLowerCase().replace(/\W+/g, '-')}`;
526554
const voice = new SNParserVoice({
527555
id: voiceId || this.getNextId('voice'),
528556
originStr: voiceHeader,
529557
});
530558

559+
// 设置完整的 meta 信息
531560
voice.setMeta({
532-
clef,
533-
transpose: undefined,
561+
voiceNumber,
562+
name: finalName,
563+
clef: finalClef,
564+
transpose: finalTranspose,
534565
});
535566

536567
voiceNodesMap.set(voiceNumber, voice);
@@ -599,13 +630,20 @@ export class AbcParser extends BaseParser<SNAbcInput> {
599630
currentVoiceId = voiceNumber;
600631
} else {
601632
// 如果声部不存在,创建默认声部
633+
// 尝试从 props.voices 中获取声部定义(向上覆盖)
634+
const voiceDefinition = props.voices?.find(
635+
(v) => v.voiceNumber === voiceNumber,
636+
);
637+
602638
const defaultVoice = new SNParserVoice({
603639
id: this.getNextId('voice'),
604640
originStr: `V:${voiceNumber}`,
605641
});
606642
defaultVoice.setMeta({
607-
clef: 'treble',
608-
transpose: undefined,
643+
voiceNumber,
644+
name: voiceDefinition?.name || `Voice ${voiceNumber}`,
645+
clef: voiceDefinition?.clef || 'treble',
646+
transpose: voiceDefinition?.transpose,
609647
});
610648
voiceNodesMap.set(voiceNumber, defaultVoice);
611649
currentVoiceId = voiceNumber;
@@ -625,13 +663,20 @@ export class AbcParser extends BaseParser<SNAbcInput> {
625663
// 如果没有当前声部,创建默认声部
626664
const defaultVoiceNumber = '1';
627665
if (!voiceNodesMap.has(defaultVoiceNumber)) {
666+
// 尝试从 props.voices 中获取声部定义(向上覆盖)
667+
const voiceDefinition = props.voices?.find(
668+
(v) => v.voiceNumber === defaultVoiceNumber,
669+
);
670+
628671
const defaultVoice = new SNParserVoice({
629672
id: this.getNextId('voice'),
630673
originStr: `V:${defaultVoiceNumber}`,
631674
});
632675
defaultVoice.setMeta({
633-
clef: 'treble',
634-
transpose: undefined,
676+
voiceNumber: defaultVoiceNumber,
677+
name: voiceDefinition?.name || `Voice ${defaultVoiceNumber}`,
678+
clef: voiceDefinition?.clef || 'treble',
679+
transpose: voiceDefinition?.transpose,
635680
});
636681
voiceNodesMap.set(defaultVoiceNumber, defaultVoice);
637682
}
@@ -975,23 +1020,49 @@ export class AbcParser extends BaseParser<SNAbcInput> {
9751020

9761021
const { metaLine = '', measuresContent = voiceData.trim() } =
9771022
voiceMatch?.groups || {};
978-
const voiceNumber = voiceMatch
979-
? voiceMatch[1]
980-
: Math.floor(Math.random() * 100).toString();
9811023

1024+
// 如果正则匹配失败,尝试从 voiceData 中提取 voiceNumber
1025+
// 这种情况不应该发生,但如果发生了,应该使用默认值而不是随机数
1026+
let voiceNumber = '1';
1027+
if (voiceMatch) {
1028+
voiceNumber = voiceMatch[1];
1029+
} else {
1030+
// 尝试从 voiceData 中查找 V:数字 格式
1031+
const fallbackMatch = voiceData.match(/V:\s*(\d+)/);
1032+
if (fallbackMatch) {
1033+
voiceNumber = fallbackMatch[1];
1034+
} else {
1035+
// 如果完全找不到,使用默认值 "1" 而不是随机数
1036+
voiceNumber = '1';
1037+
}
1038+
}
1039+
1040+
// 从 V: 定义中解析所有信息
9821041
const name = (
9831042
metaLine.match(/name="([^"]+)"/)?.[1] || `Voice ${voiceNumber}`
9841043
).trim();
9851044
const clefMatch = metaLine.match(/clef=([a-z]+)/);
9861045
const clef: SNVoiceMetaClef =
9871046
(clefMatch?.[1] as SNVoiceMetaClef) || 'treble';
1047+
const transposeMatch = metaLine.match(/transpose=([+-]?\d+)/);
1048+
const transpose = transposeMatch
1049+
? parseInt(transposeMatch[1], 10)
1050+
: undefined;
9881051

9891052
const id = `voice-${voiceNumber}-${name.toLowerCase().replace(/\W+/g, '-')}`;
9901053
const voice = new SNParserVoice({
9911054
id: id || this.getNextId('voice'),
9921055
originStr: voiceData,
9931056
});
9941057

1058+
// 设置完整的 meta 信息
1059+
voice.setMeta({
1060+
voiceNumber,
1061+
name,
1062+
clef,
1063+
transpose,
1064+
});
1065+
9951066
const lyricLines: Array<{
9961067
verse: number;
9971068
content: string;
@@ -1083,8 +1154,10 @@ export class AbcParser extends BaseParser<SNAbcInput> {
10831154

10841155
return voice
10851156
.setMeta({
1157+
voiceNumber,
1158+
name,
10861159
clef,
1087-
transpose: undefined,
1160+
transpose,
10881161
})
10891162
.addChildren(
10901163
musicMeasures.map((measureData, i) => {

0 commit comments

Comments
 (0)