abcjs 6.2.3 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +8 -0
  2. package/RELEASE.md +84 -1
  3. package/dist/abcjs-basic-min.js +2 -2
  4. package/dist/abcjs-basic.js +1775 -1034
  5. package/dist/abcjs-basic.js.map +1 -1
  6. package/dist/abcjs-plugin-min.js +2 -2
  7. package/index.js +3 -0
  8. package/package.json +1 -1
  9. package/plugin.js +1 -1
  10. package/src/api/abc_tablatures.js +48 -13
  11. package/src/api/tune-metrics.js +18 -0
  12. package/src/data/abc_tune.js +13 -2
  13. package/src/edit/abc_editarea.js +4 -1
  14. package/src/parse/abc_parse.js +2 -0
  15. package/src/parse/abc_parse_directive.js +23 -16
  16. package/src/parse/abc_parse_header.js +22 -19
  17. package/src/parse/abc_tokenizer.js +72 -7
  18. package/src/parse/tune-builder.js +60 -1
  19. package/src/synth/abc_midi_flattener.js +40 -462
  20. package/src/synth/abc_midi_sequencer.js +25 -10
  21. package/src/synth/chord-track.js +562 -0
  22. package/src/synth/create-note-map.js +2 -1
  23. package/src/synth/create-synth.js +91 -42
  24. package/src/synth/synth-controller.js +6 -2
  25. package/src/tablatures/instruments/string-patterns.js +11 -0
  26. package/src/tablatures/instruments/{violin/violin-patterns.js → tab-string-patterns.js} +6 -6
  27. package/src/tablatures/instruments/{violin/tab-violin.js → tab-string.js} +13 -11
  28. package/src/tablatures/tab-absolute-elements.js +16 -7
  29. package/src/tablatures/tab-renderer.js +22 -9
  30. package/src/test/abc_parser_lint.js +14 -12
  31. package/src/write/creation/abstract-engraver.js +6 -2
  32. package/src/write/creation/add-chord.js +102 -82
  33. package/src/write/creation/add-text-if.js +2 -2
  34. package/src/write/creation/decoration.js +16 -9
  35. package/src/write/creation/elements/bottom-text.js +62 -47
  36. package/src/write/creation/elements/rich-text.js +51 -0
  37. package/src/write/creation/elements/tie-element.js +23 -0
  38. package/src/write/creation/elements/top-text.js +37 -11
  39. package/src/write/creation/glyphs.js +1 -1
  40. package/src/write/draw/absolute.js +4 -1
  41. package/src/write/draw/draw.js +13 -4
  42. package/src/write/draw/non-music.js +3 -1
  43. package/src/write/draw/relative.js +1 -1
  44. package/src/write/draw/tempo.js +1 -1
  45. package/src/write/draw/text.js +10 -0
  46. package/src/write/engraver-controller.js +62 -17
  47. package/src/write/helpers/classes.js +1 -1
  48. package/src/write/helpers/get-font-and-attr.js +8 -1
  49. package/src/write/helpers/get-text-size.js +8 -1
  50. package/src/write/interactive/create-analysis.js +50 -0
  51. package/src/write/interactive/find-selectable-element.js +24 -0
  52. package/src/write/interactive/selection.js +5 -45
  53. package/src/write/layout/layout-in-grid.js +83 -0
  54. package/src/write/layout/layout.js +29 -24
  55. package/src/write/layout/set-upper-and-lower-elements.js +2 -0
  56. package/src/write/layout/staff-group.js +2 -2
  57. package/src/write/layout/voice-elements.js +1 -1
  58. package/src/write/layout/voice.js +1 -1
  59. package/src/write/renderer.js +3 -0
  60. package/src/write/svg.js +30 -0
  61. package/temp.txt +3 -0
  62. package/types/index.d.ts +142 -38
  63. package/version.js +1 -1
  64. package/.github/workflows/tests.yml +0 -29
  65. package/abc2xml_239/abc2xml.html +0 -769
  66. package/abc2xml_239/abc2xml.py +0 -2248
  67. package/abc2xml_239/abc2xml_changelog.html +0 -124
  68. package/abc2xml_239/lazy-river.abc +0 -26
  69. package/abc2xml_239/lazy-river.xml +0 -3698
  70. package/abc2xml_239/mean-to-me.abc +0 -22
  71. package/abc2xml_239/mean-to-me.xml +0 -2954
  72. package/abc2xml_239/pyparsing.py +0 -3672
  73. package/abc2xml_239/pyparsing.pyc +0 -0
  74. package/src/tablatures/instruments/guitar/guitar-patterns.js +0 -23
  75. package/src/tablatures/instruments/guitar/tab-guitar.js +0 -48
@@ -54,6 +54,7 @@ Object.keys(tuneBook).forEach(function (key) {
54
54
  abcjs[key] = tuneBook[key];
55
55
  });
56
56
  abcjs.renderAbc = __webpack_require__(/*! ./src/api/abc_tunebook_svg */ "./src/api/abc_tunebook_svg.js");
57
+ abcjs.tuneMetrics = __webpack_require__(/*! ./src/api/tune-metrics */ "./src/api/tune-metrics.js");
57
58
  abcjs.TimingCallbacks = __webpack_require__(/*! ./src/api/abc_timing_callbacks */ "./src/api/abc_timing_callbacks.js");
58
59
  var glyphs = __webpack_require__(/*! ./src/write/creation/glyphs */ "./src/write/creation/glyphs.js");
59
60
  abcjs.setGlyph = glyphs.setSymbol;
@@ -69,6 +70,7 @@ var supportsAudio = __webpack_require__(/*! ./src/synth/supports-audio */ "./src
69
70
  var playEvent = __webpack_require__(/*! ./src/synth/play-event */ "./src/synth/play-event.js");
70
71
  var SynthController = __webpack_require__(/*! ./src/synth/synth-controller */ "./src/synth/synth-controller.js");
71
72
  var getMidiFile = __webpack_require__(/*! ./src/synth/get-midi-file */ "./src/synth/get-midi-file.js");
73
+ var midiRenderer = __webpack_require__(/*! ./src/synth/abc_midi_renderer */ "./src/synth/abc_midi_renderer.js");
72
74
  abcjs.synth = {
73
75
  CreateSynth: CreateSynth,
74
76
  instrumentIndexToName: instrumentIndexToName,
@@ -81,7 +83,8 @@ abcjs.synth = {
81
83
  supportsAudio: supportsAudio,
82
84
  playEvent: playEvent,
83
85
  getMidiFile: getMidiFile,
84
- sequence: sequence
86
+ sequence: sequence,
87
+ midiRenderer: midiRenderer
85
88
  };
86
89
  abcjs['Editor'] = __webpack_require__(/*! ./src/edit/abc_editor */ "./src/edit/abc_editor.js");
87
90
  abcjs['EditArea'] = __webpack_require__(/*! ./src/edit/abc_editarea */ "./src/edit/abc_editarea.js");
@@ -203,17 +206,42 @@ module.exports = animation;
203
206
  * where plugin represents a plugin instance
204
207
  *
205
208
  */
206
- var ViolinTablature = __webpack_require__(/*! ../tablatures/instruments/violin/tab-violin */ "./src/tablatures/instruments/violin/tab-violin.js");
207
- var GuitarTablature = __webpack_require__(/*! ../tablatures/instruments/guitar/tab-guitar */ "./src/tablatures/instruments/guitar/tab-guitar.js");
209
+ var StringTablature = __webpack_require__(/*! ../tablatures/instruments/tab-string */ "./src/tablatures/instruments/tab-string.js");
208
210
 
209
211
  /* extend the table below when adding a new instrument plugin */
210
212
 
211
213
  // Existing tab classes
212
214
  var pluginTab = {
213
- 'violin': 'ViolinTab',
214
- 'fiddle': 'ViolinTab',
215
- 'mandolin': 'ViolinTab',
216
- 'guitar': 'GuitarTab'
215
+ 'violin': {
216
+ name: 'StringTab',
217
+ defaultTuning: ['G,', 'D', 'A', 'e'],
218
+ isTabBig: false,
219
+ tabSymbolOffset: 0
220
+ },
221
+ 'fiddle': {
222
+ name: 'StringTab',
223
+ defaultTuning: ['G,', 'D', 'A', 'e'],
224
+ isTabBig: false,
225
+ tabSymbolOffset: 0
226
+ },
227
+ 'mandolin': {
228
+ name: 'StringTab',
229
+ defaultTuning: ['G,', 'D', 'A', 'e'],
230
+ isTabBig: false,
231
+ tabSymbolOffset: 0
232
+ },
233
+ 'guitar': {
234
+ name: 'StringTab',
235
+ defaultTuning: ['E,', 'A,', 'D', 'G', 'B', 'e'],
236
+ isTabBig: true,
237
+ tabSymbolOffset: 0
238
+ },
239
+ 'fiveString': {
240
+ name: 'StringTab',
241
+ defaultTuning: ['C,', 'G,', 'D', 'A', 'e'],
242
+ isTabBig: false,
243
+ tabSymbolOffset: -.95
244
+ }
217
245
  };
218
246
  var abcTablatures = {
219
247
  inited: false,
@@ -258,7 +286,7 @@ var abcTablatures = {
258
286
  var tabName = pluginTab[instrument];
259
287
  var plugin = null;
260
288
  if (tabName) {
261
- plugin = this.plugins[tabName];
289
+ plugin = this.plugins[tabName.name];
262
290
  }
263
291
  if (plugin) {
264
292
  if (params.visualTranspose != 0) {
@@ -270,7 +298,8 @@ var abcTablatures = {
270
298
  classz: plugin,
271
299
  tuneNumber: tuneNumber,
272
300
  params: args,
273
- instance: null
301
+ instance: null,
302
+ tabType: tabName
274
303
  };
275
304
  // proceed with tab plugin init
276
305
  // plugin.init(tune, tuneNumber, args, ii);
@@ -296,20 +325,50 @@ var abcTablatures = {
296
325
  */
297
326
  layoutTablatures: function layoutTablatures(renderer, abcTune) {
298
327
  var tabs = abcTune.tablatures;
328
+
299
329
  // chack tabs request for each staffs
330
+ var staffLineCount = 0;
331
+
332
+ // Clear the suppression flag
333
+ if (tabs && tabs.length > 0) {
334
+ var nTabs = tabs.length;
335
+ for (var kk = 0; kk < nTabs; ++kk) {
336
+ if (tabs[kk] && tabs[kk].params.firstStaffOnly) {
337
+ tabs[kk].params.suppress = false;
338
+ }
339
+ }
340
+ }
300
341
  for (var ii = 0; ii < abcTune.lines.length; ii++) {
301
342
  var line = abcTune.lines[ii];
343
+ if (line.staff) {
344
+ staffLineCount++;
345
+ }
346
+
347
+ // MAE 27Nov2023
348
+ // If tab param "firstStaffOnly", remove the tab label after the first staff
349
+ if (staffLineCount > 1) {
350
+ if (tabs && tabs.length > 0) {
351
+ var nTabs = tabs.length;
352
+ for (var kk = 0; kk < nTabs; ++kk) {
353
+ if (tabs[kk].params.firstStaffOnly) {
354
+ // Set the staff draw suppression flag
355
+ tabs[kk].params.suppress = true;
356
+ }
357
+ }
358
+ }
359
+ }
302
360
  var curStaff = line.staff;
303
361
  if (curStaff) {
362
+ var maxStaves = curStaff.length;
304
363
  for (var jj = 0; jj < curStaff.length; jj++) {
305
- if (tabs[jj]) {
364
+ if (tabs[jj] && jj < maxStaves) {
306
365
  // tablature requested for staff
307
366
  var tabPlugin = tabs[jj];
308
367
  if (tabPlugin.instance == null) {
309
368
  tabPlugin.instance = new tabPlugin.classz();
310
369
  // plugin.init(tune, tuneNumber, args, ii);
311
370
  // call initer first
312
- tabPlugin.instance.init(abcTune, tabPlugin.tuneNumber, tabPlugin.params, jj);
371
+ tabPlugin.instance.init(abcTune, tabPlugin.tuneNumber, tabPlugin.params, jj, tabPlugin.tabType);
313
372
  }
314
373
  // render next
315
374
  tabPlugin.instance.render(renderer, line, jj);
@@ -324,8 +383,7 @@ var abcTablatures = {
324
383
  init: function init() {
325
384
  // just register plugin hosted by abcjs
326
385
  if (!this.inited) {
327
- this.register(new ViolinTablature());
328
- this.register(new GuitarTablature());
386
+ this.register(new StringTablature());
329
387
  this.inited = true;
330
388
  }
331
389
  }
@@ -1031,6 +1089,32 @@ module.exports = renderAbc;
1031
1089
 
1032
1090
  /***/ }),
1033
1091
 
1092
+ /***/ "./src/api/tune-metrics.js":
1093
+ /*!*********************************!*\
1094
+ !*** ./src/api/tune-metrics.js ***!
1095
+ \*********************************/
1096
+ /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
1097
+
1098
+ var tunebook = __webpack_require__(/*! ./abc_tunebook */ "./src/api/abc_tunebook.js");
1099
+ var EngraverController = __webpack_require__(/*! ../write/engraver-controller */ "./src/write/engraver-controller.js");
1100
+ var tuneMetrics = function tuneMetrics(abc, params) {
1101
+ function callback(div, tune, tuneNumber, abcString) {
1102
+ div = document.createElement("div");
1103
+ div.setAttribute("style", "visibility: hidden;");
1104
+ document.body.appendChild(div);
1105
+ var engraver_controller = new EngraverController(div, params);
1106
+ var widths = engraver_controller.getMeasureWidths(tune);
1107
+ div.parentNode.removeChild(div);
1108
+ return {
1109
+ sections: widths
1110
+ };
1111
+ }
1112
+ return tunebook.renderEngine(callback, "*", abc, params);
1113
+ };
1114
+ module.exports = tuneMetrics;
1115
+
1116
+ /***/ }),
1117
+
1034
1118
  /***/ "./src/const/key-accidentals.js":
1035
1119
  /*!**************************************!*\
1036
1120
  !*** ./src/const/key-accidentals.js ***!
@@ -1609,9 +1693,12 @@ var Tune = function Tune() {
1609
1693
  eventHash["event" + voiceTimeMilliseconds].measureStart = true;
1610
1694
  nextIsBar = false;
1611
1695
  }
1612
- if (isTiedToNext) isTiedState = voiceTimeMilliseconds;
1696
+ // TODO-PER: There doesn't seem to be a harm in letting ties be two different notes and it fixes a bug when a tie goes to a new line. If there aren't other problems with this change, then the variable can be removed completely.
1697
+ // if (isTiedToNext)
1698
+ // isTiedState = voiceTimeMilliseconds;
1613
1699
  }
1614
1700
  }
1701
+
1615
1702
  return {
1616
1703
  isTiedState: isTiedState,
1617
1704
  duration: realDuration / timeDivider,
@@ -1839,6 +1926,14 @@ var Tune = function Tune() {
1839
1926
  this.deline = function (options) {
1840
1927
  return delineTune(this.lines, options);
1841
1928
  };
1929
+ this.findSelectableElement = function (target) {
1930
+ if (this.engraver && this.engraver.selectables) return this.engraver.findSelectableElement(target);
1931
+ return null;
1932
+ };
1933
+ this.getSelectableArray = function () {
1934
+ if (this.engraver && this.engraver.selectables) return this.engraver.selectables;
1935
+ return [];
1936
+ };
1842
1937
  };
1843
1938
  module.exports = Tune;
1844
1939
 
@@ -2094,7 +2189,7 @@ try {
2094
2189
  // if we aren't in a browser, this code will crash, but it is not needed then either.
2095
2190
  }
2096
2191
  var EditArea = function EditArea(textareaid) {
2097
- this.textarea = document.getElementById(textareaid);
2192
+ if (typeof textareaid === "string") this.textarea = document.getElementById(textareaid);else this.textarea = textareaid;
2098
2193
  this.initialText = this.textarea.value;
2099
2194
  this.isDragging = false;
2100
2195
  };
@@ -2722,7 +2817,9 @@ var Parse = function Parse() {
2722
2817
  setupEvents: tune.setupEvents,
2723
2818
  setTiming: tune.setTiming,
2724
2819
  setUpAudio: tune.setUpAudio,
2725
- deline: tune.deline
2820
+ deline: tune.deline,
2821
+ findSelectableElement: tune.findSelectableElement,
2822
+ getSelectableArray: tune.getSelectableArray
2726
2823
  };
2727
2824
  if (tune.lineBreaks) t.lineBreaks = tune.lineBreaks;
2728
2825
  if (tune.visualTranspose) t.visualTranspose = tune.visualTranspose;
@@ -4169,31 +4266,30 @@ var parseDirective = {};
4169
4266
  }
4170
4267
  };
4171
4268
  parseDirective.parseFontChangeLine = function (textstr) {
4269
+ // We don't want to match two dollar signs, so change those temporarily
4270
+ textstr = textstr.replace(/\$\$/g, "\x03");
4172
4271
  var textParts = textstr.split('$');
4173
4272
  if (textParts.length > 1 && multilineVars.setfont) {
4174
- var textarr = [{
4175
- text: textParts[0]
4176
- }];
4273
+ var textarr = [];
4274
+ if (textParts[0] !== '')
4275
+ // did the original string start with `$`?
4276
+ textarr.push({
4277
+ text: textParts[0]
4278
+ });
4177
4279
  for (var i = 1; i < textParts.length; i++) {
4178
4280
  if (textParts[i][0] === '0') textarr.push({
4179
- text: textParts[i].substring(1)
4180
- });else if (textParts[i][0] === '1' && multilineVars.setfont[1]) textarr.push({
4181
- font: multilineVars.setfont[1],
4182
- text: textParts[i].substring(1)
4183
- });else if (textParts[i][0] === '2' && multilineVars.setfont[2]) textarr.push({
4184
- font: multilineVars.setfont[2],
4185
- text: textParts[i].substring(1)
4186
- });else if (textParts[i][0] === '3' && multilineVars.setfont[3]) textarr.push({
4187
- font: multilineVars.setfont[3],
4188
- text: textParts[i].substring(1)
4189
- });else if (textParts[i][0] === '4' && multilineVars.setfont[4]) textarr.push({
4190
- font: multilineVars.setfont[4],
4191
- text: textParts[i].substring(1)
4192
- });else textarr[textarr.length - 1].text += '$' + textParts[i];
4193
- }
4194
- if (textarr.length > 1) return textarr;
4195
- }
4196
- return textstr;
4281
+ text: textParts[i].substring(1).replace(/\x03/g, "$$")
4282
+ });else {
4283
+ var whichFont = parseInt(textParts[i][0], 10);
4284
+ if (multilineVars.setfont[whichFont]) textarr.push({
4285
+ font: multilineVars.setfont[whichFont],
4286
+ text: textParts[i].substring(1).replace(/\x03/g, "$$")
4287
+ });else textarr[textarr.length - 1].text += '$' + textParts[i].replace(/\x03/g, "$$");
4288
+ }
4289
+ }
4290
+ return textarr;
4291
+ }
4292
+ return textstr.replace(/\x03/g, "$$");
4197
4293
  };
4198
4294
  var positionChoices = ['auto', 'above', 'below', 'hidden'];
4199
4295
  parseDirective.addDirective = function (str) {
@@ -4241,6 +4337,9 @@ var parseDirective = {};
4241
4337
  case "jazzchords":
4242
4338
  tune.formatting.jazzchords = true;
4243
4339
  break;
4340
+ case "accentAbove":
4341
+ tune.formatting.accentAbove = true;
4342
+ break;
4244
4343
  case "germanAlphabet":
4245
4344
  tune.formatting.germanAlphabet = true;
4246
4345
  break;
@@ -4294,6 +4393,7 @@ var parseDirective = {};
4294
4393
  case "pageheight":
4295
4394
  case "pagewidth":
4296
4395
  case "rightmargin":
4396
+ case "stafftopmargin":
4297
4397
  case "staffsep":
4298
4398
  case "staffwidth":
4299
4399
  case "subtitlespace":
@@ -4432,7 +4532,7 @@ var parseDirective = {};
4432
4532
  if (sfTokens.length >= 4) {
4433
4533
  if (sfTokens[0].token === '-' && sfTokens[1].type === 'number') {
4434
4534
  var sfNum = parseInt(sfTokens[1].token);
4435
- if (sfNum >= 1 && sfNum <= 4) {
4535
+ if (sfNum >= 1 && sfNum <= 9) {
4436
4536
  if (!multilineVars.setfont) multilineVars.setfont = [];
4437
4537
  sfTokens.shift();
4438
4538
  sfTokens.shift();
@@ -4694,6 +4794,10 @@ var parseDirective = {};
4694
4794
  if (tokens.length !== 1 || tokens[0].type !== 'number') warn("Directive \"" + cmd + "\" requires a number as a parameter.");
4695
4795
  tune.formatting.fontboxpadding = tokens[0].floatt;
4696
4796
  break;
4797
+ case "stafftopmargin":
4798
+ if (tokens.length !== 1 || tokens[0].type !== 'number') warn("Directive \"" + cmd + "\" requires a number as a parameter.");
4799
+ tune.formatting.stafftopmargin = tokens[0].floatt;
4800
+ break;
4697
4801
  case "stretchlast":
4698
4802
  var sl = parseStretchLast(tokens);
4699
4803
  if (sl.value !== undefined) tune.formatting.stretchlast = sl.value;
@@ -4750,17 +4854,15 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
4750
4854
  parseDirective.initialize(tokenizer, warn, multilineVars, tune, tuneBuilder);
4751
4855
  };
4752
4856
  this.reset(tokenizer, warn, multilineVars, tune);
4753
- this.setTitle = function (title) {
4754
- if (multilineVars.hasMainTitle) tuneBuilder.addSubtitle(tokenizer.translateString(tokenizer.stripComment(title)), {
4857
+ this.setTitle = function (title, origSize) {
4858
+ if (multilineVars.hasMainTitle) tuneBuilder.addSubtitle(title, {
4755
4859
  startChar: multilineVars.iChar,
4756
- endChar: multilineVars.iChar + title.length + 2
4860
+ endChar: multilineVars.iChar + origSize + 2
4757
4861
  }); // display secondary title
4758
4862
  else {
4759
- var titleStr = tokenizer.translateString(tokenizer.theReverser(tokenizer.stripComment(title)));
4760
- if (multilineVars.titlecaps) titleStr = titleStr.toUpperCase();
4761
- tuneBuilder.addMetaText("title", titleStr, {
4863
+ tuneBuilder.addMetaText("title", title, {
4762
4864
  startChar: multilineVars.iChar,
4763
- endChar: multilineVars.iChar + title.length + 2
4865
+ endChar: multilineVars.iChar + origSize + 2
4764
4866
  });
4765
4867
  multilineVars.hasMainTitle = true;
4766
4868
  }
@@ -5120,12 +5222,13 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
5120
5222
  if (result.foundKey && tuneBuilder.hasBeginMusic()) tuneBuilder.appendStartingElement('key', startChar, endChar, parseKeyVoice.fixKey(multilineVars.clef, multilineVars.key));
5121
5223
  return [e - i + 1 + ws];
5122
5224
  case "[P:":
5225
+ var part = parseDirective.parseFontChangeLine(line.substring(i + 3, e));
5123
5226
  if (startLine || tune.lines.length <= tune.lineNum) multilineVars.partForNextLine = {
5124
- title: line.substring(i + 3, e),
5227
+ title: part,
5125
5228
  startChar: startChar,
5126
5229
  endChar: endChar
5127
5230
  };else tuneBuilder.appendElement('part', startChar, endChar, {
5128
- title: line.substring(i + 3, e)
5231
+ title: part
5129
5232
  });
5130
5233
  return [e - i + 1 + ws];
5131
5234
  case "[L:":
@@ -5216,28 +5319,34 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
5216
5319
  };
5217
5320
  this.parseHeader = function (line) {
5218
5321
  var field = metaTextHeaders[line[0]];
5219
- if (field !== undefined) {
5220
- if (field === 'unalignedWords') tuneBuilder.addMetaTextArray(field, parseDirective.parseFontChangeLine(tokenizer.translateString(tokenizer.stripComment(line.substring(2)))), {
5322
+ var origSize = line.length - 2;
5323
+ var restOfLine = tokenizer.translateString(tokenizer.stripComment(line.substring(2)));
5324
+ if (field === 'unalignedWords' || field === 'notes') {
5325
+ // These fields can be multi-line
5326
+ tuneBuilder.addMetaTextArray(field, parseDirective.parseFontChangeLine(restOfLine), {
5221
5327
  startChar: multilineVars.iChar,
5222
5328
  endChar: multilineVars.iChar + line.length
5223
- });else tuneBuilder.addMetaText(field, tokenizer.translateString(tokenizer.stripComment(line.substring(2))), {
5329
+ });
5330
+ } else if (field !== undefined) {
5331
+ // these fields are single line
5332
+ tuneBuilder.addMetaText(field, parseDirective.parseFontChangeLine(restOfLine), {
5224
5333
  startChar: multilineVars.iChar,
5225
5334
  endChar: multilineVars.iChar + line.length
5226
5335
  });
5227
- return {};
5228
5336
  } else {
5229
5337
  var startChar = multilineVars.iChar;
5230
5338
  var endChar = startChar + line.length;
5231
5339
  switch (line[0]) {
5232
5340
  case 'H':
5233
- tuneBuilder.addMetaText("history", tokenizer.translateString(tokenizer.stripComment(line.substring(2))), {
5341
+ // History is a little different because once it starts it continues until another header field is encountered
5342
+ tuneBuilder.addMetaTextArray("history", parseDirective.parseFontChangeLine(restOfLine), {
5234
5343
  startChar: multilineVars.iChar,
5235
5344
  endChar: multilineVars.iChar + line.length
5236
5345
  });
5237
5346
  line = tokenizer.peekLine();
5238
5347
  while (line && line[1] !== ':') {
5239
5348
  tokenizer.nextLine();
5240
- tuneBuilder.addMetaText("history", tokenizer.translateString(tokenizer.stripComment(line)), {
5349
+ tuneBuilder.addMetaTextArray("history", parseDirective.parseFontChangeLine(tokenizer.translateString(tokenizer.stripComment(line))), {
5241
5350
  startChar: multilineVars.iChar,
5242
5351
  endChar: multilineVars.iChar + line.length
5243
5352
  });
@@ -5262,11 +5371,11 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
5262
5371
  break;
5263
5372
  case 'P':
5264
5373
  // TODO-PER: There is more to do with parts, but the writer doesn't care.
5265
- if (multilineVars.is_in_header) tuneBuilder.addMetaText("partOrder", tokenizer.translateString(tokenizer.stripComment(line.substring(2))), {
5374
+ if (multilineVars.is_in_header) tuneBuilder.addMetaText("partOrder", parseDirective.parseFontChangeLine(restOfLine), {
5266
5375
  startChar: multilineVars.iChar,
5267
5376
  endChar: multilineVars.iChar + line.length
5268
5377
  });else multilineVars.partForNextLine = {
5269
- title: tokenizer.translateString(tokenizer.stripComment(line.substring(2))),
5378
+ title: restOfLine,
5270
5379
  startChar: startChar,
5271
5380
  endChar: endChar
5272
5381
  };
@@ -5278,7 +5387,8 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
5278
5387
  }
5279
5388
  break;
5280
5389
  case 'T':
5281
- this.setTitle(line.substring(2));
5390
+ if (multilineVars.titlecaps) restOfLine = restOfLine.toUpperCase();
5391
+ this.setTitle(parseDirective.parseFontChangeLine(tokenizer.theReverser(restOfLine)), origSize);
5282
5392
  break;
5283
5393
  case 'U':
5284
5394
  this.addUserDefinition(line, 2, line.length);
@@ -8718,9 +8828,67 @@ var Tokenizer = function Tokenizer(lines, multilineVars) {
8718
8828
  index: index
8719
8829
  };
8720
8830
  };
8831
+
8832
+ //
8833
+ // MAE 10 Jan 2023 - For better handling of tunes that have tune numbers in front of them.
8834
+ //
8835
+ // Previous version would take:
8836
+ // 21. Woman of the House, The
8837
+ // and return:
8838
+ // The 21. Woman of the House
8839
+ //
8840
+ // This fix results in:
8841
+ // 21. The Woman of the House
8842
+ //
8843
+ // Also added additional checks and handlers for lower case ", the" and ", a" since I found several tune collections with those tune name constructs
8844
+ //
8845
+ // Find an optional title number at the start of a tune title
8846
+ function getTitleNumber(str) {
8847
+ var regex = /^(\d+)\./;
8848
+
8849
+ // Use the exec method to search for the pattern in the string
8850
+ var match = regex.exec(str);
8851
+
8852
+ // Check if a match is found
8853
+ if (match) {
8854
+ // The matched number is captured in the first group (index 1)
8855
+ var foundNumber = match[1];
8856
+ return foundNumber;
8857
+ } else {
8858
+ // Return null if no match is found
8859
+ return null;
8860
+ }
8861
+ }
8862
+ var thePatterns = [{
8863
+ match: /,\s*[Tt]he$/,
8864
+ replace: "The "
8865
+ }, {
8866
+ match: /,\s*[Aa]$/,
8867
+ replace: "A "
8868
+ }, {
8869
+ match: /,\s*[Aa]n$/,
8870
+ replace: "An "
8871
+ }];
8721
8872
  this.theReverser = function (str) {
8722
- if (parseCommon.endsWith(str, ", The")) return "The " + str.substring(0, str.length - 5);
8723
- if (parseCommon.endsWith(str, ", A")) return "A " + str.substring(0, str.length - 3);
8873
+ for (var i = 0; i < thePatterns.length; i++) {
8874
+ var thisPattern = thePatterns[i];
8875
+ var match = str.match(thisPattern.match);
8876
+ if (match) {
8877
+ var theTitleNumber = getTitleNumber(str);
8878
+ if (theTitleNumber) {
8879
+ //console.log("theReverser The titlenumber:"+theTitleNumber);
8880
+
8881
+ str = str.replace(theTitleNumber + ".", "");
8882
+ str = str.trim();
8883
+ }
8884
+ var len = match[0].length;
8885
+ var result = thisPattern.replace + str.substring(0, str.length - len);
8886
+ if (theTitleNumber) {
8887
+ result = theTitleNumber + ". " + result;
8888
+ }
8889
+ return result;
8890
+ }
8891
+ }
8724
8892
  return str;
8725
8893
  };
8726
8894
  this.stripComment = function (str) {
@@ -9154,6 +9322,7 @@ module.exports = transposeChordName;
9154
9322
 
9155
9323
  var parseKeyVoice = __webpack_require__(/*! ../parse/abc_parse_key_voice */ "./src/parse/abc_parse_key_voice.js");
9156
9324
  var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/abc_common.js");
9325
+ var parseDirective = __webpack_require__(/*! ./abc_parse_directive */ "./src/parse/abc_parse_directive.js");
9157
9326
  var TuneBuilder = function TuneBuilder(tune) {
9158
9327
  var self = this;
9159
9328
  this.setVisualTranspose = function (visualTranspose) {
@@ -9313,6 +9482,8 @@ var TuneBuilder = function TuneBuilder(tune) {
9313
9482
  this.cleanUp = function (barsperstaff, staffnonote, currSlur) {
9314
9483
  this.closeLine(); // Close the last line.
9315
9484
  delete tune.runningFonts;
9485
+ simplifyMetaText(tune);
9486
+ //addRichTextToAnnotationsAndLyrics(tune)
9316
9487
 
9317
9488
  // If the tempo was created with a string like "Allegro", then the duration of a beat needs to be set at the last moment, when it is most likely known.
9318
9489
  if (tune.metaText.tempo && tune.metaText.tempo.bpm && !tune.metaText.tempo.duration) tune.metaText.tempo.duration = [tune.getBeatLength()];
@@ -10047,7 +10218,15 @@ var TuneBuilder = function TuneBuilder(tune) {
10047
10218
  tune.metaText[key] = value;
10048
10219
  tune.metaTextInfo[key] = info;
10049
10220
  } else {
10050
- tune.metaText[key] += "\n" + value;
10221
+ if (typeof tune.metaText[key] === 'string' && typeof value === 'string') tune.metaText[key] += "\n" + value;else {
10222
+ if (tune.metaText[key] === 'string') tune.metaText[key] = [{
10223
+ text: tune.metaText[key]
10224
+ }];
10225
+ if (typeof value === 'string') value = [{
10226
+ text: value
10227
+ }];
10228
+ tune.metaText[key] = tune.metaText[key].concat(value);
10229
+ }
10051
10230
  tune.metaTextInfo[key].endChar = info.endChar;
10052
10231
  }
10053
10232
  };
@@ -10065,6 +10244,46 @@ var TuneBuilder = function TuneBuilder(tune) {
10065
10244
  tune.metaTextInfo[key] = info;
10066
10245
  };
10067
10246
  };
10247
+ function isArrayOfStrings(arr) {
10248
+ if (!arr) return false;
10249
+ if (typeof arr === "string") return false;
10250
+ var str = '';
10251
+ for (var i = 0; i < arr.length; i++) {
10252
+ if (typeof arr[i] !== 'string') return false;
10253
+ }
10254
+ return true;
10255
+ }
10256
+ function simplifyMetaText(tune) {
10257
+ if (isArrayOfStrings(tune.metaText.notes)) tune.metaText.notes = tune.metaText.notes.join("\n");
10258
+ if (isArrayOfStrings(tune.metaText.history)) tune.metaText.history = tune.metaText.history.join("\n");
10259
+ }
10260
+ function addRichTextToAnnotationsAndLyrics(tune) {
10261
+ var lines = tune.lines;
10262
+ for (var i = 0; i < lines.length; i++) {
10263
+ if (lines[i].staff !== undefined) {
10264
+ for (var s = 0; s < lines[i].staff.length; s++) {
10265
+ for (var v = 0; v < lines[i].staff[s].voices.length; v++) {
10266
+ var voice = lines[i].staff[s].voices[v];
10267
+ for (var n = 0; n < voice.length; n++) {
10268
+ var element = voice[n];
10269
+ if (element.chord) {
10270
+ for (var c = 0; c < element.chord.length; c++) {
10271
+ element.chord[c].name = parseDirective.parseFontChangeLine(element.chord[c].name);
10272
+ console.log(element.chord[c].name);
10273
+ }
10274
+ }
10275
+ if (element.lyric) {
10276
+ for (var l = 0; l < element.lyric.length; l++) {
10277
+ element.lyric[l].syllable = parseDirective.parseFontChangeLine(element.lyric[l].syllable);
10278
+ console.log(element.lyric[l].syllable);
10279
+ }
10280
+ }
10281
+ }
10282
+ }
10283
+ }
10284
+ }
10285
+ }
10286
+ }
10068
10287
  module.exports = TuneBuilder;
10069
10288
 
10070
10289
  /***/ }),
@@ -11035,7 +11254,7 @@ module.exports = strTranspose;
11035
11254
  // It also extracts guitar chords to a separate voice and resolves their rhythm.
11036
11255
 
11037
11256
  var flatten;
11038
- var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/abc_common.js");
11257
+ var ChordTrack = __webpack_require__(/*! ./chord-track */ "./src/synth/chord-track.js");
11039
11258
  var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pitches-to-perc.js");
11040
11259
  (function () {
11041
11260
  "use strict";
@@ -11055,25 +11274,13 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11055
11274
  var lastNoteDurationPosition;
11056
11275
  var currentTrackName;
11057
11276
  var lastEventTime;
11277
+ var chordTrack;
11058
11278
  var meter = {
11059
11279
  num: 4,
11060
11280
  den: 4
11061
11281
  };
11062
- var chordTrack;
11063
- var chordSourceTrack;
11064
- var chordTrackFinished;
11065
- var chordChannel;
11066
- var bassInstrument = 0;
11067
- var chordInstrument = 0;
11068
11282
  var drumInstrument = 128;
11069
- var boomVolume = 64;
11070
- var chickVolume = 48;
11071
- var currentChords;
11072
- var lastChord;
11073
- var chordLastBar;
11074
11283
  var lastBarTime;
11075
- var gChordTacet = false;
11076
- var hasRhythmHead = false;
11077
11284
  var doBeatAccents = true;
11078
11285
  var stressBeat1 = 105;
11079
11286
  var stressBeatDown = 95;
@@ -11111,25 +11318,10 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11111
11318
  currentTrackName = undefined;
11112
11319
  lastEventTime = 0;
11113
11320
  percmap = percmap_;
11114
-
11115
- // For resolving chords.
11116
11321
  meter = {
11117
11322
  num: 4,
11118
11323
  den: 4
11119
11324
  };
11120
- chordTrack = [];
11121
- chordSourceTrack = false;
11122
- chordChannel = voices.length; // first free channel for chords
11123
- chordTrackFinished = false;
11124
- currentChords = [];
11125
- bassInstrument = midiOptions.bassprog && midiOptions.bassprog.length === 1 ? midiOptions.bassprog[0] : 0;
11126
- chordInstrument = midiOptions.chordprog && midiOptions.chordprog.length === 1 ? midiOptions.chordprog[0] : 0;
11127
- boomVolume = midiOptions.bassvol && midiOptions.bassvol.length === 1 ? midiOptions.bassvol[0] : 64;
11128
- chickVolume = midiOptions.chordvol && midiOptions.chordvol.length === 1 ? midiOptions.chordvol[0] : 48;
11129
- lastChord = undefined;
11130
- chordLastBar = undefined;
11131
- gChordTacet = options.chordsOff ? true : false;
11132
- hasRhythmHead = false;
11133
11325
  doBeatAccents = true;
11134
11326
  stressBeat1 = 105;
11135
11327
  stressBeatDown = 95;
@@ -11146,10 +11338,19 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11146
11338
  drumBars = 1;
11147
11339
  if (voices.length > 0 && voices[0].length > 0) pickupLength = voices[0][0].pickupLength;
11148
11340
 
11341
+ // For resolving chords.
11342
+ if (options.bassprog !== undefined && !midiOptions.bassprog) midiOptions.bassprog = [options.bassprog];
11343
+ if (options.bassvol !== undefined && !midiOptions.bassvol) midiOptions.bassvol = [options.bassvol];
11344
+ if (options.chordprog !== undefined && !midiOptions.chordprog) midiOptions.chordprog = [options.chordprog];
11345
+ if (options.chordvol !== undefined && !midiOptions.chordvol) midiOptions.chordvol = [options.chordvol];
11346
+ if (options.gchord !== undefined && !midiOptions.gchord) midiOptions.gchord = [options.gchord];
11347
+ chordTrack = new ChordTrack(voices.length, options.chordsOff, midiOptions, meter);
11348
+
11149
11349
  // First adjust the input to resolve ties, set the starting time for each note, etc. That will make the rest of the logic easier
11150
11350
  preProcess(voices, options);
11151
11351
  for (var i = 0; i < voices.length; i++) {
11152
11352
  transpose = 0;
11353
+ chordTrack.setTranspose(transpose);
11153
11354
  lastNoteDurationPosition = -1;
11154
11355
  var voice = voices[i];
11155
11356
  currentTrack = [{
@@ -11159,6 +11360,7 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11159
11360
  }];
11160
11361
  currentTrackName = undefined;
11161
11362
  lastBarTime = 0;
11363
+ chordTrack.setLastBarTime(0);
11162
11364
  var voiceOff = false;
11163
11365
  if (options.voicesOff === true) voiceOff = true;else if (options.voicesOff && options.voicesOff.length && options.voicesOff.indexOf(i) >= 0) voiceOff = true;
11164
11366
  for (var j = 0; j < voice.length; j++) {
@@ -11172,8 +11374,7 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11172
11374
  };
11173
11375
  break;
11174
11376
  case "note":
11175
- var setChordTrack = writeNote(element, voiceOff);
11176
- if (setChordTrack) chordSourceTrack = i;
11377
+ writeNote(element, voiceOff);
11177
11378
  break;
11178
11379
  case "key":
11179
11380
  accidentals = setKeySignature(element);
@@ -11181,27 +11382,27 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11181
11382
  case "meter":
11182
11383
  if (!startingMeter) startingMeter = element;
11183
11384
  meter = element;
11385
+ chordTrack.setMeter(meter);
11184
11386
  beatFraction = getBeatFraction(meter);
11185
11387
  alignDrumToMeter();
11186
11388
  break;
11187
11389
  case "tempo":
11188
11390
  if (!startingTempo) startingTempo = element.qpm;else tempoChangeFactor = element.qpm ? startingTempo / element.qpm : 1;
11391
+ chordTrack.setTempoChangeFactor(tempoChangeFactor);
11189
11392
  break;
11190
11393
  case "transpose":
11191
11394
  transpose = element.transpose;
11395
+ chordTrack.setTranspose(transpose);
11192
11396
  break;
11193
11397
  case "bar":
11194
- if (chordTrack.length > 0 && (chordSourceTrack === false || i === chordSourceTrack)) {
11195
- resolveChords(lastBarTime, timeToRealTime(element.time));
11196
- currentChords = [];
11197
- }
11398
+ chordTrack.barEnd(element);
11198
11399
  barAccidentals = [];
11199
11400
  if (i === 0)
11200
11401
  // Only write the drum part on the first voice so that it is not duplicated.
11201
11402
  writeDrum(voices.length + 1);
11202
- hasRhythmHead = false; // decide whether there are rhythm heads each measure.
11203
- chordLastBar = lastChord;
11403
+ chordTrack.setRhythmHead(false); // decide whether there are rhythm heads each measure.
11204
11404
  lastBarTime = timeToRealTime(element.time);
11405
+ chordTrack.setLastBarTime(lastBarTime);
11205
11406
  break;
11206
11407
  case "bagpipes":
11207
11408
  bagpipes = true;
@@ -11228,8 +11429,8 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11228
11429
  drumDefinition = normalizeDrumDefinition(element.params);
11229
11430
  alignDrumToMeter();
11230
11431
  break;
11231
- case "gchord":
11232
- if (!options.chordsOff) gChordTacet = element.tacet;
11432
+ case "gchordOn":
11433
+ chordTrack.gChordOn(element);
11233
11434
  break;
11234
11435
  case "beat":
11235
11436
  stressBeat1 = element.beats[0];
@@ -11246,6 +11447,13 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11246
11447
  case "beataccents":
11247
11448
  doBeatAccents = element.value;
11248
11449
  break;
11450
+ case "gchord":
11451
+ case "bassprog":
11452
+ case "chordprog":
11453
+ case "bassvol":
11454
+ case "chordvol":
11455
+ chordTrack.paramChange(element);
11456
+ break;
11249
11457
  default:
11250
11458
  // This should never happen
11251
11459
  console.log("MIDI creation. Unknown el_type: " + element.el_type + "\n"); // jshint ignore:line
@@ -11255,16 +11463,14 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11255
11463
  if (currentTrack[0].instrument === undefined) currentTrack[0].instrument = instrument ? instrument : 0;
11256
11464
  if (currentTrackName) currentTrack.unshift(currentTrackName);
11257
11465
  tracks.push(currentTrack);
11258
- if (!chordTrackEmpty())
11259
- // Don't do chords on more than one track, so turn off chord detection after we create it.
11260
- chordTrackFinished = true;
11466
+ chordTrack.finish();
11261
11467
  if (drumTrack.length > 0)
11262
11468
  // Don't do drums on more than one track, so turn off drum after we create it.
11263
11469
  drumTrackFinished = true;
11264
11470
  }
11265
11471
  // See if any notes are octaves played at the same time. If so, raise the pitch of the higher one.
11266
11472
  if (options.detuneOctave) findOctaves(tracks, parseInt(options.detuneOctave, 10));
11267
- if (!chordTrackEmpty()) tracks.push(chordTrack);
11473
+ chordTrack.addTrack(tracks);
11268
11474
  if (drumTrack.length > 0) tracks.push(drumTrack);
11269
11475
  return {
11270
11476
  tempo: startingTempo,
@@ -11281,13 +11487,6 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11281
11487
  }
11282
11488
  }
11283
11489
  }
11284
- function chordTrackEmpty() {
11285
- var isEmpty = true;
11286
- for (var i = 0; i < chordTrack.length && isEmpty; i++) {
11287
- if (chordTrack[i].cmd === 'note') isEmpty = false;
11288
- }
11289
- return isEmpty;
11290
- }
11291
11490
  function timeToRealTime(time) {
11292
11491
  return time / 1000000;
11293
11492
  }
@@ -11375,48 +11574,6 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11375
11574
  }
11376
11575
  return 0.25;
11377
11576
  }
11378
- //
11379
- // The algorithm for chords is:
11380
- // - The chords are done in a separate track.
11381
- // - If there are notes before the first chord, then put that much silence to start the track.
11382
- // - The pattern of chord expression depends on the meter, and how many chords are in a measure.
11383
- // - There is a possibility that a measure will have an incorrect number of beats, if that is the case, then
11384
- // start the pattern anew on the next measure number.
11385
- // - If a chord root is not A-G, then ignore it as if the chord wasn't there at all.
11386
- // - If a chord modification isn't in our supported list, change it to a major triad.
11387
- //
11388
- // - If there is only one chord in a measure:
11389
- // - If 2/4, play root chord
11390
- // - If cut time, play root(1) chord(3)
11391
- // - If 3/4, play root chord chord
11392
- // - If 4/4 or common time, play root chord fifth chord
11393
- // - If 6/8, play root(1) chord(3) fifth(4) chord(6)
11394
- // - For any other meter, play the full chord on each beat. (TODO-PER: expand this as more support is added.)
11395
- //
11396
- // - If there is a chord specified that is not on a beat, move it earlier to the previous beat, unless there is already a chord on that beat.
11397
- // - Otherwise, move it later, unless there is already a chord on that beat.
11398
- // - Otherwise, ignore it. (TODO-PER: expand this as more support is added.)
11399
- //
11400
- // - If there is a chord on the second beat, play a chord for the first beat instead of a bass note.
11401
- // - Likewise, if there is a chord on the fourth beat of 4/4, play a chord on the third beat instead of a bass note.
11402
- //
11403
- // If there is any note in the melody that has a rhythm head, then assume the melody controls the rhythm, so that is
11404
- // the same as a break.
11405
- var breakSynonyms = ['break', '(break)', 'no chord', 'n.c.', 'tacet'];
11406
- function findChord(elem) {
11407
- if (gChordTacet) return 'break';
11408
-
11409
- // TODO-PER: Just using the first chord if there are more than one.
11410
- if (chordTrackFinished || !elem.chord || elem.chord.length === 0) return null;
11411
-
11412
- // Return the first annotation that is a regular chord: that is, it is in the default place or is a recognized "tacet" phrase.
11413
- for (var i = 0; i < elem.chord.length; i++) {
11414
- var ch = elem.chord[i];
11415
- if (ch.position === 'default') return ch.name;
11416
- if (breakSynonyms.indexOf(ch.name.toLowerCase()) >= 0) return 'break';
11417
- }
11418
- return null;
11419
- }
11420
11577
  function calcBeat(measureStart, beatLength, currTime) {
11421
11578
  var distanceFromStart = currTime - measureStart;
11422
11579
  return distanceFromStart / beatLength;
@@ -11432,7 +11589,7 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11432
11589
  } else if (pickupLength > beat) {
11433
11590
  volume = stressBeatUp;
11434
11591
  } else {
11435
- var barLength = meter.num / meter.den;
11592
+ //var barLength = meter.num / meter.den;
11436
11593
  var barBeat = calcBeat(lastBarTime, getBeatFraction(meter), beat);
11437
11594
  if (barBeat === 0) volume = stressBeat1;else if (parseInt(barBeat, 10) === barBeat) volume = stressBeatDown;else volume = stressBeatUp;
11438
11595
  }
@@ -11444,34 +11601,6 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11444
11601
  if (volume > 127) volume = 127;
11445
11602
  return voiceOff ? 0 : volume;
11446
11603
  }
11447
- function processChord(elem) {
11448
- var firstChord = false;
11449
- var chord = findChord(elem);
11450
- if (chord) {
11451
- var c = interpretChord(chord);
11452
- // If this isn't a recognized chord, just completely ignore it.
11453
- if (c) {
11454
- // If we ever have a chord in this voice, then we add the chord track.
11455
- // However, if there are chords on more than one voice, then just use the first voice.
11456
- if (chordTrack.length === 0) {
11457
- firstChord = true;
11458
- chordTrack.push({
11459
- cmd: 'program',
11460
- channel: chordChannel,
11461
- instrument: chordInstrument
11462
- });
11463
- }
11464
- lastChord = c;
11465
- var barBeat = calcBeat(lastBarTime, getBeatFraction(meter), timeToRealTime(elem.time));
11466
- currentChords.push({
11467
- chord: lastChord,
11468
- beat: barBeat,
11469
- start: timeToRealTime(elem.time)
11470
- });
11471
- }
11472
- }
11473
- return firstChord;
11474
- }
11475
11604
  function findNoteModifications(elem, velocity) {
11476
11605
  var ret = {};
11477
11606
  if (elem.decoration) {
@@ -11656,9 +11785,10 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11656
11785
  // If there are guitar chords, then they are put in a separate track, but they have the same format.
11657
11786
  //
11658
11787
 
11659
- var trackStartingIndex = currentTrack.length;
11788
+ //var trackStartingIndex = currentTrack.length;
11789
+
11660
11790
  var velocity = processVolume(timeToRealTime(elem.time), voiceOff);
11661
- var setChordTrack = processChord(elem);
11791
+ chordTrack.processChord(elem);
11662
11792
 
11663
11793
  // if there are grace notes, then also play them.
11664
11794
  // I'm not sure there is an exact rule for the length of the notes. My rule, unless I find
@@ -11710,15 +11840,7 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11710
11840
  // TODO-PER: Can also make a different sound on style=x and style=harmonic
11711
11841
  var ePitches = elem.pitches;
11712
11842
  if (elem.style === "rhythm") {
11713
- hasRhythmHead = true;
11714
- if (lastChord && lastChord.chick) {
11715
- ePitches = [];
11716
- for (var i2 = 0; i2 < lastChord.chick.length; i2++) {
11717
- var note2 = parseCommon.clone(elem.pitches[0]);
11718
- note2.actualPitch = lastChord.chick[i2];
11719
- ePitches.push(note2);
11720
- }
11721
- }
11843
+ ePitches = chordTrack.setRhythmHead(true, elem);
11722
11844
  }
11723
11845
  if (elem.elem) elem.elem.midiPitches = [];
11724
11846
  for (var i = 0; i < ePitches.length; i++) {
@@ -11770,7 +11892,6 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11770
11892
  }
11771
11893
  var realDur = getRealDuration(elem);
11772
11894
  lastEventTime = Math.max(lastEventTime, timeToRealTime(elem.time) + durationRounded(realDur));
11773
- return setChordTrack;
11774
11895
  }
11775
11896
  function getRealDuration(elem) {
11776
11897
  if (elem.pitches && elem.pitches.length > 0 && elem.pitches[0]) return elem.pitches[0].duration;
@@ -11919,349 +12040,6 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11919
12040
  if (pitch < 0) pitch += 7;
11920
12041
  return pitch;
11921
12042
  }
11922
- var basses = {
11923
- 'A': 33,
11924
- 'B': 35,
11925
- 'C': 36,
11926
- 'D': 38,
11927
- 'E': 40,
11928
- 'F': 41,
11929
- 'G': 43
11930
- };
11931
- function interpretChord(name) {
11932
- // chords have the format:
11933
- // [root][acc][modifier][/][bass][acc]
11934
- // (The chord might be surrounded by parens. Just ignore them.)
11935
- // root must be present and must be from A-G.
11936
- // acc is optional and can be # or b
11937
- // The modifier can be a wide variety of things, like "maj7". As they are discovered, more are supported here.
11938
- // If there is a slash, then there is a bass note, which can be from A-G, with an optional acc.
11939
- // If the root is unrecognized, then "undefined" is returned and there is no chord.
11940
- // If the modifier is unrecognized, a major triad is returned.
11941
- // If the bass notes is unrecognized, it is ignored.
11942
- if (name.length === 0) return undefined;
11943
- if (name === 'break') return {
11944
- chick: []
11945
- };
11946
- var root = name.substring(0, 1);
11947
- if (root === '(') {
11948
- name = name.substring(1, name.length - 2);
11949
- if (name.length === 0) return undefined;
11950
- root = name.substring(0, 1);
11951
- }
11952
- var bass = basses[root];
11953
- if (!bass)
11954
- // If the bass note isn't listed, then this was an unknown root. Only A-G are accepted.
11955
- return undefined;
11956
- // Don't transpose the chords more than an octave.
11957
- var chordTranspose = transpose;
11958
- while (chordTranspose < -8) {
11959
- chordTranspose += 12;
11960
- }
11961
- while (chordTranspose > 8) {
11962
- chordTranspose -= 12;
11963
- }
11964
- bass += chordTranspose;
11965
- var bass2 = bass - 5; // The alternating bass is a 4th below
11966
- var chick;
11967
- if (name.length === 1) chick = chordNotes(bass, '');
11968
- var remaining = name.substring(1);
11969
- var acc = remaining.substring(0, 1);
11970
- if (acc === 'b' || acc === '♭') {
11971
- bass--;
11972
- bass2--;
11973
- remaining = remaining.substring(1);
11974
- } else if (acc === '#' || acc === '♯') {
11975
- bass++;
11976
- bass2++;
11977
- remaining = remaining.substring(1);
11978
- }
11979
- var arr = remaining.split('/');
11980
- chick = chordNotes(bass, arr[0]);
11981
- // If the 5th is altered then the bass is altered. Normally the bass is 7 from the root, so adjust if it isn't.
11982
- if (chick.length >= 3) {
11983
- var fifth = chick[2] - chick[0];
11984
- bass2 = bass2 + fifth - 7;
11985
- }
11986
- if (arr.length === 2) {
11987
- var explicitBass = basses[arr[1].substring(0, 1)];
11988
- if (explicitBass) {
11989
- var bassAcc = arr[1].substring(1);
11990
- var bassShift = {
11991
- '#': 1,
11992
- '♯': 1,
11993
- 'b': -1,
11994
- '♭': -1
11995
- }[bassAcc] || 0;
11996
- bass = basses[arr[1].substring(0, 1)] + bassShift + chordTranspose;
11997
- bass2 = bass;
11998
- }
11999
- }
12000
- return {
12001
- boom: bass,
12002
- boom2: bass2,
12003
- chick: chick
12004
- };
12005
- }
12006
- var chordIntervals = {
12007
- // diminished (all flat 5 chords)
12008
- 'dim': [0, 3, 6],
12009
- '°': [0, 3, 6],
12010
- '˚': [0, 3, 6],
12011
- 'dim7': [0, 3, 6, 9],
12012
- '°7': [0, 3, 6, 9],
12013
- '˚7': [0, 3, 6, 9],
12014
- 'ø7': [0, 3, 6, 10],
12015
- 'm7(b5)': [0, 3, 6, 10],
12016
- 'm7b5': [0, 3, 6, 10],
12017
- 'm7♭5': [0, 3, 6, 10],
12018
- '-7(b5)': [0, 3, 6, 10],
12019
- '-7b5': [0, 3, 6, 10],
12020
- '7b5': [0, 4, 6, 10],
12021
- '7(b5)': [0, 4, 6, 10],
12022
- '7♭5': [0, 4, 6, 10],
12023
- '7(b9,b5)': [0, 4, 6, 10, 13],
12024
- '7b9,b5': [0, 4, 6, 10, 13],
12025
- '7(#9,b5)': [0, 4, 6, 10, 15],
12026
- '7#9b5': [0, 4, 6, 10, 15],
12027
- 'maj7(b5)': [0, 4, 6, 11],
12028
- 'maj7b5': [0, 4, 6, 11],
12029
- '13(b5)': [0, 4, 6, 10, 14, 21],
12030
- '13b5': [0, 4, 6, 10, 14, 21],
12031
- // minor (all normal 5, minor 3 chords)
12032
- 'm': [0, 3, 7],
12033
- '-': [0, 3, 7],
12034
- 'm6': [0, 3, 7, 9],
12035
- '-6': [0, 3, 7, 9],
12036
- 'm7': [0, 3, 7, 10],
12037
- '-7': [0, 3, 7, 10],
12038
- '-(b6)': [0, 3, 7, 8],
12039
- '-b6': [0, 3, 7, 8],
12040
- '-6/9': [0, 3, 7, 9, 14],
12041
- '-7(b9)': [0, 3, 7, 10, 13],
12042
- '-7b9': [0, 3, 7, 10, 13],
12043
- '-maj7': [0, 3, 7, 11],
12044
- '-9+7': [0, 3, 7, 11, 13],
12045
- '-11': [0, 3, 7, 11, 14, 17],
12046
- 'm11': [0, 3, 7, 11, 14, 17],
12047
- '-maj9': [0, 3, 7, 11, 14],
12048
- '-∆9': [0, 3, 7, 11, 14],
12049
- 'mM9': [0, 3, 7, 11, 14],
12050
- // major (all normal 5, major 3 chords)
12051
- 'M': [0, 4, 7],
12052
- '6': [0, 4, 7, 9],
12053
- '6/9': [0, 4, 7, 9, 14],
12054
- '6add9': [0, 4, 7, 9, 14],
12055
- '69': [0, 4, 7, 9, 14],
12056
- '7': [0, 4, 7, 10],
12057
- '9': [0, 4, 7, 10, 14],
12058
- '11': [0, 7, 10, 14, 17],
12059
- '13': [0, 4, 7, 10, 14, 21],
12060
- '7b9': [0, 4, 7, 10, 13],
12061
- '7♭9': [0, 4, 7, 10, 13],
12062
- '7(b9)': [0, 4, 7, 10, 13],
12063
- '7(#9)': [0, 4, 7, 10, 15],
12064
- '7#9': [0, 4, 7, 10, 15],
12065
- '(13)': [0, 4, 7, 10, 14, 21],
12066
- '7(9,13)': [0, 4, 7, 10, 14, 21],
12067
- '7(#9,b13)': [0, 4, 7, 10, 15, 20],
12068
- '7(#11)': [0, 4, 7, 10, 14, 18],
12069
- '7#11': [0, 4, 7, 10, 14, 18],
12070
- '7(b13)': [0, 4, 7, 10, 20],
12071
- '7b13': [0, 4, 7, 10, 20],
12072
- '9(#11)': [0, 4, 7, 10, 14, 18],
12073
- '9#11': [0, 4, 7, 10, 14, 18],
12074
- '13(#11)': [0, 4, 7, 10, 18, 21],
12075
- '13#11': [0, 4, 7, 10, 18, 21],
12076
- 'maj7': [0, 4, 7, 11],
12077
- '∆7': [0, 4, 7, 11],
12078
- 'Δ7': [0, 4, 7, 11],
12079
- 'maj9': [0, 4, 7, 11, 14],
12080
- 'maj7(9)': [0, 4, 7, 11, 14],
12081
- 'maj7(11)': [0, 4, 7, 11, 17],
12082
- 'maj7(#11)': [0, 4, 7, 11, 18],
12083
- 'maj7(13)': [0, 4, 7, 14, 21],
12084
- 'maj7(9,13)': [0, 4, 7, 11, 14, 21],
12085
- '7sus4': [0, 5, 7, 10],
12086
- 'm7sus4': [0, 3, 7, 10, 17],
12087
- 'sus4': [0, 5, 7],
12088
- 'sus2': [0, 2, 7],
12089
- '7sus2': [0, 2, 7, 10],
12090
- '9sus4': [0, 5, 7, 10, 14],
12091
- '13sus4': [0, 5, 7, 10, 14, 21],
12092
- // augmented (all sharp 5 chords)
12093
- 'aug7': [0, 4, 8, 10],
12094
- '+7': [0, 4, 8, 10],
12095
- '+': [0, 4, 8],
12096
- '7#5': [0, 4, 8, 10],
12097
- '7♯5': [0, 4, 8, 10],
12098
- '7+5': [0, 4, 8, 10],
12099
- '9#5': [0, 4, 8, 10, 14],
12100
- '9♯5': [0, 4, 8, 10, 14],
12101
- '9+5': [0, 4, 8, 10, 14],
12102
- '-7(#5)': [0, 3, 8, 10],
12103
- '-7#5': [0, 3, 8, 10],
12104
- '7(#5)': [0, 4, 8, 10],
12105
- '7(b9,#5)': [0, 4, 8, 10, 13],
12106
- '7b9#5': [0, 4, 8, 10, 13],
12107
- 'maj7(#5)': [0, 4, 8, 11],
12108
- 'maj7#5': [0, 4, 8, 11],
12109
- 'maj7(#5,#11)': [0, 4, 8, 11, 18],
12110
- 'maj7#5#11': [0, 4, 8, 11, 18],
12111
- '9(#5)': [0, 4, 8, 10, 14],
12112
- '13(#5)': [0, 4, 8, 10, 14, 21],
12113
- '13#5': [0, 4, 8, 10, 14, 21]
12114
- };
12115
- function chordNotes(bass, modifier) {
12116
- var intervals = chordIntervals[modifier];
12117
- if (!intervals) {
12118
- if (modifier.slice(0, 2).toLowerCase() === 'ma' || modifier[0] === 'M') intervals = chordIntervals.M;else if (modifier[0] === 'm' || modifier[0] === '-') intervals = chordIntervals.m;else intervals = chordIntervals.M;
12119
- }
12120
- bass += 12; // the chord is an octave above the bass note.
12121
- var notes = [];
12122
- for (var i = 0; i < intervals.length; i++) {
12123
- notes.push(bass + intervals[i]);
12124
- }
12125
- return notes;
12126
- }
12127
- function writeBoom(boom, beatLength, volume, beat, noteLength) {
12128
- // undefined means there is a stop time.
12129
- if (boom !== undefined) chordTrack.push({
12130
- cmd: 'note',
12131
- pitch: boom,
12132
- volume: volume,
12133
- start: lastBarTime + beat * durationRounded(beatLength),
12134
- duration: durationRounded(noteLength),
12135
- gap: 0,
12136
- instrument: bassInstrument
12137
- });
12138
- }
12139
- function writeChick(chick, beatLength, volume, beat, noteLength) {
12140
- for (var c = 0; c < chick.length; c++) {
12141
- chordTrack.push({
12142
- cmd: 'note',
12143
- pitch: chick[c],
12144
- volume: volume,
12145
- start: lastBarTime + beat * durationRounded(beatLength),
12146
- duration: durationRounded(noteLength),
12147
- gap: 0,
12148
- instrument: chordInstrument
12149
- });
12150
- }
12151
- }
12152
- var rhythmPatterns = {
12153
- "2/2": ['boom', 'chick'],
12154
- "2/4": ['boom', 'chick'],
12155
- "3/4": ['boom', 'chick', 'chick'],
12156
- "4/4": ['boom', 'chick', 'boom2', 'chick'],
12157
- "5/4": ['boom', 'chick', 'chick', 'boom2', 'chick'],
12158
- "6/8": ['boom', '', 'chick', 'boom2', '', 'chick'],
12159
- "9/8": ['boom', '', 'chick', 'boom2', '', 'chick', 'boom2', '', 'chick'],
12160
- "12/8": ['boom', '', 'chick', 'boom2', '', 'chick', 'boom', '', 'chick', 'boom2', '', 'chick']
12161
- };
12162
- function resolveChords(startTime, endTime) {
12163
- var num = meter.num;
12164
- var den = meter.den;
12165
- var beatLength = 1 / den;
12166
- var noteLength = beatLength / 2;
12167
- var pattern = rhythmPatterns[num + '/' + den];
12168
- var thisMeasureLength = parseInt(num, 10) / parseInt(den, 10);
12169
- var portionOfAMeasure = thisMeasureLength - (endTime - startTime) / tempoChangeFactor;
12170
- if (Math.abs(portionOfAMeasure) < 0.00001) portionOfAMeasure = false;
12171
- if (!pattern || portionOfAMeasure) {
12172
- // If it is an unsupported meter, or this isn't a full bar, just chick on each beat.
12173
- pattern = [];
12174
- var beatsPresent = (endTime - startTime) / tempoChangeFactor / beatLength;
12175
- for (var p = 0; p < beatsPresent; p++) {
12176
- pattern.push("chick");
12177
- }
12178
- }
12179
- //console.log(startTime, pattern, currentChords, lastChord, portionOfAMeasure)
12180
-
12181
- if (currentChords.length === 0) {
12182
- // there wasn't a new chord this measure, so use the last chord declared.
12183
- currentChords.push({
12184
- beat: 0,
12185
- chord: lastChord
12186
- });
12187
- }
12188
- if (currentChords[0].beat !== 0 && lastChord) {
12189
- // this is the case where there is a chord declared in the measure, but not on its first beat.
12190
- if (chordLastBar) currentChords.unshift({
12191
- beat: 0,
12192
- chord: chordLastBar
12193
- });
12194
- }
12195
- if (currentChords.length === 1) {
12196
- for (var m = currentChords[0].beat; m < pattern.length; m++) {
12197
- if (!hasRhythmHead) {
12198
- switch (pattern[m]) {
12199
- case 'boom':
12200
- writeBoom(currentChords[0].chord.boom, beatLength, boomVolume, m, noteLength);
12201
- break;
12202
- case 'boom2':
12203
- writeBoom(currentChords[0].chord.boom2, beatLength, boomVolume, m, noteLength);
12204
- break;
12205
- case 'chick':
12206
- writeChick(currentChords[0].chord.chick, beatLength, chickVolume, m, noteLength);
12207
- break;
12208
- }
12209
- }
12210
- }
12211
- return;
12212
- }
12213
-
12214
- // If we are here it is because more than one chord was declared in the measure, so we have to sort out what chord goes where.
12215
-
12216
- // First, normalize the chords on beats.
12217
- var mult = beatLength === 0.125 ? 3 : 1; // If this is a compound meter then the beats in the currentChords is 1/3 of the true beat
12218
- var beats = {};
12219
- for (var i = 0; i < currentChords.length; i++) {
12220
- var cc = currentChords[i];
12221
- var b = Math.round(cc.beat * mult);
12222
- beats['' + b] = cc;
12223
- }
12224
-
12225
- // - If there is a chord on the second beat, play a chord for the first beat instead of a bass note.
12226
- // - Likewise, if there is a chord on the fourth beat of 4/4, play a chord on the third beat instead of a bass note.
12227
- for (var m2 = 0; m2 < pattern.length; m2++) {
12228
- var thisChord;
12229
- if (beats['' + m2]) thisChord = beats['' + m2];
12230
- var lastBoom;
12231
- if (!hasRhythmHead && thisChord) {
12232
- switch (pattern[m2]) {
12233
- case 'boom':
12234
- if (beats['' + (m2 + 1)])
12235
- // If there is not a chord change on the next beat, play a bass note.
12236
- writeChick(thisChord.chord.chick, beatLength, chickVolume, m2, noteLength);else {
12237
- writeBoom(thisChord.chord.boom, beatLength, boomVolume, m2, noteLength);
12238
- lastBoom = thisChord.chord.boom;
12239
- }
12240
- break;
12241
- case 'boom2':
12242
- if (beats['' + (m2 + 1)]) writeChick(thisChord.chord.chick, beatLength, chickVolume, m2, noteLength);else {
12243
- // If there is the same root as the last chord, use the alternating bass, otherwise play the root.
12244
- if (lastBoom === thisChord.chord.boom) {
12245
- writeBoom(thisChord.chord.boom2, beatLength, boomVolume, m2, noteLength);
12246
- lastBoom = undefined;
12247
- } else {
12248
- writeBoom(thisChord.chord.boom, beatLength, boomVolume, m2, noteLength);
12249
- lastBoom = thisChord.chord.boom;
12250
- }
12251
- }
12252
- break;
12253
- case 'chick':
12254
- writeChick(thisChord.chord.chick, beatLength, chickVolume, m2, noteLength);
12255
- break;
12256
- case '':
12257
- if (beats['' + m2])
12258
- // If there is an explicit chord on this beat, play it.
12259
- writeChick(thisChord.chord.chick, beatLength, chickVolume, m2, noteLength);
12260
- break;
12261
- }
12262
- }
12263
- }
12264
- }
12265
12043
  function normalizeDrumDefinition(params) {
12266
12044
  // Be very strict with the drum definition. If anything is not perfect,
12267
12045
  // just turn the drums off.
@@ -12754,6 +12532,7 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
12754
12532
  var drumBars = options.drumBars || 1;
12755
12533
  var drumIntro = options.drumIntro || 0;
12756
12534
  var drumOn = drumPattern !== "";
12535
+ var drumOffAfterIntro = !!options.drumOff;
12757
12536
  var style = []; // The note head style for each voice.
12758
12537
  var rhythmHeadThisBar = false; // Rhythm notation was detected.
12759
12538
  var crescendoSize = 50; // how much to increase or decrease volume when crescendo/diminuendo is encountered.
@@ -13207,13 +12986,13 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13207
12986
  break;
13208
12987
  case "gchordoff":
13209
12988
  voices[voiceNumber].push({
13210
- el_type: 'gchord',
12989
+ el_type: 'gchordOn',
13211
12990
  tacet: true
13212
12991
  });
13213
12992
  break;
13214
12993
  case "gchordon":
13215
12994
  voices[voiceNumber].push({
13216
- el_type: 'gchord',
12995
+ el_type: 'gchordOn',
13217
12996
  tacet: false
13218
12997
  });
13219
12998
  break;
@@ -13236,15 +13015,21 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13236
13015
  });
13237
13016
  break;
13238
13017
  case "vol":
13018
+ case "volinc":
13239
13019
  voices[voiceNumber].push({
13240
- el_type: 'vol',
13020
+ el_type: elem.cmd,
13241
13021
  volume: elem.params[0]
13242
13022
  });
13243
13023
  break;
13244
- case "volinc":
13024
+ case "swing":
13025
+ case "gchord":
13026
+ case "bassprog":
13027
+ case "chordprog":
13028
+ case "bassvol":
13029
+ case "chordvol":
13245
13030
  voices[voiceNumber].push({
13246
- el_type: 'volinc',
13247
- volume: elem.params[0]
13031
+ el_type: elem.cmd,
13032
+ param: elem.params[0]
13248
13033
  });
13249
13034
  break;
13250
13035
  default:
@@ -13286,16 +13071,19 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13286
13071
  if (voices[vv].length > insertPoint) {
13287
13072
  for (var w = 0; w < drumIntro; w++) {
13288
13073
  // If it is the last measure of intro, subtract the pickups.
13289
- if (pickups === 0 || w < drumIntro - 1) voices[vv].splice(insertPoint, 0, {
13290
- el_type: "note",
13291
- rest: {
13292
- type: "rest"
13293
- },
13294
- duration: measureLength
13295
- }, {
13296
- el_type: "bar"
13297
- });else {
13074
+ if (pickups === 0 || w < drumIntro - 1) {
13298
13075
  voices[vv].splice(insertPoint, 0, {
13076
+ el_type: "note",
13077
+ rest: {
13078
+ type: "rest"
13079
+ },
13080
+ duration: measureLength
13081
+ }, {
13082
+ el_type: "bar"
13083
+ });
13084
+ insertPoint += 2;
13085
+ } else {
13086
+ voices[vv].splice(insertPoint++, 0, {
13299
13087
  el_type: "note",
13300
13088
  rest: {
13301
13089
  type: "rest"
@@ -13304,6 +13092,19 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13304
13092
  });
13305
13093
  }
13306
13094
  }
13095
+ if (drumOffAfterIntro) {
13096
+ drumOn = false;
13097
+ voices[vv].splice(insertPoint++, 0, {
13098
+ el_type: 'drum',
13099
+ params: {
13100
+ pattern: drumPattern,
13101
+ bars: drumBars,
13102
+ intro: drumIntro,
13103
+ on: drumOn
13104
+ }
13105
+ });
13106
+ drumOffAfterIntro = false;
13107
+ }
13307
13108
  }
13308
13109
  }
13309
13110
  }
@@ -13499,6 +13300,589 @@ module.exports = centsToFactor;
13499
13300
 
13500
13301
  /***/ }),
13501
13302
 
13303
+ /***/ "./src/synth/chord-track.js":
13304
+ /*!**********************************!*\
13305
+ !*** ./src/synth/chord-track.js ***!
13306
+ \**********************************/
13307
+ /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
13308
+
13309
+ //
13310
+ // The algorithm for chords is:
13311
+ // - The chords are done in a separate track.
13312
+ // - If there are notes before the first chord, then put that much silence to start the track.
13313
+ // - The pattern of chord expression depends on the meter, and how many chords are in a measure.
13314
+ // - There is a possibility that a measure will have an incorrect number of beats, if that is the case, then
13315
+ // start the pattern anew on the next measure number.
13316
+ // - If a chord root is not A-G, then ignore it as if the chord wasn't there at all.
13317
+ // - If a chord modification isn't in our supported list, change it to a major triad.
13318
+ //
13319
+ // - There is a standard pattern of boom-chick for each time sig, or it can be overridden.
13320
+ // - For any unrecognized meter, play the full chord on each beat.
13321
+ //
13322
+ // - If there is a chord specified that is not on a beat, move it earlier to the previous beat, unless there is already a chord on that beat.
13323
+ // - Otherwise, move it later, unless there is already a chord on that beat.
13324
+ // - Otherwise, ignore it. (TODO-PER: expand this as more support is added.)
13325
+ //
13326
+ // If there is any note in the melody that has a rhythm head, then assume the melody controls the rhythm, so there is no chord added for that entire measure.
13327
+
13328
+ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/abc_common.js");
13329
+ var ChordTrack = function ChordTrack(numVoices, chordsOff, midiOptions, meter) {
13330
+ this.chordTrack = [];
13331
+ this.chordTrackFinished = false;
13332
+ this.chordChannel = numVoices; // first free channel for chords
13333
+ this.currentChords = [];
13334
+ this.lastChord;
13335
+ this.chordLastBar;
13336
+ this.chordsOff = !!chordsOff;
13337
+ this.gChordTacet = this.chordsOff;
13338
+ this.hasRhythmHead = false;
13339
+ this.transpose = 0;
13340
+ this.lastBarTime = 0;
13341
+ this.meter = meter;
13342
+ this.tempoChangeFactor = 1;
13343
+ this.bassInstrument = midiOptions.bassprog && midiOptions.bassprog.length === 1 ? midiOptions.bassprog[0] : 0;
13344
+ this.chordInstrument = midiOptions.chordprog && midiOptions.chordprog.length === 1 ? midiOptions.chordprog[0] : 0;
13345
+ this.boomVolume = midiOptions.bassvol && midiOptions.bassvol.length === 1 ? midiOptions.bassvol[0] : 64;
13346
+ this.chickVolume = midiOptions.chordvol && midiOptions.chordvol.length === 1 ? midiOptions.chordvol[0] : 48;
13347
+ this.overridePattern = midiOptions.gchord ? parseGChord(midiOptions.gchord[0]) : undefined;
13348
+ };
13349
+ ChordTrack.prototype.setMeter = function (meter) {
13350
+ this.meter = meter;
13351
+ };
13352
+ ChordTrack.prototype.setTempoChangeFactor = function (tempoChangeFactor) {
13353
+ this.tempoChangeFactor = tempoChangeFactor;
13354
+ };
13355
+ ChordTrack.prototype.setLastBarTime = function (lastBarTime) {
13356
+ this.lastBarTime = lastBarTime;
13357
+ };
13358
+ ChordTrack.prototype.setTranspose = function (transpose) {
13359
+ this.transpose = transpose;
13360
+ };
13361
+ ChordTrack.prototype.setRhythmHead = function (isRhythmHead, elem) {
13362
+ this.hasRhythmHead = isRhythmHead;
13363
+ var ePitches = [];
13364
+ if (isRhythmHead) {
13365
+ if (this.lastChord && this.lastChord.chick) {
13366
+ for (var i2 = 0; i2 < this.lastChord.chick.length; i2++) {
13367
+ var note2 = parseCommon.clone(elem.pitches[0]);
13368
+ note2.actualPitch = this.lastChord.chick[i2];
13369
+ ePitches.push(note2);
13370
+ }
13371
+ }
13372
+ }
13373
+ return ePitches;
13374
+ };
13375
+ ChordTrack.prototype.barEnd = function (element) {
13376
+ if (this.chordTrack.length > 0 && !this.chordTrackFinished) {
13377
+ this.resolveChords(this.lastBarTime, timeToRealTime(element.time));
13378
+ this.currentChords = [];
13379
+ }
13380
+ this.chordLastBar = this.lastChord;
13381
+ };
13382
+ ChordTrack.prototype.gChordOn = function (element) {
13383
+ if (!this.chordsOff) this.gChordTacet = element.tacet;
13384
+ };
13385
+ ChordTrack.prototype.paramChange = function (element) {
13386
+ switch (element.el_type) {
13387
+ case "gchord":
13388
+ this.overridePattern = parseGChord(element.param);
13389
+ break;
13390
+ case "bassprog":
13391
+ this.bassInstrument = element.param;
13392
+ break;
13393
+ case "chordprog":
13394
+ this.chordInstrument = element.param;
13395
+ break;
13396
+ case "bassvol":
13397
+ this.boomVolume = element.param;
13398
+ break;
13399
+ case "chordvol":
13400
+ this.chickVolume = element.param;
13401
+ break;
13402
+ default:
13403
+ console.log("unhandled midi param", element);
13404
+ }
13405
+ };
13406
+ ChordTrack.prototype.finish = function () {
13407
+ if (!this.chordTrackEmpty())
13408
+ // Don't do chords on more than one track, so turn off chord detection after we create it.
13409
+ this.chordTrackFinished = true;
13410
+ };
13411
+ ChordTrack.prototype.addTrack = function (tracks) {
13412
+ if (!this.chordTrackEmpty()) tracks.push(this.chordTrack);
13413
+ };
13414
+ ChordTrack.prototype.findChord = function (elem) {
13415
+ if (this.gChordTacet) return 'break';
13416
+
13417
+ // TODO-PER: Just using the first chord if there are more than one.
13418
+ if (this.chordTrackFinished || !elem.chord || elem.chord.length === 0) return null;
13419
+
13420
+ // Return the first annotation that is a regular chord: that is, it is in the default place or is a recognized "tacet" phrase.
13421
+ for (var i = 0; i < elem.chord.length; i++) {
13422
+ var ch = elem.chord[i];
13423
+ if (ch.position === 'default') return ch.name;
13424
+ if (this.breakSynonyms.indexOf(ch.name.toLowerCase()) >= 0) return 'break';
13425
+ }
13426
+ return null;
13427
+ };
13428
+ ChordTrack.prototype.interpretChord = function (name) {
13429
+ // chords have the format:
13430
+ // [root][acc][modifier][/][bass][acc]
13431
+ // (The chord might be surrounded by parens. Just ignore them.)
13432
+ // root must be present and must be from A-G.
13433
+ // acc is optional and can be # or b
13434
+ // The modifier can be a wide variety of things, like "maj7". As they are discovered, more are supported here.
13435
+ // If there is a slash, then there is a bass note, which can be from A-G, with an optional acc.
13436
+ // If the root is unrecognized, then "undefined" is returned and there is no chord.
13437
+ // If the modifier is unrecognized, a major triad is returned.
13438
+ // If the bass notes is unrecognized, it is ignored.
13439
+ if (name.length === 0) return undefined;
13440
+ if (name === 'break') return {
13441
+ chick: []
13442
+ };
13443
+ var root = name.substring(0, 1);
13444
+ if (root === '(') {
13445
+ name = name.substring(1, name.length - 2);
13446
+ if (name.length === 0) return undefined;
13447
+ root = name.substring(0, 1);
13448
+ }
13449
+ var bass = this.basses[root];
13450
+ if (!bass)
13451
+ // If the bass note isn't listed, then this was an unknown root. Only A-G are accepted.
13452
+ return undefined;
13453
+ // Don't transpose the chords more than an octave.
13454
+ var chordTranspose = this.transpose;
13455
+ while (chordTranspose < -8) {
13456
+ chordTranspose += 12;
13457
+ }
13458
+ while (chordTranspose > 8) {
13459
+ chordTranspose -= 12;
13460
+ }
13461
+ bass += chordTranspose;
13462
+ var bass2 = bass - 5; // The alternating bass is a 4th below
13463
+ var chick;
13464
+ if (name.length === 1) chick = this.chordNotes(bass, '');
13465
+ var remaining = name.substring(1);
13466
+ var acc = remaining.substring(0, 1);
13467
+ if (acc === 'b' || acc === '♭') {
13468
+ bass--;
13469
+ bass2--;
13470
+ remaining = remaining.substring(1);
13471
+ } else if (acc === '#' || acc === '♯') {
13472
+ bass++;
13473
+ bass2++;
13474
+ remaining = remaining.substring(1);
13475
+ }
13476
+ var arr = remaining.split('/');
13477
+ chick = this.chordNotes(bass, arr[0]);
13478
+ // If the 5th is altered then the bass is altered. Normally the bass is 7 from the root, so adjust if it isn't.
13479
+ if (chick.length >= 3) {
13480
+ var fifth = chick[2] - chick[0];
13481
+ bass2 = bass2 + fifth - 7;
13482
+ }
13483
+ if (arr.length === 2) {
13484
+ var explicitBass = this.basses[arr[1].substring(0, 1)];
13485
+ if (explicitBass) {
13486
+ var bassAcc = arr[1].substring(1);
13487
+ var bassShift = {
13488
+ '#': 1,
13489
+ '♯': 1,
13490
+ 'b': -1,
13491
+ '♭': -1
13492
+ }[bassAcc] || 0;
13493
+ bass = this.basses[arr[1].substring(0, 1)] + bassShift + chordTranspose;
13494
+ bass2 = bass;
13495
+ }
13496
+ }
13497
+ return {
13498
+ boom: bass,
13499
+ boom2: bass2,
13500
+ chick: chick
13501
+ };
13502
+ };
13503
+ ChordTrack.prototype.chordNotes = function (bass, modifier) {
13504
+ var intervals = this.chordIntervals[modifier];
13505
+ if (!intervals) {
13506
+ if (modifier.slice(0, 2).toLowerCase() === 'ma' || modifier[0] === 'M') intervals = this.chordIntervals.M;else if (modifier[0] === 'm' || modifier[0] === '-') intervals = this.chordIntervals.m;else intervals = this.chordIntervals.M;
13507
+ }
13508
+ bass += 12; // the chord is an octave above the bass note.
13509
+ var notes = [];
13510
+ for (var i = 0; i < intervals.length; i++) {
13511
+ notes.push(bass + intervals[i]);
13512
+ }
13513
+ return notes;
13514
+ };
13515
+ ChordTrack.prototype.writeNote = function (note, beatLength, volume, beat, noteLength, instrument) {
13516
+ // undefined means there is a stop time.
13517
+ if (note !== undefined) this.chordTrack.push({
13518
+ cmd: 'note',
13519
+ pitch: note,
13520
+ volume: volume,
13521
+ start: this.lastBarTime + beat * durationRounded(beatLength, this.tempoChangeFactor),
13522
+ duration: durationRounded(noteLength, this.tempoChangeFactor),
13523
+ gap: 0,
13524
+ instrument: instrument
13525
+ });
13526
+ };
13527
+ ChordTrack.prototype.chordTrackEmpty = function () {
13528
+ var isEmpty = true;
13529
+ for (var i = 0; i < this.chordTrack.length && isEmpty; i++) {
13530
+ if (this.chordTrack[i].cmd === 'note') isEmpty = false;
13531
+ }
13532
+ return isEmpty;
13533
+ };
13534
+ ChordTrack.prototype.resolveChords = function (startTime, endTime) {
13535
+ // If there is a rhythm head anywhere in the measure then don't add a separate rhythm track
13536
+ if (this.hasRhythmHead) return;
13537
+ var num = this.meter.num;
13538
+ var den = this.meter.den;
13539
+ var beatLength = 1 / den;
13540
+ var noteLength = beatLength / 2;
13541
+ var thisMeasureLength = parseInt(num, 10) / parseInt(den, 10);
13542
+ var portionOfAMeasure = thisMeasureLength - (endTime - startTime) / this.tempoChangeFactor;
13543
+ if (Math.abs(portionOfAMeasure) < 0.00001) portionOfAMeasure = 0;
13544
+
13545
+ // there wasn't a new chord this measure, so use the last chord declared.
13546
+ // also the case where there is a chord declared in the measure, but not on its first beat.
13547
+ if (this.currentChords.length === 0 || this.currentChords[0].beat !== 0) {
13548
+ this.currentChords.unshift({
13549
+ beat: 0,
13550
+ chord: this.chordLastBar
13551
+ });
13552
+ }
13553
+
13554
+ //console.log(this.currentChords)
13555
+ var currentChordsExpanded = expandCurrentChords(this.currentChords, 8 * num / den, beatLength);
13556
+ //console.log(currentChordsExpanded)
13557
+ var thisPattern = this.overridePattern ? this.overridePattern : this.rhythmPatterns[num + '/' + den];
13558
+ if (portionOfAMeasure) {
13559
+ thisPattern = [];
13560
+ var beatsPresent = (endTime - startTime) / this.tempoChangeFactor * 8;
13561
+ for (var p = 0; p < beatsPresent / 2; p += 2) {
13562
+ thisPattern.push("chick");
13563
+ thisPattern.push("");
13564
+ }
13565
+ }
13566
+ if (!thisPattern) {
13567
+ thisPattern = [];
13568
+ for (var p = 0; p < 8 * num / den / 2; p++) {
13569
+ thisPattern.push('chick');
13570
+ thisPattern.push("");
13571
+ }
13572
+ }
13573
+ var firstBoom = true;
13574
+ // If the pattern is overridden, it might be longer than the length of a measure. If so, then ignore the rest of it
13575
+ var minLength = Math.min(thisPattern.length, currentChordsExpanded.length);
13576
+ for (var p = 0; p < minLength; p++) {
13577
+ if (p > 0 && currentChordsExpanded[p - 1] && currentChordsExpanded[p] && currentChordsExpanded[p - 1].boom !== currentChordsExpanded[p].boom) firstBoom = true;
13578
+ var type = thisPattern[p];
13579
+ var isBoom = type.indexOf('boom') >= 0;
13580
+ // If we changed chords at a time when we're not expecting a bass note, then add an extra bass note in.
13581
+ var newBass = !isBoom && p !== 0 && (!currentChordsExpanded[p - 1] || currentChordsExpanded[p - 1].boom !== currentChordsExpanded[p].boom);
13582
+ var pitches = resolvePitch(currentChordsExpanded[p], type, firstBoom, newBass);
13583
+ if (isBoom) firstBoom = false;
13584
+ for (var oo = 0; oo < pitches.length; oo++) {
13585
+ this.writeNote(pitches[oo], 0.125, isBoom || newBass ? this.boomVolume : this.chickVolume, p, noteLength, isBoom || newBass ? this.bassInstrument : this.chordInstrument);
13586
+ if (newBass) newBass = false;else isBoom = false; // only the first note in a chord is a bass note. This handles the case where bass and chord are played at the same time.
13587
+ }
13588
+ }
13589
+
13590
+ return;
13591
+ };
13592
+ ChordTrack.prototype.processChord = function (elem) {
13593
+ if (this.chordTrackFinished) return;
13594
+ var chord = this.findChord(elem);
13595
+ if (chord) {
13596
+ var c = this.interpretChord(chord);
13597
+ // If this isn't a recognized chord, just completely ignore it.
13598
+ if (c) {
13599
+ // If we ever have a chord in this voice, then we add the chord track.
13600
+ // However, if there are chords on more than one voice, then just use the first voice.
13601
+ if (this.chordTrack.length === 0) {
13602
+ this.chordTrack.push({
13603
+ cmd: 'program',
13604
+ channel: this.chordChannel,
13605
+ instrument: this.chordInstrument
13606
+ });
13607
+ }
13608
+ this.lastChord = c;
13609
+ var barBeat = calcBeat(this.lastBarTime, timeToRealTime(elem.time));
13610
+ this.currentChords.push({
13611
+ chord: this.lastChord,
13612
+ beat: barBeat,
13613
+ start: timeToRealTime(elem.time)
13614
+ });
13615
+ }
13616
+ }
13617
+ };
13618
+ function resolvePitch(currentChord, type, firstBoom, newBass) {
13619
+ var ret = [];
13620
+ if (!currentChord) return ret;
13621
+ if (type.indexOf('boom') >= 0) ret.push(firstBoom ? currentChord.boom : currentChord.boom2);else if (newBass) ret.push(currentChord.boom);
13622
+ if (type.indexOf('chick') >= 0) {
13623
+ for (var i = 0; i < currentChord.chick.length; i++) {
13624
+ ret.push(currentChord.chick[i]);
13625
+ }
13626
+ }
13627
+ switch (type) {
13628
+ case 'DO':
13629
+ ret.push(currentChord.chick[0]);
13630
+ break;
13631
+ case 'MI':
13632
+ ret.push(currentChord.chick[1]);
13633
+ break;
13634
+ case 'SOL':
13635
+ ret.push(currentChord.chick[2]);
13636
+ break;
13637
+ case 'TI':
13638
+ currentChord.chick.length > 3 ? ret.push(currentChord.chick[2]) : ret.push(currentChord.chick[0] + 12);
13639
+ break;
13640
+ case 'TOP':
13641
+ currentChord.chick.length > 4 ? ret.push(currentChord.chick[2]) : ret.push(currentChord.chick[1] + 12);
13642
+ break;
13643
+ case 'do':
13644
+ ret.push(currentChord.chick[0] + 12);
13645
+ break;
13646
+ case 'mi':
13647
+ ret.push(currentChord.chick[1] + 12);
13648
+ break;
13649
+ case 'sol':
13650
+ ret.push(currentChord.chick[2] + 12);
13651
+ break;
13652
+ case 'ti':
13653
+ currentChord.chick.length > 3 ? ret.push(currentChord.chick[2] + 12) : ret.push(currentChord.chick[0] + 24);
13654
+ break;
13655
+ case 'top':
13656
+ currentChord.chick.length > 4 ? ret.push(currentChord.chick[2] + 12) : ret.push(currentChord.chick[1] + 24);
13657
+ break;
13658
+ }
13659
+ return ret;
13660
+ }
13661
+ function parseGChord(gchord) {
13662
+ // TODO-PER: The spec is more complicated than this but for now this will not try to do anything with error cases like the wrong number of beats.
13663
+ var pattern = [];
13664
+ for (var i = 0; i < gchord.length; i++) {
13665
+ var ch = gchord[i];
13666
+ switch (ch) {
13667
+ case 'z':
13668
+ pattern.push('');
13669
+ break;
13670
+ case '2':
13671
+ pattern.push('');
13672
+ break;
13673
+ // TODO-PER: This should extend the last note, but that's a small effect
13674
+ case 'c':
13675
+ pattern.push('chick');
13676
+ break;
13677
+ case 'b':
13678
+ pattern.push('boom&chick');
13679
+ break;
13680
+ case 'f':
13681
+ pattern.push('boom');
13682
+ break;
13683
+ case 'G':
13684
+ pattern.push('DO');
13685
+ break;
13686
+ case 'H':
13687
+ pattern.push('MI');
13688
+ break;
13689
+ case 'I':
13690
+ pattern.push('SOL');
13691
+ break;
13692
+ case 'J':
13693
+ pattern.push('TI');
13694
+ break;
13695
+ case 'K':
13696
+ pattern.push('TOP');
13697
+ break;
13698
+ case 'g':
13699
+ pattern.push('do');
13700
+ break;
13701
+ case 'h':
13702
+ pattern.push('mi');
13703
+ break;
13704
+ case 'i':
13705
+ pattern.push('sol');
13706
+ break;
13707
+ case 'j':
13708
+ pattern.push('ti');
13709
+ break;
13710
+ case 'k':
13711
+ pattern.push('top');
13712
+ break;
13713
+ }
13714
+ }
13715
+ return pattern;
13716
+ }
13717
+
13718
+ // This returns an array that has a chord for each 1/8th note position in the current measure
13719
+ function expandCurrentChords(currentChords, num8thNotes, beatLength) {
13720
+ beatLength = beatLength * 8; // this is expressed as a fraction, so that 0.25 is a quarter notes. We want it to be the number of 8th notes
13721
+ var chords = [];
13722
+ if (currentChords.length === 0) return chords;
13723
+ var currentChord = currentChords[0].chord;
13724
+ for (var i = 1; i < currentChords.length; i++) {
13725
+ var current = currentChords[i];
13726
+ while (chords.length < current.beat) {
13727
+ chords.push(currentChord);
13728
+ }
13729
+ currentChord = current.chord;
13730
+ }
13731
+ while (chords.length < num8thNotes) {
13732
+ chords.push(currentChord);
13733
+ }
13734
+ return chords;
13735
+ }
13736
+ function calcBeat(measureStart, currTime) {
13737
+ var distanceFromStart = currTime - measureStart;
13738
+ return distanceFromStart * 8;
13739
+ }
13740
+ ChordTrack.prototype.breakSynonyms = ['break', '(break)', 'no chord', 'n.c.', 'tacet'];
13741
+ ChordTrack.prototype.basses = {
13742
+ 'A': 33,
13743
+ 'B': 35,
13744
+ 'C': 36,
13745
+ 'D': 38,
13746
+ 'E': 40,
13747
+ 'F': 41,
13748
+ 'G': 43
13749
+ };
13750
+ ChordTrack.prototype.chordIntervals = {
13751
+ // diminished (all flat 5 chords)
13752
+ 'dim': [0, 3, 6],
13753
+ '°': [0, 3, 6],
13754
+ '˚': [0, 3, 6],
13755
+ 'dim7': [0, 3, 6, 9],
13756
+ '°7': [0, 3, 6, 9],
13757
+ '˚7': [0, 3, 6, 9],
13758
+ 'ø7': [0, 3, 6, 10],
13759
+ 'm7(b5)': [0, 3, 6, 10],
13760
+ 'm7b5': [0, 3, 6, 10],
13761
+ 'm7♭5': [0, 3, 6, 10],
13762
+ '-7(b5)': [0, 3, 6, 10],
13763
+ '-7b5': [0, 3, 6, 10],
13764
+ '7b5': [0, 4, 6, 10],
13765
+ '7(b5)': [0, 4, 6, 10],
13766
+ '7♭5': [0, 4, 6, 10],
13767
+ '7(b9,b5)': [0, 4, 6, 10, 13],
13768
+ '7b9,b5': [0, 4, 6, 10, 13],
13769
+ '7(#9,b5)': [0, 4, 6, 10, 15],
13770
+ '7#9b5': [0, 4, 6, 10, 15],
13771
+ 'maj7(b5)': [0, 4, 6, 11],
13772
+ 'maj7b5': [0, 4, 6, 11],
13773
+ '13(b5)': [0, 4, 6, 10, 14, 21],
13774
+ '13b5': [0, 4, 6, 10, 14, 21],
13775
+ // minor (all normal 5, minor 3 chords)
13776
+ 'm': [0, 3, 7],
13777
+ '-': [0, 3, 7],
13778
+ 'm6': [0, 3, 7, 9],
13779
+ '-6': [0, 3, 7, 9],
13780
+ 'm7': [0, 3, 7, 10],
13781
+ '-7': [0, 3, 7, 10],
13782
+ '-(b6)': [0, 3, 7, 8],
13783
+ '-b6': [0, 3, 7, 8],
13784
+ '-6/9': [0, 3, 7, 9, 14],
13785
+ '-7(b9)': [0, 3, 7, 10, 13],
13786
+ '-7b9': [0, 3, 7, 10, 13],
13787
+ '-maj7': [0, 3, 7, 11],
13788
+ '-9+7': [0, 3, 7, 11, 13],
13789
+ '-11': [0, 3, 7, 11, 14, 17],
13790
+ 'm11': [0, 3, 7, 11, 14, 17],
13791
+ '-maj9': [0, 3, 7, 11, 14],
13792
+ '-∆9': [0, 3, 7, 11, 14],
13793
+ 'mM9': [0, 3, 7, 11, 14],
13794
+ // major (all normal 5, major 3 chords)
13795
+ 'M': [0, 4, 7],
13796
+ '6': [0, 4, 7, 9],
13797
+ '6/9': [0, 4, 7, 9, 14],
13798
+ '6add9': [0, 4, 7, 9, 14],
13799
+ '69': [0, 4, 7, 9, 14],
13800
+ '7': [0, 4, 7, 10],
13801
+ '9': [0, 4, 7, 10, 14],
13802
+ '11': [0, 7, 10, 14, 17],
13803
+ '13': [0, 4, 7, 10, 14, 21],
13804
+ '7b9': [0, 4, 7, 10, 13],
13805
+ '7♭9': [0, 4, 7, 10, 13],
13806
+ '7(b9)': [0, 4, 7, 10, 13],
13807
+ '7(#9)': [0, 4, 7, 10, 15],
13808
+ '7#9': [0, 4, 7, 10, 15],
13809
+ '(13)': [0, 4, 7, 10, 14, 21],
13810
+ '7(9,13)': [0, 4, 7, 10, 14, 21],
13811
+ '7(#9,b13)': [0, 4, 7, 10, 15, 20],
13812
+ '7(#11)': [0, 4, 7, 10, 14, 18],
13813
+ '7#11': [0, 4, 7, 10, 14, 18],
13814
+ '7(b13)': [0, 4, 7, 10, 20],
13815
+ '7b13': [0, 4, 7, 10, 20],
13816
+ '9(#11)': [0, 4, 7, 10, 14, 18],
13817
+ '9#11': [0, 4, 7, 10, 14, 18],
13818
+ '13(#11)': [0, 4, 7, 10, 18, 21],
13819
+ '13#11': [0, 4, 7, 10, 18, 21],
13820
+ 'maj7': [0, 4, 7, 11],
13821
+ '∆7': [0, 4, 7, 11],
13822
+ 'Δ7': [0, 4, 7, 11],
13823
+ 'maj9': [0, 4, 7, 11, 14],
13824
+ 'maj7(9)': [0, 4, 7, 11, 14],
13825
+ 'maj7(11)': [0, 4, 7, 11, 17],
13826
+ 'maj7(#11)': [0, 4, 7, 11, 18],
13827
+ 'maj7(13)': [0, 4, 7, 14, 21],
13828
+ 'maj7(9,13)': [0, 4, 7, 11, 14, 21],
13829
+ '7sus4': [0, 5, 7, 10],
13830
+ 'm7sus4': [0, 3, 7, 10, 17],
13831
+ 'sus4': [0, 5, 7],
13832
+ 'sus2': [0, 2, 7],
13833
+ '7sus2': [0, 2, 7, 10],
13834
+ '9sus4': [0, 5, 7, 10, 14],
13835
+ '13sus4': [0, 5, 7, 10, 14, 21],
13836
+ // augmented (all sharp 5 chords)
13837
+ 'aug7': [0, 4, 8, 10],
13838
+ '+7': [0, 4, 8, 10],
13839
+ '+': [0, 4, 8],
13840
+ '7#5': [0, 4, 8, 10],
13841
+ '7♯5': [0, 4, 8, 10],
13842
+ '7+5': [0, 4, 8, 10],
13843
+ '9#5': [0, 4, 8, 10, 14],
13844
+ '9♯5': [0, 4, 8, 10, 14],
13845
+ '9+5': [0, 4, 8, 10, 14],
13846
+ '-7(#5)': [0, 3, 8, 10],
13847
+ '-7#5': [0, 3, 8, 10],
13848
+ '7(#5)': [0, 4, 8, 10],
13849
+ '7(b9,#5)': [0, 4, 8, 10, 13],
13850
+ '7b9#5': [0, 4, 8, 10, 13],
13851
+ 'maj7(#5)': [0, 4, 8, 11],
13852
+ 'maj7#5': [0, 4, 8, 11],
13853
+ 'maj7(#5,#11)': [0, 4, 8, 11, 18],
13854
+ 'maj7#5#11': [0, 4, 8, 11, 18],
13855
+ '9(#5)': [0, 4, 8, 10, 14],
13856
+ '13(#5)': [0, 4, 8, 10, 14, 21],
13857
+ '13#5': [0, 4, 8, 10, 14, 21]
13858
+ };
13859
+ ChordTrack.prototype.rhythmPatterns = {
13860
+ "2/2": ['boom', '', '', '', 'chick', '', '', ''],
13861
+ "3/2": ['boom', '', '', '', 'chick', '', '', '', 'chick', '', '', ''],
13862
+ "4/2": ['boom', '', '', '', 'chick', '', '', '', 'boom', '', '', '', 'chick', '', '', ''],
13863
+ "2/4": ['boom', '', 'chick', ''],
13864
+ "3/4": ['boom', '', 'chick', '', 'chick', ''],
13865
+ "4/4": ['boom', '', 'chick', '', 'boom', '', 'chick', ''],
13866
+ "5/4": ['boom', '', 'chick', '', 'chick', '', 'boom', '', 'chick', ''],
13867
+ "6/4": ['boom', '', 'chick', '', 'boom', '', 'chick', '', 'boom', '', 'chick', ''],
13868
+ "3/8": ['boom', '', 'chick'],
13869
+ "6/8": ['boom', '', 'chick', 'boom', '', 'chick'],
13870
+ "9/8": ['boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick'],
13871
+ "12/8": ['boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick']
13872
+ };
13873
+
13874
+ // TODO-PER: these are repeated in flattener. Can it be shared?
13875
+
13876
+ function timeToRealTime(time) {
13877
+ return time / 1000000;
13878
+ }
13879
+ function durationRounded(duration, tempoChangeFactor) {
13880
+ return Math.round(duration * tempoChangeFactor * 1000000) / 1000000;
13881
+ }
13882
+ module.exports = ChordTrack;
13883
+
13884
+ /***/ }),
13885
+
13502
13886
  /***/ "./src/synth/create-note-map.js":
13503
13887
  /*!**************************************!*\
13504
13888
  !*** ./src/synth/create-note-map.js ***!
@@ -13525,13 +13909,14 @@ var createNoteMap = function createNoteMap(sequence) {
13525
13909
  // ev contains:
13526
13910
  // {"cmd":"note","pitch":72,"volume":95,"start":0.125,"duration":0.25,"instrument":0,"gap":0}
13527
13911
  // where start and duration are in whole notes, gap is in 1/1920 of a second (i.e. MIDI ticks)
13912
+ var inst = ev.instrument !== undefined ? instrumentIndexToName[ev.instrument] : currentInstrument;
13528
13913
  if (ev.duration > 0) {
13529
13914
  var gap = ev.gap ? ev.gap : 0;
13530
13915
  var len = ev.duration;
13531
13916
  gap = Math.min(gap, len * 2 / 3);
13532
13917
  var obj = {
13533
13918
  pitch: ev.pitch,
13534
- instrument: currentInstrument,
13919
+ instrument: inst,
13535
13920
  start: Math.round(ev.start * 1000000) / 1000000,
13536
13921
  end: Math.round((ev.start + len - gap) * 1000000) / 1000000,
13537
13922
  volume: ev.volume
@@ -13794,12 +14179,13 @@ function CreateSynth() {
13794
14179
  self.audioBuffers = []; // cache of the buffers so starting play can be fast.
13795
14180
  self.duration = undefined; // the duration of the tune in seconds.
13796
14181
  self.isRunning = false; // whether there is currently a sound buffer running.
13797
- // self.options = undefined
14182
+ self.options = undefined;
14183
+ self.pickupLength = 0;
13798
14184
 
13799
14185
  // Load and cache all needed sounds
13800
14186
  self.init = function (options) {
13801
14187
  if (!options) options = {};
13802
- // self.options = options
14188
+ if (options.options) self.options = options.options;
13803
14189
  registerAudioContext(options.audioContext); // This works no matter what - if there is already an ac it is a nop; if the context is not passed in, then it creates one.
13804
14190
  var startTime = activeAudioContext().currentTime;
13805
14191
  self.debugCallback = options.debugCallback;
@@ -13868,12 +14254,14 @@ function CreateSynth() {
13868
14254
  self.flattened = options.visualObj.setUpAudio(params);
13869
14255
  var meter = options.visualObj.getMeterFraction();
13870
14256
  if (meter.den) self.meterSize = options.visualObj.getMeterFraction().num / options.visualObj.getMeterFraction().den;
14257
+ self.pickupLength = options.visualObj.getPickupLength();
13871
14258
  } else if (options.sequence) self.flattened = options.sequence;else return Promise.reject(new Error("Must pass in either a visualObj or a sequence"));
13872
14259
  self.millisecondsPerMeasure = options.millisecondsPerMeasure ? options.millisecondsPerMeasure : options.visualObj ? options.visualObj.millisecondsPerMeasure(self.flattened.tempo) : 1000;
13873
14260
  self.beatsPerMeasure = options.visualObj ? options.visualObj.getBeatsPerMeasure() : 4;
13874
14261
  self.sequenceCallback = params.sequenceCallback;
13875
14262
  self.callbackContext = params.callbackContext;
13876
14263
  self.onEnded = params.onEnded;
14264
+ self.meterFraction = options.visualObj.getMeterFraction();
13877
14265
  var allNotes = {};
13878
14266
  var cached = [];
13879
14267
  var errorNotes = [];
@@ -13884,14 +14272,15 @@ function CreateSynth() {
13884
14272
  if (event.pitch !== undefined) {
13885
14273
  var pitchNumber = event.pitch;
13886
14274
  var noteName = pitchToNoteName[pitchNumber];
14275
+ var inst = event.instrument !== undefined ? instrumentIndexToName[event.instrument] : currentInstrument;
13887
14276
  if (noteName) {
13888
- if (!allNotes[currentInstrument]) allNotes[currentInstrument] = {};
13889
- if (!soundsCache[currentInstrument] || !soundsCache[currentInstrument][noteName]) allNotes[currentInstrument][noteName] = true;else {
13890
- var label2 = currentInstrument + ":" + noteName;
14277
+ if (!allNotes[inst]) allNotes[inst] = {};
14278
+ if (!soundsCache[inst] || !soundsCache[inst][noteName]) allNotes[inst][noteName] = true;else {
14279
+ var label2 = inst + ":" + noteName;
13891
14280
  if (cached.indexOf(label2) < 0) cached.push(label2);
13892
14281
  }
13893
14282
  } else {
13894
- var label = currentInstrument + ":" + noteName;
14283
+ var label = inst + ":" + noteName;
13895
14284
  console.log("Can't find note: ", pitchNumber, label);
13896
14285
  if (errorNotes.indexOf(label) < 0) errorNotes.push(label);
13897
14286
  }
@@ -14032,8 +14421,7 @@ function CreateSynth() {
14032
14421
  // There might be a previous run that needs to be turned off.
14033
14422
  self.stop();
14034
14423
  var noteMapTracks = createNoteMap(self.flattened);
14035
- // if (self.options.swing)
14036
- // addSwing(noteMapTracks, self.options.swing, self.beatsPerMeasure)
14424
+ if (self.options.swing) addSwing(noteMapTracks, self.options.swing, self.meterFraction, self.pickupLength);
14037
14425
  if (self.sequenceCallback) self.sequenceCallback(noteMapTracks, self.callbackContext);
14038
14426
  var panDistances = setPan(noteMapTracks.length, self.pan);
14039
14427
 
@@ -14246,41 +14634,65 @@ function CreateSynth() {
14246
14634
  };
14247
14635
  }
14248
14636
  };
14249
-
14250
- // // this is a first attempt at adding a little bit of swing to the output, but the algorithm isn't correct.
14251
- // function addSwing(noteMapTracks, swing, beatsPerMeasure) {
14252
- // console.log("addSwing", noteMapTracks, swing, beatsPerMeasure)
14253
- // // Swing should be between -0.9 and 0.9. Make sure the input is between them.
14254
- // // Then that is the percentage to add to the first beat, so a negative number moves the second beat earlier.
14255
- // // A value of zero is the same as no swing at all.
14256
- // // This only works when there are an even number of beats in a measure.
14257
- // if (beatsPerMeasure % 2 !== 0)
14258
- // return;
14259
- // swing = parseFloat(swing)
14260
- // if (isNaN(swing))
14261
- // return
14262
- // if (swing < -0.9)
14263
- // swing = -0.9
14264
- // if (swing > 0.9)
14265
- // swing = 0.9
14266
- // var beatLength = (1 / beatsPerMeasure)*2
14267
- // swing = beatLength * swing
14268
- // for (var t = 0; t < noteMapTracks.length; t++) {
14269
- // var track = noteMapTracks[t];
14270
- // for (var i = 0; i < track.length; i++) {
14271
- // var event = track[i];
14272
- // if (event.start % beatLength) {
14273
- // // This is the off beat
14274
- // event.start += swing;
14275
- // } else {
14276
- // // This is the beat
14277
- // event.end += swing;
14278
- // }
14279
- // }
14280
- // }
14281
- // }
14637
+ function addSwing(noteMapTracks, swing, meterFraction, pickupLength) {
14638
+ // we can only swing in X/4 and X/8 meters.
14639
+ if (meterFraction.den != 4 && meterFraction.den != 8) return;
14640
+ swing = parseFloat(swing);
14641
+
14642
+ // 50 (or less) is no swing,
14643
+ if (isNaN(swing) || swing <= 50) return;
14644
+
14645
+ // 66 is triplet swing 2:1, and
14646
+ // 60 is swing with a ratio of 3:2.
14647
+ // 75 is the maximum swing where the first eight is played as a dotted eight and the second as a sixteenth.
14648
+ if (swing > 75) swing = 75;
14649
+
14650
+ // convert the swing percentage to a percentage of increase for the first half of the beat
14651
+ swing = swing / 50 - 1;
14652
+
14653
+ // The volume of the swung notes is increased by this factor
14654
+ // could be also in the settings. Try out values such 0.1, 0.2
14655
+ var volumeIncrease = 0.0;
14656
+
14657
+ // the beatLength in X/8 meters
14658
+ var beatLength = 0.25;
14659
+
14660
+ // in X/8 meters the 16s swing so the beatLength is halved
14661
+ if (meterFraction.den === 8) beatLength = beatLength / 2;
14662
+
14663
+ // duration of a half beat
14664
+ var halfbeatLength = beatLength / 2;
14665
+
14666
+ // the extra duration of the first swung notes and the delay of the second notes
14667
+ var swingDuration = halfbeatLength * swing;
14668
+ for (var t = 0; t < noteMapTracks.length; t++) {
14669
+ var track = noteMapTracks[t];
14670
+ for (var i = 0; i < track.length; i++) {
14671
+ var event = track[i];
14672
+ if (
14673
+ // is halfbeat
14674
+ (event.start - pickupLength) % halfbeatLength == 0 && (event.start - pickupLength) % beatLength != 0 && (
14675
+ // the previous note is on the beat or before OR there is no previous note
14676
+ i == 0 || track[i - 1].start <= track[i].start - halfbeatLength) && (
14677
+ // the next note is on the beat or after OR there is no next note
14678
+ i == track.length - 1 || track[i + 1].start >= track[i].start + halfbeatLength)) {
14679
+ var oldEventStart = event.start;
14680
+ event.start += swingDuration;
14681
+
14682
+ // Increase volume of swung notes
14683
+ event.volume *= 1 + volumeIncrease;
14684
+
14685
+ // if there is a previous note ending at the start of this note, extend its end
14686
+ // and decrease its volume
14687
+ if (i > 0 && track[i - 1].end == oldEventStart) {
14688
+ track[i - 1].end = event.start;
14689
+ track[i - 1].volume *= 1 - volumeIncrease;
14690
+ }
14691
+ }
14692
+ }
14693
+ }
14694
+ }
14282
14695
  }
14283
-
14284
14696
  module.exports = CreateSynth;
14285
14697
 
14286
14698
  /***/ }),
@@ -15046,6 +15458,8 @@ function SynthController() {
15046
15458
  self.isLoading = false;
15047
15459
  self.load = function (selector, cursorControl, visualOptions) {
15048
15460
  if (!visualOptions) visualOptions = {};
15461
+ if (visualOptions.displayPlay === undefined) visualOptions.displayPlay = true;
15462
+ if (visualOptions.displayProgress === undefined) visualOptions.displayProgress = true;
15049
15463
  self.control = new CreateSynthControl(selector, {
15050
15464
  loopHandler: visualOptions.displayLoop ? self.toggleLoop : undefined,
15051
15465
  restartHandler: visualOptions.displayRestart ? self.restart : undefined,
@@ -15063,7 +15477,7 @@ function SynthController() {
15063
15477
  self.setTune = function (visualObj, userAction, audioParams) {
15064
15478
  self.visualObj = visualObj;
15065
15479
  self.disable(false);
15066
- self.options = audioParams;
15480
+ self.options = audioParams ? audioParams : {};
15067
15481
  if (self.control) {
15068
15482
  self.pause();
15069
15483
  self.setProgress(0, 1);
@@ -15195,7 +15609,7 @@ function SynthController() {
15195
15609
  };
15196
15610
  self._randomAccess = function (ev) {
15197
15611
  var background = ev.target.classList.contains('abcjs-midi-progress-indicator') ? ev.target.parentNode : ev.target;
15198
- var percent = (ev.x - background.offsetLeft) / background.offsetWidth;
15612
+ var percent = (ev.x - background.getBoundingClientRect().left) / background.offsetWidth;
15199
15613
  if (percent < 0) percent = 0;
15200
15614
  if (percent > 1) percent = 1;
15201
15615
  self.seek(percent);
@@ -15337,87 +15751,6 @@ module.exports = SynthSequence;
15337
15751
 
15338
15752
  /***/ }),
15339
15753
 
15340
- /***/ "./src/tablatures/instruments/guitar/guitar-patterns.js":
15341
- /*!**************************************************************!*\
15342
- !*** ./src/tablatures/instruments/guitar/guitar-patterns.js ***!
15343
- \**************************************************************/
15344
- /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
15345
-
15346
- var StringPatterns = __webpack_require__(/*! ../string-patterns */ "./src/tablatures/instruments/string-patterns.js");
15347
- function GuitarPatterns(plugin) {
15348
- this.tuning = plugin._super.params.tuning;
15349
- if (!this.tuning) {
15350
- this.tuning = ['E,', 'A,', 'D', 'G', 'B', 'e'];
15351
- }
15352
- plugin.tuning = this.tuning;
15353
- this.strings = new StringPatterns(plugin);
15354
- }
15355
- GuitarPatterns.prototype.notesToNumber = function (notes, graces) {
15356
- var converter = this.strings;
15357
- return converter.notesToNumber(notes, graces);
15358
- };
15359
- GuitarPatterns.prototype.stringToPitch = function (stringNumber) {
15360
- var converter = this.strings;
15361
- return converter.stringToPitch(stringNumber);
15362
- };
15363
- module.exports = GuitarPatterns;
15364
-
15365
- /***/ }),
15366
-
15367
- /***/ "./src/tablatures/instruments/guitar/tab-guitar.js":
15368
- /*!*********************************************************!*\
15369
- !*** ./src/tablatures/instruments/guitar/tab-guitar.js ***!
15370
- \*********************************************************/
15371
- /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
15372
-
15373
- /*
15374
- Emit tab for Guitar staff
15375
- */
15376
- var StringTablature = __webpack_require__(/*! ../string-tablature */ "./src/tablatures/instruments/string-tablature.js");
15377
- var TabCommon = __webpack_require__(/*! ../../tab-common */ "./src/tablatures/tab-common.js");
15378
- var TabRenderer = __webpack_require__(/*! ../../tab-renderer */ "./src/tablatures/tab-renderer.js");
15379
- var GuitarPatterns = __webpack_require__(/*! ./guitar-patterns */ "./src/tablatures/instruments/guitar/guitar-patterns.js");
15380
-
15381
- /**
15382
- * upon init mainly store provided instances for later usage
15383
- * @param {*} abcTune the parsed tune AST tree
15384
- * @param {*} tuneNumber the parsed tune AST tree
15385
- * @param {*} params complementary args provided to Tablature Plugin
15386
- */
15387
- Plugin.prototype.init = function (abcTune, tuneNumber, params) {
15388
- var _super = new TabCommon(abcTune, tuneNumber, params);
15389
- this._super = _super;
15390
- this.abcTune = abcTune;
15391
- this.linePitch = 3;
15392
- this.nbLines = 6;
15393
- this.isTabBig = true;
15394
- this.capo = params.capo;
15395
- this.transpose = params.visualTranspose;
15396
- this.tablature = new StringTablature(this.nbLines, this.linePitch);
15397
- var semantics = new GuitarPatterns(this);
15398
- this.semantics = semantics;
15399
- };
15400
- Plugin.prototype.render = function (renderer, line, staffIndex) {
15401
- if (this._super.inError) return;
15402
- if (this.tablature.bypass(line)) return;
15403
- var rndrer = new TabRenderer(this, renderer, line, staffIndex);
15404
- rndrer.doLayout();
15405
- };
15406
- function Plugin() {}
15407
-
15408
- //
15409
- // Tablature plugin definition
15410
- //
15411
- var AbcGuitarTab = function AbcGuitarTab() {
15412
- return {
15413
- name: 'GuitarTab',
15414
- tablature: Plugin
15415
- };
15416
- };
15417
- module.exports = AbcGuitarTab;
15418
-
15419
- /***/ }),
15420
-
15421
15754
  /***/ "./src/tablatures/instruments/string-patterns.js":
15422
15755
  /*!*******************************************************!*\
15423
15756
  !*** ./src/tablatures/instruments/string-patterns.js ***!
@@ -15658,6 +15991,17 @@ StringPatterns.prototype.tabInfos = function (plugin) {
15658
15991
  return '';
15659
15992
  };
15660
15993
 
15994
+ // MAE 27 Nov 2023
15995
+ StringPatterns.prototype.suppress = function (plugin) {
15996
+ var _super = plugin._super;
15997
+ var suppress = _super.params.suppress;
15998
+ if (suppress) {
15999
+ return true;
16000
+ }
16001
+ return false;
16002
+ };
16003
+ // MAE 27 Nov 2023 End
16004
+
15661
16005
  /**
15662
16006
  * Common patterns for all string instruments
15663
16007
  * @param {} plugin
@@ -16032,16 +16376,43 @@ module.exports = TabNotes;
16032
16376
 
16033
16377
  /***/ }),
16034
16378
 
16035
- /***/ "./src/tablatures/instruments/violin/tab-violin.js":
16036
- /*!*********************************************************!*\
16037
- !*** ./src/tablatures/instruments/violin/tab-violin.js ***!
16038
- \*********************************************************/
16379
+ /***/ "./src/tablatures/instruments/tab-string-patterns.js":
16380
+ /*!***********************************************************!*\
16381
+ !*** ./src/tablatures/instruments/tab-string-patterns.js ***!
16382
+ \***********************************************************/
16383
+ /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
16384
+
16385
+ var StringPatterns = __webpack_require__(/*! ./string-patterns */ "./src/tablatures/instruments/string-patterns.js");
16386
+ function TabStringPatterns(plugin, defaultTuning) {
16387
+ this.tuning = plugin._super.params.tuning;
16388
+ if (!this.tuning) {
16389
+ this.tuning = defaultTuning;
16390
+ }
16391
+ plugin.tuning = this.tuning;
16392
+ this.strings = new StringPatterns(plugin);
16393
+ }
16394
+ TabStringPatterns.prototype.notesToNumber = function (notes, graces) {
16395
+ var converter = this.strings;
16396
+ return converter.notesToNumber(notes, graces);
16397
+ };
16398
+ TabStringPatterns.prototype.stringToPitch = function (stringNumber) {
16399
+ var converter = this.strings;
16400
+ return converter.stringToPitch(stringNumber);
16401
+ };
16402
+ module.exports = TabStringPatterns;
16403
+
16404
+ /***/ }),
16405
+
16406
+ /***/ "./src/tablatures/instruments/tab-string.js":
16407
+ /*!**************************************************!*\
16408
+ !*** ./src/tablatures/instruments/tab-string.js ***!
16409
+ \**************************************************/
16039
16410
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
16040
16411
 
16041
- var StringTablature = __webpack_require__(/*! ../string-tablature */ "./src/tablatures/instruments/string-tablature.js");
16042
- var TabCommon = __webpack_require__(/*! ../../tab-common */ "./src/tablatures/tab-common.js");
16043
- var TabRenderer = __webpack_require__(/*! ../../tab-renderer */ "./src/tablatures/tab-renderer.js");
16044
- var ViolinPatterns = __webpack_require__(/*! ./violin-patterns */ "./src/tablatures/instruments/violin/violin-patterns.js");
16412
+ var StringTablature = __webpack_require__(/*! ./string-tablature */ "./src/tablatures/instruments/string-tablature.js");
16413
+ var TabCommon = __webpack_require__(/*! ../tab-common */ "./src/tablatures/tab-common.js");
16414
+ var TabRenderer = __webpack_require__(/*! ../tab-renderer */ "./src/tablatures/tab-renderer.js");
16415
+ var TabStringPatterns = __webpack_require__(/*! ./tab-string-patterns */ "./src/tablatures/instruments/tab-string-patterns.js");
16045
16416
 
16046
16417
  /**
16047
16418
  * upon init mainly store provided instances for later usage
@@ -16049,17 +16420,19 @@ var ViolinPatterns = __webpack_require__(/*! ./violin-patterns */ "./src/tablatu
16049
16420
  * @param {*} tuneNumber the parsed tune AST tree
16050
16421
  * @param {*} params complementary args provided to Tablature Plugin
16051
16422
  */
16052
- Plugin.prototype.init = function (abcTune, tuneNumber, params) {
16423
+ Plugin.prototype.init = function (abcTune, tuneNumber, params, staffNumber, tabSettings) {
16053
16424
  var _super = new TabCommon(abcTune, tuneNumber, params);
16054
16425
  this.abcTune = abcTune;
16055
16426
  this._super = _super;
16056
16427
  this.linePitch = 3;
16057
- this.nbLines = 4;
16058
- this.isTabBig = false;
16428
+ this.nbLines = tabSettings.defaultTuning.length;
16429
+ this.isTabBig = tabSettings.isTabBig;
16430
+ this.tabSymbolOffset = tabSettings.tabSymbolOffset;
16059
16431
  this.capo = params.capo;
16060
16432
  this.transpose = params.visualTranspose;
16433
+ this.hideTabSymbol = params.hideTabSymbol;
16061
16434
  this.tablature = new StringTablature(this.nbLines, this.linePitch);
16062
- var semantics = new ViolinPatterns(this);
16435
+ var semantics = new TabStringPatterns(this, tabSettings.defaultTuning);
16063
16436
  this.semantics = semantics;
16064
16437
  };
16065
16438
  Plugin.prototype.render = function (renderer, line, staffIndex) {
@@ -16073,40 +16446,13 @@ function Plugin() {}
16073
16446
  //
16074
16447
  // Tablature plugin definition
16075
16448
  //
16076
- var AbcViolinTab = function AbcViolinTab() {
16449
+ var AbcStringTab = function AbcStringTab() {
16077
16450
  return {
16078
- name: 'ViolinTab',
16451
+ name: 'StringTab',
16079
16452
  tablature: Plugin
16080
16453
  };
16081
16454
  };
16082
- module.exports = AbcViolinTab;
16083
-
16084
- /***/ }),
16085
-
16086
- /***/ "./src/tablatures/instruments/violin/violin-patterns.js":
16087
- /*!**************************************************************!*\
16088
- !*** ./src/tablatures/instruments/violin/violin-patterns.js ***!
16089
- \**************************************************************/
16090
- /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
16091
-
16092
- var StringPatterns = __webpack_require__(/*! ../string-patterns */ "./src/tablatures/instruments/string-patterns.js");
16093
- function ViolinPatterns(plugin) {
16094
- this.tuning = plugin._super.params.tuning;
16095
- if (!this.tuning) {
16096
- this.tuning = ['G,', 'D', 'A', 'e'];
16097
- }
16098
- plugin.tuning = this.tuning;
16099
- this.strings = new StringPatterns(plugin);
16100
- }
16101
- ViolinPatterns.prototype.notesToNumber = function (notes, graces) {
16102
- var converter = this.strings;
16103
- return converter.notesToNumber(notes, graces);
16104
- };
16105
- ViolinPatterns.prototype.stringToPitch = function (stringNumber) {
16106
- var converter = this.strings;
16107
- return converter.stringToPitch(stringNumber);
16108
- };
16109
- module.exports = ViolinPatterns;
16455
+ module.exports = AbcStringTab;
16110
16456
 
16111
16457
  /***/ }),
16112
16458
 
@@ -16175,13 +16521,20 @@ function buildTabAbsolute(plugin, absX, relX) {
16175
16521
  icon: tabIcon,
16176
16522
  Ypos: tabYPos
16177
16523
  };
16178
- var tabAbsolute = new AbsoluteElement(element, 0, 0, "symbol", 0);
16179
- tabAbsolute.x = absX;
16180
- var tabRelative = new RelativeElement(tabIcon, 0, 0, 7.5, "tab");
16181
- tabRelative.x = relX;
16182
- tabAbsolute.children.push(tabRelative);
16183
- if (tabAbsolute.abcelem.el_type == 'tab') {
16184
- tabRelative.pitch = tabYPos;
16524
+
16525
+ // Offset the TAB symbol position if specified in the tab description
16526
+ tabYPos += plugin.tabSymbolOffset;
16527
+
16528
+ // For tablature like whistle tab where you want the TAB symbol hidden
16529
+ if (!plugin.hideTabSymbol) {
16530
+ var tabAbsolute = new AbsoluteElement(element, 0, 0, "symbol", 0);
16531
+ tabAbsolute.x = absX;
16532
+ var tabRelative = new RelativeElement(tabIcon, 0, 0, 7.5, "tab");
16533
+ tabRelative.x = relX;
16534
+ tabAbsolute.children.push(tabRelative);
16535
+ if (tabAbsolute.abcelem.el_type == 'tab') {
16536
+ tabRelative.pitch = tabYPos;
16537
+ }
16185
16538
  }
16186
16539
  return tabAbsolute;
16187
16540
  }
@@ -16498,12 +16851,23 @@ function buildTabName(self, dest) {
16498
16851
  var controller = self.renderer.controller;
16499
16852
  var textSize = controller.getTextSize;
16500
16853
  var tabName = stringSemantics.tabInfos(self.plugin);
16501
- var size = textSize.calc(tabName, 'tablabelfont', 'text instrumentname');
16502
- dest.tabNameInfos = {
16503
- textSize: size,
16504
- name: tabName
16505
- };
16506
- return size.height;
16854
+ var suppress = stringSemantics.suppress(self.plugin);
16855
+ var doDraw = true;
16856
+ if (suppress) {
16857
+ doDraw = false;
16858
+ }
16859
+ if (doDraw) {
16860
+ var size = textSize.calc(tabName, 'tablabelfont', 'text instrumentname');
16861
+ dest.tabNameInfos = {
16862
+ textSize: {
16863
+ height: size.height,
16864
+ width: size.width
16865
+ },
16866
+ name: tabName
16867
+ };
16868
+ return size.height;
16869
+ }
16870
+ return 0;
16507
16871
  }
16508
16872
 
16509
16873
  /**
@@ -16675,8 +17039,10 @@ TabRenderer.prototype.doLayout = function () {
16675
17039
  if (ii > 0) tabVoice.duplicate = true;
16676
17040
  var nameHeight = buildTabName(this, tabVoice) / spacing.STEP;
16677
17041
  nameHeight = Math.max(nameHeight, 1); // If there is no label for the tab line, then there needs to be a little padding
16678
- staffGroup.staffs[this.staffIndex].top += nameHeight;
16679
- staffGroup.height += nameHeight * spacing.STEP;
17042
+ // This was pushing down the top staff by the tab label height
17043
+ //staffGroup.staffs[this.staffIndex].top += nameHeight;
17044
+ staffGroup.staffs[this.staffIndex].top += 1;
17045
+ staffGroup.height += nameHeight;
16680
17046
  tabVoice.staff = staffGroupInfos;
16681
17047
  var tabVoiceIndex = voices.length;
16682
17048
  voices.splice(voices.length, 0, tabVoice);
@@ -16822,6 +17188,7 @@ var AbstractEngraver = function AbstractEngraver(getTextSize, tuneNumber, option
16822
17188
  this.percmap = options.percmap;
16823
17189
  this.initialClef = options.initialClef;
16824
17190
  this.jazzchords = !!options.jazzchords;
17191
+ this.accentAbove = !!options.accentAbove;
16825
17192
  this.germanAlphabet = !!options.germanAlphabet;
16826
17193
  this.reset();
16827
17194
  };
@@ -17618,7 +17985,10 @@ AbstractEngraver.prototype.createNote = function (elem, nostem, isSingleLineStaf
17618
17985
  roomtaken += this.addGraceNotes(elem, voice, abselem, notehead, this.stemHeight * this.voiceScale, this.isBagpipes, roomtaken);
17619
17986
  }
17620
17987
  if (elem.decoration) {
17621
- this.decoration.createDecoration(voice, elem.decoration, abselem.top, notehead ? notehead.w : 0, abselem, roomtaken, dir, abselem.bottom, elem.positioning, this.hasVocals);
17988
+ // TODO-PER: nostem is true if this is beamed. In that case we don't know where to place the decoration yet so just make a guess. This should be refactored to not place decorations until after the beams are determined.
17989
+ // This should probably be combined with moveDecorations()
17990
+ var bottom = nostem ? Math.min(-3, abselem.bottom - 6) : abselem.bottom;
17991
+ this.decoration.createDecoration(voice, elem.decoration, abselem.top, notehead ? notehead.w : 0, abselem, roomtaken, dir, bottom, elem.positioning, this.hasVocals, this.accentAbove);
17622
17992
  }
17623
17993
  if (elem.barNumber) {
17624
17994
  abselem.addFixed(new RelativeElement(elem.barNumber, -10, 0, 0, {
@@ -17790,7 +18160,7 @@ AbstractEngraver.prototype.createBarLine = function (voice, elem, isFirstStaff)
17790
18160
  abselem.addRight(anchor);
17791
18161
  }
17792
18162
  if (elem.decoration) {
17793
- this.decoration.createDecoration(voice, elem.decoration, 12, thick ? 3 : 1, abselem, 0, "down", 2, elem.positioning, this.hasVocals);
18163
+ this.decoration.createDecoration(voice, elem.decoration, 12, thick ? 3 : 1, abselem, 0, "down", 2, elem.positioning, this.hasVocals, this.accentAbove);
17794
18164
  }
17795
18165
  if (thick) {
17796
18166
  dx += 4; //3 hardcoded;
@@ -17860,100 +18230,121 @@ var addChord = function addChord(getTextSize, abselem, elem, roomTaken, roomTake
17860
18230
  for (var i = 0; i < elem.chord.length; i++) {
17861
18231
  var pos = elem.chord[i].position;
17862
18232
  var rel_position = elem.chord[i].rel_position;
17863
- var chords = elem.chord[i].name.split("\n");
17864
- for (var j = chords.length - 1; j >= 0; j--) {
17865
- // parse these in opposite order because we place them from bottom to top.
17866
- var chord = chords[j];
17867
- var x = 0;
17868
- var y;
17869
- var font;
17870
- var klass;
17871
- if (pos === "left" || pos === "right" || pos === "below" || pos === "above" || !!rel_position) {
17872
- font = 'annotationfont';
17873
- klass = "annotation";
17874
- } else {
17875
- font = 'gchordfont';
17876
- klass = "chord";
17877
- chord = translateChord(chord, jazzchords, germanAlphabet);
17878
- }
17879
- var attr = getTextSize.attr(font, klass);
17880
- var dim = getTextSize.calc(chord, font, klass);
17881
- var chordWidth = dim.width;
17882
- var chordHeight = dim.height / spacing.STEP;
17883
- switch (pos) {
17884
- case "left":
17885
- roomTaken += chordWidth + 7;
17886
- x = -roomTaken; // TODO-PER: This is just a guess from trial and error
17887
- y = elem.averagepitch;
17888
- abselem.addExtra(new RelativeElement(chord, x, chordWidth + 4, y, {
17889
- type: "text",
17890
- height: chordHeight,
17891
- dim: attr,
17892
- position: "left"
17893
- }));
17894
- break;
17895
- case "right":
17896
- roomTakenRight += 4;
17897
- x = roomTakenRight; // TODO-PER: This is just a guess from trial and error
17898
- y = elem.averagepitch;
17899
- abselem.addRight(new RelativeElement(chord, x, chordWidth + 4, y, {
17900
- type: "text",
17901
- height: chordHeight,
17902
- dim: attr,
17903
- position: "right"
17904
- }));
17905
- break;
17906
- case "below":
17907
- // setting the y-coordinate to undefined for now: it will be overwritten later on, after we figure out what the highest element on the line is.
17908
- abselem.addRight(new RelativeElement(chord, 0, 0, undefined, {
18233
+ var isAnnotation = pos === "left" || pos === "right" || pos === "below" || pos === "above" || !!rel_position;
18234
+ var font;
18235
+ var klass;
18236
+ if (isAnnotation) {
18237
+ font = 'annotationfont';
18238
+ klass = "abcjs-annotation";
18239
+ } else {
18240
+ font = 'gchordfont';
18241
+ klass = "abcjs-chord";
18242
+ }
18243
+ var attr = getTextSize.attr(font, klass);
18244
+ var name = elem.chord[i].name;
18245
+ var ret;
18246
+ //console.log("chord",name)
18247
+ if (typeof name === "string") {
18248
+ ret = chordString(name, pos, rel_position, isAnnotation, font, klass, attr, getTextSize, abselem, elem, roomTaken, roomTakenRight, noteheadWidth, jazzchords, germanAlphabet);
18249
+ roomTaken = ret.roomTaken;
18250
+ roomTakenRight = ret.roomTakenRight;
18251
+ } else {
18252
+ for (var j = 0; j < name.length; j++) {
18253
+ ret = chordString(name[j].text, pos, rel_position, isAnnotation, font, klass, attr, getTextSize, abselem, elem, roomTaken, roomTakenRight, noteheadWidth, jazzchords, germanAlphabet);
18254
+ roomTaken = ret.roomTaken;
18255
+ roomTakenRight = ret.roomTakenRight;
18256
+ }
18257
+ }
18258
+ }
18259
+ return {
18260
+ roomTaken: roomTaken,
18261
+ roomTakenRight: roomTakenRight
18262
+ };
18263
+ };
18264
+ function chordString(chordString, pos, rel_position, isAnnotation, font, klass, attr, getTextSize, abselem, elem, roomTaken, roomTakenRight, noteheadWidth, jazzchords, germanAlphabet) {
18265
+ var chords = chordString.split("\n");
18266
+ for (var j = chords.length - 1; j >= 0; j--) {
18267
+ // parse these in opposite order because we place them from bottom to top.
18268
+ var chord = chords[j];
18269
+ var x = 0;
18270
+ var y;
18271
+ if (!isAnnotation) chord = translateChord(chord, jazzchords, germanAlphabet);
18272
+ var dim = getTextSize.calc(chord, font, klass);
18273
+ var chordWidth = dim.width;
18274
+ var chordHeight = dim.height / spacing.STEP;
18275
+ switch (pos) {
18276
+ case "left":
18277
+ roomTaken += chordWidth + 7;
18278
+ x = -roomTaken; // TODO-PER: This is just a guess from trial and error
18279
+ y = elem.averagepitch;
18280
+ abselem.addExtra(new RelativeElement(chord, x, chordWidth + 4, y, {
18281
+ type: "text",
18282
+ height: chordHeight,
18283
+ dim: attr,
18284
+ position: "left"
18285
+ }));
18286
+ break;
18287
+ case "right":
18288
+ roomTakenRight += 4;
18289
+ x = roomTakenRight; // TODO-PER: This is just a guess from trial and error
18290
+ y = elem.averagepitch;
18291
+ abselem.addRight(new RelativeElement(chord, x, chordWidth + 4, y, {
18292
+ type: "text",
18293
+ height: chordHeight,
18294
+ dim: attr,
18295
+ position: "right"
18296
+ }));
18297
+ break;
18298
+ case "below":
18299
+ // setting the y-coordinate to undefined for now: it will be overwritten later on, after we figure out what the highest element on the line is.
18300
+ abselem.addRight(new RelativeElement(chord, 0, 0, undefined, {
18301
+ type: "text",
18302
+ position: "below",
18303
+ height: chordHeight,
18304
+ dim: attr,
18305
+ realWidth: chordWidth
18306
+ }));
18307
+ break;
18308
+ case "above":
18309
+ // setting the y-coordinate to undefined for now: it will be overwritten later on, after we figure out what the highest element on the line is.
18310
+ abselem.addRight(new RelativeElement(chord, 0, 0, undefined, {
18311
+ type: "text",
18312
+ position: "above",
18313
+ height: chordHeight,
18314
+ dim: attr,
18315
+ realWidth: chordWidth
18316
+ }));
18317
+ break;
18318
+ default:
18319
+ if (rel_position) {
18320
+ var relPositionY = rel_position.y + 3 * spacing.STEP; // TODO-PER: this is a fudge factor to make it line up with abcm2ps
18321
+ abselem.addRight(new RelativeElement(chord, x + rel_position.x, 0, elem.minpitch + relPositionY / spacing.STEP, {
18322
+ position: "relative",
17909
18323
  type: "text",
17910
- position: "below",
17911
18324
  height: chordHeight,
17912
- dim: attr,
17913
- realWidth: chordWidth
18325
+ dim: attr
17914
18326
  }));
17915
- break;
17916
- case "above":
18327
+ } else {
17917
18328
  // setting the y-coordinate to undefined for now: it will be overwritten later on, after we figure out what the highest element on the line is.
17918
- abselem.addRight(new RelativeElement(chord, 0, 0, undefined, {
17919
- type: "text",
17920
- position: "above",
17921
- height: chordHeight,
17922
- dim: attr,
17923
- realWidth: chordWidth
17924
- }));
17925
- break;
17926
- default:
17927
- if (rel_position) {
17928
- var relPositionY = rel_position.y + 3 * spacing.STEP; // TODO-PER: this is a fudge factor to make it line up with abcm2ps
17929
- abselem.addRight(new RelativeElement(chord, x + rel_position.x, 0, elem.minpitch + relPositionY / spacing.STEP, {
17930
- position: "relative",
17931
- type: "text",
18329
+ var pos2 = 'above';
18330
+ if (elem.positioning && elem.positioning.chordPosition) pos2 = elem.positioning.chordPosition;
18331
+ if (pos2 !== 'hidden') {
18332
+ abselem.addCentered(new RelativeElement(chord, noteheadWidth / 2, chordWidth, undefined, {
18333
+ type: "chord",
18334
+ position: pos2,
17932
18335
  height: chordHeight,
17933
- dim: attr
18336
+ dim: attr,
18337
+ realWidth: chordWidth
17934
18338
  }));
17935
- } else {
17936
- // setting the y-coordinate to undefined for now: it will be overwritten later on, after we figure out what the highest element on the line is.
17937
- var pos2 = 'above';
17938
- if (elem.positioning && elem.positioning.chordPosition) pos2 = elem.positioning.chordPosition;
17939
- if (pos2 !== 'hidden') {
17940
- abselem.addCentered(new RelativeElement(chord, noteheadWidth / 2, chordWidth, undefined, {
17941
- type: "chord",
17942
- position: pos2,
17943
- height: chordHeight,
17944
- dim: attr,
17945
- realWidth: chordWidth
17946
- }));
17947
- }
17948
18339
  }
17949
- }
18340
+ }
17950
18341
  }
17951
18342
  }
17952
18343
  return {
17953
18344
  roomTaken: roomTaken,
17954
18345
  roomTakenRight: roomTakenRight
17955
18346
  };
17956
- };
18347
+ }
17957
18348
  module.exports = addChord;
17958
18349
 
17959
18350
  /***/ }),
@@ -17982,10 +18373,11 @@ function addTextIf(rows, params, getTextSize) {
17982
18373
  font: params.font,
17983
18374
  anchor: params.anchor,
17984
18375
  startChar: params.info.startChar,
17985
- endChar: params.info.endChar
18376
+ endChar: params.info.endChar,
18377
+ 'dominant-baseline': params['dominant-baseline']
17986
18378
  };
17987
18379
  if (params.absElemType) attr.absElemType = params.absElemType;
17988
- if (!params.inGroup) attr.klass = params.klass;
18380
+ if (!params.inGroup && params.klass) attr.klass = params.klass;
17989
18381
  if (params.name) attr.name = params.name;
17990
18382
  rows.push(attr);
17991
18383
  // If there are blank lines they won't be counted by getTextSize, so just get the height of one line and multiply
@@ -18441,10 +18833,10 @@ var Decoration = function Decoration() {
18441
18833
  this.minTop = 12; // TODO-PER: this is assuming a 5-line staff. Pass that info in.
18442
18834
  this.minBottom = 0;
18443
18835
  };
18444
- var closeDecoration = function closeDecoration(voice, decoration, pitch, width, abselem, roomtaken, dir, minPitch) {
18836
+ var closeDecoration = function closeDecoration(voice, decoration, pitch, width, abselem, roomtaken, dir, minPitch, accentAbove) {
18445
18837
  var yPos;
18446
18838
  for (var i = 0; i < decoration.length; i++) {
18447
- if (decoration[i] === "staccato" || decoration[i] === "tenuto" || decoration[i] === "accent") {
18839
+ if (decoration[i] === "staccato" || decoration[i] === "tenuto" || decoration[i] === "accent" && !accentAbove) {
18448
18840
  var symbol = "scripts." + decoration[i];
18449
18841
  if (decoration[i] === "accent") symbol = "scripts.sforzato";
18450
18842
  if (yPos === undefined) yPos = dir === "down" ? pitch + 2 : minPitch - 2;else yPos = dir === "down" ? yPos + 2 : yPos - 2;
@@ -18553,7 +18945,7 @@ var compoundDecoration = function compoundDecoration(decoration, pitch, width, a
18553
18945
  }
18554
18946
  }
18555
18947
  };
18556
- var stackedDecoration = function stackedDecoration(decoration, width, abselem, yPos, positioning, minTop, minBottom) {
18948
+ var stackedDecoration = function stackedDecoration(decoration, width, abselem, yPos, positioning, minTop, minBottom, accentAbove) {
18557
18949
  function incrementPlacement(placement, height) {
18558
18950
  if (placement === 'above') yPos.above += height;else yPos.below -= height;
18559
18951
  }
@@ -18591,7 +18983,8 @@ var stackedDecoration = function stackedDecoration(decoration, width, abselem, y
18591
18983
  y = placement === 'above' ? y + height / 2 : y - height / 2; // Center the element vertically.
18592
18984
  abselem.addFixedX(new RelativeElement(symbol, deltaX, glyphs.getSymbolWidth(symbol), y, {
18593
18985
  klass: 'ornament',
18594
- thickness: glyphs.symbolHeightInPitches(symbol)
18986
+ thickness: glyphs.symbolHeightInPitches(symbol),
18987
+ position: placement
18595
18988
  }));
18596
18989
  incrementPlacement(placement, height);
18597
18990
  }
@@ -18692,6 +19085,12 @@ var stackedDecoration = function stackedDecoration(decoration, width, abselem, y
18692
19085
  case "mark":
18693
19086
  abselem.klass = "mark";
18694
19087
  break;
19088
+ case "accent":
19089
+ if (accentAbove) {
19090
+ symbolDecoration("scripts.sforzato", positioning);
19091
+ hasOne = true;
19092
+ }
19093
+ break;
18695
19094
  }
18696
19095
  }
18697
19096
  return hasOne;
@@ -18766,7 +19165,7 @@ Decoration.prototype.dynamicDecoration = function (voice, decoration, abselem, p
18766
19165
  voice.addOther(new GlissandoElem(glissando.start, glissando.stop));
18767
19166
  }
18768
19167
  };
18769
- Decoration.prototype.createDecoration = function (voice, decoration, pitch, width, abselem, roomtaken, dir, minPitch, positioning, hasVocals) {
19168
+ Decoration.prototype.createDecoration = function (voice, decoration, pitch, width, abselem, roomtaken, dir, minPitch, positioning, hasVocals, accentAbove) {
18770
19169
  if (!positioning) positioning = {
18771
19170
  ornamentPosition: 'above',
18772
19171
  volumePosition: hasVocals ? 'above' : 'below',
@@ -18778,14 +19177,15 @@ Decoration.prototype.createDecoration = function (voice, decoration, pitch, widt
18778
19177
  compoundDecoration(decoration, pitch, width, abselem, dir);
18779
19178
 
18780
19179
  // treat staccato, accent, and tenuto first (may need to shift other markers)
18781
- var yPos = closeDecoration(voice, decoration, pitch, width, abselem, roomtaken, dir, minPitch);
19180
+ var yPos = closeDecoration(voice, decoration, pitch, width, abselem, roomtaken, dir, minPitch, accentAbove);
18782
19181
  // yPos is an object containing 'above' and 'below'. That is the placement of the next symbol on either side.
18783
19182
 
18784
19183
  yPos.above = Math.max(yPos.above, this.minTop);
18785
- var hasOne = stackedDecoration(decoration, width, abselem, yPos, positioning.ornamentPosition, this.minTop, this.minBottom);
18786
- if (hasOne) {
18787
- // abselem.top = Math.max(yPos.above + 3, abselem.top); // TODO-PER: Not sure why we need this fudge factor.
18788
- }
19184
+ yPos.below = Math.min(yPos.below, minPitch);
19185
+ var hasOne = stackedDecoration(decoration, width, abselem, yPos, positioning.ornamentPosition, this.minTop, minPitch, accentAbove);
19186
+ //if (hasOne) {
19187
+ // abselem.top = Math.max(yPos.above + 3, abselem.top); // TODO-PER: Not sure why we need this fudge factor.
19188
+ //}
18789
19189
  leftDecoration(decoration, abselem, roomtaken);
18790
19190
  };
18791
19191
  module.exports = Decoration;
@@ -19145,94 +19545,109 @@ module.exports = BeamElem;
19145
19545
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
19146
19546
 
19147
19547
  var addTextIf = __webpack_require__(/*! ../add-text-if */ "./src/write/creation/add-text-if.js");
19148
- function BottomText(metaText, width, isPrint, paddingLeft, spacing, getTextSize) {
19548
+ var richText = __webpack_require__(/*! ./rich-text */ "./src/write/creation/elements/rich-text.js");
19549
+ function BottomText(metaText, width, isPrint, paddingLeft, spacing, shouldAddClasses, getTextSize) {
19149
19550
  this.rows = [];
19150
- if (metaText.unalignedWords && metaText.unalignedWords.length > 0) this.unalignedWords(metaText.unalignedWords, paddingLeft, spacing, getTextSize);
19151
- this.extraText(metaText, paddingLeft, spacing, getTextSize);
19551
+ if (metaText.unalignedWords && metaText.unalignedWords.length > 0) this.unalignedWords(metaText.unalignedWords, paddingLeft, spacing, shouldAddClasses, getTextSize);
19552
+ this.extraText(metaText, paddingLeft, spacing, shouldAddClasses, getTextSize);
19152
19553
  if (metaText.footer && isPrint) this.footer(metaText.footer, width, paddingLeft, getTextSize);
19153
19554
  }
19154
- BottomText.prototype.unalignedWords = function (unalignedWords, paddingLeft, spacing, getTextSize) {
19155
- var klass = 'meta-bottom unaligned-words';
19555
+ BottomText.prototype.unalignedWords = function (unalignedWords, marginLeft, spacing, shouldAddClasses, getTextSize) {
19556
+ var klass = shouldAddClasses ? 'abcjs-unaligned-words' : '';
19156
19557
  var defFont = 'wordsfont';
19157
- this.rows.push({
19158
- startGroup: "unalignedWords",
19159
- klass: 'abcjs-meta-bottom abcjs-unaligned-words',
19160
- name: "words"
19161
- });
19162
19558
  var space = getTextSize.calc("i", defFont, klass);
19163
19559
  this.rows.push({
19164
19560
  move: spacing.words
19165
19561
  });
19166
- for (var j = 0; j < unalignedWords.length; j++) {
19167
- if (unalignedWords[j] === '') this.rows.push({
19168
- move: space.height
19169
- });else if (typeof unalignedWords[j] === 'string') {
19170
- addTextIf(this.rows, {
19171
- marginLeft: paddingLeft,
19172
- text: unalignedWords[j],
19562
+ addMultiLine(this.rows, '', unalignedWords, marginLeft, defFont, "unalignedWords", "unalignedWords", klass, "unalignedWords", spacing, shouldAddClasses, getTextSize);
19563
+ this.rows.push({
19564
+ move: space.height
19565
+ });
19566
+ };
19567
+ function addSingleLine(rows, preface, text, marginLeft, klass, shouldAddClasses, getTextSize) {
19568
+ if (text) {
19569
+ if (preface) {
19570
+ if (typeof text === 'string') text = preface + text;else text = [{
19571
+ text: preface
19572
+ }].concat(text);
19573
+ }
19574
+ klass = shouldAddClasses ? 'abcjs-extra-text ' + klass : '';
19575
+ richText(rows, text, 'historyfont', klass, "description", marginLeft, {
19576
+ absElemType: "extraText",
19577
+ anchor: 'start'
19578
+ }, getTextSize);
19579
+ }
19580
+ }
19581
+ function addMultiLine(rows, preface, content, marginLeft, defFont, absElemType, groupName, klass, name, spacing, shouldAddClasses, getTextSize) {
19582
+ if (content) {
19583
+ klass = shouldAddClasses ? 'abcjs-extra-text ' + klass : '';
19584
+ var size = getTextSize.calc("A", defFont, klass);
19585
+ if (typeof content === 'string') {
19586
+ if (preface) content = preface + "\n" + content;
19587
+ addTextIf(rows, {
19588
+ marginLeft: marginLeft,
19589
+ text: content,
19173
19590
  font: defFont,
19174
- klass: klass,
19175
- inGroup: true,
19176
- name: "words"
19591
+ absElemType: "extraText",
19592
+ name: name,
19593
+ 'dominant-baseline': 'middle',
19594
+ klass: klass
19177
19595
  }, getTextSize);
19596
+ //rows.push({move: size.height*3/4})
19178
19597
  } else {
19179
- var largestY = 0;
19180
- var offsetX = 0;
19181
- for (var k = 0; k < unalignedWords[j].length; k++) {
19182
- var thisWord = unalignedWords[j][k];
19183
- var font = thisWord.font ? thisWord.font : defFont;
19184
- this.rows.push({
19185
- left: paddingLeft + offsetX,
19186
- text: thisWord.text,
19187
- font: font,
19598
+ rows.push({
19599
+ startGroup: groupName,
19600
+ klass: klass,
19601
+ name: name
19602
+ });
19603
+ rows.push({
19604
+ move: spacing.info
19605
+ });
19606
+ if (preface) {
19607
+ addTextIf(rows, {
19608
+ marginLeft: marginLeft,
19609
+ text: preface,
19610
+ font: defFont,
19611
+ absElemType: "extraText",
19612
+ name: name,
19613
+ 'dominant-baseline': 'middle'
19614
+ }, getTextSize);
19615
+ rows.push({
19616
+ move: size.height * 3 / 4
19617
+ });
19618
+ }
19619
+ for (var j = 0; j < content.length; j++) {
19620
+ richText(rows, content[j], defFont, '', name, marginLeft, {
19188
19621
  anchor: 'start'
19622
+ }, getTextSize);
19623
+ // TODO-PER: Hack! the string and rich lines should have used up the same amount of space without this.
19624
+ if (j < content.length - 1 && typeof content[j] === 'string' && typeof content[j + 1] !== 'string') rows.push({
19625
+ move: size.height * 3 / 4
19189
19626
  });
19190
- var size = getTextSize.calc(thisWord.text, defFont, klass);
19191
- largestY = Math.max(largestY, size.height);
19192
- offsetX += size.width;
19193
- // If the phrase ends in a space, then that is not counted in the width, so we need to add that in ourselves.
19194
- if (thisWord.text[thisWord.text.length - 1] === ' ') {
19195
- offsetX += space.width;
19196
- }
19197
19627
  }
19198
- this.rows.push({
19199
- move: largestY
19628
+ rows.push({
19629
+ endGroup: groupName,
19630
+ absElemType: absElemType,
19631
+ startChar: -1,
19632
+ endChar: -1,
19633
+ name: name
19634
+ });
19635
+ rows.push({
19636
+ move: size.height
19200
19637
  });
19201
19638
  }
19202
19639
  }
19203
- this.rows.push({
19204
- move: space.height * 2
19205
- });
19206
- this.rows.push({
19207
- endGroup: "unalignedWords",
19208
- absElemType: "unalignedWords",
19209
- startChar: -1,
19210
- endChar: -1,
19211
- name: "unalignedWords"
19212
- });
19213
- };
19214
- BottomText.prototype.extraText = function (metaText, marginLeft, spacing, getTextSize) {
19215
- var extraText = "";
19216
- if (metaText.book) extraText += "Book: " + metaText.book + "\n";
19217
- if (metaText.source) extraText += "Source: " + metaText.source + "\n";
19218
- if (metaText.discography) extraText += "Discography: " + metaText.discography + "\n";
19219
- if (metaText.notes) extraText += "Notes: " + metaText.notes + "\n";
19220
- if (metaText.transcription) extraText += "Transcription: " + metaText.transcription + "\n";
19221
- if (metaText.history) extraText += "History: " + metaText.history + "\n";
19222
- if (metaText['abc-copyright']) extraText += "Copyright: " + metaText['abc-copyright'] + "\n";
19223
- if (metaText['abc-creator']) extraText += "Creator: " + metaText['abc-creator'] + "\n";
19224
- if (metaText['abc-edited-by']) extraText += "Edited By: " + metaText['abc-edited-by'] + "\n";
19225
- if (extraText.length > 0) {
19226
- addTextIf(this.rows, {
19227
- marginLeft: marginLeft,
19228
- text: extraText,
19229
- font: 'historyfont',
19230
- klass: 'meta-bottom extra-text',
19231
- marginTop: spacing.info,
19232
- absElemType: "extraText",
19233
- name: "description"
19234
- }, getTextSize);
19235
- }
19640
+ }
19641
+ BottomText.prototype.extraText = function (metaText, marginLeft, spacing, shouldAddClasses, getTextSize) {
19642
+ addSingleLine(this.rows, "Book: ", metaText.book, marginLeft, 'abcjs-book', shouldAddClasses, getTextSize);
19643
+ addSingleLine(this.rows, "Source: ", metaText.source, marginLeft, 'abcjs-source', shouldAddClasses, getTextSize);
19644
+ addSingleLine(this.rows, "Discography: ", metaText.discography, marginLeft, 'abcjs-discography', shouldAddClasses, getTextSize);
19645
+ addMultiLine(this.rows, 'Notes:', metaText.notes, marginLeft, 'historyfont', "extraText", "notes", 'abcjs-notes', "description", spacing, shouldAddClasses, getTextSize);
19646
+ addSingleLine(this.rows, "Transcription: ", metaText.transcription, marginLeft, 'abcjs-transcription', shouldAddClasses, getTextSize);
19647
+ addMultiLine(this.rows, "History:", metaText.history, marginLeft, 'historyfont', "extraText", "history", 'abcjs-history', "description", spacing, shouldAddClasses, getTextSize);
19648
+ addSingleLine(this.rows, "Copyright: ", metaText['abc-copyright'], marginLeft, 'abcjs-copyright', shouldAddClasses, getTextSize);
19649
+ addSingleLine(this.rows, "Creator: ", metaText['abc-creator'], marginLeft, 'abcjs-creator', shouldAddClasses, getTextSize);
19650
+ addSingleLine(this.rows, "Edited By: ", metaText['abc-edited-by'], marginLeft, 'abcjs-edited-by', shouldAddClasses, getTextSize);
19236
19651
  };
19237
19652
  BottomText.prototype.footer = function (footer, width, paddingLeft, getTextSize) {
19238
19653
  var klass = 'header meta-bottom';
@@ -19574,6 +19989,76 @@ module.exports = RelativeElement;
19574
19989
 
19575
19990
  /***/ }),
19576
19991
 
19992
+ /***/ "./src/write/creation/elements/rich-text.js":
19993
+ /*!**************************************************!*\
19994
+ !*** ./src/write/creation/elements/rich-text.js ***!
19995
+ \**************************************************/
19996
+ /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
19997
+
19998
+ var addTextIf = __webpack_require__(/*! ../add-text-if */ "./src/write/creation/add-text-if.js");
19999
+ function richText(rows, str, defFont, klass, name, paddingLeft, attr, getTextSize) {
20000
+ var space = getTextSize.calc("i", defFont, klass);
20001
+ if (str === '') {
20002
+ rows.push({
20003
+ move: space.height
20004
+ });
20005
+ } else {
20006
+ if (typeof str === 'string') {
20007
+ addTextIf(rows, {
20008
+ marginLeft: paddingLeft,
20009
+ text: str,
20010
+ font: defFont,
20011
+ klass: klass,
20012
+ marginTop: attr.marginTop,
20013
+ anchor: attr.anchor,
20014
+ absElemType: attr.absElemType,
20015
+ info: attr.info,
20016
+ name: name
20017
+ }, getTextSize);
20018
+ return;
20019
+ }
20020
+ if (attr.marginTop) rows.push({
20021
+ move: attr.marginTop
20022
+ });
20023
+ var largestY = 0;
20024
+ var gap = 0;
20025
+ var row = {
20026
+ left: paddingLeft,
20027
+ anchor: attr.anchor,
20028
+ phrases: []
20029
+ };
20030
+ if (klass) row.klass = klass;
20031
+ rows.push(row);
20032
+ for (var k = 0; k < str.length; k++) {
20033
+ var thisWord = str[k];
20034
+ var font = thisWord.font ? thisWord.font : getTextSize.attr(defFont, klass).font;
20035
+ var phrase = {
20036
+ content: thisWord.text
20037
+ };
20038
+ if (font) phrase.attrs = {
20039
+ "font-family": getTextSize.getFamily(font.face),
20040
+ "font-size": font.size,
20041
+ "font-weight": font.weight,
20042
+ "font-style": font.style,
20043
+ "font-decoration": font.decoration
20044
+ };
20045
+ //if (thisWord.text) {
20046
+ row.phrases.push(phrase);
20047
+ var size = getTextSize.calc(thisWord.text, font, klass);
20048
+ largestY = Math.max(largestY, size.height);
20049
+ if (thisWord.text[thisWord.text.length - 1] === ' ') {
20050
+ gap = space.width;
20051
+ }
20052
+ }
20053
+ rows.push({
20054
+ move: largestY
20055
+ });
20056
+ }
20057
+ }
20058
+ module.exports = richText;
20059
+
20060
+ /***/ }),
20061
+
19577
20062
  /***/ "./src/write/creation/elements/separator.js":
19578
20063
  /*!**************************************************!*\
19579
20064
  !*** ./src/write/creation/elements/separator.js ***!
@@ -19966,6 +20451,23 @@ TieElem.prototype.calcSlurY = function () {
19966
20451
  this.endY = midPoint;
19967
20452
  this.endX += Math.round(this.anchor2.w / 2); // When going to the middle of the stem, bump the line to the right a little bit to make it look right.
19968
20453
  } else this.endY = this.above && beamInterferes ? this.anchor2.highestVert : this.anchor2.pitch;
20454
+ if (this.anchor1.scalex === 1) {
20455
+ // Need a way to tell if this is a grace note - if so then keep the slur as close as possible. TODO-PER-HACK: this should be more declaratively determined.
20456
+ var hasBeam1 = !!this.anchor1.parent.beam;
20457
+ var hasBeam2 = !!this.anchor2.parent.beam;
20458
+ if (hasBeam1) {
20459
+ var isLastInBeam = this.anchor1.parent === this.anchor1.parent.beam.elems[this.anchor1.parent.beam.elems.length - 1];
20460
+ if (!isLastInBeam) {
20461
+ if (this.above) this.startY = this.anchor1.parent.fixed.t;else this.startY = this.anchor1.parent.fixed.b;
20462
+ }
20463
+ }
20464
+ if (hasBeam2) {
20465
+ var isFirstInBeam = this.anchor2.parent === this.anchor2.parent.beam.elems[0];
20466
+ if (!isFirstInBeam) {
20467
+ if (this.above) this.endY = this.anchor2.parent.fixed.t;else this.endY = this.anchor2.parent.fixed.b;
20468
+ }
20469
+ }
20470
+ }
19969
20471
  } else if (this.anchor1) {
19970
20472
  this.startY = this.endY = this.anchor1.pitch;
19971
20473
  } else if (this.anchor2) {
@@ -19998,7 +20500,8 @@ module.exports = TieElem;
19998
20500
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
19999
20501
 
20000
20502
  var addTextIf = __webpack_require__(/*! ../add-text-if */ "./src/write/creation/add-text-if.js");
20001
- function TopText(metaText, metaTextInfo, formatting, lines, width, isPrint, paddingLeft, spacing, getTextSize) {
20503
+ var richText = __webpack_require__(/*! ./rich-text */ "./src/write/creation/elements/rich-text.js");
20504
+ function TopText(metaText, metaTextInfo, formatting, lines, width, isPrint, paddingLeft, spacing, shouldAddClasses, getTextSize) {
20002
20505
  this.rows = [];
20003
20506
  if (metaText.header && isPrint) {
20004
20507
  // Note: whether there is a header or not doesn't change any other positioning, so this doesn't change the Y-coordinate.
@@ -20043,31 +20546,23 @@ function TopText(metaText, metaTextInfo, formatting, lines, width, isPrint, padd
20043
20546
  var tAnchor = formatting.titleleft ? 'start' : 'middle';
20044
20547
  var tLeft = formatting.titleleft ? paddingLeft : paddingLeft + width / 2;
20045
20548
  if (metaText.title) {
20046
- addTextIf(this.rows, {
20047
- marginLeft: tLeft,
20048
- text: metaText.title,
20049
- font: 'titlefont',
20050
- klass: 'title meta-top',
20549
+ var klass = shouldAddClasses ? 'abcjs-title' : '';
20550
+ richText(this.rows, metaText.title, "titlefont", klass, 'title', tLeft, {
20051
20551
  marginTop: spacing.title,
20052
20552
  anchor: tAnchor,
20053
20553
  absElemType: "title",
20054
- info: metaTextInfo.title,
20055
- name: "title"
20554
+ info: metaTextInfo.title
20056
20555
  }, getTextSize);
20057
20556
  }
20058
20557
  if (lines.length) {
20059
20558
  var index = 0;
20060
20559
  while (index < lines.length && lines[index].subtitle) {
20061
- addTextIf(this.rows, {
20062
- marginLeft: tLeft,
20063
- text: lines[index].subtitle.text,
20064
- font: 'subtitlefont',
20065
- klass: 'text meta-top subtitle',
20560
+ var klass = shouldAddClasses ? 'abcjs-text abcjs-subtitle' : '';
20561
+ richText(this.rows, lines[index].subtitle.text, "subtitlefont", klass, 'subtitle', tLeft, {
20066
20562
  marginTop: spacing.subtitle,
20067
20563
  anchor: tAnchor,
20068
20564
  absElemType: "subtitle",
20069
- info: lines[index].subtitle,
20070
- name: "subtitle"
20565
+ info: lines[index].subtitle
20071
20566
  }, getTextSize);
20072
20567
  index++;
20073
20568
  }
@@ -20078,54 +20573,68 @@ function TopText(metaText, metaTextInfo, formatting, lines, width, isPrint, padd
20078
20573
  });
20079
20574
  if (metaText.rhythm && metaText.rhythm.length > 0) {
20080
20575
  var noMove = !!(metaText.composer || metaText.origin);
20576
+ var klass = shouldAddClasses ? 'abcjs-rhythm' : '';
20081
20577
  addTextIf(this.rows, {
20082
20578
  marginLeft: paddingLeft,
20083
20579
  text: metaText.rhythm,
20084
20580
  font: 'infofont',
20085
- klass: 'meta-top rhythm',
20581
+ klass: klass,
20086
20582
  absElemType: "rhythm",
20087
20583
  noMove: noMove,
20088
20584
  info: metaTextInfo.rhythm,
20089
20585
  name: "rhythm"
20090
20586
  }, getTextSize);
20091
20587
  }
20092
- var composerLine = "";
20093
- if (metaText.composer) composerLine += metaText.composer;
20094
- if (metaText.origin) composerLine += ' (' + metaText.origin + ')';
20095
- if (composerLine.length > 0) {
20096
- addTextIf(this.rows, {
20097
- marginLeft: paddingLeft + width,
20098
- text: composerLine,
20099
- font: 'composerfont',
20100
- klass: 'meta-top composer',
20588
+ var hasSimpleComposerLine = true;
20589
+ if (metaText.composer && typeof metaText.composer !== 'string') hasSimpleComposerLine = false;
20590
+ if (metaText.origin && typeof metaText.origin !== 'string') hasSimpleComposerLine = false;
20591
+ var composerLine = metaText.composer ? metaText.composer : '';
20592
+ if (metaText.origin) {
20593
+ if (typeof composerLine === 'string' && typeof metaText.origin === 'string') composerLine += ' (' + metaText.origin + ')';else if (typeof composerLine === 'string' && typeof metaText.origin !== 'string') {
20594
+ composerLine = [{
20595
+ text: composerLine
20596
+ }];
20597
+ composerLine.push({
20598
+ text: " ("
20599
+ });
20600
+ composerLine = composerLine.concat(metaText.origin);
20601
+ composerLine.push({
20602
+ text: ")"
20603
+ });
20604
+ } else {
20605
+ composerLine.push({
20606
+ text: " ("
20607
+ });
20608
+ composerLine = composerLine.concat(metaText.origin);
20609
+ composerLine.push({
20610
+ text: ")"
20611
+ });
20612
+ }
20613
+ }
20614
+ if (composerLine) {
20615
+ var klass = shouldAddClasses ? 'abcjs-composer' : '';
20616
+ richText(this.rows, composerLine, 'composerfont', klass, "composer", paddingLeft + width, {
20101
20617
  anchor: "end",
20102
20618
  absElemType: "composer",
20103
20619
  info: metaTextInfo.composer,
20104
- name: "composer"
20620
+ ingroup: true
20105
20621
  }, getTextSize);
20106
20622
  }
20107
20623
  }
20108
20624
  if (metaText.author && metaText.author.length > 0) {
20109
- addTextIf(this.rows, {
20110
- marginLeft: paddingLeft + width,
20111
- text: metaText.author,
20112
- font: 'composerfont',
20113
- klass: 'meta-top author',
20625
+ var klass = shouldAddClasses ? 'abcjs-author' : '';
20626
+ richText(this.rows, metaText.author, 'composerfont', klass, "author", paddingLeft + width, {
20114
20627
  anchor: "end",
20115
20628
  absElemType: "author",
20116
- info: metaTextInfo.author,
20117
- name: "author"
20629
+ info: metaTextInfo.author
20118
20630
  }, getTextSize);
20119
20631
  }
20120
20632
  if (metaText.partOrder && metaText.partOrder.length > 0) {
20121
- addTextIf(this.rows, {
20122
- marginLeft: paddingLeft,
20123
- text: metaText.partOrder,
20124
- font: 'partsfont',
20125
- klass: 'meta-top part-order',
20633
+ var klass = shouldAddClasses ? 'abcjs-part-order' : '';
20634
+ richText(this.rows, metaText.partOrder, 'partsfont', klass, "part-order", paddingLeft, {
20126
20635
  absElemType: "partOrder",
20127
20636
  info: metaTextInfo.partOrder,
20128
- name: "part-order"
20637
+ anchor: 'start'
20129
20638
  }, getTextSize);
20130
20639
  }
20131
20640
  }
@@ -20671,7 +21180,7 @@ var glyphs = {
20671
21180
  h: 7.515
20672
21181
  },
20673
21182
  ',': {
20674
- d: [['M', 1.32, -3.36], ['c', 0.57, -0.15, 1.17, 0.03, 1.59, 0.45], ['c', 0.45, 0.45, 0.60, 0.96, 0.51, 1.89], ['c', -0.09, 1.23, -0.42, 2.46, -0.99, 3.93], ['c', -0.30, 0.72, -0.72, 1.62, -0.78, 1.68], ['c', -0.18, 0.21, -0.51, 0.18, -0.66, -0.06], ['c', -0.03, -0.06, -0.06, -0.15, -0.06, -0.18], ['c', 0.00, -0.06, 0.12, -0.33, 0.24, -0.63], ['c', 0.84, -1.80, 1.02, -2.61, 0.69, -3.24], ['c', -0.12, -0.24, -0.27, -0.36, -0.75, -0.60], ['c', -0.36, -0.15, -0.42, -0.21, -0.60, -0.39], ['c', -0.69, -0.69, -0.69, -1.71, 0.00, -2.40], ['c', 0.21, -0.21, 0.51, -0.39, 0.81, -0.45], ['z']],
21183
+ d: [['M', 1.85, -3.36], ['c', 0.57, -0.15, 1.17, 0.03, 1.59, 0.45], ['c', 0.45, 0.45, 0.60, 0.96, 0.51, 1.89], ['c', -0.09, 1.23, -0.42, 2.46, -0.99, 3.93], ['c', -0.30, 0.72, -0.72, 1.62, -0.78, 1.68], ['c', -0.18, 0.21, -0.51, 0.18, -0.66, -0.06], ['c', -0.03, -0.06, -0.06, -0.15, -0.06, -0.18], ['c', 0.00, -0.06, 0.12, -0.33, 0.24, -0.63], ['c', 0.84, -1.80, 1.02, -2.61, 0.69, -3.24], ['c', -0.12, -0.24, -0.27, -0.36, -0.75, -0.60], ['c', -0.36, -0.15, -0.42, -0.21, -0.60, -0.39], ['c', -0.69, -0.69, -0.69, -1.71, 0.00, -2.40], ['c', 0.21, -0.21, 0.51, -0.39, 0.81, -0.45], ['z']],
20675
21184
  w: 3.452,
20676
21185
  h: 8.143
20677
21186
  },
@@ -20940,7 +21449,10 @@ function drawAbsolute(renderer, params, bartop, selectables, staffPos) {
20940
21449
  drawTempo(renderer, child);
20941
21450
  break;
20942
21451
  default:
20943
- drawRelativeElement(renderer, child, bartop);
21452
+ var el = drawRelativeElement(renderer, child, bartop);
21453
+ if (child.type === "symbol" && child.c && child.c.indexOf('notehead') >= 0) {
21454
+ el.setAttribute('class', 'abcjs-notehead');
21455
+ }
20944
21456
  }
20945
21457
  }
20946
21458
  var klass = params.type;
@@ -21243,7 +21755,9 @@ var spacing = __webpack_require__(/*! ../helpers/spacing */ "./src/write/helpers
21243
21755
  var Selectables = __webpack_require__(/*! ./selectables */ "./src/write/draw/selectables.js");
21244
21756
  function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, selectTypes, tuneNumber, lineOffset) {
21245
21757
  var selectables = new Selectables(renderer.paper, selectTypes, tuneNumber);
21246
- renderer.paper.openGroup();
21758
+ var groupClasses = {};
21759
+ if (classes.shouldAddClasses) groupClasses.klass = "abcjs-meta-top";
21760
+ renderer.paper.openGroup(groupClasses);
21247
21761
  renderer.moveY(renderer.padding.top);
21248
21762
  nonMusic(renderer, abcTune.topText, selectables);
21249
21763
  renderer.paper.closeGroup();
@@ -21253,7 +21767,8 @@ function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, se
21253
21767
  classes.incrLine();
21254
21768
  var abcLine = abcTune.lines[line];
21255
21769
  if (abcLine.staff) {
21256
- renderer.paper.openGroup();
21770
+ if (classes.shouldAddClasses) groupClasses.klass = "abcjs-staff-wrapper abcjs-l" + classes.lineNumber;
21771
+ renderer.paper.openGroup(groupClasses);
21257
21772
  if (abcLine.vskip) {
21258
21773
  renderer.moveY(abcLine.vskip);
21259
21774
  }
@@ -21263,14 +21778,16 @@ function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, se
21263
21778
  staffgroups.push(staffgroup);
21264
21779
  renderer.paper.closeGroup();
21265
21780
  } else if (abcLine.nonMusic) {
21266
- renderer.paper.openGroup();
21781
+ if (classes.shouldAddClasses) groupClasses.klass = "abcjs-non-music";
21782
+ renderer.paper.openGroup(groupClasses);
21267
21783
  nonMusic(renderer, abcLine.nonMusic, selectables);
21268
21784
  renderer.paper.closeGroup();
21269
21785
  }
21270
21786
  }
21271
21787
  classes.reset();
21272
21788
  if (abcTune.bottomText && abcTune.bottomText.rows && abcTune.bottomText.rows.length > 0) {
21273
- renderer.paper.openGroup();
21789
+ if (classes.shouldAddClasses) groupClasses.klass = "abcjs-meta-bottom";
21790
+ renderer.paper.openGroup(groupClasses);
21274
21791
  renderer.moveY(24); // TODO-PER: Empirically discovered. What variable should this be?
21275
21792
  nonMusic(renderer, abcTune.bottomText, selectables);
21276
21793
  renderer.paper.closeGroup();
@@ -21554,12 +22071,14 @@ function nonMusic(renderer, obj, selectables) {
21554
22071
  renderer.absolutemoveY(row.absmove);
21555
22072
  } else if (row.move) {
21556
22073
  renderer.moveY(row.move);
21557
- } else if (row.text) {
22074
+ } else if (row.text || row.phrases) {
21558
22075
  var x = row.left ? row.left : 0;
21559
22076
  var el = renderText(renderer, {
21560
22077
  x: x,
21561
22078
  y: renderer.y,
21562
22079
  text: row.text,
22080
+ phrases: row.phrases,
22081
+ 'dominant-baseline': row['dominant-baseline'],
21563
22082
  type: row.font,
21564
22083
  klass: row.klass,
21565
22084
  name: row.name,
@@ -21841,7 +22360,7 @@ function drawRelativeElement(renderer, params, bartop) {
21841
22360
  case "tabNumber":
21842
22361
  var hAnchor = "middle";
21843
22362
  var tabFont = "tabnumberfont";
21844
- var tabClass = 'tab-number';
22363
+ var tabClass = 'abcjs-tab-number';
21845
22364
  if (params.isGrace) {
21846
22365
  tabFont = "tabgracefont";
21847
22366
  y += 2.5;
@@ -22548,7 +23067,6 @@ function drawTempo(renderer, params) {
22548
23067
  klass: 'abcjs-tempo',
22549
23068
  anchor: "start",
22550
23069
  noClass: true,
22551
- "dominant-baseline": "ideographic",
22552
23070
  name: "pre"
22553
23071
  }, true);
22554
23072
  size = renderer.controller.getTextSize.calc(params.tempo.preString, 'tempofont', 'tempo', text);
@@ -22608,6 +23126,13 @@ module.exports = drawTempo;
22608
23126
  var roundNumber = __webpack_require__(/*! ./round-number */ "./src/write/draw/round-number.js");
22609
23127
  function renderText(renderer, params, alreadyInGroup) {
22610
23128
  var y = params.y;
23129
+
23130
+ // TODO-PER: Probably need to merge the regular text and rich text better. At the least, rich text loses the font box.
23131
+ if (params.phrases) {
23132
+ //richTextLine = function (phrases, x, y, klass, anchor, target)
23133
+ var elem = renderer.paper.richTextLine(params.phrases, params.x, params.y, params.klass, params.anchor);
23134
+ return elem;
23135
+ }
22611
23136
  if (params.lane) {
22612
23137
  var laneMargin = params.dim.font.size * 0.25;
22613
23138
  y += (params.dim.font.size + laneMargin) * params.lane;
@@ -22618,6 +23143,7 @@ function renderText(renderer, params, alreadyInGroup) {
22618
23143
  hash.attr["class"] = params.klass;
22619
23144
  } else hash = renderer.controller.getFontAndAttr.calc(params.type, params.klass);
22620
23145
  if (params.anchor) hash.attr["text-anchor"] = params.anchor;
23146
+ if (params['dominant-baseline']) hash.attr["dominant-baseline"] = params['dominant-baseline'];
22621
23147
  hash.attr.x = params.x;
22622
23148
  hash.attr.y = y;
22623
23149
  if (!params.centerVertically) hash.attr.y += hash.font.size;
@@ -22991,6 +23517,7 @@ var GetFontAndAttr = __webpack_require__(/*! ./helpers/get-font-and-attr */ "./s
22991
23517
  var GetTextSize = __webpack_require__(/*! ./helpers/get-text-size */ "./src/write/helpers/get-text-size.js");
22992
23518
  var draw = __webpack_require__(/*! ./draw/draw */ "./src/write/draw/draw.js");
22993
23519
  var tablatures = __webpack_require__(/*! ../api/abc_tablatures */ "./src/api/abc_tablatures.js");
23520
+ var findSelectableElement = __webpack_require__(/*! ./interactive/find-selectable-element */ "./src/write/interactive/find-selectable-element.js");
22994
23521
 
22995
23522
  /**
22996
23523
  * @class
@@ -23006,6 +23533,7 @@ var tablatures = __webpack_require__(/*! ../api/abc_tablatures */ "./src/api/abc
23006
23533
  */
23007
23534
  var EngraverController = function EngraverController(paper, params) {
23008
23535
  params = params || {};
23536
+ this.findSelectableElement = findSelectableElement;
23009
23537
  this.oneSvgPerLine = params.oneSvgPerLine;
23010
23538
  this.selectionColor = params.selectionColor;
23011
23539
  this.dragColor = params.dragColor ? params.dragColor : params.selectionColor;
@@ -23014,6 +23542,7 @@ var EngraverController = function EngraverController(paper, params) {
23014
23542
  this.responsive = params.responsive;
23015
23543
  this.space = 3 * spacing.SPACE;
23016
23544
  this.initialClef = params.initialClef;
23545
+ this.timeBasedLayout = params.timeBasedLayout;
23017
23546
  this.expandToWidest = !!params.expandToWidest;
23018
23547
  this.scale = params.scale ? parseFloat(params.scale) : 0;
23019
23548
  this.classes = new Classes({
@@ -23036,6 +23565,7 @@ var EngraverController = function EngraverController(paper, params) {
23036
23565
  this.renderer.setPaddingOverride(params);
23037
23566
  if (params.showDebug) this.renderer.showDebug = params.showDebug;
23038
23567
  if (params.jazzchords) this.jazzchords = params.jazzchords;
23568
+ if (params.accentAbove) this.accentAbove = params.accentAbove;
23039
23569
  if (params.germanAlphabet) this.germanAlphabet = params.germanAlphabet;
23040
23570
  if (params.lineThickness) this.lineThickness = params.lineThickness;
23041
23571
  this.renderer.controller = this; // TODO-GD needed for highlighting
@@ -23087,11 +23617,12 @@ EngraverController.prototype.getMeasureWidths = function (abcTune) {
23087
23617
  this.reset();
23088
23618
  this.getFontAndAttr = new GetFontAndAttr(abcTune.formatting, this.classes);
23089
23619
  this.getTextSize = new GetTextSize(this.getFontAndAttr, this.renderer.paper);
23620
+ var origJazzChords = this.jazzchords;
23090
23621
  this.setupTune(abcTune, 0);
23091
23622
  this.constructTuneElements(abcTune);
23092
23623
  // layout() sets the x-coordinate of the abcTune element here:
23093
23624
  // abcTune.lines[0].staffGroup.voices[0].children[0].x
23094
- layout(this.renderer, abcTune, 0, this.space);
23625
+ layout(this.renderer, abcTune, 0, this.space, this.timeBasedLayout);
23095
23626
  var ret = [];
23096
23627
  var section;
23097
23628
  var needNewSection = true;
@@ -23130,11 +23661,13 @@ EngraverController.prototype.getMeasureWidths = function (abcTune) {
23130
23661
  //section.height += calcHeight(abcLine.staffGroup) * spacing.STEP;
23131
23662
  } else needNewSection = true;
23132
23663
  }
23664
+ this.jazzchords = origJazzChords;
23133
23665
  return ret;
23134
23666
  };
23135
23667
  EngraverController.prototype.setupTune = function (abcTune, tuneNumber) {
23136
23668
  this.classes.reset();
23137
23669
  if (abcTune.formatting.jazzchords !== undefined) this.jazzchords = abcTune.formatting.jazzchords;
23670
+ if (abcTune.formatting.accentAbove !== undefined) this.accentAbove = abcTune.formatting.accentAbove;
23138
23671
  this.renderer.newTune(abcTune);
23139
23672
  this.engraver = new AbstractEngraver(this.getTextSize, tuneNumber, {
23140
23673
  bagpipes: abcTune.formatting.bagpipes,
@@ -23144,6 +23677,8 @@ EngraverController.prototype.setupTune = function (abcTune, tuneNumber) {
23144
23677
  percmap: abcTune.formatting.percmap,
23145
23678
  initialClef: this.initialClef,
23146
23679
  jazzchords: this.jazzchords,
23680
+ timeBasedLayout: this.timeBasedLayout,
23681
+ accentAbove: this.accentAbove,
23147
23682
  germanAlphabet: this.germanAlphabet
23148
23683
  });
23149
23684
  this.engraver.setStemHeight(this.renderer.spacing.stemHeight);
@@ -23162,7 +23697,7 @@ EngraverController.prototype.setupTune = function (abcTune, tuneNumber) {
23162
23697
  return scale;
23163
23698
  };
23164
23699
  EngraverController.prototype.constructTuneElements = function (abcTune) {
23165
- abcTune.topText = new TopText(abcTune.metaText, abcTune.metaTextInfo, abcTune.formatting, abcTune.lines, this.width, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.getTextSize);
23700
+ abcTune.topText = new TopText(abcTune.metaText, abcTune.metaTextInfo, abcTune.formatting, abcTune.lines, this.width, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.classes.shouldAddClasses, this.getTextSize);
23166
23701
 
23167
23702
  // Generate the raw staff line data
23168
23703
  var i;
@@ -23189,20 +23724,49 @@ EngraverController.prototype.constructTuneElements = function (abcTune) {
23189
23724
  abcLine.nonMusic = new Separator(abcLine.separator.spaceAbove, abcLine.separator.lineLength, abcLine.separator.spaceBelow);
23190
23725
  }
23191
23726
  }
23192
- abcTune.bottomText = new BottomText(abcTune.metaText, this.width, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.getTextSize);
23727
+ abcTune.bottomText = new BottomText(abcTune.metaText, this.width, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.classes.shouldAddClasses, this.getTextSize);
23193
23728
  };
23194
23729
  EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOffset) {
23730
+ var origJazzChords = this.jazzchords;
23195
23731
  var scale = this.setupTune(abcTune, tuneNumber);
23196
23732
 
23197
23733
  // Create all of the element objects that will appear on the page.
23198
23734
  this.constructTuneElements(abcTune);
23199
23735
 
23736
+ //Set the top text now that we know the width
23737
+
23200
23738
  // Do all the positioning, both horizontally and vertically
23201
- var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest);
23739
+ var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest, this.timeBasedLayout);
23202
23740
 
23203
23741
  //Set the top text now that we know the width
23204
23742
  if (this.expandToWidest && maxWidth > this.width + 1) {
23205
- abcTune.topText = new TopText(abcTune.metaText, abcTune.metaTextInfo, abcTune.formatting, abcTune.lines, maxWidth, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.getTextSize);
23743
+ abcTune.topText = new TopText(abcTune.metaText, abcTune.metaTextInfo, abcTune.formatting, abcTune.lines, maxWidth, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.classes.shouldAddClasses, this.getTextSize);
23744
+ if (abcTune.lines && abcTune.lines.length > 0) {
23745
+ var nlines = abcTune.lines.length;
23746
+ for (var i = 0; i < nlines; ++i) {
23747
+ var entry = abcTune.lines[i];
23748
+ if (entry.nonMusic) {
23749
+ if (entry.nonMusic.rows && entry.nonMusic.rows.length > 0) {
23750
+ var nRows = entry.nonMusic.rows.length;
23751
+ for (var j = 0; j < nRows; ++j) {
23752
+ var thisRow = entry.nonMusic.rows[j];
23753
+ // Recenter the element if it's a subtitle or centered text
23754
+ if (thisRow.left) {
23755
+ if (entry.subtitle) {
23756
+ thisRow.left = maxWidth / 2 + this.renderer.padding.left;
23757
+ } else {
23758
+ if (entry.text && entry.text.length > 0) {
23759
+ if (entry.text[0].center) {
23760
+ thisRow.left = maxWidth / 2 + this.renderer.padding.left;
23761
+ }
23762
+ }
23763
+ }
23764
+ }
23765
+ }
23766
+ }
23767
+ }
23768
+ }
23769
+ }
23206
23770
  }
23207
23771
 
23208
23772
  // Deal with tablature for staff
@@ -23216,13 +23780,14 @@ EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOf
23216
23780
  this.selectables = ret.selectables;
23217
23781
  if (this.oneSvgPerLine) {
23218
23782
  var div = this.renderer.paper.svg.parentNode;
23219
- this.svgs = splitSvgIntoLines(this.renderer, div, abcTune.metaText.title, this.responsive);
23783
+ this.svgs = splitSvgIntoLines(this.renderer, div, abcTune.metaText.title, this.responsive, scale);
23220
23784
  } else {
23221
23785
  this.svgs = [this.renderer.paper.svg];
23222
23786
  }
23223
23787
  setupSelection(this, this.svgs);
23788
+ this.jazzchords = origJazzChords;
23224
23789
  };
23225
- function splitSvgIntoLines(renderer, output, title, responsive) {
23790
+ function splitSvgIntoLines(renderer, output, title, responsive, scale) {
23226
23791
  // Each line is a top level <g> in the svg. To split it into separate
23227
23792
  // svgs iterate through each of those and put them in a new svg. Since
23228
23793
  // they are placed absolutely, the viewBox needs to be manipulated to
@@ -23245,7 +23810,7 @@ function splitSvgIntoLines(renderer, output, title, responsive) {
23245
23810
  var height = box.height + gapBetweenLines;
23246
23811
  var wrapper = document.createElement("div");
23247
23812
  var divStyles = "overflow: hidden;";
23248
- if (responsive !== 'resize') divStyles += "height:" + height + "px;";
23813
+ if (responsive !== 'resize') divStyles += "height:" + height * scale + "px;";
23249
23814
  wrapper.setAttribute("style", divStyles);
23250
23815
  var svg = duplicateSvg(source);
23251
23816
  var fullTitle = "Sheet Music for \"" + title + "\" section " + (i + 1);
@@ -23368,7 +23933,7 @@ Classes.prototype.generate = function (c) {
23368
23933
  if (!this.shouldAddClasses) return "";
23369
23934
  var ret = [];
23370
23935
  if (c && c.length > 0) ret.push(c);
23371
- if (c === "tab-number")
23936
+ if (c === "abcjs-tab-number")
23372
23937
  // TODO-PER-HACK! straighten out the tablature
23373
23938
  return ret.join(' ');
23374
23939
  if (c === "text instrument-name") return "abcjs-text abcjs-instrument-name";
@@ -23409,6 +23974,12 @@ GetFontAndAttr.prototype.updateFonts = function (fontOverrides) {
23409
23974
  if (fontOverrides.annotationfont) this.formatting.annotationfont = fontOverrides.annotationfont;
23410
23975
  if (fontOverrides.vocalfont) this.formatting.vocalfont = fontOverrides.vocalfont;
23411
23976
  };
23977
+ GetFontAndAttr.prototype.getFamily = function (type) {
23978
+ if (type[0] === '"' && type[type.length - 1] === '"') {
23979
+ return type.substring(1, type.length - 1);
23980
+ }
23981
+ return type;
23982
+ };
23412
23983
  GetFontAndAttr.prototype.calc = function (type, klass) {
23413
23984
  var font;
23414
23985
  if (typeof type === 'string') {
@@ -23441,7 +24012,7 @@ GetFontAndAttr.prototype.calc = function (type, klass) {
23441
24012
  var attr = {
23442
24013
  "font-size": font.size,
23443
24014
  'font-style': font.style,
23444
- "font-family": font.face,
24015
+ "font-family": this.getFamily(font.face),
23445
24016
  'font-weight': font.weight,
23446
24017
  'text-decoration': font.decoration,
23447
24018
  'class': this.classes.generate(klass)
@@ -23471,6 +24042,12 @@ GetTextSize.prototype.updateFonts = function (fontOverrides) {
23471
24042
  GetTextSize.prototype.attr = function (type, klass) {
23472
24043
  return this.getFontAndAttr.calc(type, klass);
23473
24044
  };
24045
+ GetTextSize.prototype.getFamily = function (type) {
24046
+ if (type[0] === '"' && type[type.length - 1] === '"') {
24047
+ return type.substring(1, type.length - 1);
24048
+ }
24049
+ return type;
24050
+ };
23474
24051
  GetTextSize.prototype.calc = function (text, type, klass, el) {
23475
24052
  var hash;
23476
24053
  // This can be passed in either a string or a font. If it is a string it names one of the standard fonts.
@@ -23486,7 +24063,7 @@ GetTextSize.prototype.calc = function (text, type, klass, el) {
23486
24063
  attr: {
23487
24064
  "font-size": type.size,
23488
24065
  "font-style": type.style,
23489
- "font-family": type.face,
24066
+ "font-family": this.getFamily(type.face),
23490
24067
  "font-weight": type.weight,
23491
24068
  "text-decoration": type.decoration,
23492
24069
  "class": this.getFontAndAttr.classes.generate(klass)
@@ -23560,6 +24137,98 @@ module.exports = spacing;
23560
24137
 
23561
24138
  /***/ }),
23562
24139
 
24140
+ /***/ "./src/write/interactive/create-analysis.js":
24141
+ /*!**************************************************!*\
24142
+ !*** ./src/write/interactive/create-analysis.js ***!
24143
+ \**************************************************/
24144
+ /***/ (function(module) {
24145
+
24146
+ function findNumber(klass, match, target, name) {
24147
+ if (klass.indexOf(match) === 0) {
24148
+ var value = klass.replace(match, '');
24149
+ var num = parseInt(value, 10);
24150
+ if ('' + num === value) target[name] = num;
24151
+ }
24152
+ }
24153
+ function createAnalysis(target, ev) {
24154
+ var classes = [];
24155
+ if (target.absEl.elemset) {
24156
+ var classObj = {};
24157
+ for (var j = 0; j < target.absEl.elemset.length; j++) {
24158
+ var es = target.absEl.elemset[j];
24159
+ if (es) {
24160
+ var klass = es.getAttribute("class").split(' ');
24161
+ for (var k = 0; k < klass.length; k++) {
24162
+ classObj[klass[k]] = true;
24163
+ }
24164
+ }
24165
+ }
24166
+ for (var kk = 0; kk < Object.keys(classObj).length; kk++) {
24167
+ classes.push(Object.keys(classObj)[kk]);
24168
+ }
24169
+ }
24170
+ var analysis = {};
24171
+ for (var ii = 0; ii < classes.length; ii++) {
24172
+ findNumber(classes[ii], "abcjs-v", analysis, "voice");
24173
+ findNumber(classes[ii], "abcjs-l", analysis, "line");
24174
+ findNumber(classes[ii], "abcjs-m", analysis, "measure");
24175
+ }
24176
+ if (target.staffPos) analysis.staffPos = target.staffPos;
24177
+ var closest = ev.target;
24178
+ while (closest && closest.dataset && !closest.dataset.name && closest.tagName.toLowerCase() !== 'svg') {
24179
+ closest = closest.parentNode;
24180
+ }
24181
+ var parent = ev.target;
24182
+ while (parent && parent.dataset && !parent.dataset.index && parent.tagName.toLowerCase() !== 'svg') {
24183
+ parent = parent.parentNode;
24184
+ }
24185
+ if (parent && parent.dataset) {
24186
+ analysis.name = parent.dataset.name;
24187
+ analysis.clickedName = closest.dataset.name;
24188
+ analysis.parentClasses = parent.classList;
24189
+ }
24190
+ if (closest && closest.classList) analysis.clickedClasses = closest.classList;
24191
+ analysis.selectableElement = target.svgEl;
24192
+ return {
24193
+ classes: classes,
24194
+ analysis: analysis
24195
+ };
24196
+ }
24197
+ module.exports = createAnalysis;
24198
+
24199
+ /***/ }),
24200
+
24201
+ /***/ "./src/write/interactive/find-selectable-element.js":
24202
+ /*!**********************************************************!*\
24203
+ !*** ./src/write/interactive/find-selectable-element.js ***!
24204
+ \**********************************************************/
24205
+ /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
24206
+
24207
+ var createAnalysis = __webpack_require__(/*! ./create-analysis */ "./src/write/interactive/create-analysis.js");
24208
+ function findSelectableElement(event) {
24209
+ var selectable = event;
24210
+ while (selectable && selectable.attributes && selectable.tagName.toLowerCase() !== 'svg' && !selectable.attributes.selectable) {
24211
+ selectable = selectable.parentNode;
24212
+ }
24213
+ if (selectable && selectable.attributes && selectable.attributes.selectable) {
24214
+ var index = selectable.attributes['data-index'].nodeValue;
24215
+ if (index) {
24216
+ index = parseInt(index, 10);
24217
+ if (index >= 0 && index < this.selectables.length) {
24218
+ var element = this.selectables[index];
24219
+ var ret = createAnalysis(element, event);
24220
+ ret.index = index;
24221
+ ret.element = element;
24222
+ return ret;
24223
+ }
24224
+ }
24225
+ }
24226
+ return null;
24227
+ }
24228
+ module.exports = findSelectableElement;
24229
+
24230
+ /***/ }),
24231
+
23563
24232
  /***/ "./src/write/interactive/highlight.js":
23564
24233
  /*!********************************************!*\
23565
24234
  !*** ./src/write/interactive/highlight.js ***!
@@ -23583,6 +24252,7 @@ module.exports = highlight;
23583
24252
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
23584
24253
 
23585
24254
  var spacing = __webpack_require__(/*! ../helpers/spacing */ "./src/write/helpers/spacing.js");
24255
+ var createAnalysis = __webpack_require__(/*! ./create-analysis */ "./src/write/interactive/create-analysis.js");
23586
24256
  function setupSelection(engraver, svgs) {
23587
24257
  engraver.rangeHighlight = rangeHighlight;
23588
24258
  if (engraver.dragging) {
@@ -23888,44 +24558,9 @@ function setSelection(dragIndex) {
23888
24558
  }
23889
24559
  }
23890
24560
  function notifySelect(target, dragStep, dragMax, dragIndex, ev) {
23891
- var classes = [];
23892
- if (target.absEl.elemset) {
23893
- var classObj = {};
23894
- for (var j = 0; j < target.absEl.elemset.length; j++) {
23895
- var es = target.absEl.elemset[j];
23896
- if (es) {
23897
- var klass = es.getAttribute("class").split(' ');
23898
- for (var k = 0; k < klass.length; k++) {
23899
- classObj[klass[k]] = true;
23900
- }
23901
- }
23902
- }
23903
- for (var kk = 0; kk < Object.keys(classObj).length; kk++) {
23904
- classes.push(Object.keys(classObj)[kk]);
23905
- }
23906
- }
23907
- var analysis = {};
23908
- for (var ii = 0; ii < classes.length; ii++) {
23909
- findNumber(classes[ii], "abcjs-v", analysis, "voice");
23910
- findNumber(classes[ii], "abcjs-l", analysis, "line");
23911
- findNumber(classes[ii], "abcjs-m", analysis, "measure");
23912
- }
23913
- if (target.staffPos) analysis.staffPos = target.staffPos;
23914
- var closest = ev.target;
23915
- while (closest && closest.dataset && !closest.dataset.name && closest.tagName.toLowerCase() !== 'svg') {
23916
- closest = closest.parentNode;
23917
- }
23918
- var parent = ev.target;
23919
- while (parent && parent.dataset && !parent.dataset.index && parent.tagName.toLowerCase() !== 'svg') {
23920
- parent = parent.parentNode;
23921
- }
23922
- if (parent && parent.dataset) {
23923
- analysis.name = parent.dataset.name;
23924
- analysis.clickedName = closest.dataset.name;
23925
- analysis.parentClasses = parent.classList;
23926
- }
23927
- if (closest && closest.classList) analysis.clickedClasses = closest.classList;
23928
- analysis.selectableElement = target.svgEl;
24561
+ var ret = createAnalysis(target, ev);
24562
+ var classes = ret.classes;
24563
+ var analysis = ret.analysis;
23929
24564
  for (var i = 0; i < this.listeners.length; i++) {
23930
24565
  this.listeners[i](target.absEl.abcelem, target.absEl.tuneNumber, classes.join(' '), analysis, {
23931
24566
  step: dragStep,
@@ -23935,13 +24570,6 @@ function notifySelect(target, dragStep, dragMax, dragIndex, ev) {
23935
24570
  }, ev);
23936
24571
  }
23937
24572
  }
23938
- function findNumber(klass, match, target, name) {
23939
- if (klass.indexOf(match) === 0) {
23940
- var value = klass.replace(match, '');
23941
- var num = parseInt(value, 10);
23942
- if ('' + num === value) target[name] = num;
23943
- }
23944
- }
23945
24573
  function clearSelection() {
23946
24574
  for (var i = 0; i < this.selected.length; i++) {
23947
24575
  this.selected[i].unhighlight(undefined, this.renderer.foregroundColor);
@@ -24296,6 +24924,94 @@ module.exports = getLeftEdgeOfStaff;
24296
24924
 
24297
24925
  /***/ }),
24298
24926
 
24927
+ /***/ "./src/write/layout/layout-in-grid.js":
24928
+ /*!********************************************!*\
24929
+ !*** ./src/write/layout/layout-in-grid.js ***!
24930
+ \********************************************/
24931
+ /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
24932
+
24933
+ var getLeftEdgeOfStaff = __webpack_require__(/*! ./get-left-edge-of-staff */ "./src/write/layout/get-left-edge-of-staff.js");
24934
+ function layoutInGrid(renderer, staffGroup, timeBasedLayout) {
24935
+ var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
24936
+ var ret = getTotalDuration(staffGroup, timeBasedLayout.minPadding);
24937
+ var totalDuration = ret.totalDuration;
24938
+ var minSpacing = ret.minSpacing;
24939
+ var totalWidth = minSpacing * totalDuration;
24940
+ if (timeBasedLayout.minWidth) totalWidth = Math.max(totalWidth, timeBasedLayout.minWidth);
24941
+ var leftAlignPadding = timeBasedLayout.minPadding ? timeBasedLayout.minPadding / 2 : 2; // If the padding isn't specified still give it some
24942
+
24943
+ staffGroup.startx = leftEdge;
24944
+ staffGroup.w = totalWidth + leftEdge;
24945
+ for (var i = 0; i < staffGroup.voices.length; i++) {
24946
+ var voice = staffGroup.voices[i];
24947
+ voice.startx = leftEdge;
24948
+ voice.w = totalWidth + leftEdge;
24949
+ var x = leftEdge;
24950
+ var afterFixedLeft = false;
24951
+ var durationUnit = 0;
24952
+ for (var j = 0; j < voice.children.length; j++) {
24953
+ var child = voice.children[j];
24954
+ if (!afterFixedLeft) {
24955
+ if (child.duration !== 0) {
24956
+ // We got to the first music element on the line
24957
+ afterFixedLeft = true;
24958
+ durationUnit = (totalWidth + leftEdge - x) / totalDuration;
24959
+ staffGroup.gridStart = x;
24960
+ } else {
24961
+ // We are still doing the preliminary stuff - clef, time sig, etc.
24962
+ child.x = x;
24963
+ x += child.w + child.minspacing;
24964
+ }
24965
+ }
24966
+ if (afterFixedLeft) {
24967
+ if (timeBasedLayout.align === 'center') child.x = x + child.duration * durationUnit / 2 - child.w / 2;else {
24968
+ // left align with padding - but no padding for barlines, they should be right aligned.
24969
+ // TODO-PER: it looks better to move bar lines one pixel to right. Not sure why.
24970
+ if (child.duration === 0) {
24971
+ child.x = x + 1 - child.w;
24972
+ } else {
24973
+ // child.extraw has the width of the accidentals - push the note to the right to take that into consideration. It will be 0 if there is nothing to the left.
24974
+ child.x = x + leftAlignPadding - child.extraw;
24975
+ }
24976
+ }
24977
+ x += child.duration * durationUnit;
24978
+ }
24979
+ for (var k = 0; k < child.children.length; k++) {
24980
+ var grandchild = child.children[k];
24981
+ // some elements don't have a dx - Tempo, for instance
24982
+ var dx = grandchild.dx ? grandchild.dx : 0;
24983
+ grandchild.x = child.x + dx;
24984
+ }
24985
+ }
24986
+ staffGroup.gridEnd = x;
24987
+ }
24988
+ return totalWidth;
24989
+ }
24990
+ function getTotalDuration(staffGroup, timeBasedLayout) {
24991
+ var maxSpacing = 0;
24992
+ var maxCount = 0;
24993
+ for (var i = 0; i < staffGroup.voices.length; i++) {
24994
+ var count = 0;
24995
+ var voice = staffGroup.voices[i];
24996
+ for (var j = 0; j < voice.children.length; j++) {
24997
+ var element = voice.children[j];
24998
+ count += element.duration;
24999
+ if (element.duration) {
25000
+ var width = (element.w + timeBasedLayout) / element.duration;
25001
+ maxSpacing = Math.max(maxSpacing, width);
25002
+ }
25003
+ }
25004
+ maxCount = Math.max(maxCount, count);
25005
+ }
25006
+ return {
25007
+ totalDuration: maxCount,
25008
+ minSpacing: maxSpacing
25009
+ };
25010
+ }
25011
+ module.exports = layoutInGrid;
25012
+
25013
+ /***/ }),
25014
+
24299
25015
  /***/ "./src/write/layout/layout.js":
24300
25016
  /*!************************************!*\
24301
25017
  !*** ./src/write/layout/layout.js ***!
@@ -24306,7 +25022,12 @@ var layoutVoice = __webpack_require__(/*! ./voice */ "./src/write/layout/voice.j
24306
25022
  var setUpperAndLowerElements = __webpack_require__(/*! ./set-upper-and-lower-elements */ "./src/write/layout/set-upper-and-lower-elements.js");
24307
25023
  var layoutStaffGroup = __webpack_require__(/*! ./staff-group */ "./src/write/layout/staff-group.js");
24308
25024
  var getLeftEdgeOfStaff = __webpack_require__(/*! ./get-left-edge-of-staff */ "./src/write/layout/get-left-edge-of-staff.js");
24309
- var layout = function layout(renderer, abctune, width, space, expandToWidest) {
25025
+ var layoutInGrid = __webpack_require__(/*! ./layout-in-grid */ "./src/write/layout/layout-in-grid.js");
25026
+
25027
+ // This sets the "x" attribute on all the children in abctune.lines
25028
+ // It also sets the "w" and "startx" attributes on "voices"
25029
+ // It also sets the "w" and "startx" attributes on "voices.children"
25030
+ var layout = function layout(renderer, abctune, width, space, expandToWidest, timeBasedLayout) {
24310
25031
  var i;
24311
25032
  var abcLine;
24312
25033
  // Adjust the x-coordinates to their absolute positions
@@ -24315,7 +25036,8 @@ var layout = function layout(renderer, abctune, width, space, expandToWidest) {
24315
25036
  abcLine = abctune.lines[i];
24316
25037
  if (abcLine.staff) {
24317
25038
  // console.log("=== line", i)
24318
- var thisWidth = setXSpacing(renderer, maxWidth, space, abcLine.staffGroup, abctune.formatting, i === abctune.lines.length - 1, false);
25039
+ var thisWidth;
25040
+ if (timeBasedLayout !== undefined) thisWidth = layoutInGrid(renderer, abcLine.staffGroup, timeBasedLayout);else thisWidth = setXSpacing(renderer, maxWidth, space, abcLine.staffGroup, abctune.formatting, i === abctune.lines.length - 1, false);
24319
25041
  // console.log(thisWidth, maxWidth)
24320
25042
  if (Math.round(thisWidth) > Math.round(maxWidth)) {
24321
25043
  // to take care of floating point weirdness
@@ -24350,40 +25072,34 @@ var layout = function layout(renderer, abctune, width, space, expandToWidest) {
24350
25072
  var setXSpacing = function setXSpacing(renderer, width, space, staffGroup, formatting, isLastLine, debug) {
24351
25073
  var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
24352
25074
  var newspace = space;
25075
+ //dumpGroup("before", staffGroup)
24353
25076
  for (var it = 0; it < 8; it++) {
24354
25077
  // TODO-PER: shouldn't need multiple passes, but each pass gets it closer to the right spacing. (Only affects long lines: normal lines break out of this loop quickly.)
24355
25078
  // console.log("iteration", it)
24356
- // dumpGroup("before", staffGroup)
24357
- var ret = layoutStaffGroup(newspace, renderer, debug, staffGroup, leftEdge);
24358
- // dumpGroup("after",staffGroup)
25079
+ var ret = layoutStaffGroup(newspace, renderer.minPadding, debug, staffGroup, leftEdge);
24359
25080
  newspace = calcHorizontalSpacing(isLastLine, formatting.stretchlast, width + renderer.padding.left, staffGroup.w, newspace, ret.spacingUnits, ret.minSpace, renderer.padding.left + renderer.padding.right);
24360
25081
  if (debug) console.log("setXSpace", it, staffGroup.w, newspace, staffGroup.minspace);
24361
25082
  if (newspace === null) break;
24362
25083
  }
25084
+ //dumpGroup("after",staffGroup)
24363
25085
  centerWholeRests(staffGroup.voices);
24364
25086
  return staffGroup.w - leftEdge;
24365
25087
  };
24366
-
24367
- // function dumpGroup(label, staffGroup) {
24368
- // var output = {
24369
- // line: staffGroup.line,
24370
- // w: staffGroup.w,
24371
- // voice: {
24372
- // i: staffGroup.voices[0].i,
24373
- // minx: staffGroup.voices[0].minx,
24374
- // nextx: staffGroup.voices[0].nextx,
24375
- // spacingduration: staffGroup.voices[0].spacingduration,
24376
- // w: staffGroup.voices[0].w,
24377
- // children: [],
24378
- // }
24379
- // }
24380
- // for (var i = 0; i < staffGroup.voices[0].children.length; i++) {
24381
- // var child = staffGroup.voices[0].children[i]
24382
- // output.voice.children.push({ fixedW: child.fixed.w, w: child.w, x: child.x, type: child.type })
24383
- // }
24384
- // console.log(label,output)
24385
- // }
24386
-
25088
+ function replacer(key, value) {
25089
+ // Filtering out properties
25090
+ if (key === 'parent') {
25091
+ return 'parent';
25092
+ }
25093
+ if (key === 'beam') {
25094
+ return 'beam';
25095
+ }
25096
+ return value;
25097
+ }
25098
+ function dumpGroup(label, staffGroup) {
25099
+ console.log("=================== " + label + " =========================");
25100
+ console.log(staffGroup);
25101
+ console.log(JSON.stringify(staffGroup, replacer, "\t"));
25102
+ }
24387
25103
  function calcHorizontalSpacing(isLastLine, stretchLast, targetWidth, lineWidth, spacing, spacingUnits, minSpace, padding) {
24388
25104
  if (isLastLine) {
24389
25105
  if (stretchLast === undefined) {
@@ -24514,6 +25230,7 @@ var setUpperAndLowerElements = function setUpperAndLowerElements(renderer, staff
24514
25230
  var addedSpace = minSpacingInPitches - forcedSpacingBetween;
24515
25231
  if (addedSpace > 0) staff.top += addedSpace;
24516
25232
  }
25233
+ staff.top += renderer.spacing.staffTopMargin / spacing.STEP;
24517
25234
  lastStaffBottom = 2 - staff.bottom; // the staff starts at position 2 and the bottom variable is negative. Therefore to find out how large the bottom is, we reverse the sign of the bottom, and add the 2 in.
24518
25235
 
24519
25236
  // Now we need a little margin on the top, so we'll just throw that in.
@@ -24672,7 +25389,7 @@ function checkLastBarX(voices) {
24672
25389
  }
24673
25390
  }
24674
25391
  }
24675
- var layoutStaffGroup = function layoutStaffGroup(spacing, renderer, debug, staffGroup, leftEdge) {
25392
+ var layoutStaffGroup = function layoutStaffGroup(spacing, minPadding, debug, staffGroup, leftEdge) {
24676
25393
  var epsilon = 0.0000001; // Fudging for inexactness of floating point math.
24677
25394
  var spacingunits = 0; // number of times we will have ended up using the spacing distance (as opposed to fixed width distances)
24678
25395
  var minspace = 1000; // a big number to start off with - used to find out what the smallest space between two notes is -- GD 2014.1.7
@@ -24728,7 +25445,7 @@ var layoutStaffGroup = function layoutStaffGroup(spacing, renderer, debug, staff
24728
25445
  if (v.voicenumber === 0) lastTopVoice = i;
24729
25446
  var topVoice = lastTopVoice !== undefined && currentvoices[lastTopVoice].voicenumber !== v.voicenumber ? currentvoices[lastTopVoice] : undefined;
24730
25447
  if (!isSameStaff(v, topVoice)) topVoice = undefined;
24731
- var voicechildx = layoutVoiceElements.layoutOneItem(x, spacing, v, renderer.minPadding, topVoice);
25448
+ var voicechildx = layoutVoiceElements.layoutOneItem(x, spacing, v, minPadding, topVoice);
24732
25449
  var dx = voicechildx - x;
24733
25450
  if (dx > 0) {
24734
25451
  x = voicechildx; //update x
@@ -24954,7 +25671,7 @@ VoiceElement.shiftRight = function (dx, voice) {
24954
25671
 
24955
25672
  // call when spacingduration has been updated
24956
25673
  VoiceElement.updateNextX = function (x, spacing, voice) {
24957
- voice.nextx = x + spacing * Math.sqrt(voice.spacingduration * 8);
25674
+ voice.nextx = x + spacing * this.getSpacingUnits(voice);
24958
25675
  };
24959
25676
  VoiceElement.updateIndices = function (voice) {
24960
25677
  if (!this.layoutEnded(voice)) {
@@ -25019,7 +25736,7 @@ function moveDecorations(beam) {
25019
25736
  var top = yAtNote(child, beam);
25020
25737
  for (var i = 0; i < child.children.length; i++) {
25021
25738
  var el = child.children[i];
25022
- if (el.klass === 'ornament') {
25739
+ if (el.klass === 'ornament' && el.position !== 'below') {
25023
25740
  if (el.bottom - padding < top) {
25024
25741
  var distance = top - el.bottom + padding; // Find the distance that it needs to move and add a little margin so the element doesn't touch the beam.
25025
25742
  el.bottom += distance;
@@ -25223,6 +25940,7 @@ Renderer.prototype.initVerticalSpace = function () {
25223
25940
  // Set the slur height factor.
25224
25941
  staffSeparation: 61.33,
25225
25942
  // Do not put a staff system closer than <unit> from the previous system.
25943
+ staffTopMargin: 0,
25226
25944
  stemHeight: 26.67 + 10,
25227
25945
  // Set the stem height.
25228
25946
  subtitle: 3.78,
@@ -25274,6 +25992,7 @@ Renderer.prototype.setVerticalSpace = function (formatting) {
25274
25992
  if (formatting.musicspace !== undefined) this.spacing.music = formatting.musicspace * 4 / 3;
25275
25993
  if (formatting.titlespace !== undefined) this.spacing.title = formatting.titlespace * 4 / 3;
25276
25994
  if (formatting.sysstaffsep !== undefined) this.spacing.systemStaffSeparation = formatting.sysstaffsep * 4 / 3;
25995
+ if (formatting.stafftopmargin !== undefined) this.spacing.staffTopMargin = formatting.stafftopmargin * 4 / 3;
25277
25996
  if (formatting.subtitlespace !== undefined) this.spacing.subtitle = formatting.subtitlespace * 4 / 3;
25278
25997
  if (formatting.topspace !== undefined) this.spacing.top = formatting.topspace * 4 / 3;
25279
25998
  if (formatting.vocalspace !== undefined) this.spacing.vocal = formatting.vocalspace * 4 / 3;
@@ -25484,6 +26203,28 @@ Svg.prototype.text = function (text, attr, target) {
25484
26203
  if (target) target.appendChild(el);else this.append(el);
25485
26204
  return el;
25486
26205
  };
26206
+ Svg.prototype.richTextLine = function (phrases, x, y, klass, anchor, target) {
26207
+ var el = document.createElementNS(svgNS, 'text');
26208
+ el.setAttribute("stroke", "none");
26209
+ el.setAttribute("class", klass);
26210
+ el.setAttribute("x", x);
26211
+ el.setAttribute("y", y);
26212
+ el.setAttribute("text-anchor", anchor);
26213
+ el.setAttribute("dominant-baseline", "middle");
26214
+ for (var i = 0; i < phrases.length; i++) {
26215
+ var phrase = phrases[i];
26216
+ var tspan = document.createElementNS(svgNS, 'tspan');
26217
+ var attrs = Object.keys(phrase.attrs);
26218
+ for (var j = 0; j < attrs.length; j++) {
26219
+ var value = phrase.attrs[attrs[j]];
26220
+ if (value !== '') tspan.setAttribute(attrs[j], value);
26221
+ }
26222
+ tspan.textContent = phrase.content;
26223
+ el.appendChild(tspan);
26224
+ }
26225
+ if (target) target.appendChild(el);else this.append(el);
26226
+ return el;
26227
+ };
25487
26228
  Svg.prototype.guessWidth = function (text, attr) {
25488
26229
  var svg = this.createDummySvg();
25489
26230
  var el = this.text(text, attr, svg);
@@ -25635,7 +26376,7 @@ module.exports = Svg;
25635
26376
  \********************/
25636
26377
  /***/ (function(module) {
25637
26378
 
25638
- var version = '6.2.3';
26379
+ var version = '6.4.0';
25639
26380
  module.exports = version;
25640
26381
 
25641
26382
  /***/ })