Skip to content

Commit 66a67d4

Browse files
committed
feat(alphatex): beat level lyrics with importer/exporter
1 parent 84903a6 commit 66a67d4

File tree

4 files changed

+66
-19
lines changed

4 files changed

+66
-19
lines changed

src/exporter/AlphaTexExporter.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,8 +524,6 @@ export class AlphaTexExporter extends ScoreExporter {
524524
this.writeChordTo(writer, chord);
525525
}
526526
}
527-
528-
// TODO: lyrics
529527
}
530528

531529
private writeChordTo(writer: AlphaTexWriter, c: Chord) {
@@ -949,6 +947,18 @@ export class AlphaTexExporter extends ScoreExporter {
949947
writer.writeString(beat.text);
950948
}
951949

950+
if (beat.lyrics != null && beat.lyrics!.length > 0) {
951+
if (beat.lyrics.length > 1) {
952+
for (let i = 0; i < beat.lyrics.length; i++) {
953+
writer.writeGroupItem(`lyrics ${i} `);
954+
writer.writeString(beat.lyrics[i]);
955+
}
956+
} else {
957+
writer.writeGroupItem('lyrics ');
958+
writer.writeString(beat.lyrics[0]);
959+
}
960+
}
961+
952962
switch (beat.graceType) {
953963
case GraceType.OnBeat:
954964
writer.writeGroupItem('gr ob');

src/importer/AlphaTexImporter.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -302,10 +302,7 @@ export class AlphaTexLexer {
302302
// unicode handling
303303

304304
// https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-ecmascript-language-types-string-type
305-
if (
306-
IOHelper.isLeadingSurrogate(previousCodepoint) &&
307-
IOHelper.isTrailingSurrogate(codepoint)
308-
) {
305+
if (IOHelper.isLeadingSurrogate(previousCodepoint) && IOHelper.isTrailingSurrogate(codepoint)) {
309306
codepoint = (previousCodepoint - 0xd800) * 0x400 + (codepoint - 0xdc00) + 0x10000;
310307
s += String.fromCodePoint(codepoint);
311308
} else if (IOHelper.isLeadingSurrogate(codepoint)) {
@@ -316,7 +313,7 @@ export class AlphaTexLexer {
316313
s += String.fromCodePoint(previousCodepoint);
317314
}
318315

319-
if(codepoint > 0){
316+
if (codepoint > 0) {
320317
s += String.fromCodePoint(codepoint);
321318
}
322319
}
@@ -2132,6 +2129,29 @@ export class AlphaTexImporter extends ScoreImporter {
21322129
return false;
21332130
}
21342131
beat.text = this.syData as string;
2132+
} else if (syData === 'lyrics') {
2133+
this.sy = this.newSy();
2134+
2135+
let lyricsLine = 0;
2136+
if (this.sy === AlphaTexSymbols.Number) {
2137+
lyricsLine = this.syData as number;
2138+
this.sy = this.newSy();
2139+
}
2140+
2141+
if (this.sy !== AlphaTexSymbols.String) {
2142+
this.error('lyrics', AlphaTexSymbols.String, true);
2143+
return false;
2144+
}
2145+
2146+
if (!beat.lyrics) {
2147+
beat.lyrics = [];
2148+
}
2149+
2150+
while (beat.lyrics!.length <= lyricsLine) {
2151+
beat.lyrics.push('');
2152+
}
2153+
2154+
beat.lyrics[lyricsLine] = this.syData as string;
21352155
} else if (syData === 'dd') {
21362156
beat.dots = 2;
21372157
} else if (syData === 'd') {

test/importer/AlphaTexImporter.test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2298,6 +2298,34 @@ describe('AlphaTexImporterTest', () => {
22982298
it('utf16', () => {
22992299
const score = parseTex(`\\title "🤘🏻" .`);
23002300

2301-
expect(score.title).to.equal("🤘🏻");
2301+
expect(score.title).to.equal('🤘🏻');
2302+
});
2303+
2304+
it('beat-lyrics', () => {
2305+
const score = parseTex(`
2306+
.
2307+
3.3.3
2308+
3.3.3 {lyrics "A"}
2309+
3.3.3 {lyrics 0 "B C D"}
2310+
3.3.3 {lyrics 0 "E" lyrics 1 "F" lyrics 2 "G"}
2311+
`);
2312+
2313+
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].lyrics).to.not.be.ok;
2314+
2315+
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].lyrics).to.be.ok;
2316+
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].lyrics!.length).to.equal(1);
2317+
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].lyrics![0]).to.equal('A');
2318+
2319+
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[2].lyrics).to.be.ok;
2320+
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[2].lyrics!.length).to.equal(1);
2321+
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[2].lyrics![0]).to.equal('B C D');
2322+
2323+
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[3].lyrics).to.be.ok;
2324+
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[3].lyrics!.length).to.equal(3);
2325+
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[3].lyrics![0]).to.equal('E');
2326+
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[3].lyrics![1]).to.equal('F');
2327+
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[3].lyrics![2]).to.equal('G');
2328+
2329+
testExportRoundtrip(score);
23022330
});
23032331
});

test/model/ComparisonHelpers.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,33 +81,22 @@ export class ComparisonHelpers {
8181

8282
ignoreKeys = ignoreKeys ?? [];
8383
ignoreKeys.push(
84-
// score level
85-
'stylesheet',
86-
87-
// track level
88-
'color',
89-
9084
// staff level
9185
'chords', // ids differ
9286
'percussionarticulations', // unsupported
9387

9488
// playback info
9589
'port',
96-
'volume',
9790
'primarychannel',
9891
'secondarychannel',
9992
'balance',
10093

10194
// beat level
10295
'chordid', // ids differ
103-
'lyrics', // missing feature
104-
'preferredbeamdirection',
10596
'timer', // value will be calculated, only showTimer is important here
10697

10798
// note level
108-
'accidentalmode',
10999
'ratioposition',
110-
'percussionarticulation',
111100

112101
// for now ignore the automations as they get reorganized from beat to masterbar level
113102
// which messes with the 1:1 validation

0 commit comments

Comments
 (0)