@k-l-lambda/lilylet 0.1.63 → 0.1.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/lib/lilylet/grammar.jison.js +1 -10
  2. package/lib/lilylet/meiEncoder.js +58 -40
  3. package/lib/source/abc/abc.d.ts +102 -0
  4. package/lib/source/abc/abc.js +25 -0
  5. package/lib/source/abc/parser.d.ts +3 -0
  6. package/lib/source/abc/parser.js +6 -0
  7. package/lib/source/lilylet/abcDecoder.d.ts +25 -0
  8. package/lib/source/lilylet/abcDecoder.js +1035 -0
  9. package/lib/source/lilylet/index.d.ts +10 -0
  10. package/lib/source/lilylet/index.js +10 -0
  11. package/lib/source/lilylet/lilypondDecoder.d.ts +29 -0
  12. package/lib/source/lilylet/lilypondDecoder.js +1223 -0
  13. package/lib/source/lilylet/lilypondEncoder.d.ts +34 -0
  14. package/lib/source/lilylet/lilypondEncoder.js +893 -0
  15. package/lib/source/lilylet/meiEncoder.d.ts +8 -0
  16. package/lib/source/lilylet/meiEncoder.js +1985 -0
  17. package/lib/source/lilylet/musicXmlDecoder.d.ts +20 -0
  18. package/lib/source/lilylet/musicXmlDecoder.js +1195 -0
  19. package/lib/source/lilylet/musicXmlEncoder.d.ts +15 -0
  20. package/lib/source/lilylet/musicXmlEncoder.js +701 -0
  21. package/lib/source/lilylet/musicXmlTypes.d.ts +199 -0
  22. package/lib/source/lilylet/musicXmlTypes.js +7 -0
  23. package/lib/source/lilylet/musicXmlUtils.d.ts +92 -0
  24. package/lib/source/lilylet/musicXmlUtils.js +469 -0
  25. package/lib/source/lilylet/parser.d.ts +14 -0
  26. package/lib/source/lilylet/parser.js +161 -0
  27. package/lib/source/lilylet/serializer.d.ts +11 -0
  28. package/lib/source/lilylet/serializer.js +791 -0
  29. package/lib/source/lilylet/types.d.ts +253 -0
  30. package/lib/source/lilylet/types.js +100 -0
  31. package/lib/tests/abc-abcjs-parse.d.ts +8 -0
  32. package/lib/tests/abc-abcjs-parse.js +90 -0
  33. package/lib/tests/abc-abcjs-svg.d.ts +1 -0
  34. package/lib/tests/abc-abcjs-svg.js +143 -0
  35. package/lib/tests/abc-decoder.d.ts +1 -0
  36. package/lib/tests/abc-decoder.js +67 -0
  37. package/lib/tests/abc-mei-compare.d.ts +1 -0
  38. package/lib/tests/abc-mei-compare.js +525 -0
  39. package/lib/tests/auto-beam.d.ts +9 -0
  40. package/lib/tests/auto-beam.js +151 -0
  41. package/lib/tests/computeMeiHashes.d.ts +1 -0
  42. package/lib/tests/computeMeiHashes.js +87 -0
  43. package/lib/tests/encoder-mutation.d.ts +9 -0
  44. package/lib/tests/encoder-mutation.js +110 -0
  45. package/lib/tests/gpt-review-issues.d.ts +5 -0
  46. package/lib/tests/gpt-review-issues.js +255 -0
  47. package/lib/tests/json-to-lyl.d.ts +1 -0
  48. package/lib/tests/json-to-lyl.js +18 -0
  49. package/lib/tests/lilypond-roundtrip.d.ts +7 -0
  50. package/lib/tests/lilypond-roundtrip.js +558 -0
  51. package/lib/tests/lilypondDecoder.d.ts +6 -0
  52. package/lib/tests/lilypondDecoder.js +95 -0
  53. package/lib/tests/ly-to-lyl.d.ts +1 -0
  54. package/lib/tests/ly-to-lyl.js +12 -0
  55. package/lib/tests/mei.d.ts +1 -0
  56. package/lib/tests/mei.js +278 -0
  57. package/lib/tests/musicxml-decoder.d.ts +4 -0
  58. package/lib/tests/musicxml-decoder.js +61 -0
  59. package/lib/tests/musicxml-detail.d.ts +4 -0
  60. package/lib/tests/musicxml-detail.js +85 -0
  61. package/lib/tests/musicxml-fprod.d.ts +9 -0
  62. package/lib/tests/musicxml-fprod.js +153 -0
  63. package/lib/tests/musicxml-roundtrip.d.ts +7 -0
  64. package/lib/tests/musicxml-roundtrip.js +296 -0
  65. package/lib/tests/musicxml-to-mei.d.ts +6 -0
  66. package/lib/tests/musicxml-to-mei.js +115 -0
  67. package/lib/tests/parser.d.ts +1 -0
  68. package/lib/tests/parser.js +17 -0
  69. package/lib/tests/render-k283.d.ts +1 -0
  70. package/lib/tests/render-k283.js +33 -0
  71. package/lib/tests/render-lyl.d.ts +1 -0
  72. package/lib/tests/render-lyl.js +35 -0
  73. package/lib/tests/unit/afterGraceInsideTuplet.test.d.ts +23 -0
  74. package/lib/tests/unit/afterGraceInsideTuplet.test.js +186 -0
  75. package/lib/tests/unit/changeStaffBeforeTuplet.test.d.ts +21 -0
  76. package/lib/tests/unit/changeStaffBeforeTuplet.test.js +356 -0
  77. package/lib/tests/unit/crossStaffDecoder.test.d.ts +15 -0
  78. package/lib/tests/unit/crossStaffDecoder.test.js +147 -0
  79. package/lib/tests/unit/crossStaffEdgeCases.test.d.ts +1 -0
  80. package/lib/tests/unit/crossStaffEdgeCases.test.js +209 -0
  81. package/lib/tests/unit/crossStaffMultiMeasure.test.d.ts +15 -0
  82. package/lib/tests/unit/crossStaffMultiMeasure.test.js +231 -0
  83. package/lib/tests/unit/fullMeasureRestDecoder.test.d.ts +11 -0
  84. package/lib/tests/unit/fullMeasureRestDecoder.test.js +154 -0
  85. package/lib/tests/unit/gptReviewIssues.test.d.ts +8 -0
  86. package/lib/tests/unit/gptReviewIssues.test.js +240 -0
  87. package/lib/tests/unit/parallelMusicDecoder.test.d.ts +13 -0
  88. package/lib/tests/unit/parallelMusicDecoder.test.js +261 -0
  89. package/lib/tests/unit/partialWarning.test.d.ts +4 -0
  90. package/lib/tests/unit/partialWarning.test.js +65 -0
  91. package/lib/tests/unit/serializerRoundTrip.test.d.ts +8 -0
  92. package/lib/tests/unit/serializerRoundTrip.test.js +263 -0
  93. package/lib/tests/unit/staffInsideTuplet.test.d.ts +25 -0
  94. package/lib/tests/unit/staffInsideTuplet.test.js +133 -0
  95. package/lib/tests/unit/timesFirstNoteEscape.test.d.ts +16 -0
  96. package/lib/tests/unit/timesFirstNoteEscape.test.js +152 -0
  97. package/lib/tests/unit/tupletWithBaseDuration.test.d.ts +17 -0
  98. package/lib/tests/unit/tupletWithBaseDuration.test.js +139 -0
  99. package/lib/tests/unit/voiceStaffParsing.test.d.ts +13 -0
  100. package/lib/tests/unit/voiceStaffParsing.test.js +118 -0
  101. package/package.json +1 -1
  102. package/source/lilylet/grammar.jison.js +1 -10
  103. package/source/lilylet/lilylet.jison +1 -10
  104. package/source/lilylet/meiEncoder.ts +65 -40
@@ -0,0 +1,253 @@
1
+ export declare enum Phonet {
2
+ c = "c",
3
+ d = "d",
4
+ e = "e",
5
+ f = "f",
6
+ g = "g",
7
+ a = "a",
8
+ b = "b"
9
+ }
10
+ export declare enum Accidental {
11
+ natural = "natural",
12
+ sharp = "sharp",
13
+ flat = "flat",
14
+ doubleSharp = "doubleSharp",
15
+ doubleFlat = "doubleFlat"
16
+ }
17
+ export declare enum Clef {
18
+ treble = "treble",
19
+ bass = "bass",
20
+ alto = "alto"
21
+ }
22
+ export declare enum StemDirection {
23
+ up = "up",
24
+ down = "down",
25
+ auto = "auto"
26
+ }
27
+ export declare enum ArticulationType {
28
+ staccato = "staccato",
29
+ staccatissimo = "staccatissimo",
30
+ tenuto = "tenuto",
31
+ marcato = "marcato",
32
+ accent = "accent",
33
+ portato = "portato"
34
+ }
35
+ export declare enum OrnamentType {
36
+ trill = "trill",
37
+ turn = "turn",
38
+ mordent = "mordent",
39
+ prall = "prall",
40
+ fermata = "fermata",
41
+ shortFermata = "shortFermata",
42
+ arpeggio = "arpeggio"
43
+ }
44
+ export declare enum DynamicType {
45
+ ppp = "ppp",
46
+ pp = "pp",
47
+ p = "p",
48
+ mp = "mp",
49
+ mf = "mf",
50
+ f = "f",
51
+ ff = "ff",
52
+ fff = "fff",
53
+ sfz = "sfz",
54
+ rfz = "rfz",
55
+ fp = "fp"
56
+ }
57
+ export declare enum HairpinType {
58
+ crescendoStart = "crescendoStart",
59
+ crescendoEnd = "crescendoEnd",
60
+ diminuendoStart = "diminuendoStart",
61
+ diminuendoEnd = "diminuendoEnd"
62
+ }
63
+ export declare enum PedalType {
64
+ sustainOn = "sustainOn",
65
+ sustainOff = "sustainOff",
66
+ sostenutoOn = "sostenutoOn",
67
+ sostenutoOff = "sostenutoOff",
68
+ unaCordaOn = "unaCordaOn",
69
+ unaCordaOff = "unaCordaOff"
70
+ }
71
+ export declare enum BarlineType {
72
+ single = "|",
73
+ double = "||",
74
+ end = "|.",
75
+ repeatStart = ".|:",
76
+ repeatEnd = ":|.",
77
+ repeatBoth = ":..:"
78
+ }
79
+ export declare enum NavigationMarkType {
80
+ coda = "coda",
81
+ segno = "segno"
82
+ }
83
+ export interface Fraction {
84
+ numerator: number;
85
+ denominator: number;
86
+ }
87
+ export interface TimeSig extends Fraction {
88
+ symbol?: 'common' | 'cut';
89
+ }
90
+ export interface Pitch {
91
+ phonet: Phonet;
92
+ accidental?: Accidental;
93
+ octave: number;
94
+ courtesy?: boolean;
95
+ }
96
+ export interface Duration {
97
+ division: number;
98
+ dots: number;
99
+ tuplet?: Fraction;
100
+ }
101
+ export declare enum Placement {
102
+ above = "above",
103
+ below = "below"
104
+ }
105
+ export interface Articulation {
106
+ markType: 'articulation';
107
+ type: ArticulationType;
108
+ placement?: Placement;
109
+ }
110
+ export interface Ornament {
111
+ markType: 'ornament';
112
+ type: OrnamentType;
113
+ }
114
+ export interface Dynamic {
115
+ markType: 'dynamic';
116
+ type: DynamicType;
117
+ }
118
+ export interface Hairpin {
119
+ markType: 'hairpin';
120
+ type: HairpinType;
121
+ }
122
+ export interface Tie {
123
+ markType: 'tie';
124
+ start: boolean;
125
+ }
126
+ export interface Slur {
127
+ markType: 'slur';
128
+ start: boolean;
129
+ }
130
+ export interface Beam {
131
+ markType: 'beam';
132
+ start: boolean;
133
+ }
134
+ export interface Pedal {
135
+ markType: 'pedal';
136
+ type: PedalType;
137
+ }
138
+ export interface Fingering {
139
+ markType: 'fingering';
140
+ finger: number;
141
+ placement?: Placement;
142
+ }
143
+ export interface NavigationMark {
144
+ markType: 'navigation';
145
+ type: NavigationMarkType;
146
+ }
147
+ export interface MarkupMark {
148
+ markType: 'markup';
149
+ content: string;
150
+ placement?: Placement;
151
+ }
152
+ export type Mark = Articulation | Ornament | Dynamic | Hairpin | Tie | Slur | Beam | Pedal | Fingering | NavigationMark | MarkupMark;
153
+ export interface KeySignature {
154
+ pitch: Phonet;
155
+ accidental?: Accidental;
156
+ mode: 'major' | 'minor';
157
+ }
158
+ export interface Tempo {
159
+ text?: string;
160
+ beat?: Duration;
161
+ bpm?: number;
162
+ }
163
+ export interface NoteEvent {
164
+ type: 'note';
165
+ pitches: Pitch[];
166
+ duration: Duration;
167
+ marks?: Mark[];
168
+ grace?: boolean;
169
+ tremolo?: number;
170
+ staff?: number;
171
+ stemDirection?: StemDirection;
172
+ }
173
+ export interface RestEvent {
174
+ type: 'rest';
175
+ duration: Duration;
176
+ invisible?: boolean;
177
+ fullMeasure?: boolean;
178
+ pitch?: Pitch;
179
+ }
180
+ export interface ContextChange {
181
+ type: 'context';
182
+ key?: KeySignature;
183
+ time?: Fraction;
184
+ partial?: Duration;
185
+ clef?: Clef;
186
+ ottava?: number;
187
+ stemDirection?: StemDirection;
188
+ tempo?: Tempo;
189
+ staff?: number;
190
+ }
191
+ export interface TremoloEvent {
192
+ type: 'tremolo';
193
+ pitchA: Pitch[];
194
+ pitchB: Pitch[];
195
+ count: number;
196
+ division: number;
197
+ }
198
+ export interface TupletEvent {
199
+ type: 'tuplet';
200
+ ratio: Fraction;
201
+ events: (NoteEvent | RestEvent | ContextChange)[];
202
+ }
203
+ export interface TimesEvent {
204
+ type: 'times';
205
+ ratio: Fraction;
206
+ events: (NoteEvent | RestEvent | ContextChange)[];
207
+ }
208
+ export interface PitchResetEvent {
209
+ type: 'pitchReset';
210
+ }
211
+ export interface BarlineEvent {
212
+ type: 'barline';
213
+ style: string;
214
+ }
215
+ export interface HarmonyEvent {
216
+ type: 'harmony';
217
+ text: string;
218
+ }
219
+ export interface MarkupEvent {
220
+ type: 'markup';
221
+ content: string;
222
+ placement?: Placement;
223
+ }
224
+ export type Event = NoteEvent | RestEvent | ContextChange | TremoloEvent | TupletEvent | TimesEvent | PitchResetEvent | BarlineEvent | HarmonyEvent | MarkupEvent;
225
+ export interface Voice {
226
+ staff: number;
227
+ events: Event[];
228
+ }
229
+ export interface Metadata {
230
+ title?: string;
231
+ subtitle?: string;
232
+ composer?: string;
233
+ arranger?: string;
234
+ lyricist?: string;
235
+ opus?: string;
236
+ instrument?: string;
237
+ genre?: string;
238
+ autoBeam?: 'auto' | 'on' | 'off';
239
+ }
240
+ export interface Part {
241
+ name?: string;
242
+ voices: Voice[];
243
+ }
244
+ export interface Measure {
245
+ key?: KeySignature;
246
+ timeSig?: TimeSig;
247
+ parts: Part[];
248
+ partial?: boolean;
249
+ }
250
+ export interface LilyletDoc {
251
+ metadata?: Metadata;
252
+ measures: Measure[];
253
+ }
@@ -0,0 +1,100 @@
1
+ // === Enums ===
2
+ export var Phonet;
3
+ (function (Phonet) {
4
+ Phonet["c"] = "c";
5
+ Phonet["d"] = "d";
6
+ Phonet["e"] = "e";
7
+ Phonet["f"] = "f";
8
+ Phonet["g"] = "g";
9
+ Phonet["a"] = "a";
10
+ Phonet["b"] = "b";
11
+ })(Phonet || (Phonet = {}));
12
+ export var Accidental;
13
+ (function (Accidental) {
14
+ Accidental["natural"] = "natural";
15
+ Accidental["sharp"] = "sharp";
16
+ Accidental["flat"] = "flat";
17
+ Accidental["doubleSharp"] = "doubleSharp";
18
+ Accidental["doubleFlat"] = "doubleFlat";
19
+ })(Accidental || (Accidental = {}));
20
+ export var Clef;
21
+ (function (Clef) {
22
+ Clef["treble"] = "treble";
23
+ Clef["bass"] = "bass";
24
+ Clef["alto"] = "alto";
25
+ })(Clef || (Clef = {}));
26
+ export var StemDirection;
27
+ (function (StemDirection) {
28
+ StemDirection["up"] = "up";
29
+ StemDirection["down"] = "down";
30
+ StemDirection["auto"] = "auto";
31
+ })(StemDirection || (StemDirection = {}));
32
+ export var ArticulationType;
33
+ (function (ArticulationType) {
34
+ ArticulationType["staccato"] = "staccato";
35
+ ArticulationType["staccatissimo"] = "staccatissimo";
36
+ ArticulationType["tenuto"] = "tenuto";
37
+ ArticulationType["marcato"] = "marcato";
38
+ ArticulationType["accent"] = "accent";
39
+ ArticulationType["portato"] = "portato";
40
+ })(ArticulationType || (ArticulationType = {}));
41
+ export var OrnamentType;
42
+ (function (OrnamentType) {
43
+ OrnamentType["trill"] = "trill";
44
+ OrnamentType["turn"] = "turn";
45
+ OrnamentType["mordent"] = "mordent";
46
+ OrnamentType["prall"] = "prall";
47
+ OrnamentType["fermata"] = "fermata";
48
+ OrnamentType["shortFermata"] = "shortFermata";
49
+ OrnamentType["arpeggio"] = "arpeggio";
50
+ })(OrnamentType || (OrnamentType = {}));
51
+ export var DynamicType;
52
+ (function (DynamicType) {
53
+ DynamicType["ppp"] = "ppp";
54
+ DynamicType["pp"] = "pp";
55
+ DynamicType["p"] = "p";
56
+ DynamicType["mp"] = "mp";
57
+ DynamicType["mf"] = "mf";
58
+ DynamicType["f"] = "f";
59
+ DynamicType["ff"] = "ff";
60
+ DynamicType["fff"] = "fff";
61
+ DynamicType["sfz"] = "sfz";
62
+ DynamicType["rfz"] = "rfz";
63
+ DynamicType["fp"] = "fp";
64
+ })(DynamicType || (DynamicType = {}));
65
+ export var HairpinType;
66
+ (function (HairpinType) {
67
+ HairpinType["crescendoStart"] = "crescendoStart";
68
+ HairpinType["crescendoEnd"] = "crescendoEnd";
69
+ HairpinType["diminuendoStart"] = "diminuendoStart";
70
+ HairpinType["diminuendoEnd"] = "diminuendoEnd";
71
+ })(HairpinType || (HairpinType = {}));
72
+ export var PedalType;
73
+ (function (PedalType) {
74
+ PedalType["sustainOn"] = "sustainOn";
75
+ PedalType["sustainOff"] = "sustainOff";
76
+ PedalType["sostenutoOn"] = "sostenutoOn";
77
+ PedalType["sostenutoOff"] = "sostenutoOff";
78
+ PedalType["unaCordaOn"] = "unaCordaOn";
79
+ PedalType["unaCordaOff"] = "unaCordaOff";
80
+ })(PedalType || (PedalType = {}));
81
+ export var BarlineType;
82
+ (function (BarlineType) {
83
+ BarlineType["single"] = "|";
84
+ BarlineType["double"] = "||";
85
+ BarlineType["end"] = "|.";
86
+ BarlineType["repeatStart"] = ".|:";
87
+ BarlineType["repeatEnd"] = ":|.";
88
+ BarlineType["repeatBoth"] = ":..:";
89
+ })(BarlineType || (BarlineType = {}));
90
+ export var NavigationMarkType;
91
+ (function (NavigationMarkType) {
92
+ NavigationMarkType["coda"] = "coda";
93
+ NavigationMarkType["segno"] = "segno";
94
+ })(NavigationMarkType || (NavigationMarkType = {}));
95
+ // === Placement Direction ===
96
+ export var Placement;
97
+ (function (Placement) {
98
+ Placement["above"] = "above";
99
+ Placement["below"] = "below";
100
+ })(Placement || (Placement = {}));
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Parse all tests/assets/abc/*.abc files with abcjs and report:
3
+ * - warnings / parse errors from abcjs
4
+ * - note / rest / bar counts
5
+ * This acts as a ground-truth reference: files abcjs parses cleanly are
6
+ * valid ABC; failures indicate either invalid ABC or abcjs limitations.
7
+ */
8
+ export {};
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Parse all tests/assets/abc/*.abc files with abcjs and report:
3
+ * - warnings / parse errors from abcjs
4
+ * - note / rest / bar counts
5
+ * This acts as a ground-truth reference: files abcjs parses cleanly are
6
+ * valid ABC; failures indicate either invalid ABC or abcjs limitations.
7
+ */
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { fileURLToPath } from "url";
11
+ import { createRequire } from "module";
12
+ const require = createRequire(import.meta.url);
13
+ const abcjs = require("abcjs");
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ const ABC_DIR = path.join(__dirname, "assets/abc");
16
+ function countElements(tune) {
17
+ const stats = [];
18
+ for (const line of tune.lines) {
19
+ if (!line.staff)
20
+ continue;
21
+ for (const staff of line.staff) {
22
+ for (let vi = 0; vi < staff.voices.length; vi++) {
23
+ if (!stats[vi])
24
+ stats[vi] = { notes: 0, rests: 0, bars: 0 };
25
+ for (const el of staff.voices[vi]) {
26
+ if (el.el_type === "note") {
27
+ if (el.rest)
28
+ stats[vi].rests++;
29
+ else
30
+ stats[vi].notes++;
31
+ }
32
+ else if (el.el_type === "bar") {
33
+ stats[vi].bars++;
34
+ }
35
+ }
36
+ }
37
+ }
38
+ }
39
+ return stats;
40
+ }
41
+ const files = fs.readdirSync(ABC_DIR)
42
+ .filter(f => f.endsWith(".abc"))
43
+ .sort();
44
+ console.log(`Parsing ${files.length} ABC files with abcjs\n`);
45
+ console.log("=".repeat(60));
46
+ let pass = 0;
47
+ let warn = 0;
48
+ const results = [];
49
+ for (const file of files) {
50
+ const content = fs.readFileSync(path.join(ABC_DIR, file), "utf-8");
51
+ let tunes;
52
+ try {
53
+ tunes = abcjs.parseOnly(content);
54
+ }
55
+ catch (e) {
56
+ console.log(`CRASH ${file}`);
57
+ // known abcjs bug: cross-bar slur continuation in inline multi-voice format
58
+ console.log(` ${e.message.split("\n")[0]}`);
59
+ continue;
60
+ }
61
+ const tune = tunes[0];
62
+ const voices = countElements(tune);
63
+ const totalNotes = voices.reduce((s, v) => s + v.notes, 0);
64
+ const totalRests = voices.reduce((s, v) => s + v.rests, 0);
65
+ const totalBars = voices.reduce((s, v) => s + v.bars, 0);
66
+ const warnings = tune.warnings;
67
+ results.push({ file, warnings, voices, totalNotes, totalRests, totalBars });
68
+ const tag = warnings ? "WARN" : "OK ";
69
+ if (warnings)
70
+ warn++;
71
+ else
72
+ pass++;
73
+ console.log(`${tag} ${file}`);
74
+ console.log(` notes=${totalNotes} rests=${totalRests} bars=${totalBars} voices=${voices.length}`);
75
+ if (warnings) {
76
+ for (const w of warnings) {
77
+ console.log(` ⚠ ${w}`);
78
+ }
79
+ }
80
+ }
81
+ console.log("\n" + "=".repeat(60));
82
+ console.log(`Results: ${pass} clean, ${warn} with warnings, out of ${files.length} files`);
83
+ // Summary: files that abcjs WARNS on — these are the ones to fix in lilylet
84
+ if (warn > 0) {
85
+ console.log("\nFiles with abcjs warnings:");
86
+ for (const r of results) {
87
+ if (r.warnings)
88
+ console.log(` ${r.file}`);
89
+ }
90
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,143 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { DOMImplementation, XMLSerializer } from "@xmldom/xmldom";
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ // Setup DOM mock for abcjs (browser DOM required)
7
+ const domImpl = new DOMImplementation();
8
+ const doc = domImpl.createDocument("http://www.w3.org/1999/xhtml", "html", null);
9
+ const body = doc.createElement("body");
10
+ (doc.documentElement || doc).appendChild(body);
11
+ const NodeProto = Object.getPrototypeOf(doc.createElement("span"));
12
+ function patchEl(el) {
13
+ if (!el || el._patched)
14
+ return el;
15
+ el._patched = true;
16
+ if (!el.style)
17
+ el.style = {};
18
+ el.addEventListener = () => { };
19
+ el.removeEventListener = () => { };
20
+ el.getBBox = () => ({ width: 8, height: 12, x: 0, y: 0 });
21
+ const origAC = el.appendChild.bind(el);
22
+ el.appendChild = (child) => { patchEl(child); return origAC(child); };
23
+ const origIB = el.insertBefore.bind(el);
24
+ el.insertBefore = (child, ref) => { patchEl(child); return origIB(child, ref); };
25
+ return el;
26
+ }
27
+ const origCE = doc.createElement.bind(doc);
28
+ const origCENS = doc.createElementNS.bind(doc);
29
+ doc.createElement = (tag) => patchEl(origCE(tag));
30
+ doc.createElementNS = (ns, tag) => patchEl(origCENS(ns, tag));
31
+ if (!Object.getOwnPropertyDescriptor(NodeProto, "children")) {
32
+ Object.defineProperty(NodeProto, "children", {
33
+ get() {
34
+ const a = [];
35
+ let c = this.firstChild;
36
+ while (c) {
37
+ if (c.nodeType === 1)
38
+ a.push(c);
39
+ c = c.nextSibling;
40
+ }
41
+ return a;
42
+ },
43
+ configurable: true,
44
+ });
45
+ }
46
+ if (!Object.getOwnPropertyDescriptor(NodeProto, "parentElement")) {
47
+ Object.defineProperty(NodeProto, "parentElement", {
48
+ get() { return (this.parentNode?.nodeType === 1) ? this.parentNode : null; },
49
+ configurable: true,
50
+ });
51
+ }
52
+ if (!Object.getOwnPropertyDescriptor(NodeProto, "textContent")) {
53
+ Object.defineProperty(NodeProto, "textContent", {
54
+ get() { return this.nodeValue || ""; },
55
+ set(v) {
56
+ while (this.firstChild)
57
+ this.removeChild(this.firstChild);
58
+ this.appendChild(doc.createTextNode(v));
59
+ },
60
+ configurable: true,
61
+ });
62
+ }
63
+ doc.querySelector = (sel) => sel === "body" ? body : null;
64
+ doc.querySelectorAll = () => [];
65
+ global.document = doc;
66
+ global.window = { addEventListener: () => { } };
67
+ // Now import abcjs (after DOM is set up)
68
+ const abcjsMod = await import("abcjs");
69
+ const abcjs = abcjsMod.default ?? abcjsMod;
70
+ const ABC_DIR = path.join(__dirname, "assets/abc");
71
+ const OUT_DIR = path.join(__dirname, "output/from-abc-svg");
72
+ const LOG_FILE = path.join(__dirname, "output/abc-abcjs-svg.log");
73
+ fs.mkdirSync(OUT_DIR, { recursive: true });
74
+ const abcFiles = fs.readdirSync(ABC_DIR).filter(f => f.endsWith(".abc")).sort();
75
+ const serializer = new XMLSerializer();
76
+ const logLines = [];
77
+ let passCount = 0;
78
+ let skipCount = 0;
79
+ for (const fname of abcFiles) {
80
+ const abcPath = path.join(ABC_DIR, fname);
81
+ const abcContent = fs.readFileSync(abcPath, "utf-8");
82
+ const baseName = fname.replace(/\.abc$/, "");
83
+ const svgPath = path.join(OUT_DIR, baseName + ".svg");
84
+ try {
85
+ // Create a fresh div container for each file
86
+ const div = doc.createElement("div");
87
+ patchEl(div);
88
+ abcjs.renderAbc(div, abcContent, {});
89
+ // Collect all non-empty SVGs (abcjs may produce one per tune + empty placeholders)
90
+ const svgs = [];
91
+ collectSvgs(div, svgs);
92
+ const nonEmpty = svgs.filter(s => s.childNodes?.length > 0);
93
+ if (nonEmpty.length === 0) {
94
+ throw new Error("No non-empty SVG element found in output");
95
+ }
96
+ const svgStr = nonEmpty.map(s => serializer.serializeToString(s)).join("\n");
97
+ fs.writeFileSync(svgPath, svgStr, "utf-8");
98
+ console.log(`PASS ${fname}`);
99
+ passCount++;
100
+ }
101
+ catch (err) {
102
+ const msg = err?.message || String(err);
103
+ console.error(`SKIP ${fname} — ${msg}`);
104
+ logLines.push(`${fname}: ${msg}`);
105
+ skipCount++;
106
+ }
107
+ }
108
+ fs.writeFileSync(LOG_FILE, logLines.join("\n") + (logLines.length ? "\n" : ""), "utf-8");
109
+ console.log(`\nResults: ${passCount} pass, ${skipCount} skip out of ${abcFiles.length}`);
110
+ if (logLines.length) {
111
+ console.log(`Error log: ${LOG_FILE}`);
112
+ }
113
+ function findLastNonEmptySvg(el) {
114
+ const svgs = [];
115
+ collectSvgs(el, svgs);
116
+ // Return the last SVG that has children
117
+ for (let i = svgs.length - 1; i >= 0; i--) {
118
+ if (svgs[i].childNodes?.length > 0)
119
+ return svgs[i];
120
+ }
121
+ return svgs[svgs.length - 1] ?? null;
122
+ }
123
+ function collectSvgs(el, out) {
124
+ if (el.tagName === "svg" || el.localName === "svg")
125
+ out.push(el);
126
+ let c = el.firstChild;
127
+ while (c) {
128
+ collectSvgs(c, out);
129
+ c = c.nextSibling;
130
+ }
131
+ }
132
+ function findFirstSvg(el) {
133
+ if (el.tagName === "svg" || el.localName === "svg")
134
+ return el;
135
+ let c = el.firstChild;
136
+ while (c) {
137
+ const found = findFirstSvg(c);
138
+ if (found)
139
+ return found;
140
+ c = c.nextSibling;
141
+ }
142
+ return null;
143
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { abcDecoder, parseCode } from "../source/lilylet/index.js";
5
+ import { serializeLilyletDoc } from "../source/lilylet/serializer.js";
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const ABC_DIR = path.join(__dirname, "assets/abc");
8
+ const OUTPUT_DIR = path.join(__dirname, "output/from-abc");
9
+ const main = () => {
10
+ const files = fs.readdirSync(ABC_DIR).filter(f => f.endsWith(".abc")).sort();
11
+ console.log(`Found ${files.length} ABC files\n`);
12
+ // Ensure output directory exists
13
+ fs.mkdirSync(OUTPUT_DIR, { recursive: true });
14
+ let pass = 0;
15
+ let fail = 0;
16
+ const errors = [];
17
+ for (const file of files) {
18
+ const filePath = path.join(ABC_DIR, file);
19
+ const baseName = path.basename(file, ".abc");
20
+ try {
21
+ const content = fs.readFileSync(filePath, "utf-8");
22
+ const doc = abcDecoder.decode(content);
23
+ if (!doc.measures || doc.measures.length === 0) {
24
+ throw new Error("No measures produced");
25
+ }
26
+ // Write JSON output
27
+ const jsonPath = path.join(OUTPUT_DIR, `${baseName}.json`);
28
+ fs.writeFileSync(jsonPath, JSON.stringify(doc, null, 2));
29
+ // Write .lyl output
30
+ const lylContent = serializeLilyletDoc(doc);
31
+ const lylPath = path.join(OUTPUT_DIR, `${baseName}.lyl`);
32
+ fs.writeFileSync(lylPath, lylContent);
33
+ const measureCount = doc.measures.length;
34
+ const noteCount = doc.measures.reduce((sum, m) => sum + m.parts.reduce((psum, p) => psum + p.voices.reduce((vsum, v) => vsum + v.events.filter(e => e.type === "note").length, 0), 0), 0);
35
+ // Verify .lyl can be parsed back
36
+ const reparsed = parseCode(lylContent);
37
+ const reparsedMeasures = reparsed.measures.length;
38
+ console.log(` ${file}`);
39
+ console.log(` Measures: ${measureCount}, Notes: ${noteCount}, Reparsed: ${reparsedMeasures} measures`);
40
+ console.log(` -> ${baseName}.json, ${baseName}.lyl`);
41
+ pass++;
42
+ }
43
+ catch (err) {
44
+ fail++;
45
+ errors.push({ file, error: err.message || String(err) });
46
+ console.log(`FAIL: ${file}`);
47
+ console.log(` ${err.message?.substring(0, 200)}\n`);
48
+ }
49
+ }
50
+ console.log(`\n${"=".repeat(60)}`);
51
+ console.log(`Results: ${pass} pass, ${fail} fail out of ${files.length}`);
52
+ console.log(`Output: ${OUTPUT_DIR}`);
53
+ if (fail > 0) {
54
+ const errorTypes = new Map();
55
+ for (const e of errors) {
56
+ const key = e.error.substring(0, 80);
57
+ errorTypes.set(key, (errorTypes.get(key) || 0) + 1);
58
+ }
59
+ console.log(`\nError distribution:`);
60
+ const sorted = Array.from(errorTypes.entries()).sort((a, b) => b[1] - a[1]);
61
+ for (const [msg, count] of sorted.slice(0, 15)) {
62
+ console.log(` ${count}x: ${msg}`);
63
+ }
64
+ }
65
+ process.exit(fail > 0 ? 1 : 0);
66
+ };
67
+ main();
@@ -0,0 +1 @@
1
+ export {};