abcjs 6.2.1 → 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 (40) hide show
  1. package/README.md +3 -0
  2. package/RELEASE.md +54 -0
  3. package/abc2xml_239/abc2xml.html +769 -0
  4. package/abc2xml_239/abc2xml.py +2248 -0
  5. package/abc2xml_239/abc2xml_changelog.html +124 -0
  6. package/abc2xml_239/lazy-river.abc +26 -0
  7. package/abc2xml_239/lazy-river.xml +3698 -0
  8. package/abc2xml_239/mean-to-me.abc +22 -0
  9. package/abc2xml_239/mean-to-me.xml +2954 -0
  10. package/abc2xml_239/pyparsing.py +3672 -0
  11. package/abc2xml_239/pyparsing.pyc +0 -0
  12. package/dist/abcjs-basic-min.js +2 -2
  13. package/dist/abcjs-basic.js +205 -91
  14. package/dist/abcjs-basic.js.map +1 -1
  15. package/dist/abcjs-plugin-min.js +2 -2
  16. package/package.json +1 -1
  17. package/src/api/abc_tablatures.js +5 -0
  18. package/src/api/abc_tunebook_svg.js +5 -3
  19. package/src/parse/abc_parse_music.js +18 -53
  20. package/src/parse/abc_parse_settings.js +165 -0
  21. package/src/synth/create-synth.js +4 -0
  22. package/src/synth/place-note.js +6 -0
  23. package/src/synth/play-event.js +7 -5
  24. package/src/tablatures/tab-absolute-elements.js +3 -2
  25. package/src/tablatures/tab-renderer.js +3 -1
  26. package/src/test/abc_parser_lint.js +12 -12
  27. package/src/write/creation/abstract-engraver.js +6 -0
  28. package/src/write/creation/decoration.js +2 -0
  29. package/src/write/creation/glyphs.js +1 -1
  30. package/src/write/draw/glissando.js +1 -0
  31. package/src/write/draw/print-line.js +16 -7
  32. package/src/write/draw/print-stem.js +16 -8
  33. package/src/write/draw/set-paper-size.js +1 -1
  34. package/src/write/draw/tie.js +9 -1
  35. package/src/write/engraver-controller.js +12 -4
  36. package/src/write/interactive/selection.js +6 -0
  37. package/src/write/layout/layout.js +33 -3
  38. package/src/write/svg.js +10 -0
  39. package/types/index.d.ts +29 -21
  40. package/version.js +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abcjs",
3
- "version": "6.2.1",
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",
@@ -13,6 +13,8 @@ var GuitarTablature = require('../tablatures/instruments/guitar/tab-guitar');
13
13
  // Existing tab classes
14
14
  var pluginTab = {
15
15
  'violin': 'ViolinTab',
16
+ 'fiddle': 'ViolinTab',
17
+ 'mandolin': 'ViolinTab',
16
18
  'guitar': 'GuitarTab'
17
19
  };
18
20
 
@@ -82,6 +84,9 @@ var abcTablatures = {
82
84
  // plugin.init(tune, tuneNumber, args, ii);
83
85
  returned.push(pluginInstance);
84
86
  nbPlugins++;
87
+ } else if (instrument === '') {
88
+ // create a placeholder - there is no tab for this staff
89
+ returned.push(null)
85
90
  } else {
86
91
  // unknown tab plugin
87
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') {
@@ -227,15 +227,17 @@ TabRenderer.prototype.doLayout = function () {
227
227
  this.tabStaff.voices = [];
228
228
  for (var ii = 0; ii < nbVoices; ii++) {
229
229
  var tabVoice = new VoiceElement(0, 0);
230
+ if (ii > 0) tabVoice.duplicate = true;
230
231
  var nameHeight = buildTabName(this, tabVoice) / spacing.STEP;
231
232
  nameHeight = Math.max(nameHeight, 1) // If there is no label for the tab line, then there needs to be a little padding
232
233
  staffGroup.staffs[this.staffIndex].top += nameHeight;
233
234
  staffGroup.height += nameHeight * spacing.STEP;
234
235
  tabVoice.staff = staffGroupInfos;
236
+ var tabVoiceIndex = voices.length
235
237
  voices.splice(voices.length, 0, tabVoice);
236
238
  var keySig = checkVoiceKeySig(voices, ii + this.staffIndex);
237
239
  this.tabStaff.voices[ii] = [];
238
- 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);
239
241
  }
240
242
  linkStaffAndTabs(staffGroup.staffs); // crossreference tabs and staff
241
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
@@ -7,14 +7,23 @@ function printLine(renderer, x1, x2, y, klass, name, dy) {
7
7
  x2 = roundNumber(x2);
8
8
  var y1 = roundNumber(y - dy);
9
9
  var y2 = roundNumber(y + dy);
10
- // TODO-PER: This fixes a firefox bug where a path needs to go over the 0.5 mark or it isn't displayed
11
- if (renderer.firefox112 && dy < 1) {
12
- var int = Math.floor(y2)
13
- var distToHalf = 0.52 - (y2 - int)
14
- if (distToHalf > 0) {
15
- y1 += distToHalf
16
- y2 += distToHalf
10
+ // TODO-PER: This fixes a firefox bug where it isn't displayed
11
+ if (renderer.firefox112) {
12
+ y += dy / 2; // Because the y coordinate is the edge of where the line goes but the width widens from the middle.
13
+ var attr = {
14
+ x1: x1,
15
+ x2: x2,
16
+ y1: y,
17
+ y2: y,
18
+ stroke: renderer.foregroundColor,
19
+ 'stroke-width': Math.abs(dy*2)
17
20
  }
21
+ if (klass)
22
+ attr['class'] = klass;
23
+ if (name)
24
+ attr['data-name'] = name;
25
+
26
+ return renderer.paper.lineToBack(attr);
18
27
  }
19
28
 
20
29
  var pathString = sprintf("M %f %f L %f %f L %f %f L %f %f z", x1, y1, x2, y1,
@@ -12,15 +12,23 @@ function printStem(renderer, x, dx, y1, y2, klass, name) {
12
12
  }
13
13
  x = roundNumber(x);
14
14
  var x2 = roundNumber(x + dx);
15
- // TODO-PER: This fixes a firefox bug where a path needs to go over the 0.5 mark or it isn't displayed
16
- if (renderer.firefox112 && Math.abs(dx) < 1) {
17
- var higher = Math.max(x,x2)
18
- var int = Math.floor(higher)
19
- var distToHalf = 0.52 - (higher - int)
20
- if (distToHalf > 0) {
21
- x += distToHalf
22
- x2 += distToHalf
15
+ // TODO-PER: This fixes a firefox bug where it isn't displayed
16
+ if (renderer.firefox112) {
17
+ x += dx / 2; // Because the x coordinate is the edge of where the line goes but the width widens from the middle.
18
+ var attr = {
19
+ x1: x,
20
+ x2: x,
21
+ y1: y1,
22
+ y2: y2,
23
+ stroke: renderer.foregroundColor,
24
+ 'stroke-width': Math.abs(dx)
23
25
  }
26
+ if (klass)
27
+ attr['class'] = klass;
28
+ if (name)
29
+ attr['data-name'] = name;
30
+
31
+ return renderer.paper.lineToBack(attr);
24
32
  }
25
33
  var pathArray = [["M", x, y1], ["L", x, y2], ["L", x2, y2], ["L", x2, y1], ["z"]];
26
34
  var attr = { path: "" };
@@ -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) {
@@ -256,14 +262,14 @@ EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOf
256
262
 
257
263
  if (this.oneSvgPerLine) {
258
264
  var div = this.renderer.paper.svg.parentNode
259
- this.svgs = splitSvgIntoLines(div, abcTune.metaText.title, this.responsive)
265
+ this.svgs = splitSvgIntoLines(this.renderer, div, abcTune.metaText.title, this.responsive)
260
266
  } else {
261
267
  this.svgs = [this.renderer.paper.svg];
262
268
  }
263
269
  setupSelection(this, this.svgs);
264
270
  };
265
271
 
266
- function splitSvgIntoLines(output, title, responsive) {
272
+ function splitSvgIntoLines(renderer, output, title, responsive) {
267
273
  // Each line is a top level <g> in the svg. To split it into separate
268
274
  // svgs iterate through each of those and put them in a new svg. Since
269
275
  // they are placed absolutely, the viewBox needs to be manipulated to
@@ -297,7 +303,9 @@ function splitSvgIntoLines(output, title, responsive) {
297
303
  svg.setAttribute("height", height)
298
304
  if (responsive === 'resize')
299
305
  svg.style.position = ''
300
- svg.setAttribute("viewBox", "0 " + nextTop + " " + width + " " + height)
306
+ // TODO-PER: Hack! Not sure why this is needed.
307
+ var viewBoxHeight = renderer.firefox112 ? height+1 : height
308
+ svg.setAttribute("viewBox", "0 " + nextTop + " " + width + " " + viewBoxHeight)
301
309
  svg.appendChild(style.cloneNode(true))
302
310
  var titleEl = document.createElement("title")
303
311
  titleEl.innerText = fullTitle