@hymnbook/abc 0.0.0 → 0.1.1
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/dist/{index.mjs → index.cjs} +202 -40
- package/dist/{index.d.mts → index.d.cts} +53 -7
- package/dist/index.d.ts +53 -7
- package/dist/index.js +153 -88
- package/package.json +21 -4
- package/src/based.ts +1 -1
- package/src/deparser.ts +186 -0
- package/src/index.ts +4 -3
- package/src/{abc.ts → parser.ts} +41 -40
- package/src/{abcTypes.ts → types.ts} +6 -3
|
@@ -1,4 +1,52 @@
|
|
|
1
|
-
|
|
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
|
+
generateAbcForVerse: () => generateAbcForVerse,
|
|
40
|
+
getField: () => getField,
|
|
41
|
+
getForVerse: () => getForVerse,
|
|
42
|
+
parse: () => parse,
|
|
43
|
+
squashNotesAndLyrics: () => squashNotesAndLyrics,
|
|
44
|
+
stringify: () => stringify,
|
|
45
|
+
validate: () => validate
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(index_exports);
|
|
48
|
+
|
|
49
|
+
// src/types.ts
|
|
2
50
|
var AbcSong = class {
|
|
3
51
|
// See also: https://abcnotation.com/wiki/abc:standard:v2.1#information_fields
|
|
4
52
|
area;
|
|
@@ -40,8 +88,8 @@ var AbcSong = class {
|
|
|
40
88
|
};
|
|
41
89
|
};
|
|
42
90
|
|
|
43
|
-
// src/
|
|
44
|
-
|
|
91
|
+
// src/parser.ts
|
|
92
|
+
var ABCJS = __toESM(require("abcjs"), 1);
|
|
45
93
|
|
|
46
94
|
// src/validation.ts
|
|
47
95
|
var ValidationError = class extends Error {
|
|
@@ -53,15 +101,155 @@ function validate(result, message) {
|
|
|
53
101
|
throw new ValidationError(message || "Value is not true");
|
|
54
102
|
}
|
|
55
103
|
|
|
56
|
-
// src/
|
|
104
|
+
// src/deparser.ts
|
|
105
|
+
var addInfoFieldsToMelody = (song, abc) => {
|
|
106
|
+
let result = "";
|
|
107
|
+
result += song.referenceNumber === void 0 ? "" : "X:" + song.referenceNumber + "\n";
|
|
108
|
+
result += song.title === void 0 ? "" : "T:" + song.title + "\n";
|
|
109
|
+
result += song.area === void 0 ? "" : "A:" + song.area + "\n";
|
|
110
|
+
result += song.book === void 0 ? "" : "B:" + song.book + "\n";
|
|
111
|
+
result += song.composer === void 0 ? "" : "C:" + song.composer + "\n";
|
|
112
|
+
result += song.discography === void 0 ? "" : "D:" + song.discography + "\n";
|
|
113
|
+
result += song.fileUrl === void 0 ? "" : "F:" + song.fileUrl + "\n";
|
|
114
|
+
result += song.group === void 0 ? "" : "G:" + song.group + "\n";
|
|
115
|
+
result += song.history === void 0 ? "" : "H:" + song.history + "\n";
|
|
116
|
+
result += song.instruction === void 0 ? "" : "I:" + song.instruction + "\n";
|
|
117
|
+
result += song.key === void 0 ? "" : "K:" + song.key + "\n";
|
|
118
|
+
result += song.unitNoteLength === void 0 ? "" : "L:" + song.unitNoteLength + "\n";
|
|
119
|
+
result += song.meter === void 0 ? "" : "M:" + song.meter + "\n";
|
|
120
|
+
result += song.macro === void 0 ? "" : "m:" + song.macro + "\n";
|
|
121
|
+
result += song.notes === void 0 ? "" : "N:" + song.notes + "\n";
|
|
122
|
+
result += song.origin === void 0 ? "" : "O:" + song.origin + "\n";
|
|
123
|
+
result += song.parts === void 0 ? "" : "P:" + song.parts + "\n";
|
|
124
|
+
result += song.tempo === void 0 ? "" : "Q:" + song.tempo + "\n";
|
|
125
|
+
result += song.rhythm === void 0 ? "" : "R:" + song.rhythm + "\n";
|
|
126
|
+
result += song.remark === void 0 ? "" : "r:" + song.remark + "\n";
|
|
127
|
+
result += song.source === void 0 ? "" : "S:" + song.source + "\n";
|
|
128
|
+
result += song.symbolLine === void 0 ? "" : "s:" + song.symbolLine + "\n";
|
|
129
|
+
result += song.userDefined === void 0 ? "" : "U:" + song.userDefined + "\n";
|
|
130
|
+
result += song.voice === void 0 ? "" : "V:" + song.voice + "\n";
|
|
131
|
+
result += song.transcription === void 0 ? "" : "Z:" + song.transcription + "\n";
|
|
132
|
+
return result + abc;
|
|
133
|
+
};
|
|
134
|
+
var durationToString = (duration) => {
|
|
135
|
+
duration /= 4;
|
|
136
|
+
if (duration > 3 / 8) return (duration * 4).toString();
|
|
137
|
+
if (duration == 1 / 4) return "";
|
|
138
|
+
if (duration == 3 / 8) return "3/2";
|
|
139
|
+
if (duration == 1 / 8) return "/2";
|
|
140
|
+
if (duration == 3 / 16) return "3/4";
|
|
141
|
+
if (duration == 1 / 16) return "/4";
|
|
142
|
+
if (duration == 3 / 32) return "3/8";
|
|
143
|
+
if (duration == 1 / 32) return "/8";
|
|
144
|
+
if (duration == 3 / 64) return "3/16";
|
|
145
|
+
return "";
|
|
146
|
+
};
|
|
147
|
+
var voiceBarToString = (voice) => {
|
|
148
|
+
let result = "";
|
|
149
|
+
switch (voice.type) {
|
|
150
|
+
case "bar_thin":
|
|
151
|
+
result += "|";
|
|
152
|
+
break;
|
|
153
|
+
case "bar_thin_thick":
|
|
154
|
+
result += "|]";
|
|
155
|
+
break;
|
|
156
|
+
case "bar_thin_thin":
|
|
157
|
+
result += "||";
|
|
158
|
+
break;
|
|
159
|
+
case "bar_thick_thin":
|
|
160
|
+
result += "[|";
|
|
161
|
+
break;
|
|
162
|
+
case "bar_right_repeat":
|
|
163
|
+
result += ":|";
|
|
164
|
+
break;
|
|
165
|
+
case "bar_left_repeat":
|
|
166
|
+
result += "|:";
|
|
167
|
+
break;
|
|
168
|
+
case "bar_dbl_repeat":
|
|
169
|
+
result += "::";
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
if (voice.startEnding) result += voice.startEnding;
|
|
173
|
+
return result + " ";
|
|
174
|
+
};
|
|
175
|
+
var voiceNoteToString = (voice, durationMultiplier, isBeam) => {
|
|
176
|
+
let notes = "";
|
|
177
|
+
let lyrics = "";
|
|
178
|
+
const duration = durationToString(voice.duration / durationMultiplier);
|
|
179
|
+
if (voice.startBeam) isBeam = true;
|
|
180
|
+
if (voice.endBeam) isBeam = false;
|
|
181
|
+
voice.chord?.forEach((chord) => {
|
|
182
|
+
const parsedChordName = chord.name.replaceAll(/♭/g, "b").replaceAll(/♯/g, "#");
|
|
183
|
+
notes += `"${parsedChordName}"`;
|
|
184
|
+
});
|
|
185
|
+
if (voice.pitches && voice.pitches?.length > 1) notes += "[";
|
|
186
|
+
voice.pitches?.forEach((pitch) => {
|
|
187
|
+
let note = pitch.name + duration;
|
|
188
|
+
if (pitch.startSlur) note = "(".repeat(pitch.startSlur.length) + note;
|
|
189
|
+
if (pitch.endSlur) note += ")".repeat(pitch.endSlur.length);
|
|
190
|
+
if (pitch.startTie) note += "-";
|
|
191
|
+
notes += note;
|
|
192
|
+
});
|
|
193
|
+
if (voice.pitches && voice.pitches?.length > 1) notes += "]";
|
|
194
|
+
if (voice.rest) {
|
|
195
|
+
if (voice.rest.type == "spacer") {
|
|
196
|
+
notes += "y";
|
|
197
|
+
} else if (voice.rest.type == "multimeasure") {
|
|
198
|
+
notes += "Z" + durationToString(voice.duration / 4);
|
|
199
|
+
} else {
|
|
200
|
+
notes += "z" + duration;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (voice.pitches) {
|
|
204
|
+
voice.lyric?.forEach((lyric) => {
|
|
205
|
+
let text = lyric.syllable.replace(/ /g, "~");
|
|
206
|
+
if (text == "") text = "*";
|
|
207
|
+
lyrics += text + lyric.divider;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
if (!isBeam) notes += " ";
|
|
211
|
+
return {
|
|
212
|
+
notes,
|
|
213
|
+
lyrics,
|
|
214
|
+
isBeam
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
var stringify = (song) => {
|
|
218
|
+
const durationParts = (song.unitNoteLength || "1/8").split("/");
|
|
219
|
+
const durationMultiplier = durationParts.length > 1 ? +durationParts[0] / +durationParts[1] : +durationParts[0];
|
|
220
|
+
const melody = song.melody.flatMap((line) => {
|
|
221
|
+
const notes = [];
|
|
222
|
+
const lyrics = [];
|
|
223
|
+
let isBeam = false;
|
|
224
|
+
line.forEach((voice) => {
|
|
225
|
+
switch (voice.el_type) {
|
|
226
|
+
case "bar":
|
|
227
|
+
notes.push(voiceBarToString(voice));
|
|
228
|
+
break;
|
|
229
|
+
case "note":
|
|
230
|
+
const result2 = voiceNoteToString(voice, durationMultiplier, isBeam);
|
|
231
|
+
notes.push(result2.notes);
|
|
232
|
+
lyrics.push(result2.lyrics);
|
|
233
|
+
isBeam = result2.isBeam;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
const result = [notes.join("").trim()];
|
|
238
|
+
const mergedLyrics = lyrics.join("").trim();
|
|
239
|
+
if (mergedLyrics.length > 0) {
|
|
240
|
+
result.push("w: " + mergedLyrics);
|
|
241
|
+
}
|
|
242
|
+
return result;
|
|
243
|
+
}).join("\n");
|
|
244
|
+
return addInfoFieldsToMelody(song, melody);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// src/parser.ts
|
|
57
248
|
var parse = (abc) => {
|
|
58
249
|
abc = abc.replace(/%.*/g, "").replaceAll(/\n+/g, "\n");
|
|
59
250
|
const song = new AbcSong();
|
|
60
251
|
extractInfoFields(abc, song);
|
|
61
252
|
const tuneObject = convertStringToAbcTune(abc);
|
|
62
|
-
if (tuneObject === void 0) {
|
|
63
|
-
return void 0;
|
|
64
|
-
}
|
|
65
253
|
song.clef = tuneObject.lines[0].staff[0].clef || song.clef;
|
|
66
254
|
song.keySignature = tuneObject.lines[0].staff[0].key || song.keySignature;
|
|
67
255
|
song.melody = tuneObject.lines.map((line) => line.staff[0].voices[0]);
|
|
@@ -149,7 +337,7 @@ var extractInfoFields = (abc, song) => {
|
|
|
149
337
|
song.transcription = getField(abc, "Z");
|
|
150
338
|
return abc.replace(/%.*\n/g, "").replace(/(^|\n) *\t*[ABCDFGHIKLMmNOPQRrSsTUVXZ]:.*/g, "").replace(/\n+/g, "\n").replace(/^\n*/g, "").replace(/\n*$/g, "");
|
|
151
339
|
};
|
|
152
|
-
var
|
|
340
|
+
var squashNotesAndLyrics = (abc) => {
|
|
153
341
|
const notes = [];
|
|
154
342
|
const lyrics = [];
|
|
155
343
|
abc.split("\n").map((it) => it.trim()).forEach((it) => {
|
|
@@ -164,41 +352,13 @@ var extractNotesAndLyrics = (abc) => {
|
|
|
164
352
|
lyrics: lyrics.join(" ")
|
|
165
353
|
};
|
|
166
354
|
};
|
|
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
355
|
var convertStringToAbcTune = (abc) => {
|
|
197
356
|
const objectArray = ABCJS.parseOnly(abc);
|
|
198
357
|
const object = objectArray;
|
|
199
358
|
validate(object != null, "Tune object may not be null");
|
|
200
359
|
validate(object.length > 0, "Tune object may not be empty");
|
|
201
360
|
validate(object[0].lines != null, "Tune object lines may not be null");
|
|
361
|
+
object[0].lines = object[0].lines.filter((it) => it.staff);
|
|
202
362
|
validate(object[0].lines.length > 0, "Tune object lines are empty");
|
|
203
363
|
validate(object[0].lines[0].staff != null, "Staffs may not be null");
|
|
204
364
|
validate(object[0].lines[0].staff.length > 0, "Staffs are empty");
|
|
@@ -246,17 +406,19 @@ var generateAbcForVerse = (verse, activeMelody) => {
|
|
|
246
406
|
const melody = getForVerse(activeMelody, verse)?.melody || activeMelody.melody;
|
|
247
407
|
return combineMelodyAndLyrics(melody, verse.abcLyrics || "");
|
|
248
408
|
};
|
|
249
|
-
export
|
|
409
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
410
|
+
0 && (module.exports = {
|
|
250
411
|
AbcSong,
|
|
251
412
|
ValidationError,
|
|
252
413
|
addInfoFieldsToMelody,
|
|
253
414
|
combineMelodyAndLyrics,
|
|
254
415
|
convertStringToAbcTune,
|
|
255
416
|
extractInfoFields,
|
|
256
|
-
extractNotesAndLyrics,
|
|
257
417
|
generateAbcForVerse,
|
|
258
418
|
getField,
|
|
259
419
|
getForVerse,
|
|
260
420
|
parse,
|
|
421
|
+
squashNotesAndLyrics,
|
|
422
|
+
stringify,
|
|
261
423
|
validate
|
|
262
|
-
};
|
|
424
|
+
});
|
|
@@ -3,7 +3,7 @@ import { SynthOptions } from 'abcjs';
|
|
|
3
3
|
type AccidentalName = "flat" | "natural" | "sharp" | "dblsharp" | "dblflat" | "quarterflat" | "quartersharp";
|
|
4
4
|
type ChordPlacement = "above" | "below" | "left" | "right" | "default";
|
|
5
5
|
type StemDirection = "up" | "down" | "auto" | "none";
|
|
6
|
-
type AbcType = "bar_thin" | "bar_thin_thick" | "bar_thin_thin" | "bar_thick_thin" | "bar_right_repeat" | "bar_left_repeat" | "
|
|
6
|
+
type AbcType = "bar_thin" | "bar_thin_thick" | "bar_thin_thin" | "bar_thick_thin" | "bar_right_repeat" | "bar_left_repeat" | "bar_dbl_repeat";
|
|
7
7
|
type AbcElementType = "note" | "bar";
|
|
8
8
|
type Clef = "treble" | "tenor" | "bass" | "alto" | "treble+8" | "tenor+8" | "bass+8" | "alto+8" | "treble-8" | "tenor-8" | "bass-8" | "alto-8" | "none" | "perc";
|
|
9
9
|
type NoteHeadType = "normal" | "harmonic" | "rhythm" | "x" | "triangle";
|
|
@@ -44,6 +44,7 @@ interface VoiceItemBar {
|
|
|
44
44
|
type: AbcType;
|
|
45
45
|
startChar: number;
|
|
46
46
|
endChar: number;
|
|
47
|
+
startEnding?: string;
|
|
47
48
|
}
|
|
48
49
|
interface NoteProperties {
|
|
49
50
|
duration: number;
|
|
@@ -51,6 +52,8 @@ interface NoteProperties {
|
|
|
51
52
|
lyric?: AbcLyric[];
|
|
52
53
|
chord?: AbcChord[];
|
|
53
54
|
rest?: AbcRest;
|
|
55
|
+
startBeam?: boolean;
|
|
56
|
+
endBeam?: boolean;
|
|
54
57
|
}
|
|
55
58
|
interface VoiceItemNote extends NoteProperties {
|
|
56
59
|
el_type: "note";
|
|
@@ -220,17 +223,48 @@ declare class AbcSong {
|
|
|
220
223
|
clef: AbcClef;
|
|
221
224
|
keySignature: KeySignature;
|
|
222
225
|
}
|
|
223
|
-
|
|
226
|
+
type NoteGroupInterface = {
|
|
224
227
|
notes: string;
|
|
225
228
|
lyrics: string;
|
|
226
|
-
}
|
|
229
|
+
};
|
|
227
230
|
|
|
228
|
-
|
|
231
|
+
/**
|
|
232
|
+
* Generates an AbcSong object from an ABC notation string.
|
|
233
|
+
* This function does some extra processing to handle slurs and lyrics properly.
|
|
234
|
+
* If you do not want this, use `convertStringToAbcTune` directly.
|
|
235
|
+
*
|
|
236
|
+
* @param abc
|
|
237
|
+
*/
|
|
238
|
+
declare const parse: (abc: string) => AbcSong;
|
|
239
|
+
/**
|
|
240
|
+
* Get an info/header field from an ABC notation string.
|
|
241
|
+
* @param abc
|
|
242
|
+
* @param field
|
|
243
|
+
* @param _default
|
|
244
|
+
*/
|
|
229
245
|
declare const getField: (abc: string, field: string, _default?: string) => (string | undefined);
|
|
246
|
+
/**
|
|
247
|
+
* Extract info/header fields from an ABC notation string into an AbcSong object and return the remaining melody string.
|
|
248
|
+
* @param abc
|
|
249
|
+
* @param song
|
|
250
|
+
*/
|
|
230
251
|
declare const extractInfoFields: (abc: string, song: AbcSong) => string;
|
|
231
|
-
|
|
232
|
-
|
|
252
|
+
/**
|
|
253
|
+
* Extract notes and lyrics from an ABC notation string as a single line for each.
|
|
254
|
+
* Note that you must first remove info/header fields before using this function.
|
|
255
|
+
* @param abc
|
|
256
|
+
*/
|
|
257
|
+
declare const squashNotesAndLyrics: (abc: string) => NoteGroupInterface;
|
|
258
|
+
/**
|
|
259
|
+
* Converts and validates an ABC notation string into a TuneObject.
|
|
260
|
+
* @param abc
|
|
261
|
+
*/
|
|
233
262
|
declare const convertStringToAbcTune: (abc: string) => TuneObject;
|
|
263
|
+
/**
|
|
264
|
+
* Combine multi line lyrics line with a multi line melody into a single ABC notation string.
|
|
265
|
+
* @param melody
|
|
266
|
+
* @param lyrics
|
|
267
|
+
*/
|
|
234
268
|
declare const combineMelodyAndLyrics: (melody: string, lyrics: string) => string;
|
|
235
269
|
|
|
236
270
|
type Verse = {
|
|
@@ -253,4 +287,16 @@ declare class ValidationError extends Error {
|
|
|
253
287
|
}
|
|
254
288
|
declare function validate(result: boolean, message?: string): void;
|
|
255
289
|
|
|
256
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Converts an AbcSong object's info fields to ABC notation and merges them with the given melody string.
|
|
292
|
+
* @param song
|
|
293
|
+
* @param abc
|
|
294
|
+
*/
|
|
295
|
+
declare const addInfoFieldsToMelody: (song: AbcSong, abc: string) => string;
|
|
296
|
+
/**
|
|
297
|
+
* Convert an AbcSong object to an ABC notation string.
|
|
298
|
+
* @param song
|
|
299
|
+
*/
|
|
300
|
+
declare const stringify: (song: AbcSong) => string;
|
|
301
|
+
|
|
302
|
+
export { type AbcChord, type AbcClef, type AbcElementType, type AbcLine, type AbcLyric, type AbcMelody, type AbcPitch, type AbcPitchStartSlur, type AbcRest, AbcSong, type AbcStaff, type AbcSubMelody, type AbcType, type Accidental, type AccidentalName, type AudioTrack, type AudioTrackNote, type AudioTrackProgram, type AudioTrackText, type AudioTracks, type BracePosition, type ChordPlacement, type ChordType, type Clef, type KeyAccidentalName, type KeyRoot, type KeySignature, type MetaText, type Mode, type NoteGroupInterface, type NoteHeadType, type NoteLetter, type NoteProperties, type StemDirection, type TuneObject, ValidationError, type Verse, type VoiceItem, type VoiceItemBar, type VoiceItemClef, type VoiceItemGap, type VoiceItemKey, type VoiceItemNote, type VoiceItemStem, addInfoFieldsToMelody, combineMelodyAndLyrics, convertStringToAbcTune, extractInfoFields, generateAbcForVerse, getField, getForVerse, parse, squashNotesAndLyrics, stringify, validate };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { SynthOptions } from 'abcjs';
|
|
|
3
3
|
type AccidentalName = "flat" | "natural" | "sharp" | "dblsharp" | "dblflat" | "quarterflat" | "quartersharp";
|
|
4
4
|
type ChordPlacement = "above" | "below" | "left" | "right" | "default";
|
|
5
5
|
type StemDirection = "up" | "down" | "auto" | "none";
|
|
6
|
-
type AbcType = "bar_thin" | "bar_thin_thick" | "bar_thin_thin" | "bar_thick_thin" | "bar_right_repeat" | "bar_left_repeat" | "
|
|
6
|
+
type AbcType = "bar_thin" | "bar_thin_thick" | "bar_thin_thin" | "bar_thick_thin" | "bar_right_repeat" | "bar_left_repeat" | "bar_dbl_repeat";
|
|
7
7
|
type AbcElementType = "note" | "bar";
|
|
8
8
|
type Clef = "treble" | "tenor" | "bass" | "alto" | "treble+8" | "tenor+8" | "bass+8" | "alto+8" | "treble-8" | "tenor-8" | "bass-8" | "alto-8" | "none" | "perc";
|
|
9
9
|
type NoteHeadType = "normal" | "harmonic" | "rhythm" | "x" | "triangle";
|
|
@@ -44,6 +44,7 @@ interface VoiceItemBar {
|
|
|
44
44
|
type: AbcType;
|
|
45
45
|
startChar: number;
|
|
46
46
|
endChar: number;
|
|
47
|
+
startEnding?: string;
|
|
47
48
|
}
|
|
48
49
|
interface NoteProperties {
|
|
49
50
|
duration: number;
|
|
@@ -51,6 +52,8 @@ interface NoteProperties {
|
|
|
51
52
|
lyric?: AbcLyric[];
|
|
52
53
|
chord?: AbcChord[];
|
|
53
54
|
rest?: AbcRest;
|
|
55
|
+
startBeam?: boolean;
|
|
56
|
+
endBeam?: boolean;
|
|
54
57
|
}
|
|
55
58
|
interface VoiceItemNote extends NoteProperties {
|
|
56
59
|
el_type: "note";
|
|
@@ -220,17 +223,48 @@ declare class AbcSong {
|
|
|
220
223
|
clef: AbcClef;
|
|
221
224
|
keySignature: KeySignature;
|
|
222
225
|
}
|
|
223
|
-
|
|
226
|
+
type NoteGroupInterface = {
|
|
224
227
|
notes: string;
|
|
225
228
|
lyrics: string;
|
|
226
|
-
}
|
|
229
|
+
};
|
|
227
230
|
|
|
228
|
-
|
|
231
|
+
/**
|
|
232
|
+
* Generates an AbcSong object from an ABC notation string.
|
|
233
|
+
* This function does some extra processing to handle slurs and lyrics properly.
|
|
234
|
+
* If you do not want this, use `convertStringToAbcTune` directly.
|
|
235
|
+
*
|
|
236
|
+
* @param abc
|
|
237
|
+
*/
|
|
238
|
+
declare const parse: (abc: string) => AbcSong;
|
|
239
|
+
/**
|
|
240
|
+
* Get an info/header field from an ABC notation string.
|
|
241
|
+
* @param abc
|
|
242
|
+
* @param field
|
|
243
|
+
* @param _default
|
|
244
|
+
*/
|
|
229
245
|
declare const getField: (abc: string, field: string, _default?: string) => (string | undefined);
|
|
246
|
+
/**
|
|
247
|
+
* Extract info/header fields from an ABC notation string into an AbcSong object and return the remaining melody string.
|
|
248
|
+
* @param abc
|
|
249
|
+
* @param song
|
|
250
|
+
*/
|
|
230
251
|
declare const extractInfoFields: (abc: string, song: AbcSong) => string;
|
|
231
|
-
|
|
232
|
-
|
|
252
|
+
/**
|
|
253
|
+
* Extract notes and lyrics from an ABC notation string as a single line for each.
|
|
254
|
+
* Note that you must first remove info/header fields before using this function.
|
|
255
|
+
* @param abc
|
|
256
|
+
*/
|
|
257
|
+
declare const squashNotesAndLyrics: (abc: string) => NoteGroupInterface;
|
|
258
|
+
/**
|
|
259
|
+
* Converts and validates an ABC notation string into a TuneObject.
|
|
260
|
+
* @param abc
|
|
261
|
+
*/
|
|
233
262
|
declare const convertStringToAbcTune: (abc: string) => TuneObject;
|
|
263
|
+
/**
|
|
264
|
+
* Combine multi line lyrics line with a multi line melody into a single ABC notation string.
|
|
265
|
+
* @param melody
|
|
266
|
+
* @param lyrics
|
|
267
|
+
*/
|
|
234
268
|
declare const combineMelodyAndLyrics: (melody: string, lyrics: string) => string;
|
|
235
269
|
|
|
236
270
|
type Verse = {
|
|
@@ -253,4 +287,16 @@ declare class ValidationError extends Error {
|
|
|
253
287
|
}
|
|
254
288
|
declare function validate(result: boolean, message?: string): void;
|
|
255
289
|
|
|
256
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Converts an AbcSong object's info fields to ABC notation and merges them with the given melody string.
|
|
292
|
+
* @param song
|
|
293
|
+
* @param abc
|
|
294
|
+
*/
|
|
295
|
+
declare const addInfoFieldsToMelody: (song: AbcSong, abc: string) => string;
|
|
296
|
+
/**
|
|
297
|
+
* Convert an AbcSong object to an ABC notation string.
|
|
298
|
+
* @param song
|
|
299
|
+
*/
|
|
300
|
+
declare const stringify: (song: AbcSong) => string;
|
|
301
|
+
|
|
302
|
+
export { type AbcChord, type AbcClef, type AbcElementType, type AbcLine, type AbcLyric, type AbcMelody, type AbcPitch, type AbcPitchStartSlur, type AbcRest, AbcSong, type AbcStaff, type AbcSubMelody, type AbcType, type Accidental, type AccidentalName, type AudioTrack, type AudioTrackNote, type AudioTrackProgram, type AudioTrackText, type AudioTracks, type BracePosition, type ChordPlacement, type ChordType, type Clef, type KeyAccidentalName, type KeyRoot, type KeySignature, type MetaText, type Mode, type NoteGroupInterface, type NoteHeadType, type NoteLetter, type NoteProperties, type StemDirection, type TuneObject, ValidationError, type Verse, type VoiceItem, type VoiceItemBar, type VoiceItemClef, type VoiceItemGap, type VoiceItemKey, type VoiceItemNote, type VoiceItemStem, addInfoFieldsToMelody, combineMelodyAndLyrics, convertStringToAbcTune, extractInfoFields, generateAbcForVerse, getField, getForVerse, parse, squashNotesAndLyrics, stringify, validate };
|
package/dist/index.js
CHANGED
|
@@ -1,51 +1,4 @@
|
|
|
1
|
-
|
|
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
|
|
1
|
+
// src/types.ts
|
|
49
2
|
var AbcSong = class {
|
|
50
3
|
// See also: https://abcnotation.com/wiki/abc:standard:v2.1#information_fields
|
|
51
4
|
area;
|
|
@@ -87,8 +40,8 @@ var AbcSong = class {
|
|
|
87
40
|
};
|
|
88
41
|
};
|
|
89
42
|
|
|
90
|
-
// src/
|
|
91
|
-
|
|
43
|
+
// src/parser.ts
|
|
44
|
+
import * as ABCJS from "abcjs";
|
|
92
45
|
|
|
93
46
|
// src/validation.ts
|
|
94
47
|
var ValidationError = class extends Error {
|
|
@@ -100,15 +53,155 @@ function validate(result, message) {
|
|
|
100
53
|
throw new ValidationError(message || "Value is not true");
|
|
101
54
|
}
|
|
102
55
|
|
|
103
|
-
// src/
|
|
56
|
+
// src/deparser.ts
|
|
57
|
+
var addInfoFieldsToMelody = (song, abc) => {
|
|
58
|
+
let result = "";
|
|
59
|
+
result += song.referenceNumber === void 0 ? "" : "X:" + song.referenceNumber + "\n";
|
|
60
|
+
result += song.title === void 0 ? "" : "T:" + song.title + "\n";
|
|
61
|
+
result += song.area === void 0 ? "" : "A:" + song.area + "\n";
|
|
62
|
+
result += song.book === void 0 ? "" : "B:" + song.book + "\n";
|
|
63
|
+
result += song.composer === void 0 ? "" : "C:" + song.composer + "\n";
|
|
64
|
+
result += song.discography === void 0 ? "" : "D:" + song.discography + "\n";
|
|
65
|
+
result += song.fileUrl === void 0 ? "" : "F:" + song.fileUrl + "\n";
|
|
66
|
+
result += song.group === void 0 ? "" : "G:" + song.group + "\n";
|
|
67
|
+
result += song.history === void 0 ? "" : "H:" + song.history + "\n";
|
|
68
|
+
result += song.instruction === void 0 ? "" : "I:" + song.instruction + "\n";
|
|
69
|
+
result += song.key === void 0 ? "" : "K:" + song.key + "\n";
|
|
70
|
+
result += song.unitNoteLength === void 0 ? "" : "L:" + song.unitNoteLength + "\n";
|
|
71
|
+
result += song.meter === void 0 ? "" : "M:" + song.meter + "\n";
|
|
72
|
+
result += song.macro === void 0 ? "" : "m:" + song.macro + "\n";
|
|
73
|
+
result += song.notes === void 0 ? "" : "N:" + song.notes + "\n";
|
|
74
|
+
result += song.origin === void 0 ? "" : "O:" + song.origin + "\n";
|
|
75
|
+
result += song.parts === void 0 ? "" : "P:" + song.parts + "\n";
|
|
76
|
+
result += song.tempo === void 0 ? "" : "Q:" + song.tempo + "\n";
|
|
77
|
+
result += song.rhythm === void 0 ? "" : "R:" + song.rhythm + "\n";
|
|
78
|
+
result += song.remark === void 0 ? "" : "r:" + song.remark + "\n";
|
|
79
|
+
result += song.source === void 0 ? "" : "S:" + song.source + "\n";
|
|
80
|
+
result += song.symbolLine === void 0 ? "" : "s:" + song.symbolLine + "\n";
|
|
81
|
+
result += song.userDefined === void 0 ? "" : "U:" + song.userDefined + "\n";
|
|
82
|
+
result += song.voice === void 0 ? "" : "V:" + song.voice + "\n";
|
|
83
|
+
result += song.transcription === void 0 ? "" : "Z:" + song.transcription + "\n";
|
|
84
|
+
return result + abc;
|
|
85
|
+
};
|
|
86
|
+
var durationToString = (duration) => {
|
|
87
|
+
duration /= 4;
|
|
88
|
+
if (duration > 3 / 8) return (duration * 4).toString();
|
|
89
|
+
if (duration == 1 / 4) return "";
|
|
90
|
+
if (duration == 3 / 8) return "3/2";
|
|
91
|
+
if (duration == 1 / 8) return "/2";
|
|
92
|
+
if (duration == 3 / 16) return "3/4";
|
|
93
|
+
if (duration == 1 / 16) return "/4";
|
|
94
|
+
if (duration == 3 / 32) return "3/8";
|
|
95
|
+
if (duration == 1 / 32) return "/8";
|
|
96
|
+
if (duration == 3 / 64) return "3/16";
|
|
97
|
+
return "";
|
|
98
|
+
};
|
|
99
|
+
var voiceBarToString = (voice) => {
|
|
100
|
+
let result = "";
|
|
101
|
+
switch (voice.type) {
|
|
102
|
+
case "bar_thin":
|
|
103
|
+
result += "|";
|
|
104
|
+
break;
|
|
105
|
+
case "bar_thin_thick":
|
|
106
|
+
result += "|]";
|
|
107
|
+
break;
|
|
108
|
+
case "bar_thin_thin":
|
|
109
|
+
result += "||";
|
|
110
|
+
break;
|
|
111
|
+
case "bar_thick_thin":
|
|
112
|
+
result += "[|";
|
|
113
|
+
break;
|
|
114
|
+
case "bar_right_repeat":
|
|
115
|
+
result += ":|";
|
|
116
|
+
break;
|
|
117
|
+
case "bar_left_repeat":
|
|
118
|
+
result += "|:";
|
|
119
|
+
break;
|
|
120
|
+
case "bar_dbl_repeat":
|
|
121
|
+
result += "::";
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
if (voice.startEnding) result += voice.startEnding;
|
|
125
|
+
return result + " ";
|
|
126
|
+
};
|
|
127
|
+
var voiceNoteToString = (voice, durationMultiplier, isBeam) => {
|
|
128
|
+
let notes = "";
|
|
129
|
+
let lyrics = "";
|
|
130
|
+
const duration = durationToString(voice.duration / durationMultiplier);
|
|
131
|
+
if (voice.startBeam) isBeam = true;
|
|
132
|
+
if (voice.endBeam) isBeam = false;
|
|
133
|
+
voice.chord?.forEach((chord) => {
|
|
134
|
+
const parsedChordName = chord.name.replaceAll(/♭/g, "b").replaceAll(/♯/g, "#");
|
|
135
|
+
notes += `"${parsedChordName}"`;
|
|
136
|
+
});
|
|
137
|
+
if (voice.pitches && voice.pitches?.length > 1) notes += "[";
|
|
138
|
+
voice.pitches?.forEach((pitch) => {
|
|
139
|
+
let note = pitch.name + duration;
|
|
140
|
+
if (pitch.startSlur) note = "(".repeat(pitch.startSlur.length) + note;
|
|
141
|
+
if (pitch.endSlur) note += ")".repeat(pitch.endSlur.length);
|
|
142
|
+
if (pitch.startTie) note += "-";
|
|
143
|
+
notes += note;
|
|
144
|
+
});
|
|
145
|
+
if (voice.pitches && voice.pitches?.length > 1) notes += "]";
|
|
146
|
+
if (voice.rest) {
|
|
147
|
+
if (voice.rest.type == "spacer") {
|
|
148
|
+
notes += "y";
|
|
149
|
+
} else if (voice.rest.type == "multimeasure") {
|
|
150
|
+
notes += "Z" + durationToString(voice.duration / 4);
|
|
151
|
+
} else {
|
|
152
|
+
notes += "z" + duration;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (voice.pitches) {
|
|
156
|
+
voice.lyric?.forEach((lyric) => {
|
|
157
|
+
let text = lyric.syllable.replace(/ /g, "~");
|
|
158
|
+
if (text == "") text = "*";
|
|
159
|
+
lyrics += text + lyric.divider;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (!isBeam) notes += " ";
|
|
163
|
+
return {
|
|
164
|
+
notes,
|
|
165
|
+
lyrics,
|
|
166
|
+
isBeam
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
var stringify = (song) => {
|
|
170
|
+
const durationParts = (song.unitNoteLength || "1/8").split("/");
|
|
171
|
+
const durationMultiplier = durationParts.length > 1 ? +durationParts[0] / +durationParts[1] : +durationParts[0];
|
|
172
|
+
const melody = song.melody.flatMap((line) => {
|
|
173
|
+
const notes = [];
|
|
174
|
+
const lyrics = [];
|
|
175
|
+
let isBeam = false;
|
|
176
|
+
line.forEach((voice) => {
|
|
177
|
+
switch (voice.el_type) {
|
|
178
|
+
case "bar":
|
|
179
|
+
notes.push(voiceBarToString(voice));
|
|
180
|
+
break;
|
|
181
|
+
case "note":
|
|
182
|
+
const result2 = voiceNoteToString(voice, durationMultiplier, isBeam);
|
|
183
|
+
notes.push(result2.notes);
|
|
184
|
+
lyrics.push(result2.lyrics);
|
|
185
|
+
isBeam = result2.isBeam;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
const result = [notes.join("").trim()];
|
|
190
|
+
const mergedLyrics = lyrics.join("").trim();
|
|
191
|
+
if (mergedLyrics.length > 0) {
|
|
192
|
+
result.push("w: " + mergedLyrics);
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
}).join("\n");
|
|
196
|
+
return addInfoFieldsToMelody(song, melody);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// src/parser.ts
|
|
104
200
|
var parse = (abc) => {
|
|
105
201
|
abc = abc.replace(/%.*/g, "").replaceAll(/\n+/g, "\n");
|
|
106
202
|
const song = new AbcSong();
|
|
107
203
|
extractInfoFields(abc, song);
|
|
108
204
|
const tuneObject = convertStringToAbcTune(abc);
|
|
109
|
-
if (tuneObject === void 0) {
|
|
110
|
-
return void 0;
|
|
111
|
-
}
|
|
112
205
|
song.clef = tuneObject.lines[0].staff[0].clef || song.clef;
|
|
113
206
|
song.keySignature = tuneObject.lines[0].staff[0].key || song.keySignature;
|
|
114
207
|
song.melody = tuneObject.lines.map((line) => line.staff[0].voices[0]);
|
|
@@ -196,7 +289,7 @@ var extractInfoFields = (abc, song) => {
|
|
|
196
289
|
song.transcription = getField(abc, "Z");
|
|
197
290
|
return abc.replace(/%.*\n/g, "").replace(/(^|\n) *\t*[ABCDFGHIKLMmNOPQRrSsTUVXZ]:.*/g, "").replace(/\n+/g, "\n").replace(/^\n*/g, "").replace(/\n*$/g, "");
|
|
198
291
|
};
|
|
199
|
-
var
|
|
292
|
+
var squashNotesAndLyrics = (abc) => {
|
|
200
293
|
const notes = [];
|
|
201
294
|
const lyrics = [];
|
|
202
295
|
abc.split("\n").map((it) => it.trim()).forEach((it) => {
|
|
@@ -211,41 +304,13 @@ var extractNotesAndLyrics = (abc) => {
|
|
|
211
304
|
lyrics: lyrics.join(" ")
|
|
212
305
|
};
|
|
213
306
|
};
|
|
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
307
|
var convertStringToAbcTune = (abc) => {
|
|
244
308
|
const objectArray = ABCJS.parseOnly(abc);
|
|
245
309
|
const object = objectArray;
|
|
246
310
|
validate(object != null, "Tune object may not be null");
|
|
247
311
|
validate(object.length > 0, "Tune object may not be empty");
|
|
248
312
|
validate(object[0].lines != null, "Tune object lines may not be null");
|
|
313
|
+
object[0].lines = object[0].lines.filter((it) => it.staff);
|
|
249
314
|
validate(object[0].lines.length > 0, "Tune object lines are empty");
|
|
250
315
|
validate(object[0].lines[0].staff != null, "Staffs may not be null");
|
|
251
316
|
validate(object[0].lines[0].staff.length > 0, "Staffs are empty");
|
|
@@ -293,18 +358,18 @@ var generateAbcForVerse = (verse, activeMelody) => {
|
|
|
293
358
|
const melody = getForVerse(activeMelody, verse)?.melody || activeMelody.melody;
|
|
294
359
|
return combineMelodyAndLyrics(melody, verse.abcLyrics || "");
|
|
295
360
|
};
|
|
296
|
-
|
|
297
|
-
0 && (module.exports = {
|
|
361
|
+
export {
|
|
298
362
|
AbcSong,
|
|
299
363
|
ValidationError,
|
|
300
364
|
addInfoFieldsToMelody,
|
|
301
365
|
combineMelodyAndLyrics,
|
|
302
366
|
convertStringToAbcTune,
|
|
303
367
|
extractInfoFields,
|
|
304
|
-
extractNotesAndLyrics,
|
|
305
368
|
generateAbcForVerse,
|
|
306
369
|
getField,
|
|
307
370
|
getForVerse,
|
|
308
371
|
parse,
|
|
372
|
+
squashNotesAndLyrics,
|
|
373
|
+
stringify,
|
|
309
374
|
validate
|
|
310
|
-
}
|
|
375
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hymnbook/abc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "./dist/index.js",
|
|
6
7
|
"module": "./dist/index.mjs",
|
|
7
8
|
"types": "./dist/index.d.ts",
|
|
8
9
|
"scripts": {
|
|
9
|
-
"
|
|
10
|
+
"test": "node --experimental-vm-modules node_modules/.bin/jest",
|
|
11
|
+
"build": "tsup",
|
|
12
|
+
"publish": "npm run test && npm run build && npm publish --access public",
|
|
13
|
+
"publish-patch": "npm version patch && npm run publish",
|
|
14
|
+
"publish-minor": "npm version minor && npm run publish",
|
|
15
|
+
"publish-major": "npm version major && npm run publish"
|
|
10
16
|
},
|
|
11
17
|
"repository": {
|
|
12
18
|
"type": "git",
|
|
@@ -23,7 +29,9 @@
|
|
|
23
29
|
},
|
|
24
30
|
"homepage": "https://github.com/sampie777/hymnbook-abc",
|
|
25
31
|
"devDependencies": {
|
|
26
|
-
"
|
|
32
|
+
"@jest/globals": "^30.2.0",
|
|
33
|
+
"jest": "^30.2.0",
|
|
34
|
+
"ts-jest": "^29.4.6",
|
|
27
35
|
"tsup": "^8.5.1",
|
|
28
36
|
"typescript": "^5.9.3"
|
|
29
37
|
},
|
|
@@ -33,5 +41,14 @@
|
|
|
33
41
|
"README.md",
|
|
34
42
|
"package.json",
|
|
35
43
|
"package-lock.json"
|
|
36
|
-
]
|
|
44
|
+
],
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"abcjs": "^6.5.2"
|
|
47
|
+
},
|
|
48
|
+
"exports": {
|
|
49
|
+
".": {
|
|
50
|
+
"import": "./dist/index.js",
|
|
51
|
+
"require": "./dist/index.cjs"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
37
54
|
}
|
package/src/based.ts
CHANGED
package/src/deparser.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { AbcSong, VoiceItemBar, VoiceItemNote } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts an AbcSong object's info fields to ABC notation and merges them with the given melody string.
|
|
5
|
+
* @param song
|
|
6
|
+
* @param abc
|
|
7
|
+
*/
|
|
8
|
+
export const addInfoFieldsToMelody = (song: AbcSong, abc: string): string => {
|
|
9
|
+
let result = "";
|
|
10
|
+
// See for following order of fields: https://abcnotation.com/wiki/abc:standard:v2.1#description_of_information_fields
|
|
11
|
+
result += song.referenceNumber === undefined ? "" : "X:" + song.referenceNumber + "\n";
|
|
12
|
+
result += song.title === undefined ? "" : "T:" + song.title + "\n";
|
|
13
|
+
result += song.area === undefined ? "" : "A:" + song.area + "\n";
|
|
14
|
+
result += song.book === undefined ? "" : "B:" + song.book + "\n";
|
|
15
|
+
result += song.composer === undefined ? "" : "C:" + song.composer + "\n";
|
|
16
|
+
result += song.discography === undefined ? "" : "D:" + song.discography + "\n";
|
|
17
|
+
result += song.fileUrl === undefined ? "" : "F:" + song.fileUrl + "\n";
|
|
18
|
+
result += song.group === undefined ? "" : "G:" + song.group + "\n";
|
|
19
|
+
result += song.history === undefined ? "" : "H:" + song.history + "\n";
|
|
20
|
+
result += song.instruction === undefined ? "" : "I:" + song.instruction + "\n";
|
|
21
|
+
result += song.key === undefined ? "" : "K:" + song.key + "\n";
|
|
22
|
+
result += song.unitNoteLength === undefined ? "" : "L:" + song.unitNoteLength + "\n";
|
|
23
|
+
result += song.meter === undefined ? "" : "M:" + song.meter + "\n";
|
|
24
|
+
result += song.macro === undefined ? "" : "m:" + song.macro + "\n";
|
|
25
|
+
result += song.notes === undefined ? "" : "N:" + song.notes + "\n";
|
|
26
|
+
result += song.origin === undefined ? "" : "O:" + song.origin + "\n";
|
|
27
|
+
result += song.parts === undefined ? "" : "P:" + song.parts + "\n";
|
|
28
|
+
result += song.tempo === undefined ? "" : "Q:" + song.tempo + "\n";
|
|
29
|
+
result += song.rhythm === undefined ? "" : "R:" + song.rhythm + "\n";
|
|
30
|
+
result += song.remark === undefined ? "" : "r:" + song.remark + "\n";
|
|
31
|
+
result += song.source === undefined ? "" : "S:" + song.source + "\n";
|
|
32
|
+
result += song.symbolLine === undefined ? "" : "s:" + song.symbolLine + "\n";
|
|
33
|
+
result += song.userDefined === undefined ? "" : "U:" + song.userDefined + "\n";
|
|
34
|
+
result += song.voice === undefined ? "" : "V:" + song.voice + "\n";
|
|
35
|
+
result += song.transcription === undefined ? "" : "Z:" + song.transcription + "\n";
|
|
36
|
+
return result + abc;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const durationToString = (duration: number): string => {
|
|
40
|
+
duration /= 4;
|
|
41
|
+
if (duration > 3 / 8) return (duration * 4).toString();
|
|
42
|
+
if (duration == 1 / 4) return "";
|
|
43
|
+
if (duration == 3 / 8) return "3/2";
|
|
44
|
+
if (duration == 1 / 8) return "/2";
|
|
45
|
+
if (duration == 3 / 16) return "3/4";
|
|
46
|
+
if (duration == 1 / 16) return "/4";
|
|
47
|
+
if (duration == 3 / 32) return "3/8";
|
|
48
|
+
if (duration == 1 / 32) return "/8";
|
|
49
|
+
if (duration == 3 / 64) return "3/16";
|
|
50
|
+
|
|
51
|
+
return ""
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const voiceBarToString = (voice: VoiceItemBar): string => {
|
|
55
|
+
let result = "";
|
|
56
|
+
|
|
57
|
+
switch (voice.type) {
|
|
58
|
+
case "bar_thin":
|
|
59
|
+
result += "|"
|
|
60
|
+
break;
|
|
61
|
+
case "bar_thin_thick":
|
|
62
|
+
result += "|]"
|
|
63
|
+
break;
|
|
64
|
+
case "bar_thin_thin":
|
|
65
|
+
result += "||"
|
|
66
|
+
break;
|
|
67
|
+
case "bar_thick_thin":
|
|
68
|
+
result += "[|"
|
|
69
|
+
break;
|
|
70
|
+
case "bar_right_repeat":
|
|
71
|
+
result += ":|"
|
|
72
|
+
break;
|
|
73
|
+
case "bar_left_repeat":
|
|
74
|
+
result += "|:"
|
|
75
|
+
break;
|
|
76
|
+
case "bar_dbl_repeat":
|
|
77
|
+
result += "::"
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (voice.startEnding) result += voice.startEnding
|
|
82
|
+
|
|
83
|
+
return result + " ";
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const voiceNoteToString = (voice: VoiceItemNote, durationMultiplier: number, isBeam: boolean) => {
|
|
87
|
+
let notes = "";
|
|
88
|
+
let lyrics = "";
|
|
89
|
+
|
|
90
|
+
const duration = durationToString(voice.duration / durationMultiplier);
|
|
91
|
+
|
|
92
|
+
if (voice.startBeam) isBeam = true;
|
|
93
|
+
if (voice.endBeam) isBeam = false;
|
|
94
|
+
|
|
95
|
+
/* Process chords */
|
|
96
|
+
voice.chord?.forEach(chord => {
|
|
97
|
+
const parsedChordName = chord.name
|
|
98
|
+
.replaceAll(/♭/g, "b")
|
|
99
|
+
.replaceAll(/♯/g, "#")
|
|
100
|
+
notes += `"${parsedChordName}"`;
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
/* Process notes */
|
|
104
|
+
if (voice.pitches && voice.pitches?.length > 1) notes += "["
|
|
105
|
+
voice.pitches?.forEach(pitch => {
|
|
106
|
+
let note = pitch.name + duration;
|
|
107
|
+
|
|
108
|
+
if (pitch.startSlur) note = "(".repeat(pitch.startSlur.length) + note;
|
|
109
|
+
if (pitch.endSlur) note += ")".repeat(pitch.endSlur.length)
|
|
110
|
+
|
|
111
|
+
if (pitch.startTie) note += "-";
|
|
112
|
+
|
|
113
|
+
notes += note;
|
|
114
|
+
})
|
|
115
|
+
if (voice.pitches && voice.pitches?.length > 1) notes += "]"
|
|
116
|
+
|
|
117
|
+
/* Process rests */
|
|
118
|
+
if (voice.rest) {
|
|
119
|
+
if (voice.rest.type == "spacer") {
|
|
120
|
+
notes += "y";
|
|
121
|
+
} else if (voice.rest.type == "multimeasure") {
|
|
122
|
+
notes += "Z" + durationToString(voice.duration / 4)
|
|
123
|
+
} else {
|
|
124
|
+
notes += "z" + duration;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Process lyrics */
|
|
129
|
+
if (voice.pitches) {
|
|
130
|
+
voice.lyric?.forEach(lyric => {
|
|
131
|
+
let text = lyric.syllable.replace(/ /g, "~");
|
|
132
|
+
if (text == "") text = "*";
|
|
133
|
+
lyrics += text + lyric.divider
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!isBeam) notes += " ";
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
notes: notes,
|
|
141
|
+
lyrics: lyrics,
|
|
142
|
+
isBeam: isBeam,
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Convert an AbcSong object to an ABC notation string.
|
|
148
|
+
* @param song
|
|
149
|
+
*/
|
|
150
|
+
export const stringify = (song: AbcSong) => {
|
|
151
|
+
const durationParts = (song.unitNoteLength || "1/8").split("/");
|
|
152
|
+
const durationMultiplier = durationParts.length > 1
|
|
153
|
+
? +durationParts[0] / +durationParts[1]
|
|
154
|
+
: +durationParts[0];
|
|
155
|
+
|
|
156
|
+
const melody = song.melody.flatMap(line => {
|
|
157
|
+
const notes: string[] = [];
|
|
158
|
+
const lyrics: string[] = [];
|
|
159
|
+
let isBeam = false;
|
|
160
|
+
|
|
161
|
+
line.forEach(voice => {
|
|
162
|
+
switch (voice.el_type) {
|
|
163
|
+
case "bar":
|
|
164
|
+
notes.push(voiceBarToString(voice))
|
|
165
|
+
break;
|
|
166
|
+
case "note":
|
|
167
|
+
const result = voiceNoteToString(voice, durationMultiplier, isBeam)
|
|
168
|
+
notes.push(result.notes)
|
|
169
|
+
lyrics.push(result.lyrics)
|
|
170
|
+
isBeam = result.isBeam
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
const result: string[] = [notes.join("").trim()]
|
|
176
|
+
const mergedLyrics = lyrics.join("").trim()
|
|
177
|
+
if (mergedLyrics.length > 0) {
|
|
178
|
+
result.push("w: " + mergedLyrics)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return result;
|
|
182
|
+
})
|
|
183
|
+
.join("\n")
|
|
184
|
+
|
|
185
|
+
return addInfoFieldsToMelody(song, melody);
|
|
186
|
+
};
|
package/src/index.ts
CHANGED
package/src/{abc.ts → parser.ts}
RENAMED
|
@@ -8,13 +8,20 @@ import {
|
|
|
8
8
|
TuneObject,
|
|
9
9
|
VoiceItem,
|
|
10
10
|
VoiceItemNote
|
|
11
|
-
} from "./
|
|
11
|
+
} from "./types";
|
|
12
12
|
import { validate } from "./validation";
|
|
13
|
+
import { addInfoFieldsToMelody } from "./deparser";
|
|
13
14
|
|
|
14
15
|
// See also https://abcnotation.com/examples
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Generates an AbcSong object from an ABC notation string.
|
|
19
|
+
* This function does some extra processing to handle slurs and lyrics properly.
|
|
20
|
+
* If you do not want this, use `convertStringToAbcTune` directly.
|
|
21
|
+
*
|
|
22
|
+
* @param abc
|
|
23
|
+
*/
|
|
24
|
+
export const parse = (abc: string): AbcSong => {
|
|
18
25
|
// Remove comments
|
|
19
26
|
abc = abc
|
|
20
27
|
.replace(/%.*/g, "")
|
|
@@ -24,9 +31,6 @@ export const parse = (abc: string): AbcSong | undefined => {
|
|
|
24
31
|
extractInfoFields(abc, song);
|
|
25
32
|
|
|
26
33
|
const tuneObject = convertStringToAbcTune(abc);
|
|
27
|
-
if (tuneObject === undefined) {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
34
|
|
|
31
35
|
// Get the first staff only (thus in case of multiple instrument play, only take the first instrument)
|
|
32
36
|
song.clef = tuneObject.lines!![0].staff!![0].clef || song.clef;
|
|
@@ -118,6 +122,12 @@ const processSlursForLine = (line: VoiceItem[]) => {
|
|
|
118
122
|
});
|
|
119
123
|
};
|
|
120
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Get an info/header field from an ABC notation string.
|
|
127
|
+
* @param abc
|
|
128
|
+
* @param field
|
|
129
|
+
* @param _default
|
|
130
|
+
*/
|
|
121
131
|
export const getField = (abc: string, field: string, _default?: string): (string | undefined) => {
|
|
122
132
|
const result = abc.match(new RegExp("(^|\n) *\t*" + field + ":(.*)?"));
|
|
123
133
|
if (result == null || result.length !== 3 || result[2] == null) {
|
|
@@ -126,6 +136,11 @@ export const getField = (abc: string, field: string, _default?: string): (string
|
|
|
126
136
|
return result[2].trim();
|
|
127
137
|
};
|
|
128
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Extract info/header fields from an ABC notation string into an AbcSong object and return the remaining melody string.
|
|
141
|
+
* @param abc
|
|
142
|
+
* @param song
|
|
143
|
+
*/
|
|
129
144
|
export const extractInfoFields = (abc: string, song: AbcSong): string => {
|
|
130
145
|
song.area = getField(abc, "A");
|
|
131
146
|
song.book = getField(abc, "B");
|
|
@@ -152,6 +167,7 @@ export const extractInfoFields = (abc: string, song: AbcSong): string => {
|
|
|
152
167
|
song.voice = getField(abc, "V");
|
|
153
168
|
song.referenceNumber = getField(abc, "X", "1");
|
|
154
169
|
song.transcription = getField(abc, "Z");
|
|
170
|
+
|
|
155
171
|
return abc
|
|
156
172
|
.replace(/%.*\n/g, "")
|
|
157
173
|
.replace(/(^|\n) *\t*[ABCDFGHIKLMmNOPQRrSsTUVXZ]:.*/g, "")
|
|
@@ -160,7 +176,12 @@ export const extractInfoFields = (abc: string, song: AbcSong): string => {
|
|
|
160
176
|
.replace(/\n*$/g, "");
|
|
161
177
|
};
|
|
162
178
|
|
|
163
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Extract notes and lyrics from an ABC notation string as a single line for each.
|
|
181
|
+
* Note that you must first remove info/header fields before using this function.
|
|
182
|
+
* @param abc
|
|
183
|
+
*/
|
|
184
|
+
export const squashNotesAndLyrics = (abc: string): NoteGroupInterface => {
|
|
164
185
|
const notes: string[] = [];
|
|
165
186
|
const lyrics: string[] = [];
|
|
166
187
|
abc.split("\n")
|
|
@@ -176,40 +197,13 @@ export const extractNotesAndLyrics = (abc: string): NoteGroupInterface => {
|
|
|
176
197
|
return {
|
|
177
198
|
notes: notes.join(" "),
|
|
178
199
|
lyrics: lyrics.join(" ")
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
export const addInfoFieldsToMelody = (song: AbcSong, abc: string): string => {
|
|
183
|
-
let result = "";
|
|
184
|
-
// See for following order of fields: https://abcnotation.com/wiki/abc:standard:v2.1#description_of_information_fields
|
|
185
|
-
result += song.referenceNumber === undefined ? "" : "X:" + song.referenceNumber + "\n";
|
|
186
|
-
result += song.title === undefined ? "" : "T:" + song.title + "\n";
|
|
187
|
-
result += song.area === undefined ? "" : "A:" + song.area + "\n";
|
|
188
|
-
result += song.book === undefined ? "" : "B:" + song.book + "\n";
|
|
189
|
-
result += song.composer === undefined ? "" : "C:" + song.composer + "\n";
|
|
190
|
-
result += song.discography === undefined ? "" : "D:" + song.discography + "\n";
|
|
191
|
-
result += song.fileUrl === undefined ? "" : "F:" + song.fileUrl + "\n";
|
|
192
|
-
result += song.group === undefined ? "" : "G:" + song.group + "\n";
|
|
193
|
-
result += song.history === undefined ? "" : "H:" + song.history + "\n";
|
|
194
|
-
result += song.instruction === undefined ? "" : "I:" + song.instruction + "\n";
|
|
195
|
-
result += song.key === undefined ? "" : "K:" + song.key + "\n";
|
|
196
|
-
result += song.unitNoteLength === undefined ? "" : "L:" + song.unitNoteLength + "\n";
|
|
197
|
-
result += song.meter === undefined ? "" : "M:" + song.meter + "\n";
|
|
198
|
-
result += song.macro === undefined ? "" : "m:" + song.macro + "\n";
|
|
199
|
-
result += song.notes === undefined ? "" : "N:" + song.notes + "\n";
|
|
200
|
-
result += song.origin === undefined ? "" : "O:" + song.origin + "\n";
|
|
201
|
-
result += song.parts === undefined ? "" : "P:" + song.parts + "\n";
|
|
202
|
-
result += song.tempo === undefined ? "" : "Q:" + song.tempo + "\n";
|
|
203
|
-
result += song.rhythm === undefined ? "" : "R:" + song.rhythm + "\n";
|
|
204
|
-
result += song.remark === undefined ? "" : "r:" + song.remark + "\n";
|
|
205
|
-
result += song.source === undefined ? "" : "S:" + song.source + "\n";
|
|
206
|
-
result += song.symbolLine === undefined ? "" : "s:" + song.symbolLine + "\n";
|
|
207
|
-
result += song.userDefined === undefined ? "" : "U:" + song.userDefined + "\n";
|
|
208
|
-
result += song.voice === undefined ? "" : "V:" + song.voice + "\n";
|
|
209
|
-
result += song.transcription === undefined ? "" : "Z:" + song.transcription + "\n";
|
|
210
|
-
return result + abc;
|
|
200
|
+
};
|
|
211
201
|
};
|
|
212
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Converts and validates an ABC notation string into a TuneObject.
|
|
205
|
+
* @param abc
|
|
206
|
+
*/
|
|
213
207
|
export const convertStringToAbcTune = (abc: string): TuneObject => {
|
|
214
208
|
const objectArray: TuneObjectArray = ABCJS.parseOnly(abc);
|
|
215
209
|
// Convert types
|
|
@@ -218,6 +212,9 @@ export const convertStringToAbcTune = (abc: string): TuneObject => {
|
|
|
218
212
|
validate(object != null, "Tune object may not be null");
|
|
219
213
|
validate(object.length > 0, "Tune object may not be empty");
|
|
220
214
|
validate(object[0].lines != null, "Tune object lines may not be null");
|
|
215
|
+
|
|
216
|
+
object[0].lines = object[0].lines.filter(it => it.staff); // Remove empty lines without staff
|
|
217
|
+
|
|
221
218
|
validate(object[0].lines.length > 0, "Tune object lines are empty");
|
|
222
219
|
validate(object[0].lines[0].staff != null, "Staffs may not be null");
|
|
223
220
|
validate(object[0].lines[0].staff!!.length > 0, "Staffs are empty");
|
|
@@ -246,7 +243,11 @@ const processAbcLyrics = (object: Array<TuneObject>) => {
|
|
|
246
243
|
);
|
|
247
244
|
};
|
|
248
245
|
|
|
249
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Combine multi line lyrics line with a multi line melody into a single ABC notation string.
|
|
248
|
+
* @param melody
|
|
249
|
+
* @param lyrics
|
|
250
|
+
*/
|
|
250
251
|
export const combineMelodyAndLyrics = (melody: string, lyrics: string): string => {
|
|
251
252
|
const song = new AbcSong();
|
|
252
253
|
const rawMelody = extractInfoFields(melody, song);
|
|
@@ -12,7 +12,7 @@ export type AbcType =
|
|
|
12
12
|
| "bar_thick_thin"
|
|
13
13
|
| "bar_right_repeat"
|
|
14
14
|
| "bar_left_repeat"
|
|
15
|
-
| "
|
|
15
|
+
| "bar_dbl_repeat";
|
|
16
16
|
export type AbcElementType = "note" | "bar";
|
|
17
17
|
export type Clef =
|
|
18
18
|
"treble"
|
|
@@ -99,6 +99,7 @@ export interface VoiceItemBar {
|
|
|
99
99
|
type: AbcType;
|
|
100
100
|
startChar: number;
|
|
101
101
|
endChar: number;
|
|
102
|
+
startEnding?: string; // Labels the repeat section
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
export interface NoteProperties {
|
|
@@ -107,6 +108,8 @@ export interface NoteProperties {
|
|
|
107
108
|
lyric?: AbcLyric[];
|
|
108
109
|
chord?: AbcChord[];
|
|
109
110
|
rest?: AbcRest;
|
|
111
|
+
startBeam?: boolean;
|
|
112
|
+
endBeam?: boolean;
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
export interface VoiceItemNote extends NoteProperties {
|
|
@@ -308,7 +311,7 @@ export class AbcSong {
|
|
|
308
311
|
};
|
|
309
312
|
}
|
|
310
313
|
|
|
311
|
-
export
|
|
312
|
-
notes: string
|
|
314
|
+
export type NoteGroupInterface = {
|
|
315
|
+
notes: string
|
|
313
316
|
lyrics: string
|
|
314
317
|
}
|