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.
Files changed (101) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +20 -9
  3. package/RELEASE.md +108 -0
  4. package/dist/abcjs-basic-min.js +2 -2
  5. package/dist/abcjs-basic-min.js.LICENSE +2 -2
  6. package/dist/abcjs-basic.js +3493 -841
  7. package/dist/abcjs-basic.js.map +1 -1
  8. package/dist/abcjs-plugin-min.js +2 -2
  9. package/dist/abcjs-plugin-min.js.LICENSE +2 -2
  10. package/dist/report-basic.html +37 -0
  11. package/dist/report-before-glyph-compress.html +37 -0
  12. package/dist/report-brown-ts-target-es5.html +37 -0
  13. package/dist/{report.html → report-dev-orig-no-babel.html} +2 -2
  14. package/dist/report-synth.html +37 -0
  15. package/docker-build.sh +1 -0
  16. package/glyphs.json +1 -0
  17. package/index.js +23 -1
  18. package/license.js +1 -1
  19. package/package.json +9 -9
  20. package/plugin.js +23 -1
  21. package/src/api/abc_tablatures.js +144 -0
  22. package/src/api/abc_timing_callbacks.js +34 -31
  23. package/src/api/abc_tunebook.js +10 -1
  24. package/src/api/abc_tunebook_svg.js +23 -27
  25. package/src/data/abc_tune.js +68 -24
  26. package/src/edit/abc_editor.js +33 -11
  27. package/src/midi/abc_midi_create.js +6 -2
  28. package/src/parse/abc_parse.js +7 -6
  29. package/src/parse/abc_parse_directive.js +19 -12
  30. package/src/parse/abc_parse_header.js +12 -12
  31. package/src/parse/abc_parse_music.js +14 -4
  32. package/src/parse/tune-builder.js +22 -29
  33. package/src/synth/abc_midi_sequencer.js +10 -0
  34. package/src/synth/create-synth.js +56 -13
  35. package/src/synth/load-note.js +31 -65
  36. package/src/synth/place-note.js +59 -60
  37. package/src/synth/register-audio-context.js +4 -1
  38. package/src/synth/supports-audio.js +9 -8
  39. package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
  40. package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
  41. package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
  42. package/src/tablatures/instruments/string-patterns.js +277 -0
  43. package/src/tablatures/instruments/string-tablature.js +56 -0
  44. package/src/tablatures/instruments/tab-note.js +282 -0
  45. package/src/tablatures/instruments/tab-notes.js +41 -0
  46. package/src/tablatures/instruments/violin/tab-violin.js +47 -0
  47. package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
  48. package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
  49. package/src/tablatures/tab-absolute-elements.js +310 -0
  50. package/src/tablatures/tab-common.js +29 -0
  51. package/src/tablatures/tab-renderer.js +243 -0
  52. package/src/tablatures/transposer.js +110 -0
  53. package/src/test/abc_parser_lint.js +62 -6
  54. package/src/write/abc_absolute_element.js +2 -2
  55. package/src/write/abc_abstract_engraver.js +9 -7
  56. package/src/write/abc_create_key_signature.js +1 -0
  57. package/src/write/abc_create_note_head.js +1 -1
  58. package/src/write/abc_engraver_controller.js +26 -13
  59. package/src/write/abc_glyphs.js +5 -2
  60. package/src/write/abc_relative_element.js +11 -3
  61. package/src/write/abc_renderer.js +5 -1
  62. package/src/write/add-chord.js +5 -2
  63. package/src/write/add-text-if.js +33 -0
  64. package/src/write/bottom-text.js +8 -29
  65. package/src/write/draw/absolute.js +12 -14
  66. package/src/write/draw/brace.js +3 -3
  67. package/src/write/draw/crescendo.js +1 -1
  68. package/src/write/draw/draw.js +5 -6
  69. package/src/write/draw/dynamics.js +8 -1
  70. package/src/write/draw/ending.js +4 -3
  71. package/src/write/draw/group-elements.js +10 -8
  72. package/src/write/draw/non-music.js +11 -6
  73. package/src/write/draw/print-line.js +24 -0
  74. package/src/write/draw/print-stem.js +12 -11
  75. package/src/write/draw/print-symbol.js +11 -10
  76. package/src/write/draw/relative.js +33 -13
  77. package/src/write/draw/selectables.js +9 -6
  78. package/src/write/draw/staff-group.js +45 -9
  79. package/src/write/draw/staff-line.js +3 -17
  80. package/src/write/draw/staff.js +15 -2
  81. package/src/write/draw/tab-line.js +40 -0
  82. package/src/write/draw/tempo.js +7 -7
  83. package/src/write/draw/text.js +11 -4
  84. package/src/write/draw/tie.js +2 -2
  85. package/src/write/draw/triplet.js +3 -3
  86. package/src/write/draw/voice.js +10 -2
  87. package/src/write/format-jazz-chord.js +15 -0
  88. package/src/write/free-text.js +20 -12
  89. package/src/write/layout/VoiceElements.js +33 -1
  90. package/src/write/layout/beam.js +2 -0
  91. package/src/write/layout/staffGroup.js +37 -2
  92. package/src/write/layout/voice.js +2 -1
  93. package/src/write/selection.js +15 -5
  94. package/src/write/separator.js +1 -1
  95. package/src/write/subtitle.js +3 -3
  96. package/src/write/svg.js +41 -14
  97. package/src/write/top-text.js +19 -25
  98. package/test.js +23 -0
  99. package/types/index.d.ts +1007 -39
  100. package/version.js +1 -1
  101. 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].end_beam = true;
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
- this.reset();
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
- else
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
- else
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 defaultSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/abcjs/";
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 alternateSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/";
18
- var alternateSoundFontUrl2 = "https://paulrosen.github.io/midi-js-soundfonts/MusyngKite/";
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 === alternateSoundFontUrl || self.soundFontUrl === alternateSoundFontUrl2)
49
- self.soundFontVolumeMultiplier = 5.0;
50
- else if (self.soundFontUrl === defaultSoundFontUrl)
51
- self.soundFontVolumeMultiplier = 0.5;
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 === defaultSoundFontUrl)
56
+ else if (self.soundFontUrl === originalSoundFontUrl)
57
57
  self.programOffsets = {
58
- "violin": 113,
59
- "trombone": 200,
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
- self.stop();
349
- self.pausedTimeSec = activeAudioContext().currentTime - self.startTimeSec;
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;
@@ -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('./sounds-cache');
5
+ var soundsCache = require("./sounds-cache");
6
6
 
7
- var getNote = function(url, instrument, name, audioContext) {
8
- return new Promise(function (resolve, reject) {
9
- if (!soundsCache[instrument])
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
- if (instrumentCache[name] === 'error') {
14
- return resolve({instrument: instrument, name: name, status: "error", message: "Unable to load sound font" + ' ' + url + ' ' + instrument + ' ' + name });
15
- }
16
- if (instrumentCache[name] === 'pending') {
17
- return resolve({instrument: instrument, name: name, status: "pending"});
18
- }
19
- if (instrumentCache[name]) {
20
- return resolve({instrument: instrument, name: name, status: "cached"});
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
- } else {
57
- instrumentCache[name] = "error"; // To keep this from trying to load repeatedly.
58
- var cantLoadMp3 = "Onload error loading sound: " + name + " " + url + " " + e.currentTarget.status + " " + e.currentTarget.statusText;
59
- if (self.debugCallback)
60
- self.debugCallback(cantLoadMp3);
61
- return resolve({instrument: instrument, name: name, status: "error", message: cantLoadMp3 });
62
- }
63
- };
64
- xhr.addEventListener("error", function () {
65
- instrumentCache[name] = "error"; // To keep this from trying to load repeatedly.
66
- var cantLoadMp3 = "Error in loading sound: " + " " + url;
67
- if (self.debugCallback)
68
- self.debugCallback(cantLoadMp3);
69
- return resolve({instrument: instrument, name: name, status: "error", message: cantLoadMp3 });
70
- }, false);
71
- xhr.send();
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;