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
package/index.js CHANGED
@@ -40,6 +40,7 @@ Object.keys(tuneBook).forEach(function (key) {
40
40
  });
41
41
 
42
42
  abcjs.renderAbc = require('./src/api/abc_tunebook_svg');
43
+ abcjs.tuneMetrics = require('./src/api/tune-metrics');
43
44
  abcjs.TimingCallbacks = require('./src/api/abc_timing_callbacks');
44
45
 
45
46
  var glyphs = require('./src/write/creation/glyphs');
@@ -57,6 +58,7 @@ var supportsAudio = require('./src/synth/supports-audio');
57
58
  var playEvent = require('./src/synth/play-event');
58
59
  var SynthController = require('./src/synth/synth-controller');
59
60
  var getMidiFile = require('./src/synth/get-midi-file');
61
+ var midiRenderer = require('./src/synth/abc_midi_renderer');
60
62
 
61
63
  abcjs.synth = {
62
64
  CreateSynth: CreateSynth,
@@ -71,6 +73,7 @@ abcjs.synth = {
71
73
  playEvent: playEvent,
72
74
  getMidiFile: getMidiFile,
73
75
  sequence: sequence,
76
+ midiRenderer: midiRenderer,
74
77
  };
75
78
 
76
79
  abcjs['Editor'] = require('./src/edit/abc_editor');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abcjs",
3
- "version": "6.2.3",
3
+ "version": "6.4.0",
4
4
  "description": "Renderer for abc music notation",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
package/plugin.js CHANGED
@@ -248,7 +248,7 @@ if (autostart &&
248
248
  }
249
249
 
250
250
  var abcjs = {
251
- plugin: Plugin
251
+ plugin: plugin
252
252
  };
253
253
 
254
254
  module.exports = abcjs;
@@ -5,17 +5,17 @@
5
5
  * where plugin represents a plugin instance
6
6
  *
7
7
  */
8
- var ViolinTablature = require('../tablatures/instruments/violin/tab-violin');
9
- var GuitarTablature = require('../tablatures/instruments/guitar/tab-guitar');
8
+ var StringTablature = require('../tablatures/instruments/tab-string');
10
9
 
11
10
  /* extend the table below when adding a new instrument plugin */
12
11
 
13
12
  // Existing tab classes
14
13
  var pluginTab = {
15
- 'violin': 'ViolinTab',
16
- 'fiddle': 'ViolinTab',
17
- 'mandolin': 'ViolinTab',
18
- 'guitar': 'GuitarTab'
14
+ 'violin': { name: 'StringTab', defaultTuning: ['G,', 'D', 'A', 'e'], isTabBig: false, tabSymbolOffset: 0},
15
+ 'fiddle': { name: 'StringTab', defaultTuning: ['G,', 'D', 'A', 'e'], isTabBig: false, tabSymbolOffset: 0},
16
+ 'mandolin': { name: 'StringTab', defaultTuning: ['G,', 'D', 'A', 'e'], isTabBig: false, tabSymbolOffset: 0},
17
+ 'guitar': { name: 'StringTab', defaultTuning: ['E,', 'A,', 'D', 'G' , 'B' , 'e'], isTabBig: true, tabSymbolOffset: 0},
18
+ 'fiveString': { name: 'StringTab', defaultTuning: ['C,', 'G,', 'D', 'A', 'e'], isTabBig: false, tabSymbolOffset: -.95},
19
19
  };
20
20
 
21
21
  var abcTablatures = {
@@ -66,7 +66,7 @@ var abcTablatures = {
66
66
  var tabName = pluginTab[instrument];
67
67
  var plugin = null;
68
68
  if (tabName) {
69
- plugin = this.plugins[tabName];
69
+ plugin = this.plugins[tabName.name];
70
70
  }
71
71
  if (plugin) {
72
72
  if (params.visualTranspose != 0) {
@@ -79,6 +79,7 @@ var abcTablatures = {
79
79
  tuneNumber: tuneNumber,
80
80
  params: args,
81
81
  instance: null,
82
+ tabType: tabName,
82
83
  };
83
84
  // proceed with tab plugin init
84
85
  // plugin.init(tune, tuneNumber, args, ii);
@@ -103,16 +104,50 @@ var abcTablatures = {
103
104
  * @param {*} renderer
104
105
  * @param {*} abcTune
105
106
  */
106
- layoutTablatures: function (renderer, abcTune) {
107
+ layoutTablatures: function layoutTablatures(renderer, abcTune) {
107
108
  var tabs = abcTune.tablatures;
109
+
108
110
  // chack tabs request for each staffs
111
+ var staffLineCount = 0;
112
+
113
+ // Clear the suppression flag
114
+ if (tabs && (tabs.length > 0)){
115
+ var nTabs = tabs.length;
116
+ for (var kk=0;kk<nTabs;++kk){
117
+ if (tabs[kk] && tabs[kk].params.firstStaffOnly){
118
+ tabs[kk].params.suppress = false;
119
+ }
120
+ }
121
+ }
122
+
109
123
  for (var ii = 0; ii < abcTune.lines.length; ii++) {
110
124
  var line = abcTune.lines[ii];
125
+
126
+ if (line.staff){
127
+ staffLineCount++;
128
+ }
129
+
130
+ // MAE 27Nov2023
131
+ // If tab param "firstStaffOnly", remove the tab label after the first staff
132
+ if (staffLineCount > 1){
133
+ if (tabs && (tabs.length > 0)){
134
+ var nTabs = tabs.length;
135
+ for (var kk=0;kk<nTabs;++kk){
136
+ if (tabs[kk].params.firstStaffOnly){
137
+ // Set the staff draw suppression flag
138
+ tabs[kk].params.suppress = true;
139
+ }
140
+ }
141
+ }
142
+ }
143
+
111
144
  var curStaff = line.staff;
112
145
  if (curStaff) {
146
+ var maxStaves = curStaff.length
113
147
  for (var jj = 0; jj < curStaff.length; jj++) {
114
- if (tabs[jj]) {
115
- // tablature requested for staff
148
+
149
+ if (tabs[jj] && jj < maxStaves) {
150
+ // tablature requested for staff
116
151
  var tabPlugin = tabs[jj];
117
152
  if (tabPlugin.instance == null) {
118
153
  tabPlugin.instance = new tabPlugin.classz();
@@ -121,7 +156,8 @@ var abcTablatures = {
121
156
  tabPlugin.instance.init(abcTune,
122
157
  tabPlugin.tuneNumber,
123
158
  tabPlugin.params,
124
- jj
159
+ jj,
160
+ tabPlugin.tabType
125
161
  );
126
162
  }
127
163
  // render next
@@ -138,8 +174,7 @@ var abcTablatures = {
138
174
  init: function () {
139
175
  // just register plugin hosted by abcjs
140
176
  if (!this.inited) {
141
- this.register(new ViolinTablature());
142
- this.register(new GuitarTablature());
177
+ this.register(new StringTablature());
143
178
  this.inited = true;
144
179
  }
145
180
  }
@@ -0,0 +1,18 @@
1
+ var tunebook = require('./abc_tunebook');
2
+ var EngraverController = require('../write/engraver-controller');
3
+
4
+ var tuneMetrics = function(abc, params) {
5
+ function callback(div, tune, tuneNumber, abcString) {
6
+ div = document.createElement("div");
7
+ div.setAttribute("style", "visibility: hidden;");
8
+ document.body.appendChild(div);
9
+ var engraver_controller = new EngraverController(div, params);
10
+ var widths = engraver_controller.getMeasureWidths(tune);
11
+ div.parentNode.removeChild(div);
12
+ return {sections: widths};
13
+ }
14
+
15
+ return tunebook.renderEngine(callback, "*", abc, params);
16
+ };
17
+
18
+ module.exports = tuneMetrics;
@@ -374,8 +374,9 @@ var Tune = function() {
374
374
  eventHash["event" + voiceTimeMilliseconds].measureStart = true;
375
375
  nextIsBar = false;
376
376
  }
377
- if (isTiedToNext)
378
- isTiedState = voiceTimeMilliseconds;
377
+ // 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.
378
+ // if (isTiedToNext)
379
+ // isTiedState = voiceTimeMilliseconds;
379
380
  }
380
381
  }
381
382
  return { isTiedState: isTiedState, duration: realDuration / timeDivider, nextIsBar: nextIsBar || element.type === 'bar' };
@@ -616,6 +617,16 @@ var Tune = function() {
616
617
  this.deline = function(options) {
617
618
  return delineTune(this.lines, options);
618
619
  }
620
+ this.findSelectableElement = function(target) {
621
+ if (this.engraver && this.engraver.selectables)
622
+ return this.engraver.findSelectableElement(target)
623
+ return null
624
+ }
625
+ this.getSelectableArray = function() {
626
+ if (this.engraver && this.engraver.selectables)
627
+ return this.engraver.selectables
628
+ return []
629
+ }
619
630
  };
620
631
 
621
632
  module.exports = Tune;
@@ -44,7 +44,10 @@ try {
44
44
  }
45
45
 
46
46
  var EditArea = function(textareaid) {
47
- this.textarea = document.getElementById(textareaid);
47
+ if (typeof textareaid === "string")
48
+ this.textarea = document.getElementById(textareaid);
49
+ else
50
+ this.textarea = textareaid;
48
51
  this.initialText = this.textarea.value;
49
52
  this.isDragging = false;
50
53
  }
@@ -46,6 +46,8 @@ var Parse = function() {
46
46
  setTiming: tune.setTiming,
47
47
  setUpAudio: tune.setUpAudio,
48
48
  deline: tune.deline,
49
+ findSelectableElement: tune.findSelectableElement,
50
+ getSelectableArray: tune.getSelectableArray,
49
51
  };
50
52
  if (tune.lineBreaks)
51
53
  t.lineBreaks = tune.lineBreaks;
@@ -684,27 +684,27 @@ var parseDirective = {};
684
684
  };
685
685
 
686
686
  parseDirective.parseFontChangeLine = function(textstr) {
687
+ // We don't want to match two dollar signs, so change those temporarily
688
+ textstr = textstr.replace(/\$\$/g,"\x03")
687
689
  var textParts = textstr.split('$');
688
690
  if (textParts.length > 1 && multilineVars.setfont) {
689
- var textarr = [ { text: textParts[0] }];
691
+ var textarr = [ ];
692
+ if (textParts[0] !== '') // did the original string start with `$`?
693
+ textarr.push({ text: textParts[0] })
690
694
  for (var i = 1; i < textParts.length; i++) {
691
695
  if (textParts[i][0] === '0')
692
- textarr.push({ text: textParts[i].substring(1) });
693
- else if (textParts[i][0] === '1' && multilineVars.setfont[1])
694
- textarr.push({font: multilineVars.setfont[1], text: textParts[i].substring(1) });
695
- else if (textParts[i][0] === '2' && multilineVars.setfont[2])
696
- textarr.push({font: multilineVars.setfont[2], text: textParts[i].substring(1) });
697
- else if (textParts[i][0] === '3' && multilineVars.setfont[3])
698
- textarr.push({font: multilineVars.setfont[3], text: textParts[i].substring(1) });
699
- else if (textParts[i][0] === '4' && multilineVars.setfont[4])
700
- textarr.push({font: multilineVars.setfont[4], text: textParts[i].substring(1) });
701
- else
702
- textarr[textarr.length-1].text += '$' + textParts[i];
696
+ textarr.push({ text: textParts[i].substring(1).replace(/\x03/g,"$$") });
697
+ else {
698
+ var whichFont = parseInt(textParts[i][0],10)
699
+ if (multilineVars.setfont[whichFont])
700
+ textarr.push({font: multilineVars.setfont[whichFont], text: textParts[i].substring(1).replace(/\x03/g,"$$") });
701
+ else
702
+ textarr[textarr.length-1].text += '$' + textParts[i].replace(/\x03/g,"$$");
703
+ }
703
704
  }
704
- if (textarr.length > 1)
705
- return textarr;
705
+ return textarr;
706
706
  }
707
- return textstr;
707
+ return textstr.replace(/\x03/g,"$$");
708
708
  };
709
709
 
710
710
  var positionChoices = [ 'auto', 'above', 'below', 'hidden' ];
@@ -748,6 +748,7 @@ var parseDirective = {};
748
748
  case "bagpipes":tune.formatting.bagpipes = true;break;
749
749
  case "flatbeams":tune.formatting.flatbeams = true;break;
750
750
  case "jazzchords":tune.formatting.jazzchords = true;break;
751
+ case "accentAbove":tune.formatting.accentAbove = true;break;
751
752
  case "germanAlphabet":tune.formatting.germanAlphabet = true;break;
752
753
  case "landscape":multilineVars.landscape = true;break;
753
754
  case "papersize":multilineVars.papersize = restOfString;break;
@@ -796,6 +797,7 @@ var parseDirective = {};
796
797
  case "pageheight":
797
798
  case "pagewidth":
798
799
  case "rightmargin":
800
+ case "stafftopmargin":
799
801
  case "staffsep":
800
802
  case "staffwidth":
801
803
  case "subtitlespace":
@@ -939,7 +941,7 @@ var parseDirective = {};
939
941
  if (sfTokens.length >= 4) {
940
942
  if (sfTokens[0].token === '-' && sfTokens[1].type === 'number') {
941
943
  var sfNum = parseInt(sfTokens[1].token);
942
- if (sfNum >= 1 && sfNum <= 4) {
944
+ if (sfNum >= 1 && sfNum <= 9) {
943
945
  if (!multilineVars.setfont)
944
946
  multilineVars.setfont = [];
945
947
  sfTokens.shift();
@@ -1196,6 +1198,11 @@ var parseDirective = {};
1196
1198
  warn("Directive \"" + cmd + "\" requires a number as a parameter.");
1197
1199
  tune.formatting.fontboxpadding = tokens[0].floatt;
1198
1200
  break;
1201
+ case "stafftopmargin":
1202
+ if (tokens.length !== 1 || tokens[0].type !== 'number')
1203
+ warn("Directive \"" + cmd + "\" requires a number as a parameter.");
1204
+ tune.formatting.stafftopmargin = tokens[0].floatt;
1205
+ break;
1199
1206
  case "stretchlast":
1200
1207
  var sl = parseStretchLast(tokens);
1201
1208
  if (sl.value !== undefined)
@@ -11,15 +11,12 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
11
11
  };
12
12
  this.reset(tokenizer, warn, multilineVars, tune);
13
13
 
14
- this.setTitle = function(title) {
14
+ this.setTitle = function(title, origSize) {
15
15
  if (multilineVars.hasMainTitle)
16
- tuneBuilder.addSubtitle(tokenizer.translateString(tokenizer.stripComment(title)), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+title.length+2}); // display secondary title
16
+ tuneBuilder.addSubtitle(title, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+origSize+2}); // display secondary title
17
17
  else
18
18
  {
19
- var titleStr = tokenizer.translateString(tokenizer.theReverser(tokenizer.stripComment(title)));
20
- if (multilineVars.titlecaps)
21
- titleStr = titleStr.toUpperCase();
22
- tuneBuilder.addMetaText("title", titleStr, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+title.length+2});
19
+ tuneBuilder.addMetaText("title", title, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+origSize+2});
23
20
  multilineVars.hasMainTitle = true;
24
21
  }
25
22
  };
@@ -371,10 +368,11 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
371
368
  tuneBuilder.appendStartingElement('key', startChar, endChar, parseKeyVoice.fixKey(multilineVars.clef, multilineVars.key));
372
369
  return [ e-i+1+ws ];
373
370
  case "[P:":
371
+ var part = parseDirective.parseFontChangeLine(line.substring(i+3, e))
374
372
  if (startLine || tune.lines.length <= tune.lineNum)
375
- multilineVars.partForNextLine = { title: line.substring(i+3, e), startChar: startChar, endChar: endChar };
373
+ multilineVars.partForNextLine = { title: part, startChar: startChar, endChar: endChar };
376
374
  else
377
- tuneBuilder.appendElement('part', startChar, endChar, {title: line.substring(i+3, e)});
375
+ tuneBuilder.appendElement('part', startChar, endChar, {title: part});
378
376
  return [ e-i+1+ws ];
379
377
  case "[L:":
380
378
  this.setDefaultLength(line, i+3, e);
@@ -477,23 +475,26 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
477
475
 
478
476
  this.parseHeader = function(line) {
479
477
  var field = metaTextHeaders[line[0]];
480
- if (field !== undefined) {
481
- if (field === 'unalignedWords')
482
- tuneBuilder.addMetaTextArray(field, parseDirective.parseFontChangeLine(tokenizer.translateString(tokenizer.stripComment(line.substring(2)))), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
483
- else
484
- tuneBuilder.addMetaText(field, tokenizer.translateString(tokenizer.stripComment(line.substring(2))), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
485
- return {};
478
+ var origSize = line.length-2
479
+ var restOfLine = tokenizer.translateString(tokenizer.stripComment(line.substring(2)))
480
+ if (field === 'unalignedWords' || field === 'notes') {
481
+ // These fields can be multi-line
482
+ tuneBuilder.addMetaTextArray(field, parseDirective.parseFontChangeLine(restOfLine), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
483
+ } else if (field !== undefined) {
484
+ // these fields are single line
485
+ tuneBuilder.addMetaText(field, parseDirective.parseFontChangeLine(restOfLine), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
486
486
  } else {
487
487
  var startChar = multilineVars.iChar;
488
488
  var endChar = startChar + line.length;
489
489
  switch(line[0])
490
490
  {
491
491
  case 'H':
492
- tuneBuilder.addMetaText("history", tokenizer.translateString(tokenizer.stripComment(line.substring(2))), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
492
+ // History is a little different because once it starts it continues until another header field is encountered
493
+ tuneBuilder.addMetaTextArray("history", parseDirective.parseFontChangeLine(restOfLine), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
493
494
  line = tokenizer.peekLine()
494
495
  while (line && line[1] !== ':') {
495
496
  tokenizer.nextLine()
496
- tuneBuilder.addMetaText("history", tokenizer.translateString(tokenizer.stripComment(line)), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
497
+ tuneBuilder.addMetaTextArray("history", parseDirective.parseFontChangeLine(tokenizer.translateString(tokenizer.stripComment(line))), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
497
498
  line = tokenizer.peekLine()
498
499
  }
499
500
  break;
@@ -518,9 +519,9 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
518
519
  case 'P':
519
520
  // TODO-PER: There is more to do with parts, but the writer doesn't care.
520
521
  if (multilineVars.is_in_header)
521
- tuneBuilder.addMetaText("partOrder", tokenizer.translateString(tokenizer.stripComment(line.substring(2))), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
522
+ tuneBuilder.addMetaText("partOrder", parseDirective.parseFontChangeLine(restOfLine), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length});
522
523
  else
523
- multilineVars.partForNextLine = { title: tokenizer.translateString(tokenizer.stripComment(line.substring(2))), startChar: startChar, endChar: endChar};
524
+ multilineVars.partForNextLine = { title: restOfLine, startChar: startChar, endChar: endChar};
524
525
  break;
525
526
  case 'Q':
526
527
  var tempo = this.setTempo(line, 2, line.length, multilineVars.iChar);
@@ -533,7 +534,9 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
533
534
  }
534
535
  break;
535
536
  case 'T':
536
- this.setTitle(line.substring(2));
537
+ if (multilineVars.titlecaps)
538
+ restOfLine = restOfLine.toUpperCase();
539
+ this.setTitle(parseDirective.parseFontChangeLine(tokenizer.theReverser(restOfLine)), origSize);
537
540
  break;
538
541
  case 'U':
539
542
  this.addUserDefinition(line, 2, line.length);
@@ -639,13 +639,78 @@ var Tokenizer = function(lines, multilineVars) {
639
639
  return {value: num/den, index: index};
640
640
  };
641
641
 
642
- this.theReverser = function(str) {
643
- if (parseCommon.endsWith(str, ", The"))
644
- return "The " + str.substring(0, str.length-5);
645
- if (parseCommon.endsWith(str, ", A"))
646
- return "A " + str.substring(0, str.length-3);
647
- return str;
648
- };
642
+ //
643
+ // MAE 10 Jan 2023 - For better handling of tunes that have tune numbers in front of them.
644
+ //
645
+ // Previous version would take:
646
+ // 21. Woman of the House, The
647
+ // and return:
648
+ // The 21. Woman of the House
649
+ //
650
+ // This fix results in:
651
+ // 21. The Woman of the House
652
+ //
653
+ // Also added additional checks and handlers for lower case ", the" and ", a" since I found several tune collections with those tune name constructs
654
+ //
655
+ // Find an optional title number at the start of a tune title
656
+ function getTitleNumber(str){
657
+
658
+ const regex = /^(\d+)\./;
659
+
660
+ // Use the exec method to search for the pattern in the string
661
+ const match = regex.exec(str);
662
+
663
+ // Check if a match is found
664
+ if (match) {
665
+
666
+ // The matched number is captured in the first group (index 1)
667
+ const foundNumber = match[1];
668
+ return foundNumber;
669
+
670
+ } else {
671
+
672
+ // Return null if no match is found
673
+ return null;
674
+
675
+ }
676
+
677
+ }
678
+
679
+ var thePatterns = [
680
+ { match: /,\s*[Tt]he$/, replace: "The " },
681
+ { match: /,\s*[Aa]$/, replace: "A " },
682
+ { match: /,\s*[Aa]n$/, replace: "An " },
683
+ ]
684
+
685
+ this.theReverser = function (str) {
686
+
687
+ for (var i = 0; i < thePatterns.length; i++) {
688
+ var thisPattern = thePatterns[i]
689
+ var match = str.match(thisPattern.match)
690
+ if (match) {
691
+ var theTitleNumber = getTitleNumber(str);
692
+ if (theTitleNumber){
693
+
694
+ //console.log("theReverser The titlenumber:"+theTitleNumber);
695
+
696
+ str = str.replace(theTitleNumber+".","");
697
+ str = str.trim();
698
+ }
699
+ var len = match[0].length
700
+ var result = thisPattern.replace + str.substring(0, str.length - len);
701
+
702
+ if (theTitleNumber){
703
+ result = theTitleNumber+". "+result;
704
+ }
705
+
706
+ return result;
707
+
708
+ }
709
+ }
710
+
711
+ return str;
712
+
713
+ };
649
714
 
650
715
  this.stripComment = function(str) {
651
716
  var i = str.indexOf('%');
@@ -1,5 +1,6 @@
1
1
  var parseKeyVoice = require('../parse/abc_parse_key_voice');
2
2
  var parseCommon = require('../parse/abc_common');
3
+ var parseDirective = require('./abc_parse_directive');
3
4
 
4
5
  var TuneBuilder = function(tune) {
5
6
  var self = this;
@@ -147,6 +148,9 @@ var TuneBuilder = function(tune) {
147
148
  this.closeLine(); // Close the last line.
148
149
  delete tune.runningFonts;
149
150
 
151
+ simplifyMetaText(tune)
152
+ //addRichTextToAnnotationsAndLyrics(tune)
153
+
150
154
  // 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.
151
155
  if (tune.metaText.tempo && tune.metaText.tempo.bpm && !tune.metaText.tempo.duration)
152
156
  tune.metaText.tempo.duration = [ tune.getBeatLength() ];
@@ -878,7 +882,15 @@ var TuneBuilder = function(tune) {
878
882
  tune.metaText[key] = value;
879
883
  tune.metaTextInfo[key] = info;
880
884
  } else {
881
- tune.metaText[key] += "\n" + value;
885
+ if (typeof tune.metaText[key] === 'string' && typeof value === 'string')
886
+ tune.metaText[key] += "\n" + value;
887
+ else {
888
+ if (tune.metaText[key] === 'string')
889
+ tune.metaText[key] = [{text: tune.metaText[key]}]
890
+ if (typeof value === 'string')
891
+ value = [{text: value}]
892
+ tune.metaText[key] =tune.metaText[key].concat(value)
893
+ }
882
894
  tune.metaTextInfo[key].endChar = info.endChar;
883
895
  }
884
896
  };
@@ -898,4 +910,51 @@ var TuneBuilder = function(tune) {
898
910
  };
899
911
  };
900
912
 
913
+ function isArrayOfStrings(arr) {
914
+ if (!arr) return false
915
+ if (typeof arr === "string") return false
916
+ var str = ''
917
+ for (var i = 0; i < arr.length; i++) {
918
+ if (typeof arr[i] !== 'string')
919
+ return false
920
+ }
921
+ return true
922
+ }
923
+
924
+ function simplifyMetaText(tune) {
925
+ if (isArrayOfStrings(tune.metaText.notes))
926
+ tune.metaText.notes = tune.metaText.notes.join("\n")
927
+ if (isArrayOfStrings(tune.metaText.history))
928
+ tune.metaText.history = tune.metaText.history.join("\n")
929
+ }
930
+
931
+ function addRichTextToAnnotationsAndLyrics(tune) {
932
+ var lines = tune.lines
933
+ for (var i = 0; i < lines.length; i++) {
934
+ if (lines[i].staff !== undefined) {
935
+ for (var s = 0; s < lines[i].staff.length; s++) {
936
+ for (var v = 0; v < lines[i].staff[s].voices.length; v++) {
937
+ var voice = lines[i].staff[s].voices[v];
938
+ for (var n = 0; n < voice.length; n++) {
939
+ var element = voice[n]
940
+ if (element.chord) {
941
+ for (var c = 0; c < element.chord.length; c++) {
942
+ element.chord[c].name = parseDirective.parseFontChangeLine(element.chord[c].name)
943
+ console.log(element.chord[c].name)
944
+ }
945
+ }
946
+ if (element.lyric) {
947
+ for (var l = 0; l < element.lyric.length; l++) {
948
+ element.lyric[l].syllable = parseDirective.parseFontChangeLine(element.lyric[l].syllable)
949
+ console.log(element.lyric[l].syllable)
950
+ }
951
+ }
952
+ }
953
+ }
954
+ }
955
+ }
956
+ }
957
+
958
+ }
959
+
901
960
  module.exports = TuneBuilder;