abcjs 6.0.0-beta.32 → 6.0.0-beta.36

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 (97) hide show
  1. package/README.md +13 -7
  2. package/RELEASE.md +130 -0
  3. package/dist/abcjs-basic-min.js +2 -2
  4. package/dist/abcjs-basic.js +3763 -825
  5. package/dist/abcjs-basic.js.map +1 -1
  6. package/dist/abcjs-plugin-min.js +2 -2
  7. package/dist/report-basic.html +37 -0
  8. package/dist/report-before-glyph-compress.html +37 -0
  9. package/dist/report-brown-ts-target-es5.html +37 -0
  10. package/dist/report-dev-orig-no-babel.html +37 -0
  11. package/dist/report-synth.html +37 -0
  12. package/docker-build.sh +1 -0
  13. package/glyphs.json +1 -0
  14. package/package.json +9 -9
  15. package/src/api/abc_tablatures.js +144 -0
  16. package/src/api/abc_timing_callbacks.js +49 -26
  17. package/src/api/abc_tunebook.js +10 -1
  18. package/src/api/abc_tunebook_svg.js +16 -22
  19. package/src/data/abc_tune.js +90 -25
  20. package/src/data/deline-tune.js +199 -0
  21. package/src/edit/abc_editor.js +33 -11
  22. package/src/midi/abc_midi_create.js +6 -2
  23. package/src/parse/abc_parse.js +10 -6
  24. package/src/parse/abc_parse_directive.js +19 -12
  25. package/src/parse/abc_parse_header.js +12 -12
  26. package/src/parse/abc_parse_music.js +15 -5
  27. package/src/parse/tune-builder.js +23 -30
  28. package/src/parse/wrap_lines.js +13 -36
  29. package/src/synth/abc_midi_flattener.js +44 -29
  30. package/src/synth/abc_midi_sequencer.js +52 -13
  31. package/src/synth/create-synth.js +22 -7
  32. package/src/synth/load-note.js +31 -65
  33. package/src/synth/place-note.js +59 -60
  34. package/src/synth/register-audio-context.js +4 -1
  35. package/src/synth/supports-audio.js +9 -8
  36. package/src/synth/synth-controller.js +5 -3
  37. package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
  38. package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
  39. package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
  40. package/src/tablatures/instruments/string-patterns.js +277 -0
  41. package/src/tablatures/instruments/string-tablature.js +56 -0
  42. package/src/tablatures/instruments/tab-note.js +282 -0
  43. package/src/tablatures/instruments/tab-notes.js +41 -0
  44. package/src/tablatures/instruments/violin/tab-violin.js +47 -0
  45. package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
  46. package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
  47. package/src/tablatures/tab-absolute-elements.js +310 -0
  48. package/src/tablatures/tab-common.js +29 -0
  49. package/src/tablatures/tab-renderer.js +243 -0
  50. package/src/tablatures/transposer.js +110 -0
  51. package/src/test/abc_parser_lint.js +62 -6
  52. package/src/write/abc_absolute_element.js +2 -2
  53. package/src/write/abc_abstract_engraver.js +9 -7
  54. package/src/write/abc_create_key_signature.js +1 -0
  55. package/src/write/abc_create_note_head.js +1 -1
  56. package/src/write/abc_engraver_controller.js +22 -9
  57. package/src/write/abc_glyphs.js +5 -2
  58. package/src/write/abc_relative_element.js +11 -3
  59. package/src/write/abc_renderer.js +5 -1
  60. package/src/write/add-chord.js +5 -2
  61. package/src/write/add-text-if.js +33 -0
  62. package/src/write/bottom-text.js +8 -29
  63. package/src/write/draw/absolute.js +12 -14
  64. package/src/write/draw/brace.js +3 -3
  65. package/src/write/draw/crescendo.js +1 -1
  66. package/src/write/draw/draw.js +3 -4
  67. package/src/write/draw/dynamics.js +8 -1
  68. package/src/write/draw/ending.js +4 -3
  69. package/src/write/draw/group-elements.js +10 -8
  70. package/src/write/draw/non-music.js +11 -6
  71. package/src/write/draw/print-line.js +24 -0
  72. package/src/write/draw/print-stem.js +12 -11
  73. package/src/write/draw/print-symbol.js +11 -10
  74. package/src/write/draw/relative.js +33 -13
  75. package/src/write/draw/selectables.js +9 -6
  76. package/src/write/draw/staff-group.js +45 -9
  77. package/src/write/draw/staff-line.js +3 -17
  78. package/src/write/draw/staff.js +15 -2
  79. package/src/write/draw/tab-line.js +40 -0
  80. package/src/write/draw/tempo.js +7 -7
  81. package/src/write/draw/text.js +11 -4
  82. package/src/write/draw/tie.js +2 -2
  83. package/src/write/draw/triplet.js +3 -3
  84. package/src/write/draw/voice.js +10 -2
  85. package/src/write/format-jazz-chord.js +15 -0
  86. package/src/write/free-text.js +20 -12
  87. package/src/write/layout/VoiceElements.js +33 -1
  88. package/src/write/layout/beam.js +2 -0
  89. package/src/write/layout/staffGroup.js +37 -2
  90. package/src/write/layout/voice.js +2 -1
  91. package/src/write/selection.js +15 -5
  92. package/src/write/separator.js +1 -1
  93. package/src/write/subtitle.js +3 -3
  94. package/src/write/svg.js +41 -14
  95. package/src/write/top-text.js +19 -25
  96. package/types/index.d.ts +1007 -39
  97. package/version.js +1 -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
  }
@@ -461,7 +462,7 @@ MusicParser.prototype.parseMusic = function(line) {
461
462
  }
462
463
 
463
464
  multilineVars.addFormattingOptions(el, tune.formatting, 'note');
464
- tuneBuilder.appendElement('note', startOfLine+chordStartChar, startOfLine+i, el);
465
+ tuneBuilder.appendElement('note', startOfLine+startI, startOfLine+i, el);
465
466
  multilineVars.measureNotEmpty = true;
466
467
  el = {};
467
468
  }
@@ -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] &&
@@ -612,7 +599,7 @@ var TuneBuilder = function(tune) {
612
599
  // Clone the object because it will be sticking around for the next line and we don't want the extra fields in it.
613
600
  var hashParams = parseCommon.clone(hashParams2);
614
601
 
615
- if (tune.lines[tune.lineNum].staff) { // be sure that we are on a music type line before doing the following.
602
+ if (tune.lines[tune.lineNum] && tune.lines[tune.lineNum].staff) { // be sure that we are on a music type line before doing the following.
616
603
  // If tune is the first item in tune staff, then we might have to initialize the staff, first.
617
604
  if (tune.lines[tune.lineNum].staff.length <= tune.staffNum) {
618
605
  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;
@@ -7,7 +7,7 @@ function wrapLines(tune, lineBreaks) {
7
7
  // tune.lines contains nested arrays: there is an array of lines (that's the part this function rewrites),
8
8
  // there is an array of staffs per line (for instance, piano will have 2, orchestra will have many)
9
9
  // there is an array of voices per staff (for instance, 4-part harmony might have bass and tenor on a single staff)
10
- var lines = removeLineBreaks(tune.lines);
10
+ var lines = tune.deline({lineBreaks: false});
11
11
  var linesBreakElements = findLineBreaks(lines, lineBreaks);
12
12
  //console.log(JSON.stringify(linesBreakElements))
13
13
  tune.lines = addLineBreaks(lines, linesBreakElements);
@@ -24,6 +24,7 @@ function addLineBreaks(lines, linesBreakElements) {
24
24
  // If the item doesn't contain "staff" then it is a non music line and should just be copied.
25
25
  var outputLines = [];
26
26
  var lastKeySig = []; // This is per staff - if the key changed then this will be populated.
27
+ var lastStem = [];
27
28
  for (var i = 0; i < linesBreakElements.length; i++) {
28
29
  var action = linesBreakElements[i];
29
30
  if (lines[action.ogLine].staff) {
@@ -43,12 +44,15 @@ function addLineBreaks(lines, linesBreakElements) {
43
44
  }
44
45
  if (lastKeySig[action.staff])
45
46
  outputLines[action.line].staff[action.staff].key = lastKeySig[action.staff];
47
+
46
48
  }
47
49
  if (!outputLines[action.line].staff[action.staff].voices[action.voice]) {
48
50
  outputLines[action.line].staff[action.staff].voices[action.voice] = [];
49
51
  }
50
52
  outputLines[action.line].staff[action.staff].voices[action.voice] =
51
53
  lines[action.ogLine].staff[action.staff].voices[action.voice].slice(action.start, action.end+1);
54
+ if (lastStem[action.staff*10+action.voice])
55
+ outputLines[action.line].staff[action.staff].voices[action.voice].unshift({el_type: "stem", direction: lastStem[action.staff*10+action.voice].direction})
52
56
  var currVoice = outputLines[action.line].staff[action.staff].voices[action.voice];
53
57
  for (var kk = currVoice.length-1; kk >= 0; kk--) {
54
58
  if (currVoice[kk].el_type === "key") {
@@ -61,6 +65,14 @@ function addLineBreaks(lines, linesBreakElements) {
61
65
  break;
62
66
  }
63
67
  }
68
+ for (kk = currVoice.length-1; kk >= 0; kk--) {
69
+ if (currVoice[kk].el_type === "stem") {
70
+ lastStem[action.staff*10+action.voice] = {
71
+ direction: currVoice[kk].direction,
72
+ };
73
+ break;
74
+ }
75
+ }
64
76
  } else {
65
77
  outputLines[action.line] = lines[action.ogLine];
66
78
  }
@@ -128,41 +140,6 @@ function findLineBreaks(lines, lineBreakArray) {
128
140
  return lineBreakIndexes;
129
141
  }
130
142
 
131
- function removeLineBreaks(lines) {
132
- // This concatenates all the music lines. If there is a non-music line then it leaves it,
133
- // so it returns an array of lines where there is no more than one staff line in a row.
134
- var outputLines = [];
135
- var startLine = true;
136
- for (var i = 0; i < lines.length; i++) {
137
- var line = lines[i];
138
- if (line.staff) {
139
- if (startLine) {
140
- outputLines.push(line);
141
- startLine = false;
142
- } else {
143
- // copy all voices to the previous line
144
- var output = outputLines[outputLines.length - 1];
145
- var staffs = line.staff;
146
- for (var j = 0; j < staffs.length; j++) {
147
- if (output.staff.length <= j)
148
- output.staff.push({ voices: []})
149
- var staff = staffs[j];
150
- var voices = staff.voices;
151
- for (var k = 0; k < voices.length; k++) {
152
- if (output.staff[j].voices.length < k)
153
- output.staff[j].voices.push([]);
154
- var voice = voices[k];
155
- output.staff[j].voices[k] = output.staff[j].voices[k].concat(voice);
156
- }
157
- }
158
- }
159
- } else {
160
- startLine = true;
161
- outputLines.push(line);
162
- }
163
- }
164
- return outputLines;
165
- }
166
143
 
167
144
  function freeFormLineBreaks(widths, lineBreakPoint) {
168
145
  var lineBreaks = [];
@@ -69,7 +69,7 @@ var pitchesToPerc = require('./pitches-to-perc');
69
69
  accidentals = [0,0,0,0,0,0,0];
70
70
  bagpipes = false;
71
71
  tracks = [];
72
- startingTempo = undefined;
72
+ startingTempo = options.qpm;
73
73
  startingMeter = undefined;
74
74
  tempoChangeFactor = 1;
75
75
  instrument = undefined;
@@ -112,7 +112,7 @@ var pitchesToPerc = require('./pitches-to-perc');
112
112
  pickupLength = voices[0][0].pickupLength;
113
113
 
114
114
  // First adjust the input to resolve ties, set the starting time for each note, etc. That will make the rest of the logic easier
115
- preProcess(voices);
115
+ preProcess(voices, options);
116
116
 
117
117
  for (var i = 0; i < voices.length; i++) {
118
118
  transpose = 0;
@@ -225,35 +225,14 @@ var pitchesToPerc = require('./pitches-to-perc');
225
225
  if (drumTrack.length > 0) // Don't do drums on more than one track, so turn off drum after we create it.
226
226
  drumTrackFinished = true;
227
227
  }
228
+ // See if any notes are octaves played at the same time. If so, raise the pitch of the higher one.
229
+ if (options.detuneOctave)
230
+ findOctaves(tracks, parseInt(options.detuneOctave, 10));
231
+
228
232
  if (chordTrack.length > 0)
229
233
  tracks.push(chordTrack);
230
234
  if (drumTrack.length > 0)
231
235
  tracks.push(drumTrack);
232
- // Adjust the tempo according to the meter. The rules are this:
233
- // 1) If the denominator is 2 or 4, then always make a beat be the denominator.
234
- //
235
- // 2) If the denominator is 8 or 16, then:
236
- // a) If the numerator is divisible by 3, the beat is 3*denominator.
237
- // b) Otherwise the beat is the denominator.
238
- //
239
- // 3) If the denominator is anything else, then don't worry about it because it doesn't make sense. Don't modify it and hope for the best.
240
- //
241
- // Right now, the startingTempo is calculated for a quarter note, so modify it if necessary.
242
- // var num = startingMeter ? parseInt(startingMeter.num, 10) : meter.num;
243
- // var den = startingMeter ? parseInt(startingMeter.den, 10) : meter.den;
244
- // if (den === 2)
245
- // startingTempo *= 2;
246
- // else if (den === 8) {
247
- // if (parseInt(num, 10) % 3 === 0)
248
- // startingTempo *= 3/2;
249
- // else
250
- // startingTempo /= 2;
251
- // } else if (den === 16) {
252
- // if (num % 3 === 0)
253
- // startingTempo *= 3/4;
254
- // else
255
- // startingTempo /= 4;
256
- // }
257
236
 
258
237
  return { tempo: startingTempo, instrument: instrument, tracks: tracks, totalDuration: lastEventTime };
259
238
  };
@@ -275,11 +254,11 @@ var pitchesToPerc = require('./pitches-to-perc');
275
254
  return Math.round(duration*tempoChangeFactor*1000000)/1000000;
276
255
  }
277
256
 
278
- function preProcess(voices) {
257
+ function preProcess(voices, options) {
279
258
  for (var i = 0; i < voices.length; i++) {
280
259
  var voice = voices[i];
281
260
  var ties = {};
282
- var startingTempo = 0;
261
+ var startingTempo = options.qpm;
283
262
  var timeCounter = 0;
284
263
  var tempoMultiplier = 1;
285
264
  for (var j = 0; j < voice.length; j++) {
@@ -1257,6 +1236,42 @@ var pitchesToPerc = require('./pitches-to-perc');
1257
1236
  start += len;
1258
1237
  }
1259
1238
  }
1239
+
1240
+ function findOctaves(tracks, detuneCents) {
1241
+ var timing = {};
1242
+ for (var i = 0; i < tracks.length; i++) {
1243
+ for (var j = 0; j < tracks[i].length; j++) {
1244
+ var note = tracks[i][j];
1245
+ if (note.cmd === "note") {
1246
+ if (timing[note.start] === undefined)
1247
+ timing[note.start] = [];
1248
+ timing[note.start].push({track: i, event: j, pitch: note.pitch});
1249
+ }
1250
+ }
1251
+ }
1252
+ var keys = Object.keys(timing);
1253
+ for (i = 0; i < keys.length; i++) {
1254
+ var arr = timing[keys[i]];
1255
+ if (arr.length > 1) {
1256
+ arr = arr.sort(function(a,b) {
1257
+ return a.pitch - b.pitch;
1258
+ });
1259
+ var topEvent = arr[arr.length-1];
1260
+ var topNote = topEvent.pitch % 12;
1261
+ var found = false;
1262
+ for (j = 0; !found && j < arr.length-1; j++) {
1263
+ if (arr[j].pitch % 12 === topNote)
1264
+ found = true;
1265
+ }
1266
+ if (found) {
1267
+ var event = tracks[topEvent.track][topEvent.event];
1268
+ if (!event.cents)
1269
+ event.cents = 0;
1270
+ event.cents += detuneCents;
1271
+ }
1272
+ }
1273
+ }
1274
+ }
1260
1275
  })();
1261
1276
 
1262
1277
  module.exports = flatten;