@hymnbook/abc 0.0.0
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.
- package/README.md +3 -0
- package/dist/index.d.mts +256 -0
- package/dist/index.d.ts +256 -0
- package/dist/index.js +310 -0
- package/dist/index.mjs +262 -0
- package/package.json +37 -0
- package/src/abc.ts +271 -0
- package/src/abcTypes.ts +314 -0
- package/src/based.ts +19 -0
- package/src/basedTypes.ts +14 -0
- package/src/index.ts +5 -0
- package/src/validation.ts +9 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
AbcSong: () => AbcSong,
|
|
34
|
+
ValidationError: () => ValidationError,
|
|
35
|
+
addInfoFieldsToMelody: () => addInfoFieldsToMelody,
|
|
36
|
+
combineMelodyAndLyrics: () => combineMelodyAndLyrics,
|
|
37
|
+
convertStringToAbcTune: () => convertStringToAbcTune,
|
|
38
|
+
extractInfoFields: () => extractInfoFields,
|
|
39
|
+
extractNotesAndLyrics: () => extractNotesAndLyrics,
|
|
40
|
+
generateAbcForVerse: () => generateAbcForVerse,
|
|
41
|
+
getField: () => getField,
|
|
42
|
+
getForVerse: () => getForVerse,
|
|
43
|
+
parse: () => parse,
|
|
44
|
+
validate: () => validate
|
|
45
|
+
});
|
|
46
|
+
module.exports = __toCommonJS(index_exports);
|
|
47
|
+
|
|
48
|
+
// src/abcTypes.ts
|
|
49
|
+
var AbcSong = class {
|
|
50
|
+
// See also: https://abcnotation.com/wiki/abc:standard:v2.1#information_fields
|
|
51
|
+
area;
|
|
52
|
+
book;
|
|
53
|
+
composer;
|
|
54
|
+
discography;
|
|
55
|
+
fileUrl;
|
|
56
|
+
group;
|
|
57
|
+
history;
|
|
58
|
+
instruction;
|
|
59
|
+
key;
|
|
60
|
+
unitNoteLength;
|
|
61
|
+
meter;
|
|
62
|
+
macro;
|
|
63
|
+
notes;
|
|
64
|
+
origin;
|
|
65
|
+
parts;
|
|
66
|
+
tempo;
|
|
67
|
+
rhythm;
|
|
68
|
+
remark;
|
|
69
|
+
source;
|
|
70
|
+
symbolLine;
|
|
71
|
+
title;
|
|
72
|
+
userDefined;
|
|
73
|
+
voice;
|
|
74
|
+
referenceNumber;
|
|
75
|
+
transcription;
|
|
76
|
+
melody = [];
|
|
77
|
+
clef = {
|
|
78
|
+
clefPos: 4,
|
|
79
|
+
type: "treble",
|
|
80
|
+
verticalPos: 0
|
|
81
|
+
};
|
|
82
|
+
keySignature = {
|
|
83
|
+
accidentals: [],
|
|
84
|
+
root: "C",
|
|
85
|
+
acc: "",
|
|
86
|
+
mode: ""
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/abc.ts
|
|
91
|
+
var ABCJS = __toESM(require("abcjs"));
|
|
92
|
+
|
|
93
|
+
// src/validation.ts
|
|
94
|
+
var ValidationError = class extends Error {
|
|
95
|
+
};
|
|
96
|
+
function validate(result, message) {
|
|
97
|
+
if (result) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
throw new ValidationError(message || "Value is not true");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/abc.ts
|
|
104
|
+
var parse = (abc) => {
|
|
105
|
+
abc = abc.replace(/%.*/g, "").replaceAll(/\n+/g, "\n");
|
|
106
|
+
const song = new AbcSong();
|
|
107
|
+
extractInfoFields(abc, song);
|
|
108
|
+
const tuneObject = convertStringToAbcTune(abc);
|
|
109
|
+
if (tuneObject === void 0) {
|
|
110
|
+
return void 0;
|
|
111
|
+
}
|
|
112
|
+
song.clef = tuneObject.lines[0].staff[0].clef || song.clef;
|
|
113
|
+
song.keySignature = tuneObject.lines[0].staff[0].key || song.keySignature;
|
|
114
|
+
song.melody = tuneObject.lines.map((line) => line.staff[0].voices[0]);
|
|
115
|
+
processSlurs(song);
|
|
116
|
+
return song;
|
|
117
|
+
};
|
|
118
|
+
var processSlurs = (song) => {
|
|
119
|
+
song.melody.forEach(processSlursForLine);
|
|
120
|
+
};
|
|
121
|
+
var processSlursForLine = (line) => {
|
|
122
|
+
const emptyLyric = () => [{ divider: " ", syllable: "" }];
|
|
123
|
+
const shiftLyrics = (notes2, fromIndex) => {
|
|
124
|
+
let shiftedLyric = void 0;
|
|
125
|
+
notes2.forEach((note, index) => {
|
|
126
|
+
if (index < fromIndex) return;
|
|
127
|
+
if (shiftedLyric === void 0) {
|
|
128
|
+
shiftedLyric = note.lyric;
|
|
129
|
+
note.lyric = emptyLyric();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const nextShiftedLyric = note.lyric;
|
|
133
|
+
note.lyric = shiftedLyric;
|
|
134
|
+
shiftedLyric = nextShiftedLyric;
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
const notes = line.filter((it) => it.el_type == "note").map((it) => it).filter((it) => it.pitches);
|
|
138
|
+
let slurLyric = void 0;
|
|
139
|
+
notes.forEach((note, index) => {
|
|
140
|
+
const endSlurPitches = note.pitches.filter((it) => it.endSlur);
|
|
141
|
+
const startSlurPitches = note.pitches.filter((it) => it.startSlur);
|
|
142
|
+
if (slurLyric === void 0 && endSlurPitches.length && startSlurPitches.length) {
|
|
143
|
+
const endSlurIds = endSlurPitches.filter((it) => it.endSlur).flatMap((it) => it.endSlur);
|
|
144
|
+
const startSlurIds = startSlurPitches.filter((it) => it.startSlur).flatMap((it) => it.startSlur).map((it) => it.label);
|
|
145
|
+
const hasUpFollowingId = startSlurIds.some((it) => endSlurIds.includes(it - 1));
|
|
146
|
+
if (hasUpFollowingId) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (slurLyric) {
|
|
151
|
+
const lyricIsSpacer = note.lyric && note.lyric.every((it) => it.syllable == "" && it.divider == " ");
|
|
152
|
+
if (!lyricIsSpacer) {
|
|
153
|
+
shiftLyrics(notes, index);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (endSlurPitches.length && !startSlurPitches.length) {
|
|
157
|
+
slurLyric = void 0;
|
|
158
|
+
}
|
|
159
|
+
if (startSlurPitches.length && !endSlurPitches.length) {
|
|
160
|
+
slurLyric = note.lyric;
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
var getField = (abc, field, _default) => {
|
|
165
|
+
const result = abc.match(new RegExp("(^|\n) * *" + field + ":(.*)?"));
|
|
166
|
+
if (result == null || result.length !== 3 || result[2] == null) {
|
|
167
|
+
return _default;
|
|
168
|
+
}
|
|
169
|
+
return result[2].trim();
|
|
170
|
+
};
|
|
171
|
+
var extractInfoFields = (abc, song) => {
|
|
172
|
+
song.area = getField(abc, "A");
|
|
173
|
+
song.book = getField(abc, "B");
|
|
174
|
+
song.composer = getField(abc, "C");
|
|
175
|
+
song.discography = getField(abc, "D");
|
|
176
|
+
song.fileUrl = getField(abc, "F");
|
|
177
|
+
song.group = getField(abc, "G");
|
|
178
|
+
song.history = getField(abc, "H");
|
|
179
|
+
song.instruction = getField(abc, "I");
|
|
180
|
+
song.key = getField(abc, "K");
|
|
181
|
+
song.unitNoteLength = getField(abc, "L");
|
|
182
|
+
song.meter = getField(abc, "M");
|
|
183
|
+
song.macro = getField(abc, "m");
|
|
184
|
+
song.notes = getField(abc, "N");
|
|
185
|
+
song.origin = getField(abc, "O");
|
|
186
|
+
song.parts = getField(abc, "P");
|
|
187
|
+
song.tempo = getField(abc, "Q");
|
|
188
|
+
song.rhythm = getField(abc, "R");
|
|
189
|
+
song.remark = getField(abc, "r");
|
|
190
|
+
song.source = getField(abc, "S");
|
|
191
|
+
song.symbolLine = getField(abc, "s");
|
|
192
|
+
song.title = getField(abc, "T");
|
|
193
|
+
song.userDefined = getField(abc, "U");
|
|
194
|
+
song.voice = getField(abc, "V");
|
|
195
|
+
song.referenceNumber = getField(abc, "X", "1");
|
|
196
|
+
song.transcription = getField(abc, "Z");
|
|
197
|
+
return abc.replace(/%.*\n/g, "").replace(/(^|\n) *\t*[ABCDFGHIKLMmNOPQRrSsTUVXZ]:.*/g, "").replace(/\n+/g, "\n").replace(/^\n*/g, "").replace(/\n*$/g, "");
|
|
198
|
+
};
|
|
199
|
+
var extractNotesAndLyrics = (abc) => {
|
|
200
|
+
const notes = [];
|
|
201
|
+
const lyrics = [];
|
|
202
|
+
abc.split("\n").map((it) => it.trim()).forEach((it) => {
|
|
203
|
+
if (it.startsWith("w:") || it.startsWith("W:")) {
|
|
204
|
+
lyrics.push(it.substring(2).trim());
|
|
205
|
+
} else {
|
|
206
|
+
notes.push(it);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
return {
|
|
210
|
+
notes: notes.join(" "),
|
|
211
|
+
lyrics: lyrics.join(" ")
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
var addInfoFieldsToMelody = (song, abc) => {
|
|
215
|
+
let result = "";
|
|
216
|
+
result += song.referenceNumber === void 0 ? "" : "X:" + song.referenceNumber + "\n";
|
|
217
|
+
result += song.title === void 0 ? "" : "T:" + song.title + "\n";
|
|
218
|
+
result += song.area === void 0 ? "" : "A:" + song.area + "\n";
|
|
219
|
+
result += song.book === void 0 ? "" : "B:" + song.book + "\n";
|
|
220
|
+
result += song.composer === void 0 ? "" : "C:" + song.composer + "\n";
|
|
221
|
+
result += song.discography === void 0 ? "" : "D:" + song.discography + "\n";
|
|
222
|
+
result += song.fileUrl === void 0 ? "" : "F:" + song.fileUrl + "\n";
|
|
223
|
+
result += song.group === void 0 ? "" : "G:" + song.group + "\n";
|
|
224
|
+
result += song.history === void 0 ? "" : "H:" + song.history + "\n";
|
|
225
|
+
result += song.instruction === void 0 ? "" : "I:" + song.instruction + "\n";
|
|
226
|
+
result += song.key === void 0 ? "" : "K:" + song.key + "\n";
|
|
227
|
+
result += song.unitNoteLength === void 0 ? "" : "L:" + song.unitNoteLength + "\n";
|
|
228
|
+
result += song.meter === void 0 ? "" : "M:" + song.meter + "\n";
|
|
229
|
+
result += song.macro === void 0 ? "" : "m:" + song.macro + "\n";
|
|
230
|
+
result += song.notes === void 0 ? "" : "N:" + song.notes + "\n";
|
|
231
|
+
result += song.origin === void 0 ? "" : "O:" + song.origin + "\n";
|
|
232
|
+
result += song.parts === void 0 ? "" : "P:" + song.parts + "\n";
|
|
233
|
+
result += song.tempo === void 0 ? "" : "Q:" + song.tempo + "\n";
|
|
234
|
+
result += song.rhythm === void 0 ? "" : "R:" + song.rhythm + "\n";
|
|
235
|
+
result += song.remark === void 0 ? "" : "r:" + song.remark + "\n";
|
|
236
|
+
result += song.source === void 0 ? "" : "S:" + song.source + "\n";
|
|
237
|
+
result += song.symbolLine === void 0 ? "" : "s:" + song.symbolLine + "\n";
|
|
238
|
+
result += song.userDefined === void 0 ? "" : "U:" + song.userDefined + "\n";
|
|
239
|
+
result += song.voice === void 0 ? "" : "V:" + song.voice + "\n";
|
|
240
|
+
result += song.transcription === void 0 ? "" : "Z:" + song.transcription + "\n";
|
|
241
|
+
return result + abc;
|
|
242
|
+
};
|
|
243
|
+
var convertStringToAbcTune = (abc) => {
|
|
244
|
+
const objectArray = ABCJS.parseOnly(abc);
|
|
245
|
+
const object = objectArray;
|
|
246
|
+
validate(object != null, "Tune object may not be null");
|
|
247
|
+
validate(object.length > 0, "Tune object may not be empty");
|
|
248
|
+
validate(object[0].lines != null, "Tune object lines may not be null");
|
|
249
|
+
validate(object[0].lines.length > 0, "Tune object lines are empty");
|
|
250
|
+
validate(object[0].lines[0].staff != null, "Staffs may not be null");
|
|
251
|
+
validate(object[0].lines[0].staff.length > 0, "Staffs are empty");
|
|
252
|
+
validate(object[0].lines[0].staff[0].voices != null, "Voices may not be null");
|
|
253
|
+
validate(object[0].lines[0].staff[0].voices.length > 0, "Voices may not be empty");
|
|
254
|
+
validate(object[0].lines[0].staff[0].voices.some((it) => it.length > 0), "Voices are all empty");
|
|
255
|
+
processAbcLyrics(object);
|
|
256
|
+
return object[0];
|
|
257
|
+
};
|
|
258
|
+
var processAbcLyrics = (object) => {
|
|
259
|
+
object[0].lines.forEach(
|
|
260
|
+
(line) => line.staff.forEach(
|
|
261
|
+
(staff) => staff.voices.forEach(
|
|
262
|
+
(element) => element.filter((voice) => voice.el_type === "note").map((voice) => voice).forEach((voice) => {
|
|
263
|
+
voice.lyric?.forEach(
|
|
264
|
+
(lyric) => lyric.syllable = lyric.syllable.replace("\xA0", " ")
|
|
265
|
+
);
|
|
266
|
+
})
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
);
|
|
270
|
+
};
|
|
271
|
+
var combineMelodyAndLyrics = (melody, lyrics) => {
|
|
272
|
+
const song = new AbcSong();
|
|
273
|
+
const rawMelody = extractInfoFields(melody, song);
|
|
274
|
+
const melodyLines = rawMelody.replaceAll(/\n+/g, "\n").trim().split("\n");
|
|
275
|
+
const lyricLines = lyrics.replaceAll(/\n+/g, "\n").trim().split("\n");
|
|
276
|
+
const mixedMelody = [];
|
|
277
|
+
for (let i = 0; i < Math.max(melodyLines.length, lyricLines.length); i++) {
|
|
278
|
+
mixedMelody.push(melodyLines[i] ?? "C ".repeat(10));
|
|
279
|
+
mixedMelody.push(lyricLines[i] ? "w: " + lyricLines[i] : "");
|
|
280
|
+
}
|
|
281
|
+
return addInfoFieldsToMelody(song, mixedMelody.join("\n")).trim();
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// src/based.ts
|
|
285
|
+
var getForVerse = (melody, verse) => melody.subMelodies.find((it) => (
|
|
286
|
+
// `.includes()` won't work due to the Realm data type of `verseUuids`.
|
|
287
|
+
it.verseUuids.some((it2) => it2 == verse.uuid)
|
|
288
|
+
));
|
|
289
|
+
var generateAbcForVerse = (verse, activeMelody) => {
|
|
290
|
+
if (activeMelody === void 0) {
|
|
291
|
+
return "";
|
|
292
|
+
}
|
|
293
|
+
const melody = getForVerse(activeMelody, verse)?.melody || activeMelody.melody;
|
|
294
|
+
return combineMelodyAndLyrics(melody, verse.abcLyrics || "");
|
|
295
|
+
};
|
|
296
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
297
|
+
0 && (module.exports = {
|
|
298
|
+
AbcSong,
|
|
299
|
+
ValidationError,
|
|
300
|
+
addInfoFieldsToMelody,
|
|
301
|
+
combineMelodyAndLyrics,
|
|
302
|
+
convertStringToAbcTune,
|
|
303
|
+
extractInfoFields,
|
|
304
|
+
extractNotesAndLyrics,
|
|
305
|
+
generateAbcForVerse,
|
|
306
|
+
getField,
|
|
307
|
+
getForVerse,
|
|
308
|
+
parse,
|
|
309
|
+
validate
|
|
310
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// src/abcTypes.ts
|
|
2
|
+
var AbcSong = class {
|
|
3
|
+
// See also: https://abcnotation.com/wiki/abc:standard:v2.1#information_fields
|
|
4
|
+
area;
|
|
5
|
+
book;
|
|
6
|
+
composer;
|
|
7
|
+
discography;
|
|
8
|
+
fileUrl;
|
|
9
|
+
group;
|
|
10
|
+
history;
|
|
11
|
+
instruction;
|
|
12
|
+
key;
|
|
13
|
+
unitNoteLength;
|
|
14
|
+
meter;
|
|
15
|
+
macro;
|
|
16
|
+
notes;
|
|
17
|
+
origin;
|
|
18
|
+
parts;
|
|
19
|
+
tempo;
|
|
20
|
+
rhythm;
|
|
21
|
+
remark;
|
|
22
|
+
source;
|
|
23
|
+
symbolLine;
|
|
24
|
+
title;
|
|
25
|
+
userDefined;
|
|
26
|
+
voice;
|
|
27
|
+
referenceNumber;
|
|
28
|
+
transcription;
|
|
29
|
+
melody = [];
|
|
30
|
+
clef = {
|
|
31
|
+
clefPos: 4,
|
|
32
|
+
type: "treble",
|
|
33
|
+
verticalPos: 0
|
|
34
|
+
};
|
|
35
|
+
keySignature = {
|
|
36
|
+
accidentals: [],
|
|
37
|
+
root: "C",
|
|
38
|
+
acc: "",
|
|
39
|
+
mode: ""
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/abc.ts
|
|
44
|
+
import * as ABCJS from "abcjs";
|
|
45
|
+
|
|
46
|
+
// src/validation.ts
|
|
47
|
+
var ValidationError = class extends Error {
|
|
48
|
+
};
|
|
49
|
+
function validate(result, message) {
|
|
50
|
+
if (result) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
throw new ValidationError(message || "Value is not true");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/abc.ts
|
|
57
|
+
var parse = (abc) => {
|
|
58
|
+
abc = abc.replace(/%.*/g, "").replaceAll(/\n+/g, "\n");
|
|
59
|
+
const song = new AbcSong();
|
|
60
|
+
extractInfoFields(abc, song);
|
|
61
|
+
const tuneObject = convertStringToAbcTune(abc);
|
|
62
|
+
if (tuneObject === void 0) {
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
song.clef = tuneObject.lines[0].staff[0].clef || song.clef;
|
|
66
|
+
song.keySignature = tuneObject.lines[0].staff[0].key || song.keySignature;
|
|
67
|
+
song.melody = tuneObject.lines.map((line) => line.staff[0].voices[0]);
|
|
68
|
+
processSlurs(song);
|
|
69
|
+
return song;
|
|
70
|
+
};
|
|
71
|
+
var processSlurs = (song) => {
|
|
72
|
+
song.melody.forEach(processSlursForLine);
|
|
73
|
+
};
|
|
74
|
+
var processSlursForLine = (line) => {
|
|
75
|
+
const emptyLyric = () => [{ divider: " ", syllable: "" }];
|
|
76
|
+
const shiftLyrics = (notes2, fromIndex) => {
|
|
77
|
+
let shiftedLyric = void 0;
|
|
78
|
+
notes2.forEach((note, index) => {
|
|
79
|
+
if (index < fromIndex) return;
|
|
80
|
+
if (shiftedLyric === void 0) {
|
|
81
|
+
shiftedLyric = note.lyric;
|
|
82
|
+
note.lyric = emptyLyric();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const nextShiftedLyric = note.lyric;
|
|
86
|
+
note.lyric = shiftedLyric;
|
|
87
|
+
shiftedLyric = nextShiftedLyric;
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
const notes = line.filter((it) => it.el_type == "note").map((it) => it).filter((it) => it.pitches);
|
|
91
|
+
let slurLyric = void 0;
|
|
92
|
+
notes.forEach((note, index) => {
|
|
93
|
+
const endSlurPitches = note.pitches.filter((it) => it.endSlur);
|
|
94
|
+
const startSlurPitches = note.pitches.filter((it) => it.startSlur);
|
|
95
|
+
if (slurLyric === void 0 && endSlurPitches.length && startSlurPitches.length) {
|
|
96
|
+
const endSlurIds = endSlurPitches.filter((it) => it.endSlur).flatMap((it) => it.endSlur);
|
|
97
|
+
const startSlurIds = startSlurPitches.filter((it) => it.startSlur).flatMap((it) => it.startSlur).map((it) => it.label);
|
|
98
|
+
const hasUpFollowingId = startSlurIds.some((it) => endSlurIds.includes(it - 1));
|
|
99
|
+
if (hasUpFollowingId) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (slurLyric) {
|
|
104
|
+
const lyricIsSpacer = note.lyric && note.lyric.every((it) => it.syllable == "" && it.divider == " ");
|
|
105
|
+
if (!lyricIsSpacer) {
|
|
106
|
+
shiftLyrics(notes, index);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (endSlurPitches.length && !startSlurPitches.length) {
|
|
110
|
+
slurLyric = void 0;
|
|
111
|
+
}
|
|
112
|
+
if (startSlurPitches.length && !endSlurPitches.length) {
|
|
113
|
+
slurLyric = note.lyric;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
var getField = (abc, field, _default) => {
|
|
118
|
+
const result = abc.match(new RegExp("(^|\n) * *" + field + ":(.*)?"));
|
|
119
|
+
if (result == null || result.length !== 3 || result[2] == null) {
|
|
120
|
+
return _default;
|
|
121
|
+
}
|
|
122
|
+
return result[2].trim();
|
|
123
|
+
};
|
|
124
|
+
var extractInfoFields = (abc, song) => {
|
|
125
|
+
song.area = getField(abc, "A");
|
|
126
|
+
song.book = getField(abc, "B");
|
|
127
|
+
song.composer = getField(abc, "C");
|
|
128
|
+
song.discography = getField(abc, "D");
|
|
129
|
+
song.fileUrl = getField(abc, "F");
|
|
130
|
+
song.group = getField(abc, "G");
|
|
131
|
+
song.history = getField(abc, "H");
|
|
132
|
+
song.instruction = getField(abc, "I");
|
|
133
|
+
song.key = getField(abc, "K");
|
|
134
|
+
song.unitNoteLength = getField(abc, "L");
|
|
135
|
+
song.meter = getField(abc, "M");
|
|
136
|
+
song.macro = getField(abc, "m");
|
|
137
|
+
song.notes = getField(abc, "N");
|
|
138
|
+
song.origin = getField(abc, "O");
|
|
139
|
+
song.parts = getField(abc, "P");
|
|
140
|
+
song.tempo = getField(abc, "Q");
|
|
141
|
+
song.rhythm = getField(abc, "R");
|
|
142
|
+
song.remark = getField(abc, "r");
|
|
143
|
+
song.source = getField(abc, "S");
|
|
144
|
+
song.symbolLine = getField(abc, "s");
|
|
145
|
+
song.title = getField(abc, "T");
|
|
146
|
+
song.userDefined = getField(abc, "U");
|
|
147
|
+
song.voice = getField(abc, "V");
|
|
148
|
+
song.referenceNumber = getField(abc, "X", "1");
|
|
149
|
+
song.transcription = getField(abc, "Z");
|
|
150
|
+
return abc.replace(/%.*\n/g, "").replace(/(^|\n) *\t*[ABCDFGHIKLMmNOPQRrSsTUVXZ]:.*/g, "").replace(/\n+/g, "\n").replace(/^\n*/g, "").replace(/\n*$/g, "");
|
|
151
|
+
};
|
|
152
|
+
var extractNotesAndLyrics = (abc) => {
|
|
153
|
+
const notes = [];
|
|
154
|
+
const lyrics = [];
|
|
155
|
+
abc.split("\n").map((it) => it.trim()).forEach((it) => {
|
|
156
|
+
if (it.startsWith("w:") || it.startsWith("W:")) {
|
|
157
|
+
lyrics.push(it.substring(2).trim());
|
|
158
|
+
} else {
|
|
159
|
+
notes.push(it);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
return {
|
|
163
|
+
notes: notes.join(" "),
|
|
164
|
+
lyrics: lyrics.join(" ")
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
var addInfoFieldsToMelody = (song, abc) => {
|
|
168
|
+
let result = "";
|
|
169
|
+
result += song.referenceNumber === void 0 ? "" : "X:" + song.referenceNumber + "\n";
|
|
170
|
+
result += song.title === void 0 ? "" : "T:" + song.title + "\n";
|
|
171
|
+
result += song.area === void 0 ? "" : "A:" + song.area + "\n";
|
|
172
|
+
result += song.book === void 0 ? "" : "B:" + song.book + "\n";
|
|
173
|
+
result += song.composer === void 0 ? "" : "C:" + song.composer + "\n";
|
|
174
|
+
result += song.discography === void 0 ? "" : "D:" + song.discography + "\n";
|
|
175
|
+
result += song.fileUrl === void 0 ? "" : "F:" + song.fileUrl + "\n";
|
|
176
|
+
result += song.group === void 0 ? "" : "G:" + song.group + "\n";
|
|
177
|
+
result += song.history === void 0 ? "" : "H:" + song.history + "\n";
|
|
178
|
+
result += song.instruction === void 0 ? "" : "I:" + song.instruction + "\n";
|
|
179
|
+
result += song.key === void 0 ? "" : "K:" + song.key + "\n";
|
|
180
|
+
result += song.unitNoteLength === void 0 ? "" : "L:" + song.unitNoteLength + "\n";
|
|
181
|
+
result += song.meter === void 0 ? "" : "M:" + song.meter + "\n";
|
|
182
|
+
result += song.macro === void 0 ? "" : "m:" + song.macro + "\n";
|
|
183
|
+
result += song.notes === void 0 ? "" : "N:" + song.notes + "\n";
|
|
184
|
+
result += song.origin === void 0 ? "" : "O:" + song.origin + "\n";
|
|
185
|
+
result += song.parts === void 0 ? "" : "P:" + song.parts + "\n";
|
|
186
|
+
result += song.tempo === void 0 ? "" : "Q:" + song.tempo + "\n";
|
|
187
|
+
result += song.rhythm === void 0 ? "" : "R:" + song.rhythm + "\n";
|
|
188
|
+
result += song.remark === void 0 ? "" : "r:" + song.remark + "\n";
|
|
189
|
+
result += song.source === void 0 ? "" : "S:" + song.source + "\n";
|
|
190
|
+
result += song.symbolLine === void 0 ? "" : "s:" + song.symbolLine + "\n";
|
|
191
|
+
result += song.userDefined === void 0 ? "" : "U:" + song.userDefined + "\n";
|
|
192
|
+
result += song.voice === void 0 ? "" : "V:" + song.voice + "\n";
|
|
193
|
+
result += song.transcription === void 0 ? "" : "Z:" + song.transcription + "\n";
|
|
194
|
+
return result + abc;
|
|
195
|
+
};
|
|
196
|
+
var convertStringToAbcTune = (abc) => {
|
|
197
|
+
const objectArray = ABCJS.parseOnly(abc);
|
|
198
|
+
const object = objectArray;
|
|
199
|
+
validate(object != null, "Tune object may not be null");
|
|
200
|
+
validate(object.length > 0, "Tune object may not be empty");
|
|
201
|
+
validate(object[0].lines != null, "Tune object lines may not be null");
|
|
202
|
+
validate(object[0].lines.length > 0, "Tune object lines are empty");
|
|
203
|
+
validate(object[0].lines[0].staff != null, "Staffs may not be null");
|
|
204
|
+
validate(object[0].lines[0].staff.length > 0, "Staffs are empty");
|
|
205
|
+
validate(object[0].lines[0].staff[0].voices != null, "Voices may not be null");
|
|
206
|
+
validate(object[0].lines[0].staff[0].voices.length > 0, "Voices may not be empty");
|
|
207
|
+
validate(object[0].lines[0].staff[0].voices.some((it) => it.length > 0), "Voices are all empty");
|
|
208
|
+
processAbcLyrics(object);
|
|
209
|
+
return object[0];
|
|
210
|
+
};
|
|
211
|
+
var processAbcLyrics = (object) => {
|
|
212
|
+
object[0].lines.forEach(
|
|
213
|
+
(line) => line.staff.forEach(
|
|
214
|
+
(staff) => staff.voices.forEach(
|
|
215
|
+
(element) => element.filter((voice) => voice.el_type === "note").map((voice) => voice).forEach((voice) => {
|
|
216
|
+
voice.lyric?.forEach(
|
|
217
|
+
(lyric) => lyric.syllable = lyric.syllable.replace("\xA0", " ")
|
|
218
|
+
);
|
|
219
|
+
})
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
var combineMelodyAndLyrics = (melody, lyrics) => {
|
|
225
|
+
const song = new AbcSong();
|
|
226
|
+
const rawMelody = extractInfoFields(melody, song);
|
|
227
|
+
const melodyLines = rawMelody.replaceAll(/\n+/g, "\n").trim().split("\n");
|
|
228
|
+
const lyricLines = lyrics.replaceAll(/\n+/g, "\n").trim().split("\n");
|
|
229
|
+
const mixedMelody = [];
|
|
230
|
+
for (let i = 0; i < Math.max(melodyLines.length, lyricLines.length); i++) {
|
|
231
|
+
mixedMelody.push(melodyLines[i] ?? "C ".repeat(10));
|
|
232
|
+
mixedMelody.push(lyricLines[i] ? "w: " + lyricLines[i] : "");
|
|
233
|
+
}
|
|
234
|
+
return addInfoFieldsToMelody(song, mixedMelody.join("\n")).trim();
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// src/based.ts
|
|
238
|
+
var getForVerse = (melody, verse) => melody.subMelodies.find((it) => (
|
|
239
|
+
// `.includes()` won't work due to the Realm data type of `verseUuids`.
|
|
240
|
+
it.verseUuids.some((it2) => it2 == verse.uuid)
|
|
241
|
+
));
|
|
242
|
+
var generateAbcForVerse = (verse, activeMelody) => {
|
|
243
|
+
if (activeMelody === void 0) {
|
|
244
|
+
return "";
|
|
245
|
+
}
|
|
246
|
+
const melody = getForVerse(activeMelody, verse)?.melody || activeMelody.melody;
|
|
247
|
+
return combineMelodyAndLyrics(melody, verse.abcLyrics || "");
|
|
248
|
+
};
|
|
249
|
+
export {
|
|
250
|
+
AbcSong,
|
|
251
|
+
ValidationError,
|
|
252
|
+
addInfoFieldsToMelody,
|
|
253
|
+
combineMelodyAndLyrics,
|
|
254
|
+
convertStringToAbcTune,
|
|
255
|
+
extractInfoFields,
|
|
256
|
+
extractNotesAndLyrics,
|
|
257
|
+
generateAbcForVerse,
|
|
258
|
+
getField,
|
|
259
|
+
getForVerse,
|
|
260
|
+
parse,
|
|
261
|
+
validate
|
|
262
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hymnbook/abc",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsup"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/sampie777/hymnbook-abc.git"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"hymnbook",
|
|
17
|
+
"abc"
|
|
18
|
+
],
|
|
19
|
+
"author": "Samuel-Anton Jansen",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/sampie777/hymnbook-abc/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/sampie777/hymnbook-abc",
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"abcjs": "git+https://github.com/paulrosen/abcjs.git#v6.2.3",
|
|
27
|
+
"tsup": "^8.5.1",
|
|
28
|
+
"typescript": "^5.9.3"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"src",
|
|
33
|
+
"README.md",
|
|
34
|
+
"package.json",
|
|
35
|
+
"package-lock.json"
|
|
36
|
+
]
|
|
37
|
+
}
|