abcjs 6.0.0-beta.33 → 6.0.0-beta.37
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/LICENSE.md +1 -1
- package/README.md +20 -9
- package/RELEASE.md +108 -0
- package/dist/abcjs-basic-min.js +2 -2
- package/dist/abcjs-basic-min.js.LICENSE +2 -2
- package/dist/abcjs-basic.js +3493 -841
- package/dist/abcjs-basic.js.map +1 -1
- package/dist/abcjs-plugin-min.js +2 -2
- package/dist/abcjs-plugin-min.js.LICENSE +2 -2
- package/dist/report-basic.html +37 -0
- package/dist/report-before-glyph-compress.html +37 -0
- package/dist/report-brown-ts-target-es5.html +37 -0
- package/dist/{report.html → report-dev-orig-no-babel.html} +2 -2
- package/dist/report-synth.html +37 -0
- package/docker-build.sh +1 -0
- package/glyphs.json +1 -0
- package/index.js +23 -1
- package/license.js +1 -1
- package/package.json +9 -9
- package/plugin.js +23 -1
- package/src/api/abc_tablatures.js +144 -0
- package/src/api/abc_timing_callbacks.js +34 -31
- package/src/api/abc_tunebook.js +10 -1
- package/src/api/abc_tunebook_svg.js +23 -27
- package/src/data/abc_tune.js +68 -24
- package/src/edit/abc_editor.js +33 -11
- package/src/midi/abc_midi_create.js +6 -2
- package/src/parse/abc_parse.js +7 -6
- package/src/parse/abc_parse_directive.js +19 -12
- package/src/parse/abc_parse_header.js +12 -12
- package/src/parse/abc_parse_music.js +14 -4
- package/src/parse/tune-builder.js +22 -29
- package/src/synth/abc_midi_sequencer.js +10 -0
- package/src/synth/create-synth.js +56 -13
- package/src/synth/load-note.js +31 -65
- package/src/synth/place-note.js +59 -60
- package/src/synth/register-audio-context.js +4 -1
- package/src/synth/supports-audio.js +9 -8
- package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
- package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
- package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
- package/src/tablatures/instruments/string-patterns.js +277 -0
- package/src/tablatures/instruments/string-tablature.js +56 -0
- package/src/tablatures/instruments/tab-note.js +282 -0
- package/src/tablatures/instruments/tab-notes.js +41 -0
- package/src/tablatures/instruments/violin/tab-violin.js +47 -0
- package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
- package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
- package/src/tablatures/tab-absolute-elements.js +310 -0
- package/src/tablatures/tab-common.js +29 -0
- package/src/tablatures/tab-renderer.js +243 -0
- package/src/tablatures/transposer.js +110 -0
- package/src/test/abc_parser_lint.js +62 -6
- package/src/write/abc_absolute_element.js +2 -2
- package/src/write/abc_abstract_engraver.js +9 -7
- package/src/write/abc_create_key_signature.js +1 -0
- package/src/write/abc_create_note_head.js +1 -1
- package/src/write/abc_engraver_controller.js +26 -13
- package/src/write/abc_glyphs.js +5 -2
- package/src/write/abc_relative_element.js +11 -3
- package/src/write/abc_renderer.js +5 -1
- package/src/write/add-chord.js +5 -2
- package/src/write/add-text-if.js +33 -0
- package/src/write/bottom-text.js +8 -29
- package/src/write/draw/absolute.js +12 -14
- package/src/write/draw/brace.js +3 -3
- package/src/write/draw/crescendo.js +1 -1
- package/src/write/draw/draw.js +5 -6
- package/src/write/draw/dynamics.js +8 -1
- package/src/write/draw/ending.js +4 -3
- package/src/write/draw/group-elements.js +10 -8
- package/src/write/draw/non-music.js +11 -6
- package/src/write/draw/print-line.js +24 -0
- package/src/write/draw/print-stem.js +12 -11
- package/src/write/draw/print-symbol.js +11 -10
- package/src/write/draw/relative.js +33 -13
- package/src/write/draw/selectables.js +9 -6
- package/src/write/draw/staff-group.js +45 -9
- package/src/write/draw/staff-line.js +3 -17
- package/src/write/draw/staff.js +15 -2
- package/src/write/draw/tab-line.js +40 -0
- package/src/write/draw/tempo.js +7 -7
- package/src/write/draw/text.js +11 -4
- package/src/write/draw/tie.js +2 -2
- package/src/write/draw/triplet.js +3 -3
- package/src/write/draw/voice.js +10 -2
- package/src/write/format-jazz-chord.js +15 -0
- package/src/write/free-text.js +20 -12
- package/src/write/layout/VoiceElements.js +33 -1
- package/src/write/layout/beam.js +2 -0
- package/src/write/layout/staffGroup.js +37 -2
- package/src/write/layout/voice.js +2 -1
- package/src/write/selection.js +15 -5
- package/src/write/separator.js +1 -1
- package/src/write/subtitle.js +3 -3
- package/src/write/svg.js +41 -14
- package/src/write/top-text.js +19 -25
- package/test.js +23 -0
- package/types/index.d.ts +1007 -39
- package/version.js +1 -1
- package/docker-start.sh +0 -1
|
@@ -39,6 +39,9 @@ var parseDirective = {};
|
|
|
39
39
|
tune.formatting.footerfont = { face: "\"Times New Roman\"", size: 12, weight: "normal", style: "normal", decoration: "none" };
|
|
40
40
|
tune.formatting.headerfont = { face: "\"Times New Roman\"", size: 12, weight: "normal", style: "normal", decoration: "none" };
|
|
41
41
|
tune.formatting.voicefont = { face: "\"Times New Roman\"", size: 13, weight: "bold", style: "normal", decoration: "none" };
|
|
42
|
+
tune.formatting.tablabelfont = { face: "\"Trebuchet MS\"", size: 16, weight: "normal", style: "normal", decoration: "none" };
|
|
43
|
+
tune.formatting.tabnumberfont = { face: "\"Arial\"", size: 11, weight: "normal", style: "normal", decoration: "none" };
|
|
44
|
+
tune.formatting.tabgracefont = { face: "\"Arial\"", size: 8, weight: "normal", style: "normal", decoration: "none" };
|
|
42
45
|
|
|
43
46
|
// these are the default fonts for these element types. In the printer, these fonts might change as the tune progresses.
|
|
44
47
|
tune.formatting.annotationfont = multilineVars.annotationfont;
|
|
@@ -390,7 +393,7 @@ var parseDirective = {};
|
|
|
390
393
|
var interpretPercMap = function(restOfString) {
|
|
391
394
|
var tokens = restOfString.split(/\s+/); // Allow multiple spaces.
|
|
392
395
|
if (tokens.length !== 2 && tokens.length !== 3)
|
|
393
|
-
return { error: 'Expected parameters "abc-note", "drum-sound", and optionally "note-head"'}
|
|
396
|
+
return { error: 'Expected parameters "abc-note", "drum-sound", and optionally "note-head"'};
|
|
394
397
|
var key = tokens[0];
|
|
395
398
|
// The percussion sound can either be a MIDI number or a drum name. If it is not a number then check for a name.
|
|
396
399
|
var pitch = parseInt(tokens[1], 10);
|
|
@@ -403,7 +406,7 @@ var parseDirective = {};
|
|
|
403
406
|
if (tokens.length === 3)
|
|
404
407
|
value.noteHead = tokens[2];
|
|
405
408
|
return { key: key, value: value };
|
|
406
|
-
}
|
|
409
|
+
};
|
|
407
410
|
|
|
408
411
|
var getRequiredMeasurement = function(cmd, tokens) {
|
|
409
412
|
var points = tokenizer.getMeasurement(tokens);
|
|
@@ -742,6 +745,7 @@ var parseDirective = {};
|
|
|
742
745
|
// titleformat: { type: "string", optional: true },
|
|
743
746
|
case "bagpipes":tune.formatting.bagpipes = true;break;
|
|
744
747
|
case "flatbeams":tune.formatting.flatbeams = true;break;
|
|
748
|
+
case "jazzchords":tune.formatting.jazzchords = true;break;
|
|
745
749
|
case "landscape":multilineVars.landscape = true;break;
|
|
746
750
|
case "papersize":multilineVars.papersize = restOfString;break;
|
|
747
751
|
case "graceslurs":
|
|
@@ -814,7 +818,7 @@ var parseDirective = {};
|
|
|
814
818
|
break;
|
|
815
819
|
case "sep":
|
|
816
820
|
if (tokens.length === 0)
|
|
817
|
-
tuneBuilder.addSeparator(14,14,85); // If no parameters are given, then there is a default size.
|
|
821
|
+
tuneBuilder.addSeparator(14,14,85, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+5}); // If no parameters are given, then there is a default size.
|
|
818
822
|
else {
|
|
819
823
|
var points = tokenizer.getMeasurement(tokens);
|
|
820
824
|
if (points.used === 0)
|
|
@@ -830,7 +834,7 @@ var parseDirective = {};
|
|
|
830
834
|
if (points.used === 0 || tokens.length !== 0)
|
|
831
835
|
return "Directive \"" + cmd + "\" requires 3 numbers: space above, space below, length of line";
|
|
832
836
|
var lenLine = points.value;
|
|
833
|
-
tuneBuilder.addSeparator(spaceAbove, spaceBelow, lenLine);
|
|
837
|
+
tuneBuilder.addSeparator(spaceAbove, spaceBelow, lenLine, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+restOfString.length});
|
|
834
838
|
}
|
|
835
839
|
break;
|
|
836
840
|
case "barsperstaff":
|
|
@@ -874,23 +878,23 @@ var parseDirective = {};
|
|
|
874
878
|
break;
|
|
875
879
|
case "begintext":
|
|
876
880
|
var textBlock = '';
|
|
877
|
-
line = tokenizer.nextLine()
|
|
881
|
+
line = tokenizer.nextLine();
|
|
878
882
|
while(line && line.indexOf('%%endtext') !== 0) {
|
|
879
883
|
if (parseCommon.startsWith(line, "%%"))
|
|
880
884
|
textBlock += line.substring(2) + "\n";
|
|
881
885
|
else
|
|
882
886
|
textBlock += line + "\n";
|
|
883
|
-
line = tokenizer.nextLine()
|
|
887
|
+
line = tokenizer.nextLine();
|
|
884
888
|
}
|
|
885
|
-
tuneBuilder.addText(textBlock);
|
|
889
|
+
tuneBuilder.addText(textBlock, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+textBlock.length+7});
|
|
886
890
|
break;
|
|
887
891
|
case "continueall":
|
|
888
892
|
multilineVars.continueall = true;
|
|
889
893
|
break;
|
|
890
894
|
case "beginps":
|
|
891
|
-
line = tokenizer.nextLine()
|
|
895
|
+
line = tokenizer.nextLine();
|
|
892
896
|
while(line && line.indexOf('%%endps') !== 0) {
|
|
893
|
-
tokenizer.nextLine()
|
|
897
|
+
tokenizer.nextLine();
|
|
894
898
|
}
|
|
895
899
|
warn("Postscript ignored", str, 0);
|
|
896
900
|
break;
|
|
@@ -901,7 +905,7 @@ var parseDirective = {};
|
|
|
901
905
|
break;
|
|
902
906
|
case "text":
|
|
903
907
|
var textstr = tokenizer.translateString(restOfString);
|
|
904
|
-
tuneBuilder.addText(parseDirective.parseFontChangeLine(textstr));
|
|
908
|
+
tuneBuilder.addText(parseDirective.parseFontChangeLine(textstr), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+restOfString.length+7});
|
|
905
909
|
break;
|
|
906
910
|
case "center":
|
|
907
911
|
var centerstr = tokenizer.translateString(restOfString);
|
|
@@ -1067,7 +1071,7 @@ var parseDirective = {};
|
|
|
1067
1071
|
case "-version":
|
|
1068
1072
|
case "-charset":
|
|
1069
1073
|
var subCmd = arr.shift();
|
|
1070
|
-
tuneBuilder.addMetaText(cmd+subCmd, arr.join(' '));
|
|
1074
|
+
tuneBuilder.addMetaText(cmd+subCmd, arr.join(' '), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+restOfString.length+5});
|
|
1071
1075
|
break;
|
|
1072
1076
|
default:
|
|
1073
1077
|
return "Unknown directive: " + cmd+arr[0];
|
|
@@ -1090,7 +1094,7 @@ var parseDirective = {};
|
|
|
1090
1094
|
if (footerArr.length > 3)
|
|
1091
1095
|
warn("Too many tabs in " + cmd + ": " + footerArr.length + " found.", restOfString, 0);
|
|
1092
1096
|
|
|
1093
|
-
tuneBuilder.addMetaTextObj(cmd, footer);
|
|
1097
|
+
tuneBuilder.addMetaTextObj(cmd, footer, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+str.length});
|
|
1094
1098
|
break;
|
|
1095
1099
|
|
|
1096
1100
|
case "midi":
|
|
@@ -1151,6 +1155,9 @@ var parseDirective = {};
|
|
|
1151
1155
|
case "vocalfont":
|
|
1152
1156
|
case "wordsfont":
|
|
1153
1157
|
case "annotationfont":
|
|
1158
|
+
case "tablabelfont":
|
|
1159
|
+
case "tabnumberfont":
|
|
1160
|
+
case "tabgracefont":
|
|
1154
1161
|
getChangingFont(cmd, tokens, value);
|
|
1155
1162
|
break;
|
|
1156
1163
|
case "scale":
|
|
@@ -13,13 +13,13 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
|
|
|
13
13
|
|
|
14
14
|
this.setTitle = function(title) {
|
|
15
15
|
if (multilineVars.hasMainTitle)
|
|
16
|
-
tuneBuilder.addSubtitle(tokenizer.translateString(tokenizer.stripComment(title))); // display secondary title
|
|
16
|
+
tuneBuilder.addSubtitle(tokenizer.translateString(tokenizer.stripComment(title)), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+title.length+2}); // display secondary title
|
|
17
17
|
else
|
|
18
18
|
{
|
|
19
19
|
var titleStr = tokenizer.translateString(tokenizer.theReverser(tokenizer.stripComment(title)));
|
|
20
20
|
if (multilineVars.titlecaps)
|
|
21
21
|
titleStr = titleStr.toUpperCase();
|
|
22
|
-
tuneBuilder.addMetaText("title", titleStr);
|
|
22
|
+
tuneBuilder.addMetaText("title", titleStr, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+title.length+2});
|
|
23
23
|
multilineVars.hasMainTitle = true;
|
|
24
24
|
}
|
|
25
25
|
};
|
|
@@ -235,7 +235,7 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
|
|
|
235
235
|
prestissimo: 210,
|
|
236
236
|
};
|
|
237
237
|
|
|
238
|
-
this.setTempo = function(line, start, end) {
|
|
238
|
+
this.setTempo = function(line, start, end, iChar) {
|
|
239
239
|
//Q - tempo; can be used to specify the notes per minute, e.g. If
|
|
240
240
|
//the meter denominator is a 4 note then Q:120 or Q:C=120
|
|
241
241
|
//is 120 quarter notes per minute. Similarly Q:C3=40 would be 40
|
|
@@ -255,7 +255,7 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
|
|
|
255
255
|
|
|
256
256
|
if (tokens.length === 0) throw "Missing parameter in Q: field";
|
|
257
257
|
|
|
258
|
-
var tempo = {};
|
|
258
|
+
var tempo = { startChar: iChar+start-2, endChar: iChar+end };
|
|
259
259
|
var delaySet = true;
|
|
260
260
|
var token = tokens.shift();
|
|
261
261
|
if (token.type === 'quote') {
|
|
@@ -381,7 +381,7 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
|
|
|
381
381
|
return [ e-i+1+ws ];
|
|
382
382
|
case "[Q:":
|
|
383
383
|
if (e > 0) {
|
|
384
|
-
var tempo = this.setTempo(line, i+3, e);
|
|
384
|
+
var tempo = this.setTempo(line, i+3, e, multilineVars.iChar);
|
|
385
385
|
if (tempo.type === 'delaySet') {
|
|
386
386
|
if (tuneBuilder.hasBeginMusic())
|
|
387
387
|
tuneBuilder.appendElement('tempo', startChar, endChar, this.calcTempo(tempo.tempo));
|
|
@@ -442,7 +442,7 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
|
|
|
442
442
|
case "Q:":
|
|
443
443
|
var e = line.indexOf('\x12', i+2);
|
|
444
444
|
if (e === -1) e = line.length;
|
|
445
|
-
var tempo = this.setTempo(line, i+2, e);
|
|
445
|
+
var tempo = this.setTempo(line, i+2, e, multilineVars.iChar);
|
|
446
446
|
if (tempo.type === 'delaySet') tuneBuilder.appendElement('tempo', multilineVars.iChar + i, multilineVars.iChar + line.length, this.calcTempo(tempo.tempo));
|
|
447
447
|
else if (tempo.type === 'immediate') tuneBuilder.appendElement('tempo', multilineVars.iChar + i, multilineVars.iChar + line.length, tempo.tempo);
|
|
448
448
|
return [ e, line.charAt(i), parseCommon.strip(line.substring(i+2))];
|
|
@@ -477,9 +477,9 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
|
|
|
477
477
|
var field = metaTextHeaders[line.charAt(0)];
|
|
478
478
|
if (field !== undefined) {
|
|
479
479
|
if (field === 'unalignedWords')
|
|
480
|
-
tuneBuilder.addMetaTextArray(field, parseDirective.parseFontChangeLine(tokenizer.translateString(tokenizer.stripComment(line.substring(2)))));
|
|
480
|
+
tuneBuilder.addMetaTextArray(field, parseDirective.parseFontChangeLine(tokenizer.translateString(tokenizer.stripComment(line.substring(2)))), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
|
|
481
481
|
else
|
|
482
|
-
tuneBuilder.addMetaText(field, tokenizer.translateString(tokenizer.stripComment(line.substring(2))));
|
|
482
|
+
tuneBuilder.addMetaText(field, tokenizer.translateString(tokenizer.stripComment(line.substring(2))), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
|
|
483
483
|
return {};
|
|
484
484
|
} else {
|
|
485
485
|
var startChar = multilineVars.iChar;
|
|
@@ -487,11 +487,11 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
|
|
|
487
487
|
switch(line.charAt(0))
|
|
488
488
|
{
|
|
489
489
|
case 'H':
|
|
490
|
-
tuneBuilder.addMetaText("history", tokenizer.translateString(tokenizer.stripComment(line.substring(2))));
|
|
490
|
+
tuneBuilder.addMetaText("history", tokenizer.translateString(tokenizer.stripComment(line.substring(2))), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
|
|
491
491
|
line = tokenizer.peekLine()
|
|
492
492
|
while (line && line.charAt(1) !== ':') {
|
|
493
493
|
tokenizer.nextLine()
|
|
494
|
-
tuneBuilder.addMetaText("history", tokenizer.translateString(tokenizer.stripComment(line)));
|
|
494
|
+
tuneBuilder.addMetaText("history", tokenizer.translateString(tokenizer.stripComment(line)), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
|
|
495
495
|
line = tokenizer.peekLine()
|
|
496
496
|
}
|
|
497
497
|
break;
|
|
@@ -516,12 +516,12 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
|
|
|
516
516
|
case 'P':
|
|
517
517
|
// TODO-PER: There is more to do with parts, but the writer doesn't care.
|
|
518
518
|
if (multilineVars.is_in_header)
|
|
519
|
-
tuneBuilder.addMetaText("partOrder", tokenizer.translateString(tokenizer.stripComment(line.substring(2))));
|
|
519
|
+
tuneBuilder.addMetaText("partOrder", tokenizer.translateString(tokenizer.stripComment(line.substring(2))), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
|
|
520
520
|
else
|
|
521
521
|
multilineVars.partForNextLine = { title: tokenizer.translateString(tokenizer.stripComment(line.substring(2))), startChar: startChar, endChar: endChar};
|
|
522
522
|
break;
|
|
523
523
|
case 'Q':
|
|
524
|
-
var tempo = this.setTempo(line, 2, line.length);
|
|
524
|
+
var tempo = this.setTempo(line, 2, line.length, multilineVars.iChar);
|
|
525
525
|
if (tempo.type === 'delaySet') multilineVars.tempo = tempo.tempo;
|
|
526
526
|
else if (tempo.type === 'immediate') {
|
|
527
527
|
if (!tune.metaText.tempo)
|
|
@@ -318,6 +318,7 @@ MusicParser.prototype.parseMusic = function(line) {
|
|
|
318
318
|
else {
|
|
319
319
|
el.startTriplet = ret.triplet;
|
|
320
320
|
el.tripletMultiplier = ret.tripletQ / ret.triplet;
|
|
321
|
+
el.tripletR = ret.num_notes;
|
|
321
322
|
tripletNotesLeft = ret.num_notes === undefined ? ret.triplet : ret.num_notes;
|
|
322
323
|
}
|
|
323
324
|
}
|
|
@@ -480,6 +481,7 @@ MusicParser.prototype.parseMusic = function(line) {
|
|
|
480
481
|
// TODO-PER: straighten this out so there is not so much copying: getCoreNote shouldn't change e'
|
|
481
482
|
if (core.accidental !== undefined) el.pitches[0].accidental = core.accidental;
|
|
482
483
|
el.pitches[0].pitch = core.pitch;
|
|
484
|
+
el.pitches[0].name = core.name;
|
|
483
485
|
if (core.midipitch || core.midipitch === 0)
|
|
484
486
|
el.pitches[0].midipitch = core.midipitch;
|
|
485
487
|
if (core.endSlur !== undefined) el.pitches[0].endSlur = core.endSlur;
|
|
@@ -672,12 +674,17 @@ var letter_to_grace = function(line, i) {
|
|
|
672
674
|
|
|
673
675
|
ii = note.endChar;
|
|
674
676
|
delete note.endChar;
|
|
677
|
+
|
|
678
|
+
if (note.end_beam) {
|
|
679
|
+
note.endBeam = true;
|
|
680
|
+
delete note.end_beam;
|
|
681
|
+
}
|
|
675
682
|
}
|
|
676
683
|
else {
|
|
677
684
|
// We shouldn't get anything but notes or a space here, so report an error
|
|
678
685
|
if (gra[1].charAt(ii) === ' ') {
|
|
679
686
|
if (gracenotes.length > 0)
|
|
680
|
-
gracenotes[gracenotes.length-1].
|
|
687
|
+
gracenotes[gracenotes.length-1].endBeam = true;
|
|
681
688
|
} else
|
|
682
689
|
warn("Unknown character '" + gra[1].charAt(ii) + "' while parsing grace note", line, i);
|
|
683
690
|
ii++;
|
|
@@ -1062,6 +1069,7 @@ var addEndBeam = function(el) {
|
|
|
1062
1069
|
|
|
1063
1070
|
var pitches = {A: 5, B: 6, C: 0, D: 1, E: 2, F: 3, G: 4, a: 12, b: 13, c: 7, d: 8, e: 9, f: 10, g: 11};
|
|
1064
1071
|
var rests = {x: 'invisible', X: 'invisible-multimeasure', y: 'spacer', z: 'rest', Z: 'multimeasure' };
|
|
1072
|
+
var accMap = { 'dblflat': '__', 'flat': '_', 'natural': '=', 'sharp': '^', 'dblsharp': '^^', 'quarterflat': '_/', 'quartersharp': '^/'};
|
|
1065
1073
|
var getCoreNote = function(line, index, el, canHaveBrokenRhythm) {
|
|
1066
1074
|
//var el = { startChar: index };
|
|
1067
1075
|
var isComplete = function(state) {
|
|
@@ -1120,6 +1128,9 @@ var getCoreNote = function(line, index, el, canHaveBrokenRhythm) {
|
|
|
1120
1128
|
case 'g':
|
|
1121
1129
|
if (state === 'startSlur' || state === 'sharp2' || state === 'flat2' || state === 'pitch') {
|
|
1122
1130
|
el.pitch = pitches[line.charAt(index)];
|
|
1131
|
+
el.name = line.charAt(index);
|
|
1132
|
+
if (el.accidental)
|
|
1133
|
+
el.name = accMap[el.accidental] + el.name;
|
|
1123
1134
|
transpose.note(multilineVars, el);
|
|
1124
1135
|
state = 'octave';
|
|
1125
1136
|
// At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below
|
|
@@ -1134,7 +1145,6 @@ var getCoreNote = function(line, index, el, canHaveBrokenRhythm) {
|
|
|
1134
1145
|
(multilineVars.currentVoice && multilineVars.currentVoice.clef === "perc")) {
|
|
1135
1146
|
var key = line.charAt(index);
|
|
1136
1147
|
if (el.accidental) {
|
|
1137
|
-
var accMap = { 'dblflat': '__', 'flat': '_', 'natural': '=', 'sharp': '^', 'dblsharp': '^^'};
|
|
1138
1148
|
key = accMap[el.accidental] + key;
|
|
1139
1149
|
}
|
|
1140
1150
|
if (tune.formatting && tune.formatting.midi && tune.formatting.midi.drummap)
|
|
@@ -1144,12 +1154,12 @@ var getCoreNote = function(line, index, el, canHaveBrokenRhythm) {
|
|
|
1144
1154
|
else return null;
|
|
1145
1155
|
break;
|
|
1146
1156
|
case ',':
|
|
1147
|
-
if (state === 'octave') {el.pitch -= 7; }
|
|
1157
|
+
if (state === 'octave') {el.pitch -= 7; el.name += ','; }
|
|
1148
1158
|
else if (isComplete(state)) {el.endChar = index;return el;}
|
|
1149
1159
|
else return null;
|
|
1150
1160
|
break;
|
|
1151
1161
|
case '\'':
|
|
1152
|
-
if (state === 'octave') {el.pitch += 7; }
|
|
1162
|
+
if (state === 'octave') {el.pitch += 7; el.name += "'"; }
|
|
1153
1163
|
else if (isComplete(state)) {el.endChar = index;return el;}
|
|
1154
1164
|
else return null;
|
|
1155
1165
|
break;
|
|
@@ -4,19 +4,6 @@ var parseCommon = require('../parse/abc_common');
|
|
|
4
4
|
var TuneBuilder = function(tune) {
|
|
5
5
|
var self = this;
|
|
6
6
|
|
|
7
|
-
this.reset = function () {
|
|
8
|
-
tune.version = "1.1.0";
|
|
9
|
-
tune.media = "screen";
|
|
10
|
-
tune.metaText = {};
|
|
11
|
-
tune.formatting = {};
|
|
12
|
-
tune.lines = [];
|
|
13
|
-
tune.staffNum = 0;
|
|
14
|
-
tune.voiceNum = 0;
|
|
15
|
-
tune.lineNum = 0;
|
|
16
|
-
tune.runningFonts = {};
|
|
17
|
-
delete tune.visualTranspose;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
7
|
this.setVisualTranspose = function(visualTranspose) {
|
|
21
8
|
if (visualTranspose)
|
|
22
9
|
tune.visualTranspose = visualTranspose;
|
|
@@ -472,7 +459,7 @@ var TuneBuilder = function(tune) {
|
|
|
472
459
|
return currSlur;
|
|
473
460
|
};
|
|
474
461
|
|
|
475
|
-
|
|
462
|
+
tune.reset();
|
|
476
463
|
|
|
477
464
|
this.getLastNote = function() {
|
|
478
465
|
if (tune.lines[tune.lineNum] && tune.lines[tune.lineNum].staff && tune.lines[tune.lineNum].staff[tune.staffNum] &&
|
|
@@ -665,8 +652,8 @@ var TuneBuilder = function(tune) {
|
|
|
665
652
|
tune.lines.push(hash);
|
|
666
653
|
};
|
|
667
654
|
|
|
668
|
-
this.addSubtitle = function(str) {
|
|
669
|
-
this.pushLine({subtitle: str});
|
|
655
|
+
this.addSubtitle = function(str, info) {
|
|
656
|
+
this.pushLine({subtitle: { text: str, startChar: info.startChar, endChar: info.endChar}});
|
|
670
657
|
};
|
|
671
658
|
|
|
672
659
|
this.addSpacing = function(num) {
|
|
@@ -677,12 +664,12 @@ var TuneBuilder = function(tune) {
|
|
|
677
664
|
this.pushLine({newpage: num});
|
|
678
665
|
};
|
|
679
666
|
|
|
680
|
-
this.addSeparator = function(spaceAbove, spaceBelow, lineLength) {
|
|
681
|
-
this.pushLine({separator: {spaceAbove: Math.round(spaceAbove), spaceBelow: Math.round(spaceBelow), lineLength: Math.round(lineLength)}});
|
|
667
|
+
this.addSeparator = function(spaceAbove, spaceBelow, lineLength, info) {
|
|
668
|
+
this.pushLine({separator: {spaceAbove: Math.round(spaceAbove), spaceBelow: Math.round(spaceBelow), lineLength: Math.round(lineLength), startChar: info.startChar, endChar: info.endChar}});
|
|
682
669
|
};
|
|
683
670
|
|
|
684
|
-
this.addText = function(str) {
|
|
685
|
-
this.pushLine({text: str});
|
|
671
|
+
this.addText = function(str, info) {
|
|
672
|
+
this.pushLine({text: { text: str, startChar: info.startChar, endChar: info.endChar}});
|
|
686
673
|
};
|
|
687
674
|
|
|
688
675
|
this.addCentered = function(str) {
|
|
@@ -699,7 +686,7 @@ var TuneBuilder = function(tune) {
|
|
|
699
686
|
|
|
700
687
|
this.containsNotesStrict = function(voice) {
|
|
701
688
|
for (var i = 0; i < voice.length; i++) {
|
|
702
|
-
if (voice[i].el_type === 'note' && voice[i].rest === undefined)
|
|
689
|
+
if (voice[i].el_type === 'note' && (voice[i].rest === undefined || voice[i].chord !== undefined))
|
|
703
690
|
return true;
|
|
704
691
|
}
|
|
705
692
|
return false;
|
|
@@ -881,23 +868,29 @@ var TuneBuilder = function(tune) {
|
|
|
881
868
|
tune.lineNum = i;
|
|
882
869
|
};
|
|
883
870
|
|
|
884
|
-
this.addMetaText = function(key, value) {
|
|
885
|
-
if (tune.metaText[key] === undefined)
|
|
871
|
+
this.addMetaText = function(key, value, info) {
|
|
872
|
+
if (tune.metaText[key] === undefined) {
|
|
886
873
|
tune.metaText[key] = value;
|
|
887
|
-
|
|
874
|
+
tune.metaTextInfo[key] = info;
|
|
875
|
+
} else {
|
|
888
876
|
tune.metaText[key] += "\n" + value;
|
|
877
|
+
tune.metaTextInfo[key].endChar = info.endChar;
|
|
878
|
+
}
|
|
889
879
|
};
|
|
890
880
|
|
|
891
|
-
this.addMetaTextArray = function(key, value) {
|
|
892
|
-
if (tune.metaText[key] === undefined)
|
|
881
|
+
this.addMetaTextArray = function(key, value, info) {
|
|
882
|
+
if (tune.metaText[key] === undefined) {
|
|
893
883
|
tune.metaText[key] = [value];
|
|
894
|
-
|
|
884
|
+
tune.metaTextInfo[key] = info;
|
|
885
|
+
} else {
|
|
895
886
|
tune.metaText[key].push(value);
|
|
887
|
+
tune.metaTextInfo[key].endChar = info.endChar;
|
|
888
|
+
}
|
|
896
889
|
};
|
|
897
|
-
this.addMetaTextObj = function(key, value) {
|
|
890
|
+
this.addMetaTextObj = function(key, value, info) {
|
|
898
891
|
tune.metaText[key] = value;
|
|
892
|
+
tune.metaTextInfo[key] = info;
|
|
899
893
|
};
|
|
900
|
-
|
|
901
894
|
};
|
|
902
895
|
|
|
903
896
|
module.exports = TuneBuilder;
|
|
@@ -235,6 +235,15 @@ var parseCommon = require("../parse/abc_common");
|
|
|
235
235
|
if (elem.startTriplet) {
|
|
236
236
|
tripletMultiplier = elem.tripletMultiplier;
|
|
237
237
|
tripletDurationTotal = elem.startTriplet * tripletMultiplier * elem.duration;
|
|
238
|
+
if (elem.startTriplet != elem.tripletR) { // most commonly (3:2:2
|
|
239
|
+
if (v + elem.tripletR <= voice.length) {
|
|
240
|
+
var durationTotal = 0;
|
|
241
|
+
for (var w = v; w < v + elem.tripletR; w++) {
|
|
242
|
+
durationTotal += voice[w].duration;
|
|
243
|
+
}
|
|
244
|
+
tripletDurationTotal = tripletMultiplier * durationTotal;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
238
247
|
noteElem.duration = noteElem.duration * tripletMultiplier;
|
|
239
248
|
noteElem.duration = Math.round(noteElem.duration*1000000)/1000000;
|
|
240
249
|
tripletDurationCount = noteElem.duration;
|
|
@@ -264,6 +273,7 @@ var parseCommon = require("../parse/abc_common");
|
|
|
264
273
|
}
|
|
265
274
|
break;
|
|
266
275
|
case "key":
|
|
276
|
+
case "keySignature":
|
|
267
277
|
addKey(voices[voiceNumber], elem);
|
|
268
278
|
break;
|
|
269
279
|
case "meter":
|
|
@@ -12,10 +12,10 @@ var soundsCache = require('./sounds-cache');
|
|
|
12
12
|
// TODO-PER: remove the midi tests from here: I don't think the object can be constructed unless it passes.
|
|
13
13
|
var notSupportedMessage = "MIDI is not supported in this browser.";
|
|
14
14
|
|
|
15
|
-
var
|
|
15
|
+
var originalSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/abcjs/";
|
|
16
16
|
// These are the original soundfonts supplied. They will need a volume boost:
|
|
17
|
-
var
|
|
18
|
-
var
|
|
17
|
+
var defaultSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/";
|
|
18
|
+
var alternateSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/MusyngKite/";
|
|
19
19
|
|
|
20
20
|
function CreateSynth() {
|
|
21
21
|
var self = this;
|
|
@@ -43,20 +43,61 @@ function CreateSynth() {
|
|
|
43
43
|
self.soundFontUrl = params.soundFontUrl ? params.soundFontUrl : defaultSoundFontUrl;
|
|
44
44
|
if (self.soundFontUrl[self.soundFontUrl.length-1] !== '/')
|
|
45
45
|
self.soundFontUrl += '/';
|
|
46
|
-
if (params.soundFontVolumeMultiplier)
|
|
46
|
+
if (params.soundFontVolumeMultiplier || params.soundFontVolumeMultiplier === 0)
|
|
47
47
|
self.soundFontVolumeMultiplier = params.soundFontVolumeMultiplier;
|
|
48
|
-
else if (self.soundFontUrl ===
|
|
49
|
-
self.soundFontVolumeMultiplier =
|
|
50
|
-
else if (self.soundFontUrl ===
|
|
51
|
-
self.soundFontVolumeMultiplier = 0.
|
|
48
|
+
else if (self.soundFontUrl === defaultSoundFontUrl || self.soundFontUrl === alternateSoundFontUrl)
|
|
49
|
+
self.soundFontVolumeMultiplier = 3.0;
|
|
50
|
+
else if (self.soundFontUrl === originalSoundFontUrl)
|
|
51
|
+
self.soundFontVolumeMultiplier = 0.4;
|
|
52
52
|
else
|
|
53
53
|
self.soundFontVolumeMultiplier = 1.0;
|
|
54
54
|
if (params.programOffsets)
|
|
55
55
|
self.programOffsets = params.programOffsets;
|
|
56
|
-
else if (self.soundFontUrl ===
|
|
56
|
+
else if (self.soundFontUrl === originalSoundFontUrl)
|
|
57
57
|
self.programOffsets = {
|
|
58
|
-
"
|
|
59
|
-
"
|
|
58
|
+
"bright_acoustic_piano": 20,
|
|
59
|
+
"honkytonk_piano": 20,
|
|
60
|
+
"electric_piano_1": 30,
|
|
61
|
+
"electric_piano_2": 30,
|
|
62
|
+
"harpsichord": 40,
|
|
63
|
+
"clavinet": 20,
|
|
64
|
+
"celesta": 20,
|
|
65
|
+
"glockenspiel": 40,
|
|
66
|
+
"vibraphone": 30,
|
|
67
|
+
"marimba": 35,
|
|
68
|
+
"xylophone": 30,
|
|
69
|
+
"tubular_bells": 35,
|
|
70
|
+
"dulcimer": 30,
|
|
71
|
+
"drawbar_organ": 20,
|
|
72
|
+
"percussive_organ": 25,
|
|
73
|
+
"rock_organ": 20,
|
|
74
|
+
"church_organ": 40,
|
|
75
|
+
"reed_organ": 40,
|
|
76
|
+
"accordion": 40,
|
|
77
|
+
"harmonica": 40,
|
|
78
|
+
"acoustic_guitar_nylon": 20,
|
|
79
|
+
"acoustic_guitar_steel": 30,
|
|
80
|
+
"electric_guitar_jazz": 25,
|
|
81
|
+
"electric_guitar_clean": 15,
|
|
82
|
+
"electric_guitar_muted": 35,
|
|
83
|
+
"overdriven_guitar": 25,
|
|
84
|
+
"distortion_guitar": 20,
|
|
85
|
+
"guitar_harmonics": 30,
|
|
86
|
+
"electric_bass_finger": 15,
|
|
87
|
+
"electric_bass_pick": 30,
|
|
88
|
+
"fretless_bass": 40,
|
|
89
|
+
"violin": 105,
|
|
90
|
+
"viola": 50,
|
|
91
|
+
"cello": 40,
|
|
92
|
+
"contrabass": 60,
|
|
93
|
+
"trumpet": 10,
|
|
94
|
+
"trombone": 90,
|
|
95
|
+
"alto_sax": 20,
|
|
96
|
+
"tenor_sax": 20,
|
|
97
|
+
"clarinet": 20,
|
|
98
|
+
"flute": 50,
|
|
99
|
+
"banjo": 50,
|
|
100
|
+
"woodblock": 20,
|
|
60
101
|
};
|
|
61
102
|
else
|
|
62
103
|
self.programOffsets = {};
|
|
@@ -345,8 +386,8 @@ function CreateSynth() {
|
|
|
345
386
|
if (self.debugCallback)
|
|
346
387
|
self.debugCallback("pause called");
|
|
347
388
|
|
|
348
|
-
|
|
349
|
-
self.pausedTimeSec
|
|
389
|
+
self.pausedTimeSec = self.stop();
|
|
390
|
+
return self.pausedTimeSec;
|
|
350
391
|
};
|
|
351
392
|
|
|
352
393
|
self.resume = function() {
|
|
@@ -395,6 +436,8 @@ function CreateSynth() {
|
|
|
395
436
|
}
|
|
396
437
|
});
|
|
397
438
|
self.directSource = [];
|
|
439
|
+
var elapsed = activeAudioContext().currentTime - self.startTimeSec;
|
|
440
|
+
return elapsed;
|
|
398
441
|
};
|
|
399
442
|
self.finished = function() {
|
|
400
443
|
self.startTimeSec = undefined;
|
package/src/synth/load-note.js
CHANGED
|
@@ -2,74 +2,40 @@
|
|
|
2
2
|
// url = the base url for the soundfont
|
|
3
3
|
// instrument = the instrument name (e.g. "acoustic_grand_piano")
|
|
4
4
|
// name = the pitch name (e.g. "A3")
|
|
5
|
-
var soundsCache = require(
|
|
5
|
+
var soundsCache = require("./sounds-cache");
|
|
6
6
|
|
|
7
|
-
var getNote = function(url, instrument, name, audioContext) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
soundsCache[instrument] = {};
|
|
11
|
-
var instrumentCache = soundsCache[instrument];
|
|
7
|
+
var getNote = function (url, instrument, name, audioContext) {
|
|
8
|
+
if (!soundsCache[instrument]) soundsCache[instrument] = {};
|
|
9
|
+
var instrumentCache = soundsCache[instrument];
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// if (this.debugCallback)
|
|
24
|
-
// this.debugCallback(`Loading sound: ${instrument} ${name}`);
|
|
25
|
-
instrumentCache[name] = "pending"; // This can be called in parallel, so don't call it a second time before the first one has loaded.
|
|
26
|
-
var xhr = new XMLHttpRequest();
|
|
27
|
-
xhr.open('GET', url+instrument+'-mp3/'+name+'.mp3', true);
|
|
28
|
-
xhr.responseType = 'arraybuffer';
|
|
29
|
-
|
|
30
|
-
var self = this;
|
|
31
|
-
function onSuccess(audioBuffer) {
|
|
32
|
-
instrumentCache[name] = audioBuffer;
|
|
33
|
-
// if (self.debugCallback)
|
|
34
|
-
// self.debugCallback(`Sound loaded: ${instrument} ${name} ${url}`);
|
|
35
|
-
resolve({instrument: instrument, name: name, status: "loaded"});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function onFailure(error) {
|
|
39
|
-
error = "Can't decode sound. " + url + ' ' + instrument + ' ' + name + ' ' + error;
|
|
40
|
-
if (self.debugCallback)
|
|
41
|
-
self.debugCallback(error);
|
|
42
|
-
return resolve({instrument: instrument, name: name, status: "error", message: error });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
xhr.onload = function (e) {
|
|
46
|
-
if (this.status === 200) {
|
|
47
|
-
try {
|
|
48
|
-
var promise = audioContext.decodeAudioData(this.response, onSuccess, onFailure);
|
|
49
|
-
// older browsers only have the callback. Newer ones will report an unhandled
|
|
50
|
-
// rejection if catch isn't handled so we need both. We don't need to report it twice, though.
|
|
51
|
-
if (promise && promise.catch)
|
|
52
|
-
promise.catch(function () {});
|
|
53
|
-
} catch(error) {
|
|
54
|
-
reject(error);
|
|
11
|
+
if (!instrumentCache[name])
|
|
12
|
+
instrumentCache[name] = new Promise(function (resolve, reject) {
|
|
13
|
+
var xhr = new XMLHttpRequest();
|
|
14
|
+
let noteUrl = url + instrument + "-mp3/" + name + ".mp3";
|
|
15
|
+
xhr.open("GET", noteUrl, true);
|
|
16
|
+
xhr.responseType = "arraybuffer";
|
|
17
|
+
xhr.onload = function () {
|
|
18
|
+
if (xhr.status !== 200) {
|
|
19
|
+
reject(Error("Can't load sound at " + noteUrl));
|
|
20
|
+
return
|
|
55
21
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
22
|
+
var maybePromise = audioContext.decodeAudioData(xhr.response, resolve, function () {
|
|
23
|
+
reject(Error("Can't decode sound at " + noteUrl));
|
|
24
|
+
});
|
|
25
|
+
// In older browsers `BaseAudioContext.decodeAudio()` did not return a promise
|
|
26
|
+
if (maybePromise && typeof maybePromise.catch === "function") maybePromise.catch(reject);
|
|
27
|
+
};
|
|
28
|
+
xhr.onerror = function () {
|
|
29
|
+
reject(Error("Can't load sound at " + noteUrl));
|
|
30
|
+
};
|
|
31
|
+
xhr.send();
|
|
32
|
+
})
|
|
33
|
+
.catch(err => {
|
|
34
|
+
console.error("Didn't load note", instrument, name, ":", err.message);
|
|
35
|
+
throw err;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return instrumentCache[name];
|
|
73
39
|
};
|
|
74
40
|
|
|
75
41
|
module.exports = getNote;
|