Skip to content

Commit 49e6454

Browse files
committed
feat(parser): 增强 ABC 解析器以支持属性的向上查找和简化解析逻辑
- 新增 findInheritedProp 方法,统一向上查找属性的逻辑,提升代码复用性。 - 更新 getTimeSignature、getKeySignature、getTempo 和 getTimeUnit 方法,使用 findInheritedProp 进行属性查找,简化实现。 - 引入 id-generator 模块,支持 ID 的生成逻辑,确保解析过程中的唯一性。 该变更提升了 ABC 解析器的灵活性和可维护性,确保乐谱信息的准确解析和存储。
1 parent ec6e6dc commit 49e6454

File tree

11 files changed

+1511
-1184
lines changed

11 files changed

+1511
-1184
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* 统一的 ID 生成器工具
3+
*
4+
* 提供全局唯一 ID 生成功能,支持多种使用方式:
5+
* - 全局单例模式(默认)
6+
* - 独立实例模式(用于测试或多实例场景)
7+
*/
8+
9+
/**
10+
* ID 生成器类
11+
*/
12+
class IdGenerator {
13+
private counter = 0;
14+
15+
/**
16+
* 生成下一个 ID
17+
*
18+
* @param prefix - ID 前缀(如 'note', 'measure', 'section')
19+
* @returns 生成的 ID(如 'note-1', 'measure-2')
20+
*/
21+
next(prefix: string): string {
22+
return `${prefix}-${++this.counter}`;
23+
}
24+
25+
/**
26+
* 重置计数器(主要用于测试)
27+
*/
28+
reset(): void {
29+
this.counter = 0;
30+
}
31+
32+
/**
33+
* 获取当前计数器值
34+
*/
35+
getCounter(): number {
36+
return this.counter;
37+
}
38+
}
39+
40+
/**
41+
* 全局单例 ID 生成器
42+
*/
43+
const globalIdGenerator = new IdGenerator();
44+
45+
/**
46+
* 生成下一个全局唯一 ID
47+
*
48+
* @param prefix - ID 前缀(如 'note', 'measure', 'section')
49+
* @returns 生成的 ID(如 'note-1', 'measure-2')
50+
*
51+
* @example
52+
* ```typescript
53+
* const noteId = generateId('note'); // 'note-1'
54+
* const measureId = generateId('measure'); // 'measure-2'
55+
* const restId = generateId('rest'); // 'rest-3'
56+
* ```
57+
*/
58+
export function generateId(prefix: string): string {
59+
return globalIdGenerator.next(prefix);
60+
}
61+
62+
/**
63+
* 重置全局 ID 计数器(主要用于测试)
64+
*
65+
* @example
66+
* ```typescript
67+
* resetIdCounter();
68+
* const id1 = generateId('note'); // 'note-1'
69+
* const id2 = generateId('note'); // 'note-2'
70+
* ```
71+
*/
72+
export function resetIdCounter(): void {
73+
globalIdGenerator.reset();
74+
}
75+
76+
/**
77+
* 获取当前全局 ID 计数器值
78+
*
79+
* @returns 当前计数器值
80+
*/
81+
export function getIdCounter(): number {
82+
return globalIdGenerator.getCounter();
83+
}
84+
85+
/**
86+
* 创建独立的 ID 生成器实例
87+
*
88+
* 用于需要独立 ID 命名空间的场景(如测试、多实例)
89+
*
90+
* @returns ID 生成器实例
91+
*
92+
* @example
93+
* ```typescript
94+
* const gen1 = createIdGenerator();
95+
* const gen2 = createIdGenerator();
96+
*
97+
* gen1.next('note'); // 'note-1'
98+
* gen1.next('note'); // 'note-2'
99+
* gen2.next('note'); // 'note-1' (独立计数)
100+
* ```
101+
*/
102+
export function createIdGenerator(): IdGenerator {
103+
return new IdGenerator();
104+
}
105+
106+
/**
107+
* 导出类型(用于类型声明)
108+
*/
109+
export type { IdGenerator };
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './time-unit';
2+
export * from './id-generator';

packages/simple-notation/src/data/node/node.ts

Lines changed: 50 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -109,94 +109,82 @@ export class SNParserNode<
109109
}
110110

111111
/**
112-
* 向上查找拍号(若未设置则返回 4/4)
112+
* 通用的向上查找属性方法
113113
*
114-
* 从当前节点开始向上追溯父节点,查找最近定义的拍号
114+
* 从父节点开始向上追溯,查找最近定义的属性
115115
*
116-
* @returns 拍号对象,包含 numerator(分子)和 denominator(分母)
116+
* @param propName - 属性名称
117+
* @param defaultValue - 默认值
118+
* @param validator - 可选的验证函数
119+
* @returns 找到的属性值或默认值
117120
*/
118-
getTimeSignature(): { numerator: number; denominator: number } {
119-
// 从当前节点开始向上遍历父节点链
121+
protected findInheritedProp<T>(
122+
propName: string,
123+
defaultValue: T,
124+
validator?: (value: any) => boolean,
125+
): T {
120126
let current: SNParserNode | undefined = this.parent;
121127
while (current) {
122-
const props = current.props as SNMusicProps | SNScoreProps | undefined;
123-
if (
124-
props?.timeSignature &&
125-
typeof props.timeSignature.numerator === 'number' &&
126-
typeof props.timeSignature.denominator === 'number'
127-
) {
128-
return {
129-
numerator: props.timeSignature.numerator,
130-
denominator: props.timeSignature.denominator,
131-
};
128+
const props = current.props as any;
129+
const value = props?.[propName];
130+
131+
if (value !== undefined) {
132+
// 如果提供了验证器,使用验证器检查
133+
if (validator ? validator(value) : true) {
134+
return value as T;
135+
}
132136
}
133137
current = current.parent;
134138
}
135-
// 如果找不到,返回默认值 4/4
136-
return { numerator: 4, denominator: 4 };
139+
return defaultValue;
137140
}
138141

139142
/**
140-
* 向上查找调号(若未设置则返回 C 大调
143+
* 向上查找拍号(若未设置则返回 4/4
141144
*
142-
* 从当前节点开始向上追溯父节点,查找最近定义的调号
145+
* @returns 拍号对象,包含 numerator(分子)和 denominator(分母)
146+
*/
147+
getTimeSignature(): { numerator: number; denominator: number } {
148+
return this.findInheritedProp(
149+
'timeSignature',
150+
{ numerator: 4, denominator: 4 },
151+
(value) =>
152+
typeof value?.numerator === 'number' &&
153+
typeof value?.denominator === 'number',
154+
);
155+
}
156+
157+
/**
158+
* 向上查找调号(若未设置则返回 C 大调)
143159
*
144160
* @returns 调号对象,包含 letter(字母)和 symbol(符号)
145161
*/
146162
getKeySignature(): { letter: string; symbol: 'natural' | 'sharp' | 'flat' } {
147-
// 从当前节点开始向上遍历父节点链
148-
let current: SNParserNode | undefined = this.parent;
149-
while (current) {
150-
const props = current.props as SNMusicProps | SNScoreProps | undefined;
151-
if (props?.keySignature) {
152-
return props.keySignature;
153-
}
154-
current = current.parent;
155-
}
156-
// 如果找不到,返回默认值 C 大调
157-
return { letter: 'C', symbol: 'natural' };
163+
return this.findInheritedProp('keySignature', {
164+
letter: 'C',
165+
symbol: 'natural',
166+
});
158167
}
159168

160169
/**
161170
* 向上查找速度(若未设置则返回 120 BPM)
162171
*
163-
* 从当前节点开始向上追溯父节点,查找最近定义的速度
164-
*
165172
* @returns 速度对象,包含 value(值)和 unit(单位)
166173
*/
167174
getTempo(): { value: number; unit: 'BPM' } {
168-
// 从当前节点开始向上遍历父节点链
169-
let current: SNParserNode | undefined = this.parent;
170-
while (current) {
171-
const props = current.props as SNMusicProps | SNScoreProps | undefined;
172-
if (props?.tempo) {
173-
return props.tempo;
174-
}
175-
current = current.parent;
176-
}
177-
// 如果找不到,返回默认值 120 BPM
178-
return { value: 120, unit: 'BPM' };
175+
return this.findInheritedProp('tempo', { value: 120, unit: 'BPM' });
179176
}
180177

181178
/**
182179
* 向上查找时间单位(若未设置则返回默认值)
183180
*
184-
* 从当前节点开始向上追溯父节点,查找最近定义的时间单位
185-
*
186181
* @returns 时间单位对象,包含 ticksPerWhole 和 ticksPerBeat
187182
*/
188183
getTimeUnit(): { ticksPerWhole: number; ticksPerBeat: number } {
189-
// 从当前节点开始向上遍历父节点链
190-
let current: SNParserNode | undefined = this.parent;
191-
while (current) {
192-
const props = current.props as SNMusicProps | SNScoreProps | undefined;
193-
if (props?.timeUnit) {
194-
return props.timeUnit;
195-
}
196-
current = current.parent;
197-
}
198-
// 如果找不到,返回默认值
199-
return { ticksPerWhole: 48, ticksPerBeat: 12 };
184+
return this.findInheritedProp('timeUnit', {
185+
ticksPerWhole: 48,
186+
ticksPerBeat: 12,
187+
});
200188
}
201189

202190
/**
@@ -215,20 +203,6 @@ export class SNParserNode<
215203
transpose?: number;
216204
[key: string]: unknown;
217205
}> {
218-
// 从当前节点开始向上遍历父节点链
219-
// 注意:从子节点到父节点遍历,子节点的定义会覆盖父节点的定义
220-
let current: SNParserNode | undefined = this.parent;
221-
const voicesMap = new Map<
222-
string,
223-
{
224-
voiceNumber: string;
225-
name?: string;
226-
clef?: 'treble' | 'bass' | 'alto' | 'tenor';
227-
transpose?: number;
228-
[key: string]: unknown;
229-
}
230-
>();
231-
232206
// 收集所有父节点的声部定义(从子节点到父节点)
233207
const allVoices: Array<{
234208
voiceNumber: string;
@@ -238,22 +212,23 @@ export class SNParserNode<
238212
[key: string]: unknown;
239213
}> = [];
240214

215+
let current: SNParserNode | undefined = this.parent;
241216
while (current) {
242217
const props = current.props as SNScoreProps | undefined;
243218
if (props?.voices && props.voices.length > 0) {
244-
// 收集声部定义(从子节点到父节点)
245219
allVoices.push(...props.voices);
246220
}
247221
current = current.parent;
248222
}
249223

250-
// 从子节点到父节点合并声部定义(子节点的定义会覆盖父节点的定义
251-
// 由于 allVoices 是从子节点到父节点收集的,所以后面的定义会覆盖前面的定义
224+
// 按 voiceNumber 合并(子节点定义覆盖父节点定义
225+
const voicesMap = new Map<string, (typeof allVoices)[0]>();
252226
for (const voice of allVoices) {
253-
voicesMap.set(voice.voiceNumber, { ...voice });
227+
if (!voicesMap.has(voice.voiceNumber)) {
228+
voicesMap.set(voice.voiceNumber, { ...voice });
229+
}
254230
}
255231

256-
// 返回声部定义数组
257232
return Array.from(voicesMap.values());
258233
}
259234
}

0 commit comments

Comments
 (0)