@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/src/abc.ts
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import * as ABCJS from "abcjs";
|
|
2
|
+
import { TuneObjectArray } from "abcjs";
|
|
3
|
+
import {
|
|
4
|
+
AbcLyric,
|
|
5
|
+
AbcPitchStartSlur,
|
|
6
|
+
AbcSong,
|
|
7
|
+
NoteGroupInterface,
|
|
8
|
+
TuneObject,
|
|
9
|
+
VoiceItem,
|
|
10
|
+
VoiceItemNote
|
|
11
|
+
} from "./abcTypes";
|
|
12
|
+
import { validate } from "./validation";
|
|
13
|
+
|
|
14
|
+
// See also https://abcnotation.com/examples
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export const parse = (abc: string): AbcSong | undefined => {
|
|
18
|
+
// Remove comments
|
|
19
|
+
abc = abc
|
|
20
|
+
.replace(/%.*/g, "")
|
|
21
|
+
.replaceAll(/\n+/g, "\n")
|
|
22
|
+
|
|
23
|
+
const song = new AbcSong();
|
|
24
|
+
extractInfoFields(abc, song);
|
|
25
|
+
|
|
26
|
+
const tuneObject = convertStringToAbcTune(abc);
|
|
27
|
+
if (tuneObject === undefined) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Get the first staff only (thus in case of multiple instrument play, only take the first instrument)
|
|
32
|
+
song.clef = tuneObject.lines!![0].staff!![0].clef || song.clef;
|
|
33
|
+
song.keySignature = tuneObject.lines!![0].staff!![0].key || song.keySignature;
|
|
34
|
+
song.melody = tuneObject.lines!!.map(line => line.staff!![0].voices!![0])
|
|
35
|
+
|
|
36
|
+
processSlurs(song);
|
|
37
|
+
|
|
38
|
+
return song;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const processSlurs = (song: AbcSong) => {
|
|
42
|
+
song.melody.forEach(processSlursForLine)
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const processSlursForLine = (line: VoiceItem[]) => {
|
|
46
|
+
const emptyLyric = (): AbcLyric[] => [{ divider: " ", syllable: "" }];
|
|
47
|
+
|
|
48
|
+
const shiftLyrics = (notes: VoiceItemNote[], fromIndex: number) => {
|
|
49
|
+
let shiftedLyric: AbcLyric[] | undefined = undefined;
|
|
50
|
+
notes.forEach((note, index) => {
|
|
51
|
+
if (index < fromIndex) return;
|
|
52
|
+
if (shiftedLyric === undefined) {
|
|
53
|
+
shiftedLyric = note.lyric;
|
|
54
|
+
note.lyric = emptyLyric();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const nextShiftedLyric = note.lyric;
|
|
59
|
+
note.lyric = shiftedLyric;
|
|
60
|
+
shiftedLyric = nextShiftedLyric;
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const notes = line
|
|
65
|
+
.filter(it => it.el_type == "note")
|
|
66
|
+
.map(it => it as VoiceItemNote)
|
|
67
|
+
.filter(it => it.pitches);
|
|
68
|
+
|
|
69
|
+
let slurLyric: AbcLyric[] | undefined = undefined;
|
|
70
|
+
notes.forEach((note, index) => {
|
|
71
|
+
const endSlurPitches = note.pitches!!.filter(it => it.endSlur);
|
|
72
|
+
const startSlurPitches = note.pitches!!.filter(it => it.startSlur);
|
|
73
|
+
|
|
74
|
+
// Search for an increased startSlur identifier (101 -> 102, not 101 -> 201)
|
|
75
|
+
// to identify ABC melodies as '(D)' which ends and starts a slur with itself.
|
|
76
|
+
// Due to buggy ABC parser behavior, the slur doesn't first start and end with
|
|
77
|
+
// itself (having the same identifier), but ends first and start a new slur (with increased identifier).
|
|
78
|
+
// Of course, it is possible that the note is in an actual slur (like '(a (b) c)'),
|
|
79
|
+
// but then the `slurLyric` would be set.
|
|
80
|
+
if (slurLyric === undefined && endSlurPitches.length && startSlurPitches.length) {
|
|
81
|
+
const endSlurIds = endSlurPitches
|
|
82
|
+
.filter(it => it.endSlur)
|
|
83
|
+
.flatMap(it => it.endSlur!!);
|
|
84
|
+
|
|
85
|
+
const startSlurIds = startSlurPitches
|
|
86
|
+
.filter(it => it.startSlur)
|
|
87
|
+
.flatMap(it => it.startSlur as AbcPitchStartSlur[])
|
|
88
|
+
.map(it => it.label);
|
|
89
|
+
|
|
90
|
+
const hasUpFollowingId = startSlurIds.some(it => endSlurIds.includes(it - 1));
|
|
91
|
+
|
|
92
|
+
if (hasUpFollowingId) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// When inside a slur
|
|
98
|
+
if (slurLyric) {
|
|
99
|
+
// Check if lyric is a spacer ('*'). These are shown as lyrics with zero length syllables.
|
|
100
|
+
// If it's a spacer, ignore the shift, as the spacer already does this job for us.
|
|
101
|
+
const lyricIsSpacer = note.lyric
|
|
102
|
+
&& note.lyric.every(it => it.syllable == "" && it.divider == " ");
|
|
103
|
+
|
|
104
|
+
if (!lyricIsSpacer) {
|
|
105
|
+
shiftLyrics(notes, index);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// When slur ends
|
|
110
|
+
if (endSlurPitches.length && !startSlurPitches.length) {
|
|
111
|
+
slurLyric = undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// When slur starts
|
|
115
|
+
if (startSlurPitches.length && !endSlurPitches.length) {
|
|
116
|
+
slurLyric = note.lyric;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const getField = (abc: string, field: string, _default?: string): (string | undefined) => {
|
|
122
|
+
const result = abc.match(new RegExp("(^|\n) *\t*" + field + ":(.*)?"));
|
|
123
|
+
if (result == null || result.length !== 3 || result[2] == null) {
|
|
124
|
+
return _default;
|
|
125
|
+
}
|
|
126
|
+
return result[2].trim();
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export const extractInfoFields = (abc: string, song: AbcSong): string => {
|
|
130
|
+
song.area = getField(abc, "A");
|
|
131
|
+
song.book = getField(abc, "B");
|
|
132
|
+
song.composer = getField(abc, "C");
|
|
133
|
+
song.discography = getField(abc, "D");
|
|
134
|
+
song.fileUrl = getField(abc, "F");
|
|
135
|
+
song.group = getField(abc, "G");
|
|
136
|
+
song.history = getField(abc, "H");
|
|
137
|
+
song.instruction = getField(abc, "I");
|
|
138
|
+
song.key = getField(abc, "K");
|
|
139
|
+
song.unitNoteLength = getField(abc, "L");
|
|
140
|
+
song.meter = getField(abc, "M");
|
|
141
|
+
song.macro = getField(abc, "m");
|
|
142
|
+
song.notes = getField(abc, "N");
|
|
143
|
+
song.origin = getField(abc, "O");
|
|
144
|
+
song.parts = getField(abc, "P");
|
|
145
|
+
song.tempo = getField(abc, "Q");
|
|
146
|
+
song.rhythm = getField(abc, "R");
|
|
147
|
+
song.remark = getField(abc, "r");
|
|
148
|
+
song.source = getField(abc, "S");
|
|
149
|
+
song.symbolLine = getField(abc, "s");
|
|
150
|
+
song.title = getField(abc, "T");
|
|
151
|
+
song.userDefined = getField(abc, "U");
|
|
152
|
+
song.voice = getField(abc, "V");
|
|
153
|
+
song.referenceNumber = getField(abc, "X", "1");
|
|
154
|
+
song.transcription = getField(abc, "Z");
|
|
155
|
+
return abc
|
|
156
|
+
.replace(/%.*\n/g, "")
|
|
157
|
+
.replace(/(^|\n) *\t*[ABCDFGHIKLMmNOPQRrSsTUVXZ]:.*/g, "")
|
|
158
|
+
.replace(/\n+/g, "\n")
|
|
159
|
+
.replace(/^\n*/g, "")
|
|
160
|
+
.replace(/\n*$/g, "");
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const extractNotesAndLyrics = (abc: string): NoteGroupInterface => {
|
|
164
|
+
const notes: string[] = [];
|
|
165
|
+
const lyrics: string[] = [];
|
|
166
|
+
abc.split("\n")
|
|
167
|
+
.map(it => it.trim())
|
|
168
|
+
.forEach(it => {
|
|
169
|
+
if (it.startsWith("w:") || it.startsWith("W:")) {
|
|
170
|
+
lyrics.push(it.substring(2).trim());
|
|
171
|
+
} else {
|
|
172
|
+
notes.push(it);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
notes: notes.join(" "),
|
|
178
|
+
lyrics: lyrics.join(" ")
|
|
179
|
+
} as NoteGroupInterface;
|
|
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;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export const convertStringToAbcTune = (abc: string): TuneObject => {
|
|
214
|
+
const objectArray: TuneObjectArray = ABCJS.parseOnly(abc);
|
|
215
|
+
// Convert types
|
|
216
|
+
const object: TuneObject[] = objectArray as unknown as TuneObject[];
|
|
217
|
+
|
|
218
|
+
validate(object != null, "Tune object may not be null");
|
|
219
|
+
validate(object.length > 0, "Tune object may not be empty");
|
|
220
|
+
validate(object[0].lines != null, "Tune object lines may not be null");
|
|
221
|
+
validate(object[0].lines.length > 0, "Tune object lines are empty");
|
|
222
|
+
validate(object[0].lines[0].staff != null, "Staffs may not be null");
|
|
223
|
+
validate(object[0].lines[0].staff!!.length > 0, "Staffs are empty");
|
|
224
|
+
validate(object[0].lines[0].staff!![0].voices != null, "Voices may not be null");
|
|
225
|
+
validate(object[0].lines[0].staff!![0].voices!!.length > 0, "Voices may not be empty");
|
|
226
|
+
validate(object[0].lines[0].staff!![0].voices!!.some(it => it.length > 0), "Voices are all empty");
|
|
227
|
+
|
|
228
|
+
processAbcLyrics(object);
|
|
229
|
+
|
|
230
|
+
return object[0];
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const processAbcLyrics = (object: Array<TuneObject>) => {
|
|
234
|
+
object[0].lines!!.forEach(line =>
|
|
235
|
+
line.staff!!.forEach(staff =>
|
|
236
|
+
staff.voices!!.forEach(element =>
|
|
237
|
+
element.filter(voice => voice.el_type === "note")
|
|
238
|
+
.map(voice => voice as VoiceItemNote)
|
|
239
|
+
.forEach(voice => {
|
|
240
|
+
voice.lyric?.forEach(lyric =>
|
|
241
|
+
lyric.syllable = lyric.syllable.replace(" ", " ")
|
|
242
|
+
);
|
|
243
|
+
})
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Match each lyric line with each melody line
|
|
250
|
+
export const combineMelodyAndLyrics = (melody: string, lyrics: string): string => {
|
|
251
|
+
const song = new AbcSong();
|
|
252
|
+
const rawMelody = extractInfoFields(melody, song);
|
|
253
|
+
|
|
254
|
+
const melodyLines = rawMelody
|
|
255
|
+
.replaceAll(/\n+/g, "\n")
|
|
256
|
+
.trim()
|
|
257
|
+
.split("\n")
|
|
258
|
+
const lyricLines = lyrics
|
|
259
|
+
.replaceAll(/\n+/g, "\n")
|
|
260
|
+
.trim()
|
|
261
|
+
.split("\n")
|
|
262
|
+
|
|
263
|
+
const mixedMelody: string[] = [];
|
|
264
|
+
for (let i = 0; i < Math.max(melodyLines.length, lyricLines.length); i++) {
|
|
265
|
+
// When the lines are not of equal length, we allow to show empty lines for missing lines.
|
|
266
|
+
mixedMelody.push(melodyLines[i] ?? "C ".repeat(10));
|
|
267
|
+
mixedMelody.push(lyricLines[i] ? "w: " + lyricLines[i] : "");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return addInfoFieldsToMelody(song, mixedMelody.join("\n")).trim();
|
|
271
|
+
}
|
package/src/abcTypes.ts
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// See also: https://github.com/paulrosen/abcjs/blob/2616d88ddf0222e255c508f944df3089960c13dc/types/index.d.ts
|
|
2
|
+
|
|
3
|
+
import { SynthOptions } from "abcjs";
|
|
4
|
+
|
|
5
|
+
export type AccidentalName = "flat" | "natural" | "sharp" | "dblsharp" | "dblflat" | "quarterflat" | "quartersharp";
|
|
6
|
+
export type ChordPlacement = "above" | "below" | "left" | "right" | "default";
|
|
7
|
+
export type StemDirection = "up" | "down" | "auto" | "none";
|
|
8
|
+
export type AbcType =
|
|
9
|
+
"bar_thin"
|
|
10
|
+
| "bar_thin_thick"
|
|
11
|
+
| "bar_thin_thin"
|
|
12
|
+
| "bar_thick_thin"
|
|
13
|
+
| "bar_right_repeat"
|
|
14
|
+
| "bar_left_repeat"
|
|
15
|
+
| "bar_double_repeat";
|
|
16
|
+
export type AbcElementType = "note" | "bar";
|
|
17
|
+
export type Clef =
|
|
18
|
+
"treble"
|
|
19
|
+
| "tenor"
|
|
20
|
+
| "bass"
|
|
21
|
+
| "alto"
|
|
22
|
+
| "treble+8"
|
|
23
|
+
| "tenor+8"
|
|
24
|
+
| "bass+8"
|
|
25
|
+
| "alto+8"
|
|
26
|
+
| "treble-8"
|
|
27
|
+
| "tenor-8"
|
|
28
|
+
| "bass-8"
|
|
29
|
+
| "alto-8"
|
|
30
|
+
| "none"
|
|
31
|
+
| "perc";
|
|
32
|
+
export type NoteHeadType = "normal" | "harmonic" | "rhythm" | "x" | "triangle";
|
|
33
|
+
export type NoteLetter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "a" | "b" | "c" | "d" | "e" | "f" | "g";
|
|
34
|
+
export type KeyRoot = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "HP" | "Hp" | "none";
|
|
35
|
+
export type KeyAccidentalName = "" | "#" | "b";
|
|
36
|
+
export type Mode = "" | "m" | "Dor" | "Mix" | "Loc" | "Phr" | "Lyd";
|
|
37
|
+
export type ChordType =
|
|
38
|
+
""
|
|
39
|
+
| "m"
|
|
40
|
+
| "7"
|
|
41
|
+
| "m7"
|
|
42
|
+
| "maj7"
|
|
43
|
+
| "M7"
|
|
44
|
+
| "6"
|
|
45
|
+
| "m6"
|
|
46
|
+
| "aug"
|
|
47
|
+
| "+"
|
|
48
|
+
| "aug7"
|
|
49
|
+
| "dim"
|
|
50
|
+
| "dim7"
|
|
51
|
+
| "9"
|
|
52
|
+
|
|
|
53
|
+
"m9"
|
|
54
|
+
| "maj9"
|
|
55
|
+
| "M9"
|
|
56
|
+
| "11"
|
|
57
|
+
| "dim9"
|
|
58
|
+
| "sus"
|
|
59
|
+
| "sus9"
|
|
60
|
+
| "7sus4"
|
|
61
|
+
| "7sus9"
|
|
62
|
+
| "5";
|
|
63
|
+
export type BracePosition = "start" | "continue" | "end";
|
|
64
|
+
|
|
65
|
+
type NumberFunction = () => number;
|
|
66
|
+
|
|
67
|
+
export interface AbcChord {
|
|
68
|
+
name: string;
|
|
69
|
+
position: ChordPlacement;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface AbcPitchStartSlur {
|
|
73
|
+
label: number; // An identifier for this slur in the current slur sequence (if there are more than one slurs for this note)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface AbcPitch {
|
|
77
|
+
pitch: number;
|
|
78
|
+
verticalPos: number;
|
|
79
|
+
name?: string;
|
|
80
|
+
accidental?: AccidentalName;
|
|
81
|
+
startTie?: {};
|
|
82
|
+
endTie?: boolean;
|
|
83
|
+
startSlur?: AbcPitchStartSlur[];
|
|
84
|
+
endSlur?: number[]; // An array of slur identifiers which should end here
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface AbcRest {
|
|
88
|
+
type: "rest" | "whole" | "multimeasure" | "spacer";
|
|
89
|
+
text?: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface AbcLyric {
|
|
93
|
+
syllable: string;
|
|
94
|
+
divider: " " | "-" | "_";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface VoiceItemBar {
|
|
98
|
+
el_type: "bar";
|
|
99
|
+
type: AbcType;
|
|
100
|
+
startChar: number;
|
|
101
|
+
endChar: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface NoteProperties {
|
|
105
|
+
duration: number;
|
|
106
|
+
pitches?: AbcPitch[];
|
|
107
|
+
lyric?: AbcLyric[];
|
|
108
|
+
chord?: AbcChord[];
|
|
109
|
+
rest?: AbcRest;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface VoiceItemNote extends NoteProperties {
|
|
113
|
+
el_type: "note";
|
|
114
|
+
startChar: number;
|
|
115
|
+
endChar: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface VoiceItemStem {
|
|
119
|
+
el_type: "stem";
|
|
120
|
+
direction: StemDirection;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface VoiceItemClef {
|
|
124
|
+
el_type: "clef";
|
|
125
|
+
stafflines?: number;
|
|
126
|
+
staffscale?: number;
|
|
127
|
+
transpose?: number;
|
|
128
|
+
type: Clef;
|
|
129
|
+
verticalPos: number;
|
|
130
|
+
clefPos?: number;
|
|
131
|
+
startChar: number;
|
|
132
|
+
endChar: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface VoiceItemGap {
|
|
136
|
+
el_type: "gap";
|
|
137
|
+
gap: number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface VoiceItemKey extends KeySignature {
|
|
141
|
+
el_type: "key";
|
|
142
|
+
startChar: number;
|
|
143
|
+
endChar: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export type VoiceItem = VoiceItemClef | VoiceItemBar | VoiceItemGap | VoiceItemKey | VoiceItemStem | VoiceItemNote;
|
|
147
|
+
|
|
148
|
+
export interface Accidental {
|
|
149
|
+
acc: AccidentalName;
|
|
150
|
+
note: NoteLetter;
|
|
151
|
+
verticalPos: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface AbcClef {
|
|
155
|
+
clefPos: number;
|
|
156
|
+
type: Clef;
|
|
157
|
+
verticalPos: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface KeySignature {
|
|
161
|
+
accidentals?: Array<Accidental>;
|
|
162
|
+
root: KeyRoot;
|
|
163
|
+
acc: KeyAccidentalName;
|
|
164
|
+
mode: Mode;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface AbcStaff {
|
|
168
|
+
barNumber?: number;
|
|
169
|
+
brace: BracePosition;
|
|
170
|
+
bracket: BracePosition;
|
|
171
|
+
connectBarLines: BracePosition;
|
|
172
|
+
stafflines?: number;
|
|
173
|
+
clef?: AbcClef;
|
|
174
|
+
key?: KeySignature;
|
|
175
|
+
voices?: Array<Array<VoiceItem>>;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface AbcLine {
|
|
179
|
+
staff?: AbcStaff[];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface MetaText {
|
|
183
|
+
"abc-copyright"?: string;
|
|
184
|
+
"abc-creator"?: string;
|
|
185
|
+
"abc-version"?: string;
|
|
186
|
+
"abc-charset"?: string;
|
|
187
|
+
"abc-edited-by"?: string;
|
|
188
|
+
author?: string;
|
|
189
|
+
book?: string;
|
|
190
|
+
composer?: string;
|
|
191
|
+
discography?: string;
|
|
192
|
+
footer?: {
|
|
193
|
+
left: string;
|
|
194
|
+
center: string;
|
|
195
|
+
right: string;
|
|
196
|
+
};
|
|
197
|
+
group?: string;
|
|
198
|
+
header?: {
|
|
199
|
+
left: string;
|
|
200
|
+
center: string;
|
|
201
|
+
right: string;
|
|
202
|
+
};
|
|
203
|
+
history?: string;
|
|
204
|
+
instruction?: string;
|
|
205
|
+
measurebox?: boolean;
|
|
206
|
+
notes?: string;
|
|
207
|
+
origin?: string;
|
|
208
|
+
partOrder?: string;
|
|
209
|
+
rhythm?: string;
|
|
210
|
+
source?: string;
|
|
211
|
+
textBlock?: string;
|
|
212
|
+
title?: string;
|
|
213
|
+
transcription?: string;
|
|
214
|
+
url?: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface TuneObject {
|
|
218
|
+
formatting: object;
|
|
219
|
+
media: string;
|
|
220
|
+
version: string;
|
|
221
|
+
metaText: MetaText;
|
|
222
|
+
lines: AbcLine[];
|
|
223
|
+
|
|
224
|
+
getTotalTime: NumberFunction;
|
|
225
|
+
getTotalBeats: NumberFunction;
|
|
226
|
+
getBarLength: NumberFunction;
|
|
227
|
+
getBeatLength: NumberFunction;
|
|
228
|
+
getBeatsPerMeasure: NumberFunction;
|
|
229
|
+
getBpm: NumberFunction;
|
|
230
|
+
getPickupLength: NumberFunction;
|
|
231
|
+
getKeySignature: () => KeySignature;
|
|
232
|
+
getElementFromChar: (charPos: number) => VoiceItem | null;
|
|
233
|
+
millisecondsPerMeasure: NumberFunction;
|
|
234
|
+
lineBreaks?: Array<number>;
|
|
235
|
+
visualTranspose?: number;
|
|
236
|
+
setUpAudio: (options?: SynthOptions) => AudioTracks;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface AudioTracks {
|
|
240
|
+
tempo: number;
|
|
241
|
+
instrument: number;
|
|
242
|
+
tracks: AudioTrack[][];
|
|
243
|
+
totalDuration: number;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export type AudioTrack = AudioTrackProgram | AudioTrackText | AudioTrackNote;
|
|
247
|
+
|
|
248
|
+
export interface AudioTrackProgram {
|
|
249
|
+
cmd: "program";
|
|
250
|
+
channel: number;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export interface AudioTrackText {
|
|
254
|
+
cmd: "text";
|
|
255
|
+
// ...?
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface AudioTrackNote {
|
|
259
|
+
cmd: "note";
|
|
260
|
+
duration: number;
|
|
261
|
+
volume: number;
|
|
262
|
+
pitch: number;
|
|
263
|
+
gap: number;
|
|
264
|
+
instrument: number;
|
|
265
|
+
start: number;
|
|
266
|
+
startChar: number;
|
|
267
|
+
endChar: number;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export class AbcSong {
|
|
271
|
+
// See also: https://abcnotation.com/wiki/abc:standard:v2.1#information_fields
|
|
272
|
+
area?: string;
|
|
273
|
+
book?: string;
|
|
274
|
+
composer?: string;
|
|
275
|
+
discography?: string;
|
|
276
|
+
fileUrl?: string;
|
|
277
|
+
group?: string;
|
|
278
|
+
history?: string;
|
|
279
|
+
instruction?: string;
|
|
280
|
+
key?: string;
|
|
281
|
+
unitNoteLength?: string;
|
|
282
|
+
meter?: string;
|
|
283
|
+
macro?: string;
|
|
284
|
+
notes?: string;
|
|
285
|
+
origin?: string;
|
|
286
|
+
parts?: string;
|
|
287
|
+
tempo?: string;
|
|
288
|
+
rhythm?: string;
|
|
289
|
+
remark?: string;
|
|
290
|
+
source?: string;
|
|
291
|
+
symbolLine?: string;
|
|
292
|
+
title?: string;
|
|
293
|
+
userDefined?: string;
|
|
294
|
+
voice?: string;
|
|
295
|
+
referenceNumber?: string;
|
|
296
|
+
transcription?: string;
|
|
297
|
+
melody: VoiceItem[][] = [];
|
|
298
|
+
clef: AbcClef = {
|
|
299
|
+
clefPos: 4,
|
|
300
|
+
type: "treble",
|
|
301
|
+
verticalPos: 0
|
|
302
|
+
};
|
|
303
|
+
keySignature: KeySignature = {
|
|
304
|
+
accidentals: [],
|
|
305
|
+
root: "C",
|
|
306
|
+
acc: "",
|
|
307
|
+
mode: ""
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export interface NoteGroupInterface {
|
|
312
|
+
notes: string,
|
|
313
|
+
lyrics: string
|
|
314
|
+
}
|
package/src/based.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { combineMelodyAndLyrics } from "./abc";
|
|
2
|
+
import { AbcMelody, AbcSubMelody, Verse } from "./basedTypes";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export const getForVerse = (melody: AbcMelody, verse: Verse): AbcSubMelody | undefined =>
|
|
6
|
+
melody.subMelodies.find(it =>
|
|
7
|
+
// `.includes()` won't work due to the Realm data type of `verseUuids`.
|
|
8
|
+
it.verseUuids.some(it => it == verse.uuid));
|
|
9
|
+
|
|
10
|
+
export const generateAbcForVerse = (
|
|
11
|
+
verse: Verse,
|
|
12
|
+
activeMelody?: AbcMelody
|
|
13
|
+
): string => {
|
|
14
|
+
if (activeMelody === undefined) {
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
const melody = getForVerse(activeMelody, verse)?.melody || activeMelody.melody;
|
|
18
|
+
return combineMelodyAndLyrics(melody, verse.abcLyrics || "")
|
|
19
|
+
};
|
package/src/index.ts
ADDED