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
@@ -6,7 +6,10 @@ var Selectables = require('./selectables');
6
6
 
7
7
  function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, selectTypes, tuneNumber, lineOffset) {
8
8
  var selectables = new Selectables(renderer.paper, selectTypes, tuneNumber);
9
- renderer.paper.openGroup()
9
+ var groupClasses = {}
10
+ if (classes.shouldAddClasses)
11
+ groupClasses.klass = "abcjs-meta-top"
12
+ renderer.paper.openGroup(groupClasses)
10
13
  renderer.moveY(renderer.padding.top);
11
14
  nonMusic(renderer, abcTune.topText, selectables);
12
15
  renderer.paper.closeGroup()
@@ -16,7 +19,9 @@ function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, se
16
19
  classes.incrLine();
17
20
  var abcLine = abcTune.lines[line];
18
21
  if (abcLine.staff) {
19
- renderer.paper.openGroup()
22
+ if (classes.shouldAddClasses)
23
+ groupClasses.klass = "abcjs-staff-wrapper abcjs-l" + classes.lineNumber
24
+ renderer.paper.openGroup(groupClasses)
20
25
  if (abcLine.vskip) {
21
26
  renderer.moveY(abcLine.vskip);
22
27
  }
@@ -27,7 +32,9 @@ function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, se
27
32
  staffgroups.push(staffgroup);
28
33
  renderer.paper.closeGroup()
29
34
  } else if (abcLine.nonMusic) {
30
- renderer.paper.openGroup()
35
+ if (classes.shouldAddClasses)
36
+ groupClasses.klass = "abcjs-non-music"
37
+ renderer.paper.openGroup(groupClasses)
31
38
  nonMusic(renderer, abcLine.nonMusic, selectables);
32
39
  renderer.paper.closeGroup()
33
40
  }
@@ -35,7 +42,9 @@ function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, se
35
42
 
36
43
  classes.reset();
37
44
  if (abcTune.bottomText && abcTune.bottomText.rows && abcTune.bottomText.rows.length > 0) {
38
- renderer.paper.openGroup()
45
+ if (classes.shouldAddClasses)
46
+ groupClasses.klass = "abcjs-meta-bottom"
47
+ renderer.paper.openGroup(groupClasses)
39
48
  renderer.moveY(24); // TODO-PER: Empirically discovered. What variable should this be?
40
49
  nonMusic(renderer, abcTune.bottomText, selectables);
41
50
  renderer.paper.closeGroup()
@@ -8,12 +8,14 @@ function nonMusic(renderer, obj, selectables) {
8
8
  renderer.absolutemoveY(row.absmove);
9
9
  } else if (row.move) {
10
10
  renderer.moveY(row.move);
11
- } else if (row.text) {
11
+ } else if (row.text || row.phrases) {
12
12
  var x = row.left ? row.left : 0;
13
13
  var el = renderText(renderer, {
14
14
  x: x,
15
15
  y: renderer.y,
16
16
  text: row.text,
17
+ phrases: row.phrases,
18
+ 'dominant-baseline': row['dominant-baseline'],
17
19
  type: row.font,
18
20
  klass: row.klass,
19
21
  name: row.name,
@@ -27,7 +27,7 @@ function drawRelativeElement(renderer, params, bartop) {
27
27
  case "tabNumber":
28
28
  var hAnchor = "middle";
29
29
  var tabFont = "tabnumberfont";
30
- var tabClass = 'tab-number';
30
+ var tabClass = 'abcjs-tab-number';
31
31
  if (params.isGrace) {
32
32
  tabFont = "tabgracefont";
33
33
  y += 2.5;
@@ -16,7 +16,7 @@ function drawTempo(renderer, params) {
16
16
  var text;
17
17
  var size;
18
18
  if (params.tempo.preString) {
19
- text = renderText(renderer, { x: x, y: y, text: params.tempo.preString, type: 'tempofont', klass: 'abcjs-tempo', anchor: "start", noClass: true, "dominant-baseline": "ideographic", name: "pre" }, true);
19
+ text = renderText(renderer, { x: x, y: y, text: params.tempo.preString, type: 'tempofont', klass: 'abcjs-tempo', anchor: "start", noClass: true, name: "pre" }, true);
20
20
  size = renderer.controller.getTextSize.calc(params.tempo.preString, 'tempofont', 'tempo', text);
21
21
  var preWidth = size.width;
22
22
  var charWidth = preWidth / params.tempo.preString.length; // Just get some average number to increase the spacing.
@@ -2,6 +2,14 @@ var roundNumber = require("./round-number");
2
2
 
3
3
  function renderText(renderer, params, alreadyInGroup) {
4
4
  var y = params.y;
5
+
6
+ // TODO-PER: Probably need to merge the regular text and rich text better. At the least, rich text loses the font box.
7
+ if (params.phrases) {
8
+ //richTextLine = function (phrases, x, y, klass, anchor, target)
9
+ var elem = renderer.paper.richTextLine(params.phrases, params.x, params.y, params.klass, params.anchor);
10
+ return elem;
11
+ }
12
+
5
13
  if (params.lane) {
6
14
  var laneMargin = params.dim.font.size * 0.25;
7
15
  y += (params.dim.font.size + laneMargin) * params.lane;
@@ -15,6 +23,8 @@ function renderText(renderer, params, alreadyInGroup) {
15
23
  hash = renderer.controller.getFontAndAttr.calc(params.type, params.klass);
16
24
  if (params.anchor)
17
25
  hash.attr["text-anchor"] = params.anchor;
26
+ if (params['dominant-baseline'])
27
+ hash.attr["dominant-baseline"] = params['dominant-baseline'];
18
28
  hash.attr.x = params.x;
19
29
  hash.attr.y = y;
20
30
  if (!params.centerVertically)
@@ -17,6 +17,7 @@ var GetFontAndAttr = require('./helpers/get-font-and-attr');
17
17
  var GetTextSize = require('./helpers/get-text-size');
18
18
  var draw = require('./draw/draw');
19
19
  var tablatures = require('../api/abc_tablatures');
20
+ var findSelectableElement = require('./interactive/find-selectable-element');
20
21
 
21
22
  /**
22
23
  * @class
@@ -32,6 +33,7 @@ var tablatures = require('../api/abc_tablatures');
32
33
  */
33
34
  var EngraverController = function (paper, params) {
34
35
  params = params || {};
36
+ this.findSelectableElement = findSelectableElement;
35
37
  this.oneSvgPerLine = params.oneSvgPerLine;
36
38
  this.selectionColor = params.selectionColor;
37
39
  this.dragColor = params.dragColor ? params.dragColor : params.selectionColor;
@@ -40,6 +42,7 @@ var EngraverController = function (paper, params) {
40
42
  this.responsive = params.responsive;
41
43
  this.space = 3 * spacing.SPACE;
42
44
  this.initialClef = params.initialClef;
45
+ this.timeBasedLayout = params.timeBasedLayout;
43
46
  this.expandToWidest = !!params.expandToWidest;
44
47
  this.scale = params.scale ? parseFloat(params.scale) : 0;
45
48
  this.classes = new Classes({ shouldAddClasses: params.add_classes });
@@ -65,6 +68,8 @@ var EngraverController = function (paper, params) {
65
68
  this.renderer.showDebug = params.showDebug;
66
69
  if (params.jazzchords)
67
70
  this.jazzchords = params.jazzchords;
71
+ if (params.accentAbove)
72
+ this.accentAbove = params.accentAbove;
68
73
  if (params.germanAlphabet)
69
74
  this.germanAlphabet = params.germanAlphabet;
70
75
  if (params.lineThickness)
@@ -123,12 +128,13 @@ EngraverController.prototype.getMeasureWidths = function (abcTune) {
123
128
  this.reset();
124
129
  this.getFontAndAttr = new GetFontAndAttr(abcTune.formatting, this.classes);
125
130
  this.getTextSize = new GetTextSize(this.getFontAndAttr, this.renderer.paper);
131
+ var origJazzChords = this.jazzchords
126
132
 
127
133
  this.setupTune(abcTune, 0);
128
134
  this.constructTuneElements(abcTune);
129
135
  // layout() sets the x-coordinate of the abcTune element here:
130
136
  // abcTune.lines[0].staffGroup.voices[0].children[0].x
131
- layout(this.renderer, abcTune, 0, this.space);
137
+ layout(this.renderer, abcTune, 0, this.space, this.timeBasedLayout);
132
138
 
133
139
  var ret = [];
134
140
  var section;
@@ -170,6 +176,7 @@ EngraverController.prototype.getMeasureWidths = function (abcTune) {
170
176
  } else
171
177
  needNewSection = true;
172
178
  }
179
+ this.jazzchords = origJazzChords
173
180
  return ret;
174
181
  };
175
182
 
@@ -178,6 +185,8 @@ EngraverController.prototype.setupTune = function (abcTune, tuneNumber) {
178
185
 
179
186
  if (abcTune.formatting.jazzchords !== undefined)
180
187
  this.jazzchords = abcTune.formatting.jazzchords;
188
+ if (abcTune.formatting.accentAbove !== undefined)
189
+ this.accentAbove = abcTune.formatting.accentAbove;
181
190
 
182
191
  this.renderer.newTune(abcTune);
183
192
  this.engraver = new AbstractEngraver(this.getTextSize, tuneNumber, {
@@ -187,6 +196,8 @@ EngraverController.prototype.setupTune = function (abcTune, tuneNumber) {
187
196
  percmap: abcTune.formatting.percmap,
188
197
  initialClef: this.initialClef,
189
198
  jazzchords: this.jazzchords,
199
+ timeBasedLayout: this.timeBasedLayout,
200
+ accentAbove: this.accentAbove,
190
201
  germanAlphabet: this.germanAlphabet
191
202
  });
192
203
  this.engraver.setStemHeight(this.renderer.spacing.stemHeight);
@@ -206,7 +217,7 @@ EngraverController.prototype.setupTune = function (abcTune, tuneNumber) {
206
217
  };
207
218
 
208
219
  EngraverController.prototype.constructTuneElements = function (abcTune) {
209
- 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);
220
+ 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);
210
221
 
211
222
  // Generate the raw staff line data
212
223
  var i;
@@ -233,43 +244,77 @@ EngraverController.prototype.constructTuneElements = function (abcTune) {
233
244
  abcLine.nonMusic = new Separator(abcLine.separator.spaceAbove, abcLine.separator.lineLength, abcLine.separator.spaceBelow);
234
245
  }
235
246
  }
236
- abcTune.bottomText = new BottomText(abcTune.metaText, this.width, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.getTextSize);
247
+ abcTune.bottomText = new BottomText(abcTune.metaText, this.width, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.classes.shouldAddClasses, this.getTextSize);
237
248
  };
238
249
 
239
250
  EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOffset) {
240
- var scale = this.setupTune(abcTune, tuneNumber);
241
251
 
252
+ var origJazzChords = this.jazzchords
253
+ var scale = this.setupTune(abcTune, tuneNumber);
254
+
242
255
  // Create all of the element objects that will appear on the page.
243
256
  this.constructTuneElements(abcTune);
244
-
257
+
258
+ //Set the top text now that we know the width
259
+
245
260
  // Do all the positioning, both horizontally and vertically
246
- var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest);
247
-
261
+ var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest, this.timeBasedLayout);
262
+
248
263
  //Set the top text now that we know the width
249
- if (this.expandToWidest && maxWidth > this.width+1) {
250
- 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);
264
+ if (this.expandToWidest && maxWidth > this.width + 1) {
265
+
266
+ 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);
267
+
268
+ if ((abcTune.lines)&&(abcTune.lines.length > 0)){
269
+ var nlines = abcTune.lines.length;
270
+
271
+ for (var i=0;i<nlines;++i){
272
+ var entry = abcTune.lines[i];
273
+ if (entry.nonMusic){
274
+ if ((entry.nonMusic.rows) && (entry.nonMusic.rows.length > 0)){
275
+ var nRows = entry.nonMusic.rows.length;
276
+ for (var j=0;j<nRows;++j){
277
+ var thisRow = entry.nonMusic.rows[j];
278
+ // Recenter the element if it's a subtitle or centered text
279
+ if (thisRow.left){
280
+ if (entry.subtitle){
281
+ thisRow.left = (maxWidth/2) + this.renderer.padding.left;
282
+ } else {
283
+ if ((entry.text)&&(entry.text.length>0)){
284
+ if (entry.text[0].center){
285
+ thisRow.left = (maxWidth/2) + this.renderer.padding.left;
286
+ }
287
+ }
288
+ }
289
+ }
290
+ }
291
+ }
292
+ }
293
+ }
294
+ }
251
295
  }
252
296
 
253
297
  // Deal with tablature for staff
254
298
  if (abcTune.tablatures) {
255
- tablatures.layoutTablatures(this.renderer, abcTune);
299
+ tablatures.layoutTablatures(this.renderer, abcTune);
256
300
  }
257
-
301
+
258
302
  // Do all the writing to the SVG
259
303
  var ret = draw(this.renderer, this.classes, abcTune, this.width, maxWidth, this.responsive, scale, this.selectTypes, tuneNumber, lineOffset);
260
304
  this.staffgroups = ret.staffgroups;
261
305
  this.selectables = ret.selectables;
262
-
263
306
  if (this.oneSvgPerLine) {
264
- var div = this.renderer.paper.svg.parentNode
265
- this.svgs = splitSvgIntoLines(this.renderer, div, abcTune.metaText.title, this.responsive)
307
+ var div = this.renderer.paper.svg.parentNode;
308
+ this.svgs = splitSvgIntoLines(this.renderer, div, abcTune.metaText.title, this.responsive, scale);
266
309
  } else {
267
- this.svgs = [this.renderer.paper.svg];
310
+ this.svgs = [this.renderer.paper.svg];
268
311
  }
269
312
  setupSelection(this, this.svgs);
313
+
314
+ this.jazzchords = origJazzChords
270
315
  };
271
316
 
272
- function splitSvgIntoLines(renderer, output, title, responsive) {
317
+ function splitSvgIntoLines(renderer, output, title, responsive, scale) {
273
318
  // Each line is a top level <g> in the svg. To split it into separate
274
319
  // svgs iterate through each of those and put them in a new svg. Since
275
320
  // they are placed absolutely, the viewBox needs to be manipulated to
@@ -294,7 +339,7 @@ function splitSvgIntoLines(renderer, output, title, responsive) {
294
339
  var wrapper = document.createElement("div");
295
340
  var divStyles = "overflow: hidden;"
296
341
  if (responsive !== 'resize')
297
- divStyles += "height:" + height + "px;"
342
+ divStyles += "height:" + (height * scale) + "px;"
298
343
  wrapper.setAttribute("style", divStyles)
299
344
  var svg = duplicateSvg(source)
300
345
  var fullTitle = "Sheet Music for \"" + title + "\" section " + (i + 1)
@@ -79,7 +79,7 @@ Classes.prototype.generate = function (c) {
79
79
  return "";
80
80
  var ret = [];
81
81
  if (c && c.length > 0) ret.push(c);
82
- if (c === "tab-number") // TODO-PER-HACK! straighten out the tablature
82
+ if (c === "abcjs-tab-number") // TODO-PER-HACK! straighten out the tablature
83
83
  return ret.join(' ')
84
84
  if (c === "text instrument-name")
85
85
  return "abcjs-text abcjs-instrument-name"
@@ -14,6 +14,13 @@ GetFontAndAttr.prototype.updateFonts = function (fontOverrides) {
14
14
  this.formatting.vocalfont = fontOverrides.vocalfont;
15
15
  };
16
16
 
17
+ GetFontAndAttr.prototype.getFamily = function (type) {
18
+ if (type[0] === '"' && type[type.length-1] === '"') {
19
+ return type.substring(1, type.length-1)
20
+ }
21
+ return type
22
+ };
23
+
17
24
  GetFontAndAttr.prototype.calc = function (type, klass) {
18
25
  var font;
19
26
  if (typeof type === 'string') {
@@ -30,7 +37,7 @@ GetFontAndAttr.prototype.calc = function (type, klass) {
30
37
 
31
38
  var attr = {
32
39
  "font-size": font.size, 'font-style': font.style,
33
- "font-family": font.face, 'font-weight': font.weight, 'text-decoration': font.decoration,
40
+ "font-family": this.getFamily(font.face), 'font-weight': font.weight, 'text-decoration': font.decoration,
34
41
  'class': this.classes.generate(klass)
35
42
  };
36
43
  return { font: font, attr: attr };
@@ -11,6 +11,13 @@ GetTextSize.prototype.attr = function (type, klass) {
11
11
  return this.getFontAndAttr.calc(type, klass);
12
12
  };
13
13
 
14
+ GetTextSize.prototype.getFamily = function (type) {
15
+ if (type[0] === '"' && type[type.length-1] === '"') {
16
+ return type.substring(1, type.length-1)
17
+ }
18
+ return type
19
+ };
20
+
14
21
  GetTextSize.prototype.calc = function (text, type, klass, el) {
15
22
  var hash;
16
23
  // This can be passed in either a string or a font. If it is a string it names one of the standard fonts.
@@ -28,7 +35,7 @@ GetTextSize.prototype.calc = function (text, type, klass, el) {
28
35
  attr: {
29
36
  "font-size": type.size,
30
37
  "font-style": type.style,
31
- "font-family": type.face,
38
+ "font-family": this.getFamily(type.face),
32
39
  "font-weight": type.weight,
33
40
  "text-decoration": type.decoration,
34
41
  "class": this.getFontAndAttr.classes.generate(klass)
@@ -0,0 +1,50 @@
1
+ function findNumber(klass, match, target, name) {
2
+ if (klass.indexOf(match) === 0) {
3
+ var value = klass.replace(match, '');
4
+ var num = parseInt(value, 10);
5
+ if ('' + num === value)
6
+ target[name] = num;
7
+ }
8
+ }
9
+
10
+ function createAnalysis(target, ev) {
11
+ var classes = [];
12
+ if (target.absEl.elemset) {
13
+ var classObj = {};
14
+ for (var j = 0; j < target.absEl.elemset.length; j++) {
15
+ var es = target.absEl.elemset[j];
16
+ if (es) {
17
+ var klass = es.getAttribute("class").split(' ');
18
+ for (var k = 0; k < klass.length; k++)
19
+ classObj[klass[k]] = true;
20
+ }
21
+ }
22
+ for (var kk = 0; kk < Object.keys(classObj).length; kk++)
23
+ classes.push(Object.keys(classObj)[kk]);
24
+ }
25
+ var analysis = {};
26
+ for (var ii = 0; ii < classes.length; ii++) {
27
+ findNumber(classes[ii], "abcjs-v", analysis, "voice");
28
+ findNumber(classes[ii], "abcjs-l", analysis, "line");
29
+ findNumber(classes[ii], "abcjs-m", analysis, "measure");
30
+ }
31
+ if (target.staffPos)
32
+ analysis.staffPos = target.staffPos;
33
+ var closest = ev.target;
34
+ while (closest && closest.dataset && !closest.dataset.name && closest.tagName.toLowerCase() !== 'svg')
35
+ closest = closest.parentNode;
36
+ var parent = ev.target;
37
+ while (parent && parent.dataset && !parent.dataset.index && parent.tagName.toLowerCase() !== 'svg')
38
+ parent = parent.parentNode;
39
+ if (parent && parent.dataset) {
40
+ analysis.name = parent.dataset.name;
41
+ analysis.clickedName = closest.dataset.name;
42
+ analysis.parentClasses = parent.classList;
43
+ }
44
+ if (closest && closest.classList)
45
+ analysis.clickedClasses = closest.classList;
46
+ analysis.selectableElement = target.svgEl;
47
+ return {classes: classes, analysis: analysis}
48
+ }
49
+
50
+ module.exports = createAnalysis;
@@ -0,0 +1,24 @@
1
+ var createAnalysis = require('./create-analysis');
2
+
3
+ function findSelectableElement(event) {
4
+ var selectable = event
5
+ while (selectable && selectable.attributes && selectable.tagName.toLowerCase() !== 'svg' && !selectable.attributes.selectable) {
6
+ selectable = selectable.parentNode
7
+ }
8
+ if (selectable && selectable.attributes && selectable.attributes.selectable) {
9
+ var index = selectable.attributes['data-index'].nodeValue
10
+ if (index) {
11
+ index = parseInt(index, 10)
12
+ if (index >= 0 && index < this.selectables.length) {
13
+ var element = this.selectables[index]
14
+ var ret = createAnalysis(element, event)
15
+ ret.index = index
16
+ ret.element = element
17
+ return ret
18
+ }
19
+ }
20
+ }
21
+ return null
22
+ }
23
+
24
+ module.exports = findSelectableElement;
@@ -1,4 +1,5 @@
1
1
  var spacing = require('../helpers/spacing');
2
+ var createAnalysis = require('./create-analysis');
2
3
 
3
4
  function setupSelection(engraver, svgs) {
4
5
  engraver.rangeHighlight = rangeHighlight;
@@ -339,58 +340,17 @@ function setSelection(dragIndex) {
339
340
  }
340
341
  }
341
342
 
343
+
342
344
  function notifySelect(target, dragStep, dragMax, dragIndex, ev) {
343
- var classes = [];
344
- if (target.absEl.elemset) {
345
- var classObj = {};
346
- for (var j = 0; j < target.absEl.elemset.length; j++) {
347
- var es = target.absEl.elemset[j];
348
- if (es) {
349
- var klass = es.getAttribute("class").split(' ');
350
- for (var k = 0; k < klass.length; k++)
351
- classObj[klass[k]] = true;
352
- }
353
- }
354
- for (var kk = 0; kk < Object.keys(classObj).length; kk++)
355
- classes.push(Object.keys(classObj)[kk]);
356
- }
357
- var analysis = {};
358
- for (var ii = 0; ii < classes.length; ii++) {
359
- findNumber(classes[ii], "abcjs-v", analysis, "voice");
360
- findNumber(classes[ii], "abcjs-l", analysis, "line");
361
- findNumber(classes[ii], "abcjs-m", analysis, "measure");
362
- }
363
- if (target.staffPos)
364
- analysis.staffPos = target.staffPos;
365
- var closest = ev.target;
366
- while (closest && closest.dataset && !closest.dataset.name && closest.tagName.toLowerCase() !== 'svg')
367
- closest = closest.parentNode;
368
- var parent = ev.target;
369
- while (parent && parent.dataset && !parent.dataset.index && parent.tagName.toLowerCase() !== 'svg')
370
- parent = parent.parentNode;
371
- if (parent && parent.dataset) {
372
- analysis.name = parent.dataset.name;
373
- analysis.clickedName = closest.dataset.name;
374
- analysis.parentClasses = parent.classList;
375
- }
376
- if (closest && closest.classList)
377
- analysis.clickedClasses = closest.classList;
378
- analysis.selectableElement = target.svgEl;
345
+ var ret = createAnalysis(target, ev)
346
+ var classes = ret.classes
347
+ var analysis = ret.analysis
379
348
 
380
349
  for (var i = 0; i < this.listeners.length; i++) {
381
350
  this.listeners[i](target.absEl.abcelem, target.absEl.tuneNumber, classes.join(' '), analysis, { step: dragStep, max: dragMax, index: dragIndex, setSelection: setSelection.bind(this) }, ev);
382
351
  }
383
352
  }
384
353
 
385
- function findNumber(klass, match, target, name) {
386
- if (klass.indexOf(match) === 0) {
387
- var value = klass.replace(match, '');
388
- var num = parseInt(value, 10);
389
- if ('' + num === value)
390
- target[name] = num;
391
- }
392
- }
393
-
394
354
  function clearSelection() {
395
355
  for (var i = 0; i < this.selected.length; i++) {
396
356
  this.selected[i].unhighlight(undefined, this.renderer.foregroundColor);
@@ -0,0 +1,83 @@
1
+ var getLeftEdgeOfStaff = require('./get-left-edge-of-staff');
2
+
3
+ function layoutInGrid(renderer, staffGroup, timeBasedLayout) {
4
+ var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
5
+ var ret = getTotalDuration(staffGroup, timeBasedLayout.minPadding)
6
+ var totalDuration = ret.totalDuration
7
+ var minSpacing = ret.minSpacing
8
+ var totalWidth = minSpacing * totalDuration
9
+ if (timeBasedLayout.minWidth)
10
+ totalWidth = Math.max(totalWidth, timeBasedLayout.minWidth)
11
+ var leftAlignPadding = timeBasedLayout.minPadding ? timeBasedLayout.minPadding/2 : 2 // If the padding isn't specified still give it some
12
+
13
+ staffGroup.startx = leftEdge
14
+ staffGroup.w = totalWidth + leftEdge
15
+ for (var i = 0; i < staffGroup.voices.length; i++) {
16
+ var voice = staffGroup.voices[i]
17
+ voice.startx = leftEdge
18
+ voice.w = totalWidth + leftEdge
19
+
20
+ var x = leftEdge
21
+ var afterFixedLeft = false
22
+ var durationUnit = 0
23
+ for (var j = 0; j < voice.children.length; j++) {
24
+ var child = voice.children[j]
25
+ if (!afterFixedLeft) {
26
+ if (child.duration !== 0) {
27
+ // We got to the first music element on the line
28
+ afterFixedLeft = true
29
+ durationUnit = (totalWidth + leftEdge - x) / totalDuration
30
+ staffGroup.gridStart = x
31
+ } else {
32
+ // We are still doing the preliminary stuff - clef, time sig, etc.
33
+ child.x = x
34
+ x += child.w + child.minspacing
35
+ }
36
+ }
37
+ if (afterFixedLeft) {
38
+ if (timeBasedLayout.align === 'center')
39
+ child.x = x + (child.duration * durationUnit) / 2 - child.w / 2
40
+ else {
41
+ // left align with padding - but no padding for barlines, they should be right aligned.
42
+ // TODO-PER: it looks better to move bar lines one pixel to right. Not sure why.
43
+ if (child.duration === 0) {
44
+ child.x = x + 1 - child.w
45
+ } else {
46
+ // 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.
47
+ child.x = x + leftAlignPadding - child.extraw
48
+ }
49
+ }
50
+ x += child.duration * durationUnit
51
+ }
52
+ for (var k = 0; k < child.children.length; k++) {
53
+ var grandchild = child.children[k]
54
+ // some elements don't have a dx - Tempo, for instance
55
+ var dx = grandchild.dx ? grandchild.dx : 0
56
+ grandchild.x = child.x + dx
57
+ }
58
+ }
59
+ staffGroup.gridEnd = x
60
+ }
61
+ return totalWidth
62
+ }
63
+
64
+ function getTotalDuration(staffGroup, timeBasedLayout) {
65
+ var maxSpacing = 0
66
+ var maxCount = 0
67
+ for (var i = 0; i < staffGroup.voices.length; i++) {
68
+ var count = 0
69
+ var voice = staffGroup.voices[i]
70
+ for (var j = 0; j < voice.children.length; j++) {
71
+ var element = voice.children[j]
72
+ count += element.duration
73
+ if (element.duration) {
74
+ var width = (element.w+timeBasedLayout) / element.duration
75
+ maxSpacing = Math.max(maxSpacing, width)
76
+ }
77
+ }
78
+ maxCount = Math.max(maxCount, count)
79
+ }
80
+ return { totalDuration: maxCount, minSpacing: maxSpacing}
81
+ }
82
+
83
+ module.exports = layoutInGrid;
@@ -2,8 +2,12 @@ var layoutVoice = require('./voice');
2
2
  var setUpperAndLowerElements = require('./set-upper-and-lower-elements');
3
3
  var layoutStaffGroup = require('./staff-group');
4
4
  var getLeftEdgeOfStaff = require('./get-left-edge-of-staff');
5
+ var layoutInGrid = require('./layout-in-grid');
5
6
 
6
- var layout = function (renderer, abctune, width, space, expandToWidest) {
7
+ // This sets the "x" attribute on all the children in abctune.lines
8
+ // It also sets the "w" and "startx" attributes on "voices"
9
+ // It also sets the "w" and "startx" attributes on "voices.children"
10
+ var layout = function (renderer, abctune, width, space, expandToWidest, timeBasedLayout) {
7
11
  var i;
8
12
  var abcLine;
9
13
  // Adjust the x-coordinates to their absolute positions
@@ -12,7 +16,11 @@ var layout = function (renderer, abctune, width, space, expandToWidest) {
12
16
  abcLine = abctune.lines[i];
13
17
  if (abcLine.staff) {
14
18
  // console.log("=== line", i)
15
- var thisWidth = setXSpacing(renderer, maxWidth, space, abcLine.staffGroup, abctune.formatting, i === abctune.lines.length - 1, false);
19
+ var thisWidth;
20
+ if (timeBasedLayout !== undefined)
21
+ thisWidth = layoutInGrid(renderer, abcLine.staffGroup, timeBasedLayout);
22
+ else
23
+ thisWidth = setXSpacing(renderer, maxWidth, space, abcLine.staffGroup, abctune.formatting, i === abctune.lines.length - 1, false);
16
24
  // console.log(thisWidth, maxWidth)
17
25
  if (Math.round(thisWidth) > Math.round(maxWidth)) { // to take care of floating point weirdness
18
26
  maxWidth = thisWidth
@@ -46,39 +54,36 @@ var layout = function (renderer, abctune, width, space, expandToWidest) {
46
54
  var setXSpacing = function (renderer, width, space, staffGroup, formatting, isLastLine, debug) {
47
55
  var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
48
56
  var newspace = space;
57
+ //dumpGroup("before", staffGroup)
49
58
  for (var it = 0; it < 8; it++) { // 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.)
50
59
  // console.log("iteration", it)
51
- // dumpGroup("before", staffGroup)
52
- var ret = layoutStaffGroup(newspace, renderer, debug, staffGroup, leftEdge);
53
- // dumpGroup("after",staffGroup)
60
+ var ret = layoutStaffGroup(newspace, renderer.minPadding, debug, staffGroup, leftEdge);
54
61
  newspace = calcHorizontalSpacing(isLastLine, formatting.stretchlast, width + renderer.padding.left, staffGroup.w, newspace, ret.spacingUnits, ret.minSpace, renderer.padding.left + renderer.padding.right);
55
62
  if (debug)
56
63
  console.log("setXSpace", it, staffGroup.w, newspace, staffGroup.minspace);
57
64
  if (newspace === null) break;
58
65
  }
66
+ //dumpGroup("after",staffGroup)
59
67
  centerWholeRests(staffGroup.voices);
60
68
  return staffGroup.w - leftEdge
61
69
  };
62
70
 
63
- // function dumpGroup(label, staffGroup) {
64
- // var output = {
65
- // line: staffGroup.line,
66
- // w: staffGroup.w,
67
- // voice: {
68
- // i: staffGroup.voices[0].i,
69
- // minx: staffGroup.voices[0].minx,
70
- // nextx: staffGroup.voices[0].nextx,
71
- // spacingduration: staffGroup.voices[0].spacingduration,
72
- // w: staffGroup.voices[0].w,
73
- // children: [],
74
- // }
75
- // }
76
- // for (var i = 0; i < staffGroup.voices[0].children.length; i++) {
77
- // var child = staffGroup.voices[0].children[i]
78
- // output.voice.children.push({ fixedW: child.fixed.w, w: child.w, x: child.x, type: child.type })
79
- // }
80
- // console.log(label,output)
81
- // }
71
+ function replacer(key, value) {
72
+ // Filtering out properties
73
+ if (key === 'parent') {
74
+ return 'parent';
75
+ }
76
+ if (key === 'beam') {
77
+ return 'beam';
78
+ }
79
+ return value;
80
+ }
81
+
82
+ function dumpGroup(label, staffGroup) {
83
+ console.log("=================== " + label + " =========================")
84
+ console.log(staffGroup)
85
+ console.log(JSON.stringify(staffGroup, replacer, "\t"))
86
+ }
82
87
 
83
88
  function calcHorizontalSpacing(isLastLine, stretchLast, targetWidth, lineWidth, spacing, spacingUnits, minSpace, padding) {
84
89
  if (isLastLine) {
@@ -87,6 +87,8 @@ var setUpperAndLowerElements = function (renderer, staffGroup) {
87
87
  if (addedSpace > 0)
88
88
  staff.top += addedSpace;
89
89
  }
90
+ staff.top += renderer.spacing.staffTopMargin / spacing.STEP
91
+
90
92
  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.
91
93
 
92
94
  // Now we need a little margin on the top, so we'll just throw that in.