abcjs 6.2.2 → 6.2.3

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 (36) hide show
  1. package/RELEASE.md +39 -0
  2. package/abc2xml_239/abc2xml.html +769 -0
  3. package/abc2xml_239/abc2xml.py +2248 -0
  4. package/abc2xml_239/abc2xml_changelog.html +124 -0
  5. package/abc2xml_239/lazy-river.abc +26 -0
  6. package/abc2xml_239/lazy-river.xml +3698 -0
  7. package/abc2xml_239/mean-to-me.abc +22 -0
  8. package/abc2xml_239/mean-to-me.xml +2954 -0
  9. package/abc2xml_239/pyparsing.py +3672 -0
  10. package/abc2xml_239/pyparsing.pyc +0 -0
  11. package/dist/abcjs-basic-min.js +2 -2
  12. package/dist/abcjs-basic.js +160 -71
  13. package/dist/abcjs-basic.js.map +1 -1
  14. package/dist/abcjs-plugin-min.js +2 -2
  15. package/package.json +1 -1
  16. package/src/api/abc_tablatures.js +3 -0
  17. package/src/api/abc_tunebook_svg.js +5 -3
  18. package/src/parse/abc_parse_music.js +18 -53
  19. package/src/parse/abc_parse_settings.js +165 -0
  20. package/src/synth/create-synth.js +4 -0
  21. package/src/synth/place-note.js +6 -0
  22. package/src/synth/play-event.js +7 -5
  23. package/src/tablatures/tab-absolute-elements.js +3 -2
  24. package/src/tablatures/tab-renderer.js +2 -1
  25. package/src/test/abc_parser_lint.js +12 -12
  26. package/src/write/creation/abstract-engraver.js +6 -0
  27. package/src/write/creation/decoration.js +2 -0
  28. package/src/write/creation/glyphs.js +1 -1
  29. package/src/write/draw/glissando.js +1 -0
  30. package/src/write/draw/set-paper-size.js +1 -1
  31. package/src/write/draw/tie.js +9 -1
  32. package/src/write/engraver-controller.js +7 -1
  33. package/src/write/interactive/selection.js +6 -0
  34. package/src/write/layout/layout.js +33 -3
  35. package/types/index.d.ts +28 -20
  36. package/version.js +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abcjs",
3
- "version": "6.2.2",
3
+ "version": "6.2.3",
4
4
  "description": "Renderer for abc music notation",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -84,6 +84,9 @@ var abcTablatures = {
84
84
  // plugin.init(tune, tuneNumber, args, ii);
85
85
  returned.push(pluginInstance);
86
86
  nbPlugins++;
87
+ } else if (instrument === '') {
88
+ // create a placeholder - there is no tab for this staff
89
+ returned.push(null)
87
90
  } else {
88
91
  // unknown tab plugin
89
92
  //this.emit_error('Undefined tablature plugin: ' + tabName)
@@ -117,12 +117,12 @@ var renderAbc = function(output, abc, parserParams, engraverParams, renderParams
117
117
  div.setAttribute("style", "visibility: hidden;");
118
118
  document.body.appendChild(div);
119
119
  }
120
- if (params.afterParsing)
121
- params.afterParsing(tune, tuneNumber, abcString);
122
120
  if (!removeDiv && params.wrap && params.staffwidth) {
123
- tune = doLineWrapping(div, tune, tuneNumber, abcString, params);
121
+ tune = doLineWrapping(div, tune, tuneNumber, abcString, params);
124
122
  return tune;
125
123
  }
124
+ if (params.afterParsing)
125
+ params.afterParsing(tune, tuneNumber, abcString);
126
126
  renderOne(div, tune, params, tuneNumber, 0);
127
127
  if (removeDiv)
128
128
  div.parentNode.removeChild(div);
@@ -145,6 +145,8 @@ function doLineWrapping(div, tune, tuneNumber, abcString, params) {
145
145
  if (warnings)
146
146
  tune.warnings = warnings;
147
147
  }
148
+ if (params.afterParsing)
149
+ params.afterParsing(tune, tuneNumber, abcString);
148
150
  renderOne(div, tune, ret.revisedParams, tuneNumber, 0);
149
151
  tune.explanation = ret.explanation;
150
152
  return tune;
@@ -9,6 +9,20 @@ var tune;
9
9
  var tuneBuilder;
10
10
  var header;
11
11
 
12
+ var {
13
+ legalAccents,
14
+ volumeDecorations,
15
+ dynamicDecorations,
16
+ accentPseudonyms,
17
+ accentDynamicPseudonyms,
18
+ nonDecorations,
19
+ durations,
20
+ pitches,
21
+ rests,
22
+ accMap,
23
+ tripletQ
24
+ } = require('./abc_parse_settings')
25
+
12
26
  var MusicParser = function(_tokenizer, _warn, _multilineVars, _tune, _tuneBuilder, _header) {
13
27
  tokenizer = _tokenizer;
14
28
  warn = _warn;
@@ -76,7 +90,6 @@ var MusicParser = function(_tokenizer, _warn, _multilineVars, _tune, _tuneBuilde
76
90
  // double-quote: chord symbol
77
91
  // less-than, greater-than, slash: duration
78
92
  // back-tick, space, tab: space
79
- var nonDecorations = "ABCDEFGabcdefgxyzZ[]|^_{"; // use this to prescreen so we don't have to look for a decoration at every note.
80
93
 
81
94
  var isInTie = function(multilineVars, overlayLevel, el) {
82
95
  if (multilineVars.inTie[overlayLevel] === undefined)
@@ -540,15 +553,8 @@ MusicParser.prototype.parseMusic = function(line) {
540
553
  // Create a warning if this is not a displayable duration.
541
554
  // The first item on a line is a regular note value, each item after that represents a dot placed after the previous note.
542
555
  // Only durations less than a whole note are tested because whole note durations have some tricky rules.
543
- var durations = [
544
- 0.5, 0.75, 0.875, 0.9375, 0.96875, 0.984375,
545
- 0.25, 0.375, 0.4375, 0.46875, 0.484375, 0.4921875,
546
- 0.125, 0.1875, 0.21875, 0.234375, 0.2421875, 0.24609375,
547
- 0.0625, 0.09375, 0.109375, 0.1171875, 0.12109375, 0.123046875,
548
- 0.03125, 0.046875, 0.0546875, 0.05859375, 0.060546875, 0.0615234375,
549
- 0.015625, 0.0234375, 0.02734375, 0.029296875, 0.0302734375, 0.03076171875,
550
- ];
551
- if (el.duration < 1 && durations.indexOf(el.duration) === -1 && el.duration !== 0) {
556
+
557
+ if (el.duration < 1 && durations.indexOf(el.duration) === -1 && el.duration !== 0) {
552
558
  if (!el.rest || el.rest.type !== 'spacer')
553
559
  warn("Duration not representable: " + line.substring(startI, i), line, i);
554
560
  }
@@ -717,35 +723,8 @@ function durationOfMeasure(multilineVars) {
717
723
  return parseInt(meter.value[0].num, 10) / parseInt(meter.value[0].den, 10);
718
724
  }
719
725
 
720
- var legalAccents = [
721
- "trill", "lowermordent", "uppermordent", "mordent", "pralltriller", "accent",
722
- "fermata", "invertedfermata", "tenuto", "0", "1", "2", "3", "4", "5", "+", "wedge",
723
- "open", "thumb", "snap", "turn", "roll", "breath", "shortphrase", "mediumphrase", "longphrase",
724
- "segno", "coda", "D.S.", "D.C.", "fine", "beambr1", "beambr2",
725
- "slide", "marcato",
726
- "upbow", "downbow", "/", "//", "///", "////", "trem1", "trem2", "trem3", "trem4",
727
- "turnx", "invertedturn", "invertedturnx", "trill(", "trill)", "arpeggio", "xstem", "mark", "umarcato",
728
- "style=normal", "style=harmonic", "style=rhythm", "style=x", "style=triangle", "D.C.alcoda", "D.C.alfine", "D.S.alcoda", "D.S.alfine", "editorial", "courtesy"
729
- ];
730
-
731
- var volumeDecorations = [
732
- "p", "pp", "f", "ff", "mf", "mp", "ppp", "pppp", "fff", "ffff", "sfz"
733
- ];
734
-
735
- var dynamicDecorations = [
736
- "crescendo(", "crescendo)", "diminuendo(", "diminuendo)", "glissando(", "glissando)"
737
- ];
738
-
739
- var accentPseudonyms = [
740
- ["<", "accent"], [">", "accent"], ["tr", "trill"],
741
- ["plus", "+"], [ "emphasis", "accent"],
742
- [ "^", "umarcato" ], [ "marcato", "umarcato" ]
743
- ];
744
-
745
- var accentDynamicPseudonyms = [
746
- ["<(", "crescendo("], ["<)", "crescendo)"],
747
- [">(", "diminuendo("], [">)", "diminuendo)"]
748
- ];
726
+
727
+
749
728
 
750
729
  var letter_to_accent = function(line, i) {
751
730
  var macro = multilineVars.macros[line[i]];
@@ -877,17 +856,6 @@ var letter_to_bar = function(line, curr_pos) {
877
856
  return [ret.len+retRep.len, ret.token, retRep.token];
878
857
  };
879
858
 
880
- var tripletQ = {
881
- 2: 3,
882
- 3: 2,
883
- 4: 3,
884
- 5: 2, // TODO-PER: not handling 6/8 rhythm yet
885
- 6: 2,
886
- 7: 2, // TODO-PER: not handling 6/8 rhythm yet
887
- 8: 3,
888
- 9: 2 // TODO-PER: not handling 6/8 rhythm yet
889
- };
890
-
891
859
  var letter_to_open_slurs_and_triplets = function(line, i) {
892
860
  // consume spaces, and look for all the open parens. If there is a number after the open paren,
893
861
  // that is a triplet. Otherwise that is a slur. Collect all the slurs and the first triplet.
@@ -1050,9 +1018,6 @@ var addEndBeam = function(el) {
1050
1018
  return el;
1051
1019
  };
1052
1020
 
1053
- var pitches = {A: 5, B: 6, C: 0, D: 1, E: 2, F: 3, G: 4, a: 12, b: 13, c: 7, d: 8, e: 9, f: 10, g: 11};
1054
- var rests = {x: 'invisible', X: 'invisible-multimeasure', y: 'spacer', z: 'rest', Z: 'multimeasure' };
1055
- var accMap = { 'dblflat': '__', 'flat': '_', 'natural': '=', 'sharp': '^', 'dblsharp': '^^', 'quarterflat': '_/', 'quartersharp': '^/'};
1056
1021
  var getCoreNote = function(line, index, el, canHaveBrokenRhythm) {
1057
1022
  //var el = { startChar: index };
1058
1023
  var isComplete = function(state) {
@@ -0,0 +1,165 @@
1
+ module.exports.legalAccents = [
2
+ 'trill',
3
+ 'lowermordent',
4
+ 'uppermordent',
5
+ 'mordent',
6
+ 'pralltriller',
7
+ 'accent',
8
+ 'fermata',
9
+ 'invertedfermata',
10
+ 'tenuto',
11
+ '0',
12
+ '1',
13
+ '2',
14
+ '3',
15
+ '4',
16
+ '5',
17
+ '+',
18
+ 'wedge',
19
+ 'open',
20
+ 'thumb',
21
+ 'snap',
22
+ 'turn',
23
+ 'roll',
24
+ 'breath',
25
+ 'shortphrase',
26
+ 'mediumphrase',
27
+ 'longphrase',
28
+ 'segno',
29
+ 'coda',
30
+ 'D.S.',
31
+ 'D.C.',
32
+ 'fine',
33
+ 'beambr1',
34
+ 'beambr2',
35
+ 'slide',
36
+ 'marcato',
37
+ 'upbow',
38
+ 'downbow',
39
+ '/',
40
+ '//',
41
+ '///',
42
+ '////',
43
+ 'trem1',
44
+ 'trem2',
45
+ 'trem3',
46
+ 'trem4',
47
+ 'turnx',
48
+ 'invertedturn',
49
+ 'invertedturnx',
50
+ 'trill(',
51
+ 'trill)',
52
+ 'arpeggio',
53
+ 'xstem',
54
+ 'mark',
55
+ 'umarcato',
56
+ 'style=normal',
57
+ 'style=harmonic',
58
+ 'style=rhythm',
59
+ 'style=x',
60
+ 'style=triangle',
61
+ 'D.C.alcoda',
62
+ 'D.C.alfine',
63
+ 'D.S.alcoda',
64
+ 'D.S.alfine',
65
+ 'editorial',
66
+ 'courtesy'
67
+ ];
68
+
69
+ module.exports.volumeDecorations = [
70
+ 'p',
71
+ 'pp',
72
+ 'f',
73
+ 'ff',
74
+ 'mf',
75
+ 'mp',
76
+ 'ppp',
77
+ 'pppp',
78
+ 'fff',
79
+ 'ffff',
80
+ 'sfz'
81
+ ];
82
+
83
+ module.exports.dynamicDecorations = [
84
+ 'crescendo(',
85
+ 'crescendo)',
86
+ 'diminuendo(',
87
+ 'diminuendo)',
88
+ 'glissando(',
89
+ 'glissando)',
90
+ '~(',
91
+ '~)'
92
+ ];
93
+
94
+ module.exports.accentPseudonyms = [
95
+ ['<', 'accent'],
96
+ ['>', 'accent'],
97
+ ['tr', 'trill'],
98
+ ['plus', '+'],
99
+ ['emphasis', 'accent'],
100
+ ['^', 'umarcato'],
101
+ ['marcato', 'umarcato']
102
+ ];
103
+
104
+ module.exports.accentDynamicPseudonyms = [
105
+ ['<(', 'crescendo('],
106
+ ['<)', 'crescendo)'],
107
+ ['>(', 'diminuendo('],
108
+ ['>)', 'diminuendo)']
109
+ ];
110
+
111
+ module.exports.nonDecorations = 'ABCDEFGabcdefgxyzZ[]|^_{'; // use this to prescreen so we don't have to look for a decoration at every note.
112
+
113
+ module.exports.durations = [
114
+ 0.5, 0.75, 0.875, 0.9375, 0.96875, 0.984375, 0.25, 0.375, 0.4375, 0.46875,
115
+ 0.484375, 0.4921875, 0.125, 0.1875, 0.21875, 0.234375, 0.2421875, 0.24609375,
116
+ 0.0625, 0.09375, 0.109375, 0.1171875, 0.12109375, 0.123046875, 0.03125,
117
+ 0.046875, 0.0546875, 0.05859375, 0.060546875, 0.0615234375, 0.015625,
118
+ 0.0234375, 0.02734375, 0.029296875, 0.0302734375, 0.03076171875
119
+ ];
120
+
121
+ module.exports.pitches = {
122
+ A: 5,
123
+ B: 6,
124
+ C: 0,
125
+ D: 1,
126
+ E: 2,
127
+ F: 3,
128
+ G: 4,
129
+ a: 12,
130
+ b: 13,
131
+ c: 7,
132
+ d: 8,
133
+ e: 9,
134
+ f: 10,
135
+ g: 11
136
+ };
137
+
138
+ module.exports.rests = {
139
+ x: 'invisible',
140
+ X: 'invisible-multimeasure',
141
+ y: 'spacer',
142
+ z: 'rest',
143
+ Z: 'multimeasure'
144
+ };
145
+
146
+ module.exports.accMap = {
147
+ dblflat: '__',
148
+ flat: '_',
149
+ natural: '=',
150
+ sharp: '^',
151
+ dblsharp: '^^',
152
+ quarterflat: '_/',
153
+ quartersharp: '^/'
154
+ };
155
+
156
+ module.exports.tripletQ = {
157
+ 2: 3,
158
+ 3: 2,
159
+ 4: 3,
160
+ 5: 2, // TODO-PER: not handling 6/8 rhythm yet
161
+ 6: 2,
162
+ 7: 2, // TODO-PER: not handling 6/8 rhythm yet
163
+ 8: 3,
164
+ 9: 2 // TODO-PER: not handling 6/8 rhythm yet
165
+ };
@@ -511,6 +511,10 @@ function CreateSynth() {
511
511
  return self.audioBuffers[0];
512
512
  };
513
513
 
514
+ self.getIsRunning = function() {
515
+ return self.isRunning;
516
+ }
517
+
514
518
  /////////////// Private functions //////////////
515
519
 
516
520
  self._deviceCapable = function() {
@@ -17,6 +17,12 @@ function placeNote(outputAudioBuffer, sampleRate, sound, startArray, volumeMulti
17
17
  len = 0.005; // Have some small audible length no matter how short the note is.
18
18
  var offlineCtx = new OfflineAC(2,Math.floor((len+fadeTimeSec)*sampleRate),sampleRate);
19
19
  var noteName = pitchToNoteName[sound.pitch];
20
+ if (!soundsCache[sound.instrument]) {
21
+ // It shouldn't happen that the entire instrument cache wasn't created, but this has been seen in practice, so guard against it.
22
+ if (debugCallback)
23
+ debugCallback('placeNote skipped (instrument empty): '+sound.instrument+':'+noteName)
24
+ return Promise.resolve();
25
+ }
20
26
  var noteBufferPromise = soundsCache[sound.instrument][noteName];
21
27
 
22
28
  if (!noteBufferPromise) {
@@ -2,7 +2,7 @@ var SynthSequence = require('./synth-sequence');
2
2
  var CreateSynth = require('./create-synth');
3
3
  var activeAudioContext = require("./active-audio-context");
4
4
 
5
- function playEvent(midiPitches, midiGracePitches, millisecondsPerMeasure) {
5
+ function playEvent(midiPitches, midiGracePitches, millisecondsPerMeasure, soundFontUrl, debugCallback) {
6
6
  var sequence = new SynthSequence();
7
7
 
8
8
  for (var i = 0; i < midiPitches.length; i++) {
@@ -21,18 +21,20 @@ function playEvent(midiPitches, midiGracePitches, millisecondsPerMeasure) {
21
21
  var ac = activeAudioContext();
22
22
  if (ac.state === "suspended") {
23
23
  return ac.resume().then(function () {
24
- return doPlay(sequence, millisecondsPerMeasure);
24
+ return doPlay(sequence, millisecondsPerMeasure, soundFontUrl, debugCallback);
25
25
  });
26
26
  } else {
27
- return doPlay(sequence, millisecondsPerMeasure);
27
+ return doPlay(sequence, millisecondsPerMeasure, soundFontUrl, debugCallback);
28
28
  }
29
29
  }
30
30
 
31
- function doPlay(sequence, millisecondsPerMeasure) {
31
+ function doPlay(sequence, millisecondsPerMeasure, soundFontUrl, debugCallback) {
32
32
  var buffer = new CreateSynth();
33
33
  return buffer.init({
34
34
  sequence: sequence,
35
- millisecondsPerMeasure: millisecondsPerMeasure
35
+ millisecondsPerMeasure: millisecondsPerMeasure,
36
+ options: { soundFontUrl: soundFontUrl },
37
+ debugCallback: debugCallback,
36
38
  }).then(function () {
37
39
  return buffer.prime();
38
40
  }).then(function () {
@@ -177,10 +177,11 @@ TabAbsoluteElements.prototype.build = function (plugin,
177
177
  tabVoice,
178
178
  voiceIndex,
179
179
  staffIndex,
180
- keySig ) {
180
+ keySig,
181
+ tabVoiceIndex ) {
181
182
  var staffSize = getInitialStaffSize(staffAbsolute);
182
183
  var source = staffAbsolute[staffIndex+voiceIndex];
183
- var dest = staffAbsolute[staffSize+staffIndex+voiceIndex];
184
+ var dest = staffAbsolute[tabVoiceIndex];
184
185
  var tabPos = null;
185
186
  var defNote = null;
186
187
  if (source.children[0].abcelem.el_type != 'clef') {
@@ -233,10 +233,11 @@ TabRenderer.prototype.doLayout = function () {
233
233
  staffGroup.staffs[this.staffIndex].top += nameHeight;
234
234
  staffGroup.height += nameHeight * spacing.STEP;
235
235
  tabVoice.staff = staffGroupInfos;
236
+ var tabVoiceIndex = voices.length
236
237
  voices.splice(voices.length, 0, tabVoice);
237
238
  var keySig = checkVoiceKeySig(voices, ii + this.staffIndex);
238
239
  this.tabStaff.voices[ii] = [];
239
- this.absolutes.build(this.plugin, voices, this.tabStaff.voices[ii], ii , this.staffIndex ,keySig);
240
+ this.absolutes.build(this.plugin, voices, this.tabStaff.voices[ii], ii , this.staffIndex ,keySig, tabVoiceIndex);
240
241
  }
241
242
  linkStaffAndTabs(staffGroup.staffs); // crossreference tabs and staff
242
243
  };
@@ -51,18 +51,18 @@
51
51
  var parseCommon = require('../parse/abc_common');
52
52
  var JSONSchema = require('./jsonschema-b4');
53
53
 
54
- var ParserLint = function() {
55
- "use strict";
56
- var decorationList = { type: 'array', optional: true, items: { type: 'string', Enum: [
57
- "trill", "lowermordent", "uppermordent", "mordent", "pralltriller", "accent",
58
- "fermata", "invertedfermata", "tenuto", "0", "1", "2", "3", "4", "5", "+", "wedge",
59
- "open", "thumb", "snap", "turn", "roll", "irishroll", "breath", "shortphrase", "mediumphrase", "longphrase",
60
- "segno", "coda", "D.S.", "D.C.", "fine", "crescendo(", "crescendo)", "diminuendo(", "diminuendo)", "glissando(", "glissando)",
61
- "p", "pp", "f", "ff", "mf", "mp", "ppp", "pppp", "fff", "ffff", "sfz", "repeatbar", "repeatbar2", "slide",
62
- "upbow", "downbow", "staccato", "trem1", "trem2", "trem3", "trem4",
63
- "/", "//", "///", "////", "turnx", "invertedturn", "invertedturnx", "arpeggio", "trill(", "trill)", "xstem",
64
- "mark", "marcato", "umarcato", "D.C.alcoda", "D.C.alfine", "D.S.alcoda", "D.S.alfine", "editorial", "courtesy"
65
- ] } };
54
+ var { legalAccents } = require('../parse/abc_parse_settings');
55
+
56
+ var ParserLint = function () {
57
+ 'use strict';
58
+ var decorationList = {
59
+ type: 'array',
60
+ optional: true,
61
+ items: {
62
+ type: 'string',
63
+ Enum: legalAccents
64
+ }
65
+ };
66
66
 
67
67
  var tempoProperties = {
68
68
  duration: { type: "array", optional: true, output: "join", requires: [ 'bpm'], items: { type: "number"} },
@@ -747,6 +747,12 @@ AbstractEngraver.prototype.addNoteToAbcElement = function (abselem, elem, dot, s
747
747
  else
748
748
  p1 += 1;
749
749
  }
750
+ if (noteHead && noteHead.c === 'noteheads.triangle.quarter') {
751
+ if (dir === 'down')
752
+ p2 -= 0.7;
753
+ else
754
+ p1 -= 1.2;
755
+ }
750
756
  abselem.addRight(new RelativeElement(null, dx, 0, p1, { "type": "stem", "pitch2": p2, linewidth: width, bottom: p1 - 1 }));
751
757
  //var RelativeElement = function RelativeElement(c, dx, w, pitch, opt) {
752
758
  min = Math.min(p1, p2);
@@ -313,10 +313,12 @@ Decoration.prototype.dynamicDecoration = function (voice, decoration, abselem, p
313
313
  crescendo = { start: this.startCrescendoX, stop: abselem };
314
314
  this.startCrescendoX = undefined;
315
315
  break;
316
+ case '~(':
316
317
  case "glissando(":
317
318
  this.startGlissandoX = abselem;
318
319
  glissando = undefined;
319
320
  break;
321
+ case '~)':
320
322
  case "glissando)":
321
323
  glissando = { start: this.startGlissandoX, stop: abselem };
322
324
  this.startGlissandoX = undefined;
@@ -105,7 +105,7 @@ glyphs['noteheads.slash.quarter'] = { d: [['M', 9, -6], ['l', 0, 4], ['l', -9, 9
105
105
 
106
106
  glyphs['noteheads.harmonic.quarter'] = { d: [['M', 3.63, -4.02], ['c', 0.09, -0.06, 0.18, -0.09, 0.24, -0.03], ['c', 0.03, 0.03, 0.87, 0.93, 1.83, 2.01], ['c', 1.50, 1.65, 1.80, 1.98, 1.80, 2.04], ['c', 0.00, 0.06, -0.30, 0.39, -1.80, 2.04], ['c', -0.96, 1.08, -1.80, 1.98, -1.83, 2.01], ['c', -0.06, 0.06, -0.15, 0.03, -0.24, -0.03], ['c', -0.12, -0.09, -3.54, -3.84, -3.60, -3.93], ['c', -0.03, -0.03, -0.03, -0.09, -0.03, -0.15], ['c', 0.03, -0.06, 3.45, -3.84, 3.63, -3.96], ['z']], w: 7.5, h: 8.165 };
107
107
 
108
- glyphs['noteheads.triangle.quarter'] = { d: [['M', 0, 0], ['l', 9, 0], ['l', -4.5, -9], ['z']], w: 9, h: 9 };
108
+ glyphs['noteheads.triangle.quarter'] = { d: [['M', 0, 4], ['l', 9, 0], ['l', -4.5, -9], ['z']], w: 9, h: 9 };
109
109
 
110
110
  var pathClone = function (pathArray) {
111
111
  var res = [];
@@ -11,6 +11,7 @@ function drawGlissando(renderer, params, selectables) {
11
11
  var rightY = renderer.calcY(params.anchor2.heads[0].pitch)
12
12
  var leftX = params.anchor1.x + params.anchor1.w / 2
13
13
  var rightX = params.anchor2.x + params.anchor2.w / 2
14
+
14
15
  var len = lineLength(leftX, leftY, rightX, rightY)
15
16
  var marginLeft = params.anchor1.w / 2 + margin
16
17
  var marginRight = params.anchor2.w / 2 + margin
@@ -1,5 +1,5 @@
1
1
  function setPaperSize(renderer, maxwidth, scale, responsive) {
2
- var w = (maxwidth + renderer.padding.right) * scale;
2
+ var w = (maxwidth + renderer.padding.left + renderer.padding.right) * scale;
3
3
  var h = (renderer.y + renderer.padding.bottom) * scale;
4
4
  if (renderer.isPrint)
5
5
  h = Math.max(h, 1056); // 11in x 72pt/in x 1.33px/pt
@@ -17,7 +17,15 @@ function drawTie(renderer, params, linestartx, lineendx, selectables) {
17
17
  klass = "abcjs-hint";
18
18
  var fudgeY = params.fixedY ? 1.5 : 0; // TODO-PER: This just compensates for drawArc, which contains too much knowledge of ties and slurs.
19
19
  var el = drawArc(renderer, params.startX, params.endX, params.startY + fudgeY, params.endY + fudgeY, params.above, klass, params.isTie, params.dotted);
20
- selectables.wrapSvgEl({ el_type: "slur", startChar: -1, endChar: -1 }, el);
20
+ var startChar = -1
21
+ // This gets the start and end points of the contents of the slur. We assume that the parenthesis are just to the outside of that.
22
+ if (params.anchor1 && !params.isTie)
23
+ startChar = params.anchor1.parent.abcelem.startChar - 1
24
+ var endChar = -1
25
+ if (params.anchor2 && !params.isTie)
26
+ endChar = params.anchor2.parent.abcelem.endChar + 1
27
+
28
+ selectables.wrapSvgEl({ el_type: "slur", startChar: startChar, endChar: endChar }, el);
21
29
  return [el];
22
30
  }
23
31
 
@@ -40,6 +40,7 @@ var EngraverController = function (paper, params) {
40
40
  this.responsive = params.responsive;
41
41
  this.space = 3 * spacing.SPACE;
42
42
  this.initialClef = params.initialClef;
43
+ this.expandToWidest = !!params.expandToWidest;
43
44
  this.scale = params.scale ? parseFloat(params.scale) : 0;
44
45
  this.classes = new Classes({ shouldAddClasses: params.add_classes });
45
46
  if (!(this.scale > 0.1))
@@ -242,7 +243,12 @@ EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOf
242
243
  this.constructTuneElements(abcTune);
243
244
 
244
245
  // Do all the positioning, both horizontally and vertically
245
- var maxWidth = layout(this.renderer, abcTune, this.width, this.space);
246
+ var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest);
247
+
248
+ //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);
251
+ }
246
252
 
247
253
  // Deal with tablature for staff
248
254
  if (abcTune.tablatures) {
@@ -127,6 +127,8 @@ function keyboardSelection(ev) {
127
127
  }
128
128
 
129
129
  function findElementInHistory(selectables, el) {
130
+ if (!el)
131
+ return -1;
130
132
  for (var i = 0; i < selectables.length; i++) {
131
133
  if (el.dataset.index === selectables[i].svgEl.dataset.index)
132
134
  return i;
@@ -195,9 +197,13 @@ function getBestMatchCoordinates(dim, ev, scale) {
195
197
 
196
198
  function getTarget(target) {
197
199
  // This searches up the dom for the first item containing the attribute "selectable", or stopping at the SVG.
200
+ if (!target)
201
+ return null;
198
202
  if (target.tagName === "svg")
199
203
  return target;
200
204
 
205
+ if (!target.getAttribute)
206
+ return null;
201
207
  var found = target.getAttribute("selectable");
202
208
  while (!found) {
203
209
  if (!target.parentElement)
@@ -3,7 +3,7 @@ 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
5
 
6
- var layout = function (renderer, abctune, width, space) {
6
+ var layout = function (renderer, abctune, width, space, expandToWidest) {
7
7
  var i;
8
8
  var abcLine;
9
9
  // Adjust the x-coordinates to their absolute positions
@@ -11,8 +11,14 @@ var layout = function (renderer, abctune, width, space) {
11
11
  for (i = 0; i < abctune.lines.length; i++) {
12
12
  abcLine = abctune.lines[i];
13
13
  if (abcLine.staff) {
14
- setXSpacing(renderer, width, space, abcLine.staffGroup, abctune.formatting, i === abctune.lines.length - 1, false);
15
- if (abcLine.staffGroup.w > maxWidth) maxWidth = abcLine.staffGroup.w;
14
+ // console.log("=== line", i)
15
+ var thisWidth = setXSpacing(renderer, maxWidth, space, abcLine.staffGroup, abctune.formatting, i === abctune.lines.length - 1, false);
16
+ // console.log(thisWidth, maxWidth)
17
+ if (Math.round(thisWidth) > Math.round(maxWidth)) { // to take care of floating point weirdness
18
+ maxWidth = thisWidth
19
+ if (expandToWidest)
20
+ i = -1 // do the calculations over with the new width
21
+ }
16
22
  }
17
23
  }
18
24
 
@@ -41,15 +47,39 @@ var setXSpacing = function (renderer, width, space, staffGroup, formatting, isLa
41
47
  var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
42
48
  var newspace = space;
43
49
  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
+ // console.log("iteration", it)
51
+ // dumpGroup("before", staffGroup)
44
52
  var ret = layoutStaffGroup(newspace, renderer, debug, staffGroup, leftEdge);
53
+ // dumpGroup("after",staffGroup)
45
54
  newspace = calcHorizontalSpacing(isLastLine, formatting.stretchlast, width + renderer.padding.left, staffGroup.w, newspace, ret.spacingUnits, ret.minSpace, renderer.padding.left + renderer.padding.right);
46
55
  if (debug)
47
56
  console.log("setXSpace", it, staffGroup.w, newspace, staffGroup.minspace);
48
57
  if (newspace === null) break;
49
58
  }
50
59
  centerWholeRests(staffGroup.voices);
60
+ return staffGroup.w - leftEdge
51
61
  };
52
62
 
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
+ // }
82
+
53
83
  function calcHorizontalSpacing(isLastLine, stretchLast, targetWidth, lineWidth, spacing, spacingUnits, minSpace, padding) {
54
84
  if (isLastLine) {
55
85
  if (stretchLast === undefined) {