abcjs 6.0.0-beta.33 → 6.0.0-beta.37

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 (101) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +20 -9
  3. package/RELEASE.md +108 -0
  4. package/dist/abcjs-basic-min.js +2 -2
  5. package/dist/abcjs-basic-min.js.LICENSE +2 -2
  6. package/dist/abcjs-basic.js +3493 -841
  7. package/dist/abcjs-basic.js.map +1 -1
  8. package/dist/abcjs-plugin-min.js +2 -2
  9. package/dist/abcjs-plugin-min.js.LICENSE +2 -2
  10. package/dist/report-basic.html +37 -0
  11. package/dist/report-before-glyph-compress.html +37 -0
  12. package/dist/report-brown-ts-target-es5.html +37 -0
  13. package/dist/{report.html → report-dev-orig-no-babel.html} +2 -2
  14. package/dist/report-synth.html +37 -0
  15. package/docker-build.sh +1 -0
  16. package/glyphs.json +1 -0
  17. package/index.js +23 -1
  18. package/license.js +1 -1
  19. package/package.json +9 -9
  20. package/plugin.js +23 -1
  21. package/src/api/abc_tablatures.js +144 -0
  22. package/src/api/abc_timing_callbacks.js +34 -31
  23. package/src/api/abc_tunebook.js +10 -1
  24. package/src/api/abc_tunebook_svg.js +23 -27
  25. package/src/data/abc_tune.js +68 -24
  26. package/src/edit/abc_editor.js +33 -11
  27. package/src/midi/abc_midi_create.js +6 -2
  28. package/src/parse/abc_parse.js +7 -6
  29. package/src/parse/abc_parse_directive.js +19 -12
  30. package/src/parse/abc_parse_header.js +12 -12
  31. package/src/parse/abc_parse_music.js +14 -4
  32. package/src/parse/tune-builder.js +22 -29
  33. package/src/synth/abc_midi_sequencer.js +10 -0
  34. package/src/synth/create-synth.js +56 -13
  35. package/src/synth/load-note.js +31 -65
  36. package/src/synth/place-note.js +59 -60
  37. package/src/synth/register-audio-context.js +4 -1
  38. package/src/synth/supports-audio.js +9 -8
  39. package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
  40. package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
  41. package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
  42. package/src/tablatures/instruments/string-patterns.js +277 -0
  43. package/src/tablatures/instruments/string-tablature.js +56 -0
  44. package/src/tablatures/instruments/tab-note.js +282 -0
  45. package/src/tablatures/instruments/tab-notes.js +41 -0
  46. package/src/tablatures/instruments/violin/tab-violin.js +47 -0
  47. package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
  48. package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
  49. package/src/tablatures/tab-absolute-elements.js +310 -0
  50. package/src/tablatures/tab-common.js +29 -0
  51. package/src/tablatures/tab-renderer.js +243 -0
  52. package/src/tablatures/transposer.js +110 -0
  53. package/src/test/abc_parser_lint.js +62 -6
  54. package/src/write/abc_absolute_element.js +2 -2
  55. package/src/write/abc_abstract_engraver.js +9 -7
  56. package/src/write/abc_create_key_signature.js +1 -0
  57. package/src/write/abc_create_note_head.js +1 -1
  58. package/src/write/abc_engraver_controller.js +26 -13
  59. package/src/write/abc_glyphs.js +5 -2
  60. package/src/write/abc_relative_element.js +11 -3
  61. package/src/write/abc_renderer.js +5 -1
  62. package/src/write/add-chord.js +5 -2
  63. package/src/write/add-text-if.js +33 -0
  64. package/src/write/bottom-text.js +8 -29
  65. package/src/write/draw/absolute.js +12 -14
  66. package/src/write/draw/brace.js +3 -3
  67. package/src/write/draw/crescendo.js +1 -1
  68. package/src/write/draw/draw.js +5 -6
  69. package/src/write/draw/dynamics.js +8 -1
  70. package/src/write/draw/ending.js +4 -3
  71. package/src/write/draw/group-elements.js +10 -8
  72. package/src/write/draw/non-music.js +11 -6
  73. package/src/write/draw/print-line.js +24 -0
  74. package/src/write/draw/print-stem.js +12 -11
  75. package/src/write/draw/print-symbol.js +11 -10
  76. package/src/write/draw/relative.js +33 -13
  77. package/src/write/draw/selectables.js +9 -6
  78. package/src/write/draw/staff-group.js +45 -9
  79. package/src/write/draw/staff-line.js +3 -17
  80. package/src/write/draw/staff.js +15 -2
  81. package/src/write/draw/tab-line.js +40 -0
  82. package/src/write/draw/tempo.js +7 -7
  83. package/src/write/draw/text.js +11 -4
  84. package/src/write/draw/tie.js +2 -2
  85. package/src/write/draw/triplet.js +3 -3
  86. package/src/write/draw/voice.js +10 -2
  87. package/src/write/format-jazz-chord.js +15 -0
  88. package/src/write/free-text.js +20 -12
  89. package/src/write/layout/VoiceElements.js +33 -1
  90. package/src/write/layout/beam.js +2 -0
  91. package/src/write/layout/staffGroup.js +37 -2
  92. package/src/write/layout/voice.js +2 -1
  93. package/src/write/selection.js +15 -5
  94. package/src/write/separator.js +1 -1
  95. package/src/write/subtitle.js +3 -3
  96. package/src/write/svg.js +41 -14
  97. package/src/write/top-text.js +19 -25
  98. package/test.js +23 -0
  99. package/types/index.d.ts +1007 -39
  100. package/version.js +1 -1
  101. package/docker-start.sh +0 -1
@@ -25,6 +25,7 @@ var TimingCallbacks = function(target, params) {
25
25
  self.currentBeat = 0;
26
26
  self.currentEvent = 0;
27
27
  self.currentLine = 0;
28
+ self.currentTime = 0;
28
29
  self.isPaused = false;
29
30
  self.isRunning = false;
30
31
  self.pausedPercent = null;
@@ -53,41 +54,41 @@ var TimingCallbacks = function(target, params) {
53
54
  }
54
55
 
55
56
  if (!self.isPaused && self.isRunning) {
56
- var currentTime = timestamp - self.startTime;
57
- currentTime += 16; // Add a little slop because this function isn't called exactly.
58
- while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < currentTime) {
57
+ self.currentTime = timestamp - self.startTime;
58
+ self.currentTime += 16; // Add a little slop because this function isn't called exactly.
59
+ while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < self.currentTime) {
59
60
  if (self.eventCallback && self.noteTimings[self.currentEvent].type === 'event') {
60
61
  var thisStartTime = self.startTime; // the event callback can call seek and change the position from beneath us.
61
62
  self.eventCallback(self.noteTimings[self.currentEvent]);
62
63
  if (thisStartTime !== self.startTime) {
63
- currentTime = timestamp - self.startTime;
64
+ self.currentTime = timestamp - self.startTime;
64
65
  }
65
66
  }
66
67
  self.currentEvent++;
67
68
  }
68
- if (self.lineEndCallback && self.lineEndTimings.length > self.currentLine && self.lineEndTimings[self.currentLine].milliseconds < currentTime && self.currentEvent < self.noteTimings.length) {
69
- var leftEvent = self.noteTimings[self.currentEvent].milliseconds === currentTime ? self.noteTimings[self.currentEvent] : self.noteTimings[self.currentEvent-1]
70
- self.lineEndCallback(self.lineEndTimings[self.currentLine], leftEvent, { line: self.currentLine, endTimings: self.lineEndTimings, currentTime: currentTime });
69
+ if (self.lineEndCallback && self.lineEndTimings.length > self.currentLine && self.lineEndTimings[self.currentLine].milliseconds < self.currentTime && self.currentEvent < self.noteTimings.length) {
70
+ var leftEvent = self.noteTimings[self.currentEvent].milliseconds === self.currentTime ? self.noteTimings[self.currentEvent] : self.noteTimings[self.currentEvent-1]
71
+ self.lineEndCallback(self.lineEndTimings[self.currentLine], leftEvent, { line: self.currentLine, endTimings: self.lineEndTimings, currentTime: self.currentTime });
71
72
  self.currentLine++;
72
73
  }
73
- if (currentTime < self.lastMoment) {
74
+ if (self.currentTime < self.lastMoment) {
74
75
  requestAnimationFrame(self.doTiming);
75
- if (self.currentBeat * self.millisecondsPerBeat < currentTime) {
76
+ if (self.currentBeat * self.millisecondsPerBeat < self.currentTime) {
76
77
  var ret = self.doBeatCallback(timestamp);
77
78
  if (ret !== null)
78
- currentTime = ret;
79
+ self.currentTime = ret;
79
80
  }
80
81
  } else if (self.currentBeat <= self.totalBeats) {
81
82
  // Because of timing issues (for instance, if the browser tab isn't active), the beat callbacks might not have happened when they are supposed to. To keep the client programs from having to deal with that, this will keep calling the loop until all of them have been sent.
82
83
  if (self.beatCallback) {
83
84
  var ret2 = self.doBeatCallback(timestamp);
84
85
  if (ret2 !== null)
85
- currentTime = ret2;
86
+ self.currentTime = ret2;
86
87
  requestAnimationFrame(self.doTiming);
87
88
  }
88
89
  }
89
90
 
90
- if (currentTime >= self.lastMoment) {
91
+ if (self.currentTime >= self.lastMoment) {
91
92
  if (self.eventCallback) {
92
93
  // At the end, the event callback can return "continue" to keep from stopping.
93
94
  // The event callback can either be a promise or not.
@@ -203,7 +204,7 @@ var TimingCallbacks = function(target, params) {
203
204
  }
204
205
  };
205
206
 
206
- self.start = function(offsetPercent) {
207
+ self.start = function(offsetPercent, units) {
207
208
  self.isRunning = true;
208
209
  if (self.isPaused) {
209
210
  self.isPaused = false;
@@ -211,13 +212,13 @@ var TimingCallbacks = function(target, params) {
211
212
  self.justUnpaused = true;
212
213
  }
213
214
  if (offsetPercent) {
214
- self.setProgress(offsetPercent);
215
+ self.setProgress(offsetPercent, units);
215
216
  } else if (offsetPercent === 0) {
216
217
  self.reset();
217
218
  } else if (self.pausedPercent !== null) {
218
219
  var now = performance.now();
219
- var currentTime = self.lastMoment * self.pausedPercent;
220
- self.startTime = now - currentTime;
220
+ self.currentTime = self.lastMoment * self.pausedPercent;
221
+ self.startTime = now - self.currentTime;
221
222
  self.pausedPercent = null;
222
223
  self.reportNext = true;
223
224
  }
@@ -234,6 +235,9 @@ var TimingCallbacks = function(target, params) {
234
235
  self.joggerTimer = null;
235
236
  }
236
237
  };
238
+ self.currentMillisecond = function() {
239
+ return self.currentTime;
240
+ };
237
241
  self.reset = function() {
238
242
  self.currentBeat = 0;
239
243
  self.currentEvent = 0;
@@ -247,20 +251,19 @@ var TimingCallbacks = function(target, params) {
247
251
  };
248
252
  self.setProgress = function(position, units) {
249
253
  // the effect of this function is to move startTime so that the callbacks happen correctly for the new seek.
250
- var currentTime;
251
254
  var percent;
252
255
  switch (units) {
253
256
  case "seconds":
254
- currentTime = position * 1000;
255
- if (currentTime < 0) currentTime = 0;
256
- if (currentTime > self.lastMoment) currentTime = self.lastMoment;
257
- percent = currentTime / self.lastMoment;
257
+ self.currentTime = position * 1000;
258
+ if (self.currentTime < 0) self.currentTime = 0;
259
+ if (self.currentTime > self.lastMoment) self.currentTime = self.lastMoment;
260
+ percent = self.currentTime / self.lastMoment;
258
261
  break;
259
262
  case "beats":
260
- currentTime = position * self.millisecondsPerBeat * self.beatSubdivisions;
261
- if (currentTime < 0) currentTime = 0;
262
- if (currentTime > self.lastMoment) currentTime = self.lastMoment;
263
- percent = currentTime / self.lastMoment;
263
+ self.currentTime = position * self.millisecondsPerBeat * self.beatSubdivisions;
264
+ if (self.currentTime < 0) self.currentTime = 0;
265
+ if (self.currentTime > self.lastMoment) self.currentTime = self.lastMoment;
266
+ percent = self.currentTime / self.lastMoment;
264
267
  break;
265
268
  default:
266
269
  // this is "percent" or any illegal value
@@ -268,7 +271,7 @@ var TimingCallbacks = function(target, params) {
268
271
  percent = position;
269
272
  if (percent < 0) percent = 0;
270
273
  if (percent > 1) percent = 1;
271
- currentTime = self.lastMoment * percent;
274
+ self.currentTime = self.lastMoment * percent;
272
275
  break;
273
276
  }
274
277
 
@@ -276,25 +279,25 @@ var TimingCallbacks = function(target, params) {
276
279
  self.pausedPercent = percent;
277
280
 
278
281
  var now = performance.now();
279
- self.startTime = now - currentTime;
282
+ self.startTime = now - self.currentTime;
280
283
 
281
284
  var oldEvent = self.currentEvent;
282
285
  self.currentEvent = 0;
283
- while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < currentTime) {
286
+ while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < self.currentTime) {
284
287
  self.currentEvent++;
285
288
  }
286
289
 
287
290
  if (self.lineEndCallback) {
288
291
  self.currentLine = 0;
289
- while (self.lineEndTimings.length > self.currentLine && self.lineEndTimings[self.currentLine].milliseconds + self.lineEndAnticipation < currentTime) {
292
+ while (self.lineEndTimings.length > self.currentLine && self.lineEndTimings[self.currentLine].milliseconds + self.lineEndAnticipation < self.currentTime) {
290
293
  self.currentLine++;
291
294
  }
292
295
  }
293
296
 
294
297
  var oldBeat = self.currentBeat;
295
- self.currentBeat = Math.floor(currentTime / self.millisecondsPerBeat);
298
+ self.currentBeat = Math.floor(self.currentTime / self.millisecondsPerBeat);
296
299
  if (self.beatCallback && oldBeat !== self.currentBeat) // If the movement caused the beat to change, then immediately report it to the client.
297
- self.doBeatCallback(self.startTime+currentTime);
300
+ self.doBeatCallback(self.startTime+self.currentTime);
298
301
 
299
302
  if (self.eventCallback && self.currentEvent >= 0 && self.noteTimings[self.currentEvent].type === 'event')
300
303
  self.eventCallback(self.noteTimings[self.currentEvent]);
@@ -2,6 +2,8 @@
2
2
 
3
3
  var Parse = require('../parse/abc_parse');
4
4
  var bookParser = require('../parse/abc_parse_book');
5
+ var tablatures = require('./abc_tablatures');
6
+
5
7
 
6
8
  var tunebook = {};
7
9
 
@@ -81,6 +83,13 @@ var tunebook = {};
81
83
  if (currentTune >= 0 && currentTune < book.tunes.length) {
82
84
  abcParser.parse(book.tunes[currentTune].abc, params, book.tunes[currentTune].startPos - book.header.length);
83
85
  var tune = abcParser.getTune();
86
+ //
87
+ // Init tablatures plugins
88
+ //
89
+ if (params.tablature) {
90
+ tablatures.init();
91
+ tune.tablatures = tablatures.preparePlugins(tune, currentTune, params);
92
+ }
84
93
  var warnings = abcParser.getWarnings();
85
94
  if (warnings)
86
95
  tune.warnings = warnings;
@@ -93,7 +102,7 @@ var tunebook = {};
93
102
  }
94
103
  currentTune++;
95
104
  }
96
- return ret;
105
+ return ret;
97
106
  };
98
107
 
99
108
  function flattenTune(tuneObj) {
@@ -5,6 +5,8 @@ var EngraverController = require('../write/abc_engraver_controller');
5
5
  var Parse = require('../parse/abc_parse');
6
6
  var wrap = require('../parse/wrap_lines');
7
7
  var parseCommon = require("../parse/abc_common");
8
+ // var tablatures = require('./abc_tablatures');
9
+
8
10
 
9
11
  var resizeDivs = {};
10
12
  function resizeOuter() {
@@ -26,7 +28,7 @@ try {
26
28
  // if we aren't in a browser, this code will crash, but it is not needed then either.
27
29
  }
28
30
 
29
- function renderOne(div, tune, params, tuneNumber) {
31
+ function renderOne(div, tune, params, tuneNumber, lineOffset) {
30
32
  if (params.viewportHorizontal) {
31
33
  // Create an inner div that holds the music, so that the passed in div will be the viewport.
32
34
  div.innerHTML = '<div class="abcjs-inner"></div>';
@@ -48,7 +50,7 @@ function renderOne(div, tune, params, tuneNumber) {
48
50
  else
49
51
  div.innerHTML = "";
50
52
  var engraver_controller = new EngraverController(div, params);
51
- engraver_controller.engraveABC(tune, tuneNumber);
53
+ engraver_controller.engraveABC(tune, tuneNumber, lineOffset);
52
54
  tune.engraver = engraver_controller;
53
55
  if (params.viewportVertical || params.viewportHorizontal) {
54
56
  // If we added a wrapper around the div, then we need to size the wrapper, too.
@@ -63,8 +65,6 @@ function renderEachLineSeparately(div, tune, params, tuneNumber) {
63
65
  obj.formatting = tune.formatting;
64
66
  obj.media = tune.media;
65
67
  obj.version = tune.version;
66
- obj.metaText = {};
67
- obj.lines = [];
68
68
  return obj;
69
69
  }
70
70
 
@@ -80,14 +80,7 @@ function renderEachLineSeparately(div, tune, params, tuneNumber) {
80
80
 
81
81
  if (i === 0) {
82
82
  // These items go on top of the music
83
- tuneLine.metaText.tempo = tune.metaText.tempo;
84
- tuneLine.metaText.title = tune.metaText.title;
85
- tuneLine.metaText.header = tune.metaText.header;
86
- tuneLine.metaText.rhythm = tune.metaText.rhythm;
87
- tuneLine.metaText.origin = tune.metaText.origin;
88
- tuneLine.metaText.composer = tune.metaText.composer;
89
- tuneLine.metaText.author = tune.metaText.author;
90
- tuneLine.metaText.partOrder = tune.metaText.partOrder;
83
+ tuneLine.copyTopInfo(tune);
91
84
  }
92
85
 
93
86
  // push the lines until we get to a music line
@@ -106,17 +99,7 @@ function renderEachLineSeparately(div, tune, params, tuneNumber) {
106
99
 
107
100
  // These items go below the music
108
101
  tuneLine = tunes[tunes.length-1];
109
- tuneLine.metaText.unalignedWords = tune.metaText.unalignedWords;
110
- tuneLine.metaText.book = tune.metaText.book;
111
- tuneLine.metaText.source = tune.metaText.source;
112
- tuneLine.metaText.discography = tune.metaText.discography;
113
- tuneLine.metaText.notes = tune.metaText.notes;
114
- tuneLine.metaText.transcription = tune.metaText.transcription;
115
- tuneLine.metaText.history = tune.metaText.history;
116
- tuneLine.metaText['abc-copyright'] = tune.metaText['abc-copyright'];
117
- tuneLine.metaText['abc-creator'] = tune.metaText['abc-creator'];
118
- tuneLine.metaText['abc-edited-by'] = tune.metaText['abc-edited-by'];
119
- tuneLine.metaText.footer = tune.metaText.footer;
102
+ tuneLine.copyBottomInfo(tune);
120
103
 
121
104
  // Now create sub-divs and render each line. Need to copy the params to change the padding for the interior slices.
122
105
  var ep = {};
@@ -127,7 +110,10 @@ function renderEachLineSeparately(div, tune, params, tuneNumber) {
127
110
  }
128
111
  var origPaddingTop = ep.paddingtop;
129
112
  var origPaddingBottom = ep.paddingbottom;
113
+ var currentScrollY = div.parentNode.scrollTop; // If there is scrolling it will be lost during the redraw so remember it.
114
+ var currentScrollX = div.parentNode.scrollLeft;
130
115
  div.innerHTML = "";
116
+ var lineCount = 0;
131
117
  for (var k = 0; k < tunes.length; k++) {
132
118
  var lineEl = document.createElement("div");
133
119
  div.appendChild(lineEl);
@@ -145,9 +131,10 @@ function renderEachLineSeparately(div, tune, params, tuneNumber) {
145
131
  if (k < tunes.length-1) {
146
132
  // If it is not the last line, force stretchlast. If it is, stretchlast might have been set by the input parameters.
147
133
  tunes[k].formatting = parseCommon.clone(tunes[k].formatting);
148
- tunes[k].formatting.stretchlast = true
134
+ tunes[k].formatting.stretchlast = true;
149
135
  }
150
- renderOne(lineEl, tunes[k], ep, tuneNumber);
136
+ renderOne(lineEl, tunes[k], ep, tuneNumber, lineCount);
137
+ lineCount += tunes[k].lines.length;
151
138
  if (k === 0)
152
139
  tune.engraver = tunes[k].engraver;
153
140
  else {
@@ -157,6 +144,9 @@ function renderEachLineSeparately(div, tune, params, tuneNumber) {
157
144
  tune.engraver.staffgroups.push(tunes[k].engraver.staffgroups[0]);
158
145
  }
159
146
  }
147
+ if (currentScrollX || currentScrollY) {
148
+ div.parentNode.scrollTo(currentScrollX, currentScrollY);
149
+ }
160
150
  }
161
151
 
162
152
  // A quick way to render a tune from javascript when interactivity is not required.
@@ -186,6 +176,9 @@ var renderAbc = function(output, abc, parserParams, engraverParams, renderParams
186
176
  params[key] = parserParams[key];
187
177
  }
188
178
  }
179
+ if (params.warnings_id && params.tablature) {
180
+ params.tablature.warning_id = params.warnings_id;
181
+ }
189
182
  }
190
183
  if (engraverParams) {
191
184
  for (key in engraverParams) {
@@ -222,7 +215,7 @@ var renderAbc = function(output, abc, parserParams, engraverParams, renderParams
222
215
  return tune;
223
216
  }
224
217
  else if (removeDiv || !params.oneSvgPerLine || tune.lines.length < 2)
225
- renderOne(div, tune, params, tuneNumber);
218
+ renderOne(div, tune, params, tuneNumber, 0);
226
219
  else
227
220
  renderEachLineSeparately(div, tune, params, tuneNumber);
228
221
  if (removeDiv)
@@ -242,9 +235,12 @@ function doLineWrapping(div, tune, tuneNumber, abcString, params) {
242
235
  var abcParser = new Parse();
243
236
  abcParser.parse(abcString, ret.revisedParams);
244
237
  tune = abcParser.getTune();
238
+ var warnings = abcParser.getWarnings();
239
+ if (warnings)
240
+ tune.warnings = warnings;
245
241
  }
246
242
  if (!params.oneSvgPerLine || tune.lines.length < 2)
247
- renderOne(div, tune, ret.revisedParams, tuneNumber);
243
+ renderOne(div, tune, ret.revisedParams, tuneNumber, 0);
248
244
  else
249
245
  renderEachLineSeparately(div, tune, ret.revisedParams, tuneNumber);
250
246
  tune.explanation = ret.explanation;
@@ -12,6 +12,48 @@ var delineTune = require("./deline-tune");
12
12
  * @alternateClassName ABCJS.Tune
13
13
  */
14
14
  var Tune = function() {
15
+ this.reset = function () {
16
+ this.version = "1.1.0";
17
+ this.media = "screen";
18
+ this.metaText = {};
19
+ this.metaTextInfo = {};
20
+ this.formatting = {};
21
+ this.lines = [];
22
+ this.staffNum = 0;
23
+ this.voiceNum = 0;
24
+ this.lineNum = 0;
25
+ this.runningFonts = {};
26
+ delete this.visualTranspose;
27
+ };
28
+ this.reset();
29
+
30
+ function copy(dest, src, prop, attrs) {
31
+ for (var i = 0; i < attrs.length; i++)
32
+ dest[prop][attrs[i]] = src[prop][attrs[i]];
33
+ }
34
+
35
+ this.copyTopInfo = function(src) {
36
+ var attrs = ['tempo', 'title', 'header', 'rhythm', 'origin', 'composer', 'author', 'partOrder'];
37
+ copy(this, src, "metaText", attrs);
38
+ copy(this, src, "metaTextInfo", attrs);
39
+ };
40
+
41
+ this.copyBottomInfo = function(src) {
42
+ var attrs = ['unalignedWords',
43
+ 'book',
44
+ 'source',
45
+ 'discography',
46
+ 'notes',
47
+ 'transcription',
48
+ 'history',
49
+ 'abc-copyright',
50
+ 'abc-creator',
51
+ 'abc-edited-by',
52
+ 'footer']
53
+ copy(this, src, "metaText", attrs);
54
+ copy(this, src, "metaTextInfo", attrs);
55
+ };
56
+
15
57
  // The structure consists of a hash with the following two items:
16
58
  // metaText: a hash of {key, value}, where key is one of: title, author, rhythm, source, transcription, unalignedWords, etc...
17
59
  // tempo: { noteLength: number (e.g. .125), bpm: number }
@@ -346,30 +388,32 @@ var Tune = function() {
346
388
  var tempos = {};
347
389
  for (var line = 0; line < this.engraver.staffgroups.length; line++) {
348
390
  var group = this.engraver.staffgroups[line];
349
- var firstStaff = group.staffs[0];
350
- var middleC = firstStaff.absoluteY;
351
- var top = middleC - firstStaff.top * spacing.STEP;
352
- var lastStaff = group.staffs[group.staffs.length - 1];
353
- middleC = lastStaff.absoluteY;
354
- var bottom = middleC - lastStaff.bottom * spacing.STEP;
355
- var height = bottom - top;
356
-
357
- var voices = group.voices;
358
- for (var v = 0; v < voices.length; v++) {
359
- var noteFound = false;
360
- if (!voicesArr[v])
361
- voicesArr[v] = [];
362
- if (measureNumber[v] === undefined)
363
- measureNumber[v] = 0;
364
- var elements = voices[v].children;
365
- for (var elem = 0; elem < elements.length; elem++) {
366
- if (elements[elem].type === "tempo")
367
- tempos[measureNumber[v]] = this.getBpm(elements[elem].abcelem);
368
- voicesArr[v].push({top: top, height: height, line: group.line, measureNumber: measureNumber[v], elem: elements[elem]});
369
- if (elements[elem].type === 'bar' && noteFound) // Count the measures by counting the bar lines, but skip a bar line that appears at the left of the music, before any notes.
370
- measureNumber[v]++;
371
- if (elements[elem].type === 'note' || elements[elem].type === 'rest')
372
- noteFound = true;
391
+ if (group && group.staffs && group.staffs.length > 0) {
392
+ var firstStaff = group.staffs[0];
393
+ var middleC = firstStaff.absoluteY;
394
+ var top = middleC - firstStaff.top * spacing.STEP;
395
+ var lastStaff = group.staffs[group.staffs.length - 1];
396
+ middleC = lastStaff.absoluteY;
397
+ var bottom = middleC - lastStaff.bottom * spacing.STEP;
398
+ var height = bottom - top;
399
+
400
+ var voices = group.voices;
401
+ for (var v = 0; v < voices.length; v++) {
402
+ var noteFound = false;
403
+ if (!voicesArr[v])
404
+ voicesArr[v] = [];
405
+ if (measureNumber[v] === undefined)
406
+ measureNumber[v] = 0;
407
+ var elements = voices[v].children;
408
+ for (var elem = 0; elem < elements.length; elem++) {
409
+ if (elements[elem].type === "tempo")
410
+ tempos[measureNumber[v]] = this.getBpm(elements[elem].abcelem);
411
+ voicesArr[v].push({top: top, height: height, line: group.line, measureNumber: measureNumber[v], elem: elements[elem]});
412
+ if (elements[elem].type === 'bar' && noteFound) // Count the measures by counting the bar lines, but skip a bar line that appears at the left of the music, before any notes.
413
+ measureNumber[v]++;
414
+ if (elements[elem].type === 'note' || elements[elem].type === 'rest')
415
+ noteFound = true;
416
+ }
373
417
  }
374
418
  }
375
419
  }
@@ -82,6 +82,17 @@ function gatherAbcParams(params) {
82
82
  }
83
83
  }
84
84
  }
85
+ /*
86
+ if (params.tablature_options) {
87
+ abcjsParams['tablatures'] = params.tablature_options;
88
+ }
89
+ */
90
+ if (abcjsParams.tablature) {
91
+ if (params.warnings_id) {
92
+ // store for plugin error handling
93
+ abcjsParams.tablature.warnings_id = params.warnings_id;
94
+ }
95
+ }
85
96
  return abcjsParams;
86
97
  }
87
98
 
@@ -123,7 +134,7 @@ var Editor = function(editarea, params) {
123
134
  el: params.synth.el,
124
135
  cursorControl: params.synth.cursorControl,
125
136
  options: params.synth.options
126
- }
137
+ };
127
138
  }
128
139
  }
129
140
  // If the user wants midi, then store the elements that it will be written to. The element could either be passed in as an id,
@@ -219,16 +230,23 @@ Editor.prototype.redrawMidi = function() {
219
230
  Editor.prototype.modelChanged = function() {
220
231
  if (this.bReentry)
221
232
  return; // TODO is this likely? maybe, if we rewrite abc immediately w/ abc2abc
222
- this.bReentry = true;
223
- this.timerId = null;
224
- if (this.synth && this.synth.synthControl)
225
- this.synth.synthControl.disable(true);
226
-
227
- this.tunes = renderAbc(this.div, this.currentAbc, this.abcjsParams);
228
- if (this.tunes.length > 0) {
229
- this.warnings = this.tunes[0].warnings;
230
- }
231
- this.redrawMidi();
233
+ this.bReentry = true;
234
+ try {
235
+ this.timerId = null;
236
+ if (this.synth && this.synth.synthControl)
237
+ this.synth.synthControl.disable(true);
238
+
239
+ this.tunes = renderAbc(this.div, this.currentAbc, this.abcjsParams);
240
+ if (this.tunes.length > 0) {
241
+ this.warnings = this.tunes[0].warnings;
242
+ }
243
+ this.redrawMidi();
244
+ } catch(error) {
245
+ console.error("ABCJS error: ", error);
246
+ if (!this.warnings)
247
+ this.warnings = [];
248
+ this.warnings.push(error.message);
249
+ }
232
250
 
233
251
  if (this.warningsdiv) {
234
252
  this.warningsdiv.innerHTML = (this.warnings) ? this.warnings.join("<br />") : "No errors";
@@ -251,6 +269,8 @@ Editor.prototype.paramChanged = function(engraverParams) {
251
269
  };
252
270
 
253
271
  Editor.prototype.synthParamChanged = function(options) {
272
+ if (!this.synth)
273
+ return;
254
274
  this.synth.options = {};
255
275
  if (options) {
256
276
  for (var key in options) {
@@ -371,6 +391,8 @@ Editor.prototype.pause = function(shouldPause) {
371
391
  };
372
392
 
373
393
  Editor.prototype.millisecondsPerMeasure = function() {
394
+ if (!this.synth || !this.synth.synthControl || !this.synth.synthControl.visualObj)
395
+ return 0;
374
396
  return this.synth.synthControl.visualObj.millisecondsPerMeasure();
375
397
  };
376
398
 
@@ -19,6 +19,8 @@ var create;
19
19
  title = title.substring(0,124) + '...';
20
20
  var key = abcTune.getKeySignature();
21
21
  var time = abcTune.getMeterFraction();
22
+ var beatsPerSecond = commands.tempo / 60;
23
+ //var beatLength = abcTune.getBeatLength();
22
24
  midi.setGlobalInfo(commands.tempo, title, key, time);
23
25
 
24
26
  for (var i = 0; i < commands.tracks.length; i++) {
@@ -38,9 +40,11 @@ var create;
38
40
  midi.setInstrument(event.instrument);
39
41
  break;
40
42
  case 'note':
43
+ var gapLengthInBeats = event.gap * beatsPerSecond;
41
44
  var start = event.start;
42
- var end = start + event.duration;
43
- // TODO: end is affected by event.gap, too.
45
+ // The staccato and legato are indicated by event.gap.
46
+ // event.gap is in seconds but the durations are in whole notes.
47
+ var end = start + event.duration - gapLengthInBeats;
44
48
  if (!notePlacement[start])
45
49
  notePlacement[start] = [];
46
50
  notePlacement[start].push({ pitch: event.pitch, volume: event.volume, cents: event.cents });
@@ -24,6 +24,7 @@ var Parse = function() {
24
24
  lines: tune.lines,
25
25
  media: tune.media,
26
26
  metaText: tune.metaText,
27
+ metaTextInfo: tune.metaTextInfo,
27
28
  version: tune.version,
28
29
 
29
30
  addElementToEvents: tune.addElementToEvents,
@@ -137,8 +138,10 @@ var Parse = function() {
137
138
  };
138
139
  for (var i = 0; i < this.inTie.length; i++) {
139
140
  this.endingHoldOver.inTie.push([]);
140
- for (var j = 0; j < this.inTie[i].length; j++) {
141
- this.endingHoldOver.inTie[i].push(this.inTie[i][j]);
141
+ if (this.inTie[i]) { // if a voice is suppressed there might be a gap in the array.
142
+ for (var j = 0; j < this.inTie[i].length; j++) {
143
+ this.endingHoldOver.inTie[i].push(this.inTie[i][j]);
144
+ }
142
145
  }
143
146
  }
144
147
  for (var key in this.inTieChord) {
@@ -188,9 +191,7 @@ var Parse = function() {
188
191
  var bad_char = line.charAt(col_num);
189
192
  if (bad_char === ' ')
190
193
  bad_char = "SPACE";
191
- var clean_line = encode(line.substring(0, col_num)) +
192
- '<span style="text-decoration:underline;font-size:1.3em;font-weight:bold;">' + bad_char + '</span>' +
193
- encode(line.substring(col_num+1));
194
+ var clean_line = encode(line.substring(col_num - 64, col_num)) + '<span style="text-decoration:underline;font-size:1.3em;font-weight:bold;">' + bad_char + '</span>' + encode(line.substring(col_num + 1).substring(0,64));
194
195
  addWarning("Music Line:" + tokenizer.lineIndex + ":" + (col_num+1) + ': ' + str + ": " + clean_line);
195
196
  addWarningObject({message:str, line:line, startChar: multilineVars.iChar + col_num, column: col_num});
196
197
  };
@@ -473,7 +474,7 @@ var Parse = function() {
473
474
  // switches.transpose: change the key signature, chords, and notes by a number of half-steps.
474
475
  if (!switches) switches = {};
475
476
  if (!startPos) startPos = 0;
476
- tuneBuilder.reset();
477
+ tune.reset();
477
478
 
478
479
  // Take care of whatever line endings come our way
479
480
  // Tack on newline temporarily to make the last line continuation work