Skip to content

Commit 508f325

Browse files
committed
feat(parser): 增强 ABC 解析器以支持分数时值和简写分数解析
- 更新音符和休止符的解析逻辑,支持整数、分数和简写形式的时值表示。 - 新增 parseDurationString 方法,处理不同格式的时值字符串,提升解析的灵活性。 - 更新文档注释,明确支持的时值格式,确保用户理解解析规则。 该变更提升了 ABC 解析器对音符时值的处理能力,确保乐谱信息的准确解析和存储。
1 parent 5222e43 commit 508f325

File tree

2 files changed

+68
-14
lines changed

2 files changed

+68
-14
lines changed

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

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,14 @@ export class AbcElementParser {
4343
* @example
4444
* ```typescript
4545
* // 解析音符
46-
* parser.parseElement('C'); // SNParserNote
46+
* parser.parseElement('C'); // SNParserNote (默认长度)
4747
* parser.parseElement('C#'); // SNParserNote (升C)
48-
* parser.parseElement('C4'); // SNParserNote (四分音符C)
48+
* parser.parseElement('C4'); // SNParserNote (4倍默认长度,如默认为1/4时为全音符)
49+
* parser.parseElement('C/2'); // SNParserNote (1/2倍默认长度,如默认为1/4时为八分音符)
4950
*
5051
* // 解析休止符
51-
* parser.parseElement('z'); // SNParserRest
52-
* parser.parseElement('z4'); // SNParserRest (四分休止符)
52+
* parser.parseElement('z'); // SNParserRest (默认长度)
53+
* parser.parseElement('z4'); // SNParserRest (4倍默认长度)
5354
*
5455
* // 解析连音线
5556
* parser.parseElement('-'); // SNParserTie
@@ -99,8 +100,9 @@ export class AbcElementParser {
99100
}
100101

101102
// 4. 解析音符(带装饰符)
103+
// 支持整数(C4)、分数(C/2, C3/2)和简写(C/)三种时值表示
102104
const noteMatch = noteStr.match(
103-
/^(\^+\/?|_+\/?|=?)([A-Ga-g])([,']*)(\d*)(\.*)$/,
105+
/^(\^+\/?|_+\/?|=?)([A-Ga-g])([,']*)(\d+\/\d+|\/\d*|\d*)(\.*)$/,
104106
);
105107
if (noteMatch) {
106108
const note = this.parseNote(
@@ -159,12 +161,14 @@ export class AbcElementParser {
159161

160162
if (timeUnit) {
161163
const restStr = trimmed.slice(1);
162-
const durationStr = restStr.match(/^(\d+)/)?.[1];
164+
// 支持整数(z4)、分数(z/2, z3/2)和简写(z/)三种时值表示
165+
const durationStr = restStr.match(/^(\d+\/\d+|\/\d*|\d+)/)?.[1];
163166
const dotCount = (restStr.match(/\./g) || []).length;
164167

165-
const noteValue = durationStr
166-
? 1 / parseInt(durationStr, 10)
167-
: defaultNoteLength || 1 / 4;
168+
const noteValue = this.parseDurationString(
169+
durationStr,
170+
defaultNoteLength,
171+
);
168172

169173
const dottedNoteValue = this.calculateDottedNoteValue(
170174
noteValue,
@@ -206,9 +210,10 @@ export class AbcElementParser {
206210
// 解析时值
207211
let duration: number;
208212
if (timeUnit) {
209-
const noteValue = durationStr
210-
? 1 / parseInt(durationStr, 10)
211-
: defaultNoteLength || 1 / 4;
213+
const noteValue = this.parseDurationString(
214+
durationStr,
215+
defaultNoteLength,
216+
);
212217

213218
const dotCount = (trimmed.match(/\./g) || []).length;
214219
const dottedNoteValue = this.calculateDottedNoteValue(
@@ -257,6 +262,54 @@ export class AbcElementParser {
257262
}
258263
}
259264

265+
/**
266+
* 解析时值字符串
267+
*
268+
* 支持三种格式:
269+
* - 整数:4 表示 4倍默认长度(如 C4 表示全音符)
270+
* - 分数:3/2 表示 3/2倍默认长度
271+
* - 简写分数:/ 或 /2 表示 1/2倍默认长度
272+
*
273+
* @param durationStr - 时值字符串(如 "4", "/2", "3/2", "/")
274+
* @param defaultNoteLength - 默认音符长度
275+
* @returns 音符时值(相对于全音符的比例)
276+
*/
277+
private parseDurationString(
278+
durationStr: string | undefined,
279+
defaultNoteLength?: number,
280+
): number {
281+
const defaultLength = defaultNoteLength || 1 / 4;
282+
283+
if (!durationStr) {
284+
return defaultLength;
285+
}
286+
287+
// 处理分数形式:3/2, /2, /
288+
if (durationStr.includes('/')) {
289+
if (durationStr === '/') {
290+
// / 简写表示 1/2
291+
return defaultLength * 0.5;
292+
}
293+
294+
const parts = durationStr.split('/');
295+
if (parts[0] === '') {
296+
// /2 形式:表示默认长度的 1/2
297+
// 例如:L:1/4 时,/2 = (1/4) × (1/2) = 1/8(八分音符)
298+
const denominator = parseInt(parts[1], 10);
299+
return defaultLength / denominator;
300+
} else {
301+
// 3/2 形式表示 3/2
302+
const numerator = parseInt(parts[0], 10);
303+
const denominator = parseInt(parts[1], 10);
304+
return defaultLength * (numerator / denominator);
305+
}
306+
}
307+
308+
// 处理整数形式:4 表示 4倍默认长度
309+
const multiplier = parseInt(durationStr, 10);
310+
return defaultLength * multiplier;
311+
}
312+
260313
/**
261314
* 计算带附点的音符时值
262315
*

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,11 @@ export class AbcTokenizer {
262262
const len = measureData.length;
263263
let noteEnd = startPos;
264264

265-
// 音符后可跟:变音符号(^_=)、变音记号(#b)、八度符号(',")、时值(数字)、附点(.)
265+
// 音符后可跟:变音符号(^_=)、变音记号(#b)、八度符号(',")、时值(数字/分数)、附点(.)
266+
// 注意:必须包含 / 字符以支持分数时值(如 C/2, C3/2)
266267
while (
267268
noteEnd + 1 < len &&
268-
/[\^_=#b',.\d]/.test(measureData[noteEnd + 1])
269+
/[\^_=#b',./\d]/.test(measureData[noteEnd + 1])
269270
) {
270271
noteEnd++;
271272
}

0 commit comments

Comments
 (0)