abcjs 6.4.0 → 6.4.2

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/RELEASE.md +42 -0
  2. package/dist/abcjs-basic-min.js +2 -2
  3. package/dist/abcjs-basic.js +1252 -1139
  4. package/dist/abcjs-basic.js.map +1 -1
  5. package/dist/abcjs-plugin-min.js +2 -2
  6. package/package.json +1 -1
  7. package/src/api/abc_tunebook.js +1 -2
  8. package/src/api/abc_tunebook_svg.js +0 -1
  9. package/src/midi/abc_midi_create.js +22 -7
  10. package/src/parse/abc_common.js +3 -11
  11. package/src/parse/abc_parse.js +1 -1
  12. package/src/parse/abc_parse_directive.js +44 -3
  13. package/src/parse/abc_parse_header.js +6 -4
  14. package/src/parse/abc_parse_key_voice.js +10 -6
  15. package/src/parse/abc_parse_music.js +22 -5
  16. package/src/parse/tune-builder.js +675 -643
  17. package/src/synth/abc_midi_flattener.js +3 -1
  18. package/src/synth/abc_midi_sequencer.js +18 -3
  19. package/src/synth/chord-track.js +86 -20
  20. package/src/synth/create-synth-control.js +1 -2
  21. package/src/synth/create-synth.js +1 -1
  22. package/src/tablatures/abc_tablatures.js +184 -0
  23. package/src/tablatures/instruments/string-patterns.js +266 -268
  24. package/src/tablatures/instruments/string-tablature.js +38 -35
  25. package/src/tablatures/instruments/tab-note.js +186 -181
  26. package/src/tablatures/instruments/tab-notes.js +30 -35
  27. package/src/tablatures/instruments/tab-string.js +43 -25
  28. package/src/tablatures/render/tab-absolute-elements.js +303 -0
  29. package/src/tablatures/render/tab-renderer.js +244 -0
  30. package/src/test/abc_parser_lint.js +2 -3
  31. package/src/write/creation/abstract-engraver.js +1 -1
  32. package/src/write/engraver-controller.js +1 -1
  33. package/temp.txt +9 -0
  34. package/types/index.d.ts +1 -1
  35. package/version.js +1 -1
  36. package/src/api/abc_tablatures.js +0 -184
  37. package/src/tablatures/instruments/tab-string-patterns.js +0 -23
  38. package/src/tablatures/tab-absolute-elements.js +0 -301
  39. package/src/tablatures/tab-common.js +0 -29
  40. package/src/tablatures/tab-renderer.js +0 -259
@@ -193,205 +193,6 @@ module.exports = animation;
193
193
 
194
194
  /***/ }),
195
195
 
196
- /***/ "./src/api/abc_tablatures.js":
197
- /*!***********************************!*\
198
- !*** ./src/api/abc_tablatures.js ***!
199
- \***********************************/
200
- /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
201
-
202
- /*
203
- * Tablature Plugins
204
- * tablature are defined dynamically and registered inside abcjs
205
- * by calling abcTablatures.register(plugin)
206
- * where plugin represents a plugin instance
207
- *
208
- */
209
- var StringTablature = __webpack_require__(/*! ../tablatures/instruments/tab-string */ "./src/tablatures/instruments/tab-string.js");
210
-
211
- /* extend the table below when adding a new instrument plugin */
212
-
213
- // Existing tab classes
214
- var pluginTab = {
215
- 'violin': {
216
- name: 'StringTab',
217
- defaultTuning: ['G,', 'D', 'A', 'e'],
218
- isTabBig: false,
219
- tabSymbolOffset: 0
220
- },
221
- 'fiddle': {
222
- name: 'StringTab',
223
- defaultTuning: ['G,', 'D', 'A', 'e'],
224
- isTabBig: false,
225
- tabSymbolOffset: 0
226
- },
227
- 'mandolin': {
228
- name: 'StringTab',
229
- defaultTuning: ['G,', 'D', 'A', 'e'],
230
- isTabBig: false,
231
- tabSymbolOffset: 0
232
- },
233
- 'guitar': {
234
- name: 'StringTab',
235
- defaultTuning: ['E,', 'A,', 'D', 'G', 'B', 'e'],
236
- isTabBig: true,
237
- tabSymbolOffset: 0
238
- },
239
- 'fiveString': {
240
- name: 'StringTab',
241
- defaultTuning: ['C,', 'G,', 'D', 'A', 'e'],
242
- isTabBig: false,
243
- tabSymbolOffset: -.95
244
- }
245
- };
246
- var abcTablatures = {
247
- inited: false,
248
- plugins: {},
249
- /**
250
- * to be called once per plugin for registration
251
- * @param {*} plugin
252
- */
253
- register: function register(plugin) {
254
- var name = plugin.name;
255
- var tablature = plugin.tablature;
256
- this.plugins[name] = tablature;
257
- },
258
- setError: function setError(tune, msg) {
259
- if (tune.warnings) {
260
- tune.warning.push(msg);
261
- } else {
262
- tune.warnings = [msg];
263
- }
264
- },
265
- /**
266
- * handle params for current processed score
267
- * @param {*} tune current tune
268
- * @param {*} tuneNumber number in tune list
269
- * @param {*} params params to be processed for tablature
270
- * @return prepared tablatures plugin instances for current tune
271
- */
272
- preparePlugins: function preparePlugins(tune, tuneNumber, params) {
273
- var returned = null;
274
- var nbPlugins = 0;
275
- if (params.tablature) {
276
- // validate requested plugins
277
- var tabs = params.tablature;
278
- returned = [];
279
- for (var ii = 0; ii < tabs.length; ii++) {
280
- var args = tabs[ii];
281
- var instrument = args['instrument'];
282
- if (instrument == null) {
283
- this.setError(tune, "tablature 'instrument' is missing");
284
- return returned;
285
- }
286
- var tabName = pluginTab[instrument];
287
- var plugin = null;
288
- if (tabName) {
289
- plugin = this.plugins[tabName.name];
290
- }
291
- if (plugin) {
292
- if (params.visualTranspose != 0) {
293
- // populate transposition request to tabs
294
- args.visualTranspose = params.visualTranspose;
295
- }
296
- args.abcSrc = params.tablature.abcSrc;
297
- var pluginInstance = {
298
- classz: plugin,
299
- tuneNumber: tuneNumber,
300
- params: args,
301
- instance: null,
302
- tabType: tabName
303
- };
304
- // proceed with tab plugin init
305
- // plugin.init(tune, tuneNumber, args, ii);
306
- returned.push(pluginInstance);
307
- nbPlugins++;
308
- } else if (instrument === '') {
309
- // create a placeholder - there is no tab for this staff
310
- returned.push(null);
311
- } else {
312
- // unknown tab plugin
313
- //this.emit_error('Undefined tablature plugin: ' + tabName)
314
- this.setError(tune, 'Undefined tablature plugin: ' + instrument);
315
- return returned;
316
- }
317
- }
318
- }
319
- return returned;
320
- },
321
- /**
322
- * Call requested plugin
323
- * @param {*} renderer
324
- * @param {*} abcTune
325
- */
326
- layoutTablatures: function layoutTablatures(renderer, abcTune) {
327
- var tabs = abcTune.tablatures;
328
-
329
- // chack tabs request for each staffs
330
- var staffLineCount = 0;
331
-
332
- // Clear the suppression flag
333
- if (tabs && tabs.length > 0) {
334
- var nTabs = tabs.length;
335
- for (var kk = 0; kk < nTabs; ++kk) {
336
- if (tabs[kk] && tabs[kk].params.firstStaffOnly) {
337
- tabs[kk].params.suppress = false;
338
- }
339
- }
340
- }
341
- for (var ii = 0; ii < abcTune.lines.length; ii++) {
342
- var line = abcTune.lines[ii];
343
- if (line.staff) {
344
- staffLineCount++;
345
- }
346
-
347
- // MAE 27Nov2023
348
- // If tab param "firstStaffOnly", remove the tab label after the first staff
349
- if (staffLineCount > 1) {
350
- if (tabs && tabs.length > 0) {
351
- var nTabs = tabs.length;
352
- for (var kk = 0; kk < nTabs; ++kk) {
353
- if (tabs[kk].params.firstStaffOnly) {
354
- // Set the staff draw suppression flag
355
- tabs[kk].params.suppress = true;
356
- }
357
- }
358
- }
359
- }
360
- var curStaff = line.staff;
361
- if (curStaff) {
362
- var maxStaves = curStaff.length;
363
- for (var jj = 0; jj < curStaff.length; jj++) {
364
- if (tabs[jj] && jj < maxStaves) {
365
- // tablature requested for staff
366
- var tabPlugin = tabs[jj];
367
- if (tabPlugin.instance == null) {
368
- tabPlugin.instance = new tabPlugin.classz();
369
- // plugin.init(tune, tuneNumber, args, ii);
370
- // call initer first
371
- tabPlugin.instance.init(abcTune, tabPlugin.tuneNumber, tabPlugin.params, jj, tabPlugin.tabType);
372
- }
373
- // render next
374
- tabPlugin.instance.render(renderer, line, jj);
375
- }
376
- }
377
- }
378
- }
379
- },
380
- /**
381
- * called once internally to register internal plugins
382
- */
383
- init: function init() {
384
- // just register plugin hosted by abcjs
385
- if (!this.inited) {
386
- this.register(new StringTablature());
387
- this.inited = true;
388
- }
389
- }
390
- };
391
- module.exports = abcTablatures;
392
-
393
- /***/ }),
394
-
395
196
  /***/ "./src/api/abc_timing_callbacks.js":
396
197
  /*!*****************************************!*\
397
198
  !*** ./src/api/abc_timing_callbacks.js ***!
@@ -714,7 +515,7 @@ function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" =
714
515
 
715
516
  var Parse = __webpack_require__(/*! ../parse/abc_parse */ "./src/parse/abc_parse.js");
716
517
  var bookParser = __webpack_require__(/*! ../parse/abc_parse_book */ "./src/parse/abc_parse_book.js");
717
- var tablatures = __webpack_require__(/*! ./abc_tablatures */ "./src/api/abc_tablatures.js");
518
+ var tablatures = __webpack_require__(/*! ../tablatures/abc_tablatures */ "./src/tablatures/abc_tablatures.js");
718
519
  var tunebook = {};
719
520
  (function () {
720
521
  "use strict";
@@ -785,7 +586,6 @@ var tunebook = {};
785
586
  // Init tablatures plugins
786
587
  //
787
588
  if (params.tablature) {
788
- tablatures.init();
789
589
  tune.tablatures = tablatures.preparePlugins(tune, currentTune, params);
790
590
  }
791
591
  var warnings = abcParser.getWarnings();
@@ -955,8 +755,6 @@ var Tune = __webpack_require__(/*! ../data/abc_tune */ "./src/data/abc_tune.js")
955
755
  var EngraverController = __webpack_require__(/*! ../write/engraver-controller */ "./src/write/engraver-controller.js");
956
756
  var Parse = __webpack_require__(/*! ../parse/abc_parse */ "./src/parse/abc_parse.js");
957
757
  var wrap = __webpack_require__(/*! ../parse/wrap_lines */ "./src/parse/wrap_lines.js");
958
- // var tablatures = require('./abc_tablatures');
959
-
960
758
  var resizeDivs = {};
961
759
  function resizeOuter() {
962
760
  var width = window.innerWidth;
@@ -2630,9 +2428,21 @@ var create;
2630
2428
  if (title && title.length > 128) title = title.substring(0, 124) + '...';
2631
2429
  var key = abcTune.getKeySignature();
2632
2430
  var time = abcTune.getMeterFraction();
2633
- var beatsPerSecond = commands.tempo / 60;
2431
+
2432
+ // MAE 7 July 2024 - Fix for */8 meter tempos
2433
+ var tempo = commands.tempo;
2434
+ var beatsPerSecond = tempo / 60;
2435
+
2436
+ // Fix tempo for */8 meters
2437
+ if (time.den == 8) {
2438
+ // Compute the tempo based on the actual milliseconds per measure, scaled by the number of eight notes and halved to get tempo in bpm.
2439
+ var msPerMeasure = abcTune.millisecondsPerMeasure();
2440
+ tempo = 60000 / (msPerMeasure / time.num) / 2;
2441
+ beatsPerSecond = tempo / 60;
2442
+ }
2443
+
2634
2444
  //var beatLength = abcTune.getBeatLength();
2635
- midi.setGlobalInfo(commands.tempo, title, key, time);
2445
+ midi.setGlobalInfo(tempo, title, key, time);
2636
2446
  for (var i = 0; i < commands.tracks.length; i++) {
2637
2447
  midi.startTrack();
2638
2448
  var notePlacement = {};
@@ -2718,27 +2528,20 @@ module.exports = create;
2718
2528
  \*********************************/
2719
2529
  /***/ (function(module) {
2720
2530
 
2721
- // abc_parse.js: parses a string representing ABC Music Notation into a usable internal structure.
2531
+ // abc_common.js: Some common utility functions.
2722
2532
 
2723
2533
  var parseCommon = {};
2724
- parseCommon.clone = function (source) {
2725
- var destination = {};
2726
- for (var property in source) {
2727
- if (source.hasOwnProperty(property)) destination[property] = source[property];
2728
- }
2729
- return destination;
2730
- };
2731
2534
  parseCommon.cloneArray = function (source) {
2732
2535
  var destination = [];
2733
2536
  for (var i = 0; i < source.length; i++) {
2734
- destination.push(parseCommon.clone(source[i]));
2537
+ destination.push(Object.assign({}, source[i]));
2735
2538
  }
2736
2539
  return destination;
2737
2540
  };
2738
2541
  parseCommon.cloneHashOfHash = function (source) {
2739
2542
  var destination = {};
2740
2543
  for (var property in source) {
2741
- if (source.hasOwnProperty(property)) destination[property] = parseCommon.clone(source[property]);
2544
+ if (source.hasOwnProperty(property)) destination[property] = Object.assign({}, source[property]);
2742
2545
  }
2743
2546
  return destination;
2744
2547
  };
@@ -3230,7 +3033,7 @@ var Parse = function Parse() {
3230
3033
  });
3231
3034
  for (var i = 0; i < nextVoice.length; i++) {
3232
3035
  var element = nextVoice[i];
3233
- var hint = parseCommon.clone(element);
3036
+ var hint = Object.assign({}, element);
3234
3037
  voice.push(hint);
3235
3038
  if (element.el_type === 'bar') return;
3236
3039
  }
@@ -4157,7 +3960,7 @@ var parseDirective = {};
4157
3960
  };
4158
3961
  var midiCmdParam0 = ["nobarlines", "barlines", "beataccents", "nobeataccents", "droneon", "droneoff", "drumon", "drumoff", "fermatafixed", "fermataproportional", "gchordon", "gchordoff", "controlcombo", "temperamentnormal", "noportamento"];
4159
3962
  var midiCmdParam1String = ["gchord", "ptstress", "beatstring"];
4160
- var midiCmdParam1Integer = ["bassvol", "chordvol", "bassprog", "chordprog", "c", "channel", "beatmod", "deltaloudness", "drumbars", "gracedivider", "makechordchannels", "randomchordattack", "chordattack", "stressmodel", "transpose", "rtranspose", "vol", "volinc"];
3963
+ var midiCmdParam1Integer = ["bassvol", "chordvol", "c", "channel", "beatmod", "deltaloudness", "drumbars", "gracedivider", "makechordchannels", "randomchordattack", "chordattack", "stressmodel", "transpose", "rtranspose", "vol", "volinc", "gchordbars"];
4161
3964
  var midiCmdParam1Integer1OptionalInteger = ["program"];
4162
3965
  var midiCmdParam2Integer = ["ratio", "snt", "bendvelocity", "pitchbend", "control", "temperamentlinear"];
4163
3966
  var midiCmdParam4Integer = ["beat"];
@@ -4165,6 +3968,7 @@ var parseDirective = {};
4165
3968
  var midiCmdParam1String1Integer = ["portamento"];
4166
3969
  var midiCmdParamFraction = ["expand", "grace", "trim"];
4167
3970
  var midiCmdParam1StringVariableIntegers = ["drum", "chordname"];
3971
+ var midiCmdParam1Integer1OptionalString = ["bassprog", "chordprog"];
4168
3972
  var parseMidiCommand = function parseMidiCommand(midi, tune, restOfString) {
4169
3973
  var midi_cmd = midi.shift().token;
4170
3974
  var midi_params = [];
@@ -4256,6 +4060,34 @@ var parseDirective = {};
4256
4060
  midi_params.push(p.intt);
4257
4061
  }
4258
4062
  }
4063
+ } else if (midiCmdParam1Integer1OptionalString.indexOf(midi_cmd) >= 0) {
4064
+ // ONE INT PARAMETER, ONE OPTIONAL string
4065
+ if (midi.length !== 1 && midi.length !== 2) warn("Expected one or two parameters in MIDI " + midi_cmd, restOfString, 0);else if (midi[0].type !== "number") warn("Expected integer parameter in MIDI " + midi_cmd, restOfString, 0);else if (midi.length === 2 && midi[1].type !== "alpha") warn("Expected alpha parameter in MIDI " + midi_cmd, restOfString, 0);else {
4066
+ midi_params.push(midi[0].intt);
4067
+
4068
+ // Currently only bassprog and chordprog with optional octave shifts use this path
4069
+ if (midi.length === 2) {
4070
+ var cmd = midi[1].token;
4071
+ if (cmd.indexOf("octave=") != -1) {
4072
+ cmd = cmd.replace("octave=", "");
4073
+ cmd = parseInt(cmd);
4074
+ if (!isNaN(cmd)) {
4075
+ // Limit range from -1 to 3 octaves
4076
+ if (cmd < -1) {
4077
+ warn("Expected octave= in MIDI " + midi_cmd + ' to be >= -1 (recv:' + cmd + ')');
4078
+ cmd = -1;
4079
+ }
4080
+ if (cmd > 3) {
4081
+ warn("Expected octave= in MIDI " + midi_cmd + ' to be <= 3 (recv:' + cmd + ')');
4082
+ cmd = 3;
4083
+ }
4084
+ midi_params.push(cmd);
4085
+ } else warn("Expected octave value in MIDI" + midi_cmd);
4086
+ } else {
4087
+ warn("Expected octave= in MIDI" + midi_cmd);
4088
+ }
4089
+ }
4090
+ }
4259
4091
  }
4260
4092
  if (tuneBuilder.hasBeginMusic()) tuneBuilder.appendElement('midi', -1, -1, {
4261
4093
  cmd: midi_cmd,
@@ -5201,6 +5033,7 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
5201
5033
  }
5202
5034
  };
5203
5035
  this.letter_to_inline_header = function (line, i, startLine) {
5036
+ var needsNewLine = false;
5204
5037
  var ws = tokenizer.eatWhiteSpace(line, i);
5205
5038
  i += ws;
5206
5039
  if (line.length >= i + 5 && line[i] === '[' && line[i + 2] === ':') {
@@ -5247,9 +5080,9 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
5247
5080
  break;
5248
5081
  case "[V:":
5249
5082
  if (e > 0) {
5250
- parseKeyVoice.parseVoice(line, i + 3, e);
5083
+ needsNewLine = parseKeyVoice.parseVoice(line, i + 3, e);
5251
5084
  //startNewLine();
5252
- return [e - i + 1 + ws, line[i + 1], line.substring(i + 3, e)];
5085
+ return [e - i + 1 + ws, line[i + 1], line.substring(i + 3, e), needsNewLine];
5253
5086
  }
5254
5087
  break;
5255
5088
  case "[r:":
@@ -5262,6 +5095,7 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
5262
5095
  return [0];
5263
5096
  };
5264
5097
  this.letter_to_body_header = function (line, i) {
5098
+ var needsNewLine = false;
5265
5099
  if (line.length >= i + 3) {
5266
5100
  switch (line.substring(i, i + 2)) {
5267
5101
  case "I:":
@@ -5292,9 +5126,9 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
5292
5126
  if (tempo.type === 'delaySet') tuneBuilder.appendElement('tempo', multilineVars.iChar + i, multilineVars.iChar + line.length, this.calcTempo(tempo.tempo));else if (tempo.type === 'immediate') tuneBuilder.appendElement('tempo', multilineVars.iChar + i, multilineVars.iChar + line.length, tempo.tempo);
5293
5127
  return [e, line[i], parseCommon.strip(line.substring(i + 2))];
5294
5128
  case "V:":
5295
- parseKeyVoice.parseVoice(line, i + 2, line.length);
5129
+ needsNewLine = parseKeyVoice.parseVoice(line, i + 2, line.length);
5296
5130
  // startNewLine();
5297
- return [line.length, line[i], parseCommon.strip(line.substring(i + 2))];
5131
+ return [line.length, line[i], parseCommon.strip(line.substring(i + 2)), needsNewLine];
5298
5132
  default:
5299
5133
  // TODO: complain about unhandled header
5300
5134
  }
@@ -5432,7 +5266,6 @@ module.exports = ParseHeader;
5432
5266
  \******************************************/
5433
5267
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
5434
5268
 
5435
- var parseCommon = __webpack_require__(/*! ./abc_common */ "./src/parse/abc_common.js");
5436
5269
  var parseDirective = __webpack_require__(/*! ./abc_parse_directive */ "./src/parse/abc_parse_directive.js");
5437
5270
  var transpose = __webpack_require__(/*! ./abc_transpose */ "./src/parse/abc_transpose.js");
5438
5271
  var parseKeyVoice = {};
@@ -5683,7 +5516,7 @@ var parseKeyVoice = {};
5683
5516
  mode: key.mode
5684
5517
  };
5685
5518
  key.accidentals.forEach(function (k) {
5686
- ret.accidentals.push(parseCommon.clone(k));
5519
+ ret.accidentals.push(Object.assign({}, k));
5687
5520
  });
5688
5521
  return ret;
5689
5522
  };
@@ -5749,7 +5582,7 @@ var parseKeyVoice = {};
5749
5582
  }
5750
5583
  };
5751
5584
  parseKeyVoice.fixKey = function (clef, key) {
5752
- var fixedKey = parseCommon.clone(key);
5585
+ var fixedKey = Object.assign({}, key);
5753
5586
  parseKeyVoice.addPosToKey(clef, fixedKey);
5754
5587
  return fixedKey;
5755
5588
  };
@@ -6180,8 +6013,13 @@ var parseKeyVoice = {};
6180
6013
  return ret;
6181
6014
  };
6182
6015
  var setCurrentVoice = function setCurrentVoice(id) {
6183
- multilineVars.currentVoice = multilineVars.voices[id];
6184
- tuneBuilder.setCurrentVoice(multilineVars.currentVoice.staffNum, multilineVars.currentVoice.index);
6016
+ var currentVoice = multilineVars.voices[id];
6017
+ if (multilineVars.currentVoice) {
6018
+ if (multilineVars.currentVoice.index === currentVoice.index && multilineVars.currentVoice.staffNum === currentVoice.staffNum) return; // there was no change so don't reset it.
6019
+ }
6020
+
6021
+ multilineVars.currentVoice = currentVoice;
6022
+ return tuneBuilder.setCurrentVoice(currentVoice.staffNum, currentVoice.index, id);
6185
6023
  };
6186
6024
  parseKeyVoice.parseVoice = function (line, i, e) {
6187
6025
  //First truncate the string to the first non-space character after V: through either the
@@ -6433,7 +6271,7 @@ var parseKeyVoice = {};
6433
6271
  if (staffInfo.subname) {
6434
6272
  if (s.subname) s.subname.push(staffInfo.subname);else s.subname = [staffInfo.subname];
6435
6273
  }
6436
- setCurrentVoice(id);
6274
+ return setCurrentVoice(id);
6437
6275
  };
6438
6276
  })();
6439
6277
  module.exports = parseKeyVoice;
@@ -6446,7 +6284,6 @@ module.exports = parseKeyVoice;
6446
6284
  \**************************************/
6447
6285
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
6448
6286
 
6449
- var parseCommon = __webpack_require__(/*! ./abc_common */ "./src/parse/abc_common.js");
6450
6287
  var parseKeyVoice = __webpack_require__(/*! ./abc_parse_key_voice */ "./src/parse/abc_parse_key_voice.js");
6451
6288
  var transpose = __webpack_require__(/*! ./abc_transpose */ "./src/parse/abc_transpose.js");
6452
6289
  var tokenizer;
@@ -6579,6 +6416,7 @@ MusicParser.prototype.parseMusic = function (line) {
6579
6416
  var retInlineHeader = header.letter_to_inline_header(line, i, delayStartNewLine);
6580
6417
  if (retInlineHeader[0] > 0) {
6581
6418
  i += retInlineHeader[0];
6419
+ //console.log("inline header", retInlineHeader)
6582
6420
  if (retInlineHeader[1] === 'V') delayStartNewLine = true; // fixes bug on this: c[V:2]d
6583
6421
  // TODO-PER: Handle inline headers
6584
6422
  //multilineVars.start_new_line = false;
@@ -6858,6 +6696,9 @@ MusicParser.prototype.parseMusic = function (line) {
6858
6696
  if (ch === '-' || ch === ')' || ch === ' ' || ch === '<' || ch === '>') i--; // Subtracting one because one is automatically added below
6859
6697
  else postChordDone = true;
6860
6698
  break;
6699
+ case '0':
6700
+ chordDuration = 0;
6701
+ break;
6861
6702
  default:
6862
6703
  postChordDone = true;
6863
6704
  break;
@@ -6947,7 +6788,11 @@ MusicParser.prototype.parseMusic = function (line) {
6947
6788
  if (!el.rest || el.rest.type !== 'spacer') warn("Duration not representable: " + line.substring(startI, i), line, i);
6948
6789
  }
6949
6790
  multilineVars.addFormattingOptions(el, tune.formatting, 'note');
6950
- tuneBuilder.appendElement('note', startOfLine + startI, startOfLine + i, el);
6791
+ var succeeded = tuneBuilder.appendElement('note', startOfLine + startI, startOfLine + i, el);
6792
+ if (!succeeded) {
6793
+ this.startNewLine();
6794
+ tuneBuilder.appendElement('note', startOfLine + startI, startOfLine + i, el);
6795
+ }
6951
6796
  multilineVars.measureNotEmpty = true;
6952
6797
  el = {};
6953
6798
  }
@@ -7049,7 +6894,10 @@ var letter_to_grace = function letter_to_grace(line, i) {
7049
6894
  // The grace note durations should not be affected by the default length: they should be based on 1/16, so if that isn't the default, then multiply here.
7050
6895
  note.duration = note.duration / (multilineVars.default_length * 8);
7051
6896
  if (acciaccatura) note.acciaccatura = true;
7052
- gracenotes.push(note);
6897
+ if (note.rest) {
6898
+ // don't allow rests inside gracenotes
6899
+ warn("Rests not allowed as grace notes '" + gra[1][ii] + "' while parsing grace note", line, i);
6900
+ } else gracenotes.push(note);
7053
6901
  if (inTie) {
7054
6902
  note.endTie = true;
7055
6903
  inTie = false;
@@ -7280,7 +7128,7 @@ MusicParser.prototype.startNewLine = function () {
7280
7128
  endChar: -1
7281
7129
  };
7282
7130
  if (multilineVars.partForNextLine.title) params.part = multilineVars.partForNextLine;
7283
- params.clef = multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].clef !== undefined ? parseCommon.clone(multilineVars.staves[multilineVars.currentVoice.staffNum].clef) : parseCommon.clone(multilineVars.clef);
7131
+ params.clef = multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].clef !== undefined ? Object.assign({}, multilineVars.staves[multilineVars.currentVoice.staffNum].clef) : Object.assign({}, multilineVars.clef);
7284
7132
  var scoreTranspose = multilineVars.currentVoice ? multilineVars.currentVoice.scoreTranspose : 0;
7285
7133
  params.key = parseKeyVoice.standardKey(multilineVars.key.root + multilineVars.key.acc + multilineVars.key.mode, multilineVars.key.root, multilineVars.key.acc, scoreTranspose);
7286
7134
  params.key.mode = multilineVars.key.mode;
@@ -7334,6 +7182,11 @@ MusicParser.prototype.startNewLine = function () {
7334
7182
  if (multilineVars.currentVoice.color) params.color = multilineVars.currentVoice.color;
7335
7183
  if (multilineVars.currentVoice.style) params.style = multilineVars.currentVoice.style;
7336
7184
  if (multilineVars.currentVoice.transpose) params.clef.transpose = multilineVars.currentVoice.transpose;
7185
+ params.currentVoice = multilineVars.currentVoice;
7186
+ var voices = Object.keys(multilineVars.voices);
7187
+ for (var mv = 0; mv < voices.length; mv++) {
7188
+ if (params.currentVoice.staffNum === multilineVars.voices[voices[mv]].staffNum && params.currentVoice.index === multilineVars.voices[voices[mv]].index) params.currentVoiceName = voices[mv];
7189
+ }
7337
7190
  }
7338
7191
  var isFirstVoice = multilineVars.currentVoice === undefined || multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0;
7339
7192
  if (multilineVars.barNumbers === 0 && isFirstVoice && multilineVars.currBarNumber !== 1) params.barNumber = multilineVars.currBarNumber;
@@ -9321,166 +9174,19 @@ module.exports = transposeChordName;
9321
9174
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
9322
9175
 
9323
9176
  var parseKeyVoice = __webpack_require__(/*! ../parse/abc_parse_key_voice */ "./src/parse/abc_parse_key_voice.js");
9324
- var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/abc_common.js");
9325
- var parseDirective = __webpack_require__(/*! ./abc_parse_directive */ "./src/parse/abc_parse_directive.js");
9177
+ //var parseCommon = require('../parse/abc_common');
9178
+ //var parseDirective = require('./abc_parse_directive');
9179
+
9326
9180
  var TuneBuilder = function TuneBuilder(tune) {
9327
9181
  var self = this;
9182
+ var voiceDefs = {};
9183
+ var currentVoiceName = '';
9184
+ tune.reset();
9328
9185
  this.setVisualTranspose = function (visualTranspose) {
9329
9186
  if (visualTranspose) tune.visualTranspose = visualTranspose;
9330
9187
  };
9331
- this.resolveOverlays = function () {
9332
- var madeChanges = false;
9333
- var durationsPerLines = [];
9334
- for (var i = 0; i < tune.lines.length; i++) {
9335
- var line = tune.lines[i];
9336
- if (line.staff) {
9337
- for (var j = 0; j < line.staff.length; j++) {
9338
- var staff = line.staff[j];
9339
- var overlayVoice = [];
9340
- for (var k = 0; k < staff.voices.length; k++) {
9341
- var voice = staff.voices[k];
9342
- overlayVoice.push({
9343
- hasOverlay: false,
9344
- voice: [],
9345
- snip: []
9346
- });
9347
- durationsPerLines[i] = 0;
9348
- var durationThisBar = 0;
9349
- var inOverlay = false;
9350
- var overlayDuration = 0;
9351
- var snipStart = -1;
9352
- for (var kk = 0; kk < voice.length; kk++) {
9353
- var event = voice[kk];
9354
- if (event.el_type === "overlay" && !inOverlay) {
9355
- madeChanges = true;
9356
- inOverlay = true;
9357
- snipStart = kk;
9358
- overlayVoice[k].hasOverlay = true;
9359
- if (overlayDuration === 0) overlayDuration = durationsPerLines[i];
9360
- // If this isn't the first line, we also need invisible rests on the previous lines.
9361
- // So, if the next voice doesn't appear in a previous line, create it
9362
- for (var ii = 0; ii < i; ii++) {
9363
- if (durationsPerLines[ii] && tune.lines[ii].staff && staff.voices.length >= tune.lines[ii].staff[0].voices.length) {
9364
- tune.lines[ii].staff[0].voices.push([{
9365
- el_type: "note",
9366
- duration: durationsPerLines[ii],
9367
- rest: {
9368
- type: "invisible"
9369
- },
9370
- startChar: event.startChar,
9371
- endChar: event.endChar
9372
- }]);
9373
- }
9374
- }
9375
- } else if (event.el_type === "bar") {
9376
- if (inOverlay) {
9377
- // delete the overlay events from this array without messing up this loop.
9378
- inOverlay = false;
9379
- overlayVoice[k].snip.push({
9380
- start: snipStart,
9381
- len: kk - snipStart
9382
- });
9383
- overlayVoice[k].voice.push(event); // Also end the overlay with the barline.
9384
- } else {
9385
- // This keeps the voices lined up: if the overlay isn't in the first measure then we need a bunch of invisible rests.
9386
- if (durationThisBar > 0) overlayVoice[k].voice.push({
9387
- el_type: "note",
9388
- duration: durationThisBar,
9389
- rest: {
9390
- type: "invisible"
9391
- },
9392
- startChar: event.startChar,
9393
- endChar: event.endChar
9394
- });
9395
- overlayVoice[k].voice.push(event);
9396
- }
9397
- durationThisBar = 0;
9398
- } else if (event.el_type === "note") {
9399
- if (inOverlay) {
9400
- overlayVoice[k].voice.push(event);
9401
- } else {
9402
- durationThisBar += event.duration;
9403
- durationsPerLines[i] += event.duration;
9404
- }
9405
- } else if (event.el_type === "scale" || event.el_type === "stem" || event.el_type === "overlay" || event.el_type === "style" || event.el_type === "transpose" || event.el_type === "color") {
9406
- // These types of events are duplicated on the overlay layer.
9407
- overlayVoice[k].voice.push(event);
9408
- }
9409
- }
9410
- if (overlayVoice[k].hasOverlay && overlayVoice[k].snip.length === 0) {
9411
- // there was no closing bar, so we didn't set the snip amount.
9412
- overlayVoice[k].snip.push({
9413
- start: snipStart,
9414
- len: voice.length - snipStart
9415
- });
9416
- }
9417
- }
9418
- for (k = 0; k < overlayVoice.length; k++) {
9419
- var ov = overlayVoice[k];
9420
- if (ov.hasOverlay) {
9421
- ov.voice.splice(0, 0, {
9422
- el_type: "stem",
9423
- direction: "down"
9424
- });
9425
- staff.voices.push(ov.voice);
9426
- for (var kkk = ov.snip.length - 1; kkk >= 0; kkk--) {
9427
- var snip = ov.snip[kkk];
9428
- staff.voices[k].splice(snip.start, snip.len);
9429
- staff.voices[k].splice(snip.start + 1, 0, {
9430
- el_type: "stem",
9431
- direction: "auto"
9432
- });
9433
- var indexOfLastBar = findLastBar(staff.voices[k], snip.start);
9434
- staff.voices[k].splice(indexOfLastBar, 0, {
9435
- el_type: "stem",
9436
- direction: "up"
9437
- });
9438
- }
9439
- // remove ending marks from the overlay voice so they are not repeated
9440
- for (kkk = 0; kkk < staff.voices[staff.voices.length - 1].length; kkk++) {
9441
- staff.voices[staff.voices.length - 1][kkk] = parseCommon.clone(staff.voices[staff.voices.length - 1][kkk]);
9442
- var el = staff.voices[staff.voices.length - 1][kkk];
9443
- if (el.el_type === 'bar' && el.startEnding) {
9444
- delete el.startEnding;
9445
- }
9446
- if (el.el_type === 'bar' && el.endEnding) delete el.endEnding;
9447
- }
9448
- }
9449
- }
9450
- }
9451
- }
9452
- }
9453
- return madeChanges;
9454
- };
9455
- function findLastBar(voice, start) {
9456
- for (var i = start - 1; i > 0 && voice[i].el_type !== "bar"; i--) {}
9457
- return i;
9458
- }
9459
- function fixTitles(lines) {
9460
- // We might have name and subname defined. We now know what line everything is on, so we can determine which to use.
9461
- var firstMusicLine = true;
9462
- for (var i = 0; i < lines.length; i++) {
9463
- var line = lines[i];
9464
- if (line.staff) {
9465
- for (var j = 0; j < line.staff.length; j++) {
9466
- var staff = line.staff[j];
9467
- if (staff.title) {
9468
- var hasATitle = false;
9469
- for (var k = 0; k < staff.title.length; k++) {
9470
- if (staff.title[k]) {
9471
- staff.title[k] = firstMusicLine ? staff.title[k].name : staff.title[k].subname;
9472
- if (staff.title[k]) hasATitle = true;else staff.title[k] = '';
9473
- } else staff.title[k] = '';
9474
- }
9475
- if (!hasATitle) delete staff.title;
9476
- }
9477
- }
9478
- firstMusicLine = false;
9479
- }
9480
- }
9481
- }
9482
9188
  this.cleanUp = function (barsperstaff, staffnonote, currSlur) {
9483
- this.closeLine(); // Close the last line.
9189
+ closeLine(tune); // Close the last line.
9484
9190
  delete tune.runningFonts;
9485
9191
  simplifyMetaText(tune);
9486
9192
  //addRichTextToAnnotationsAndLyrics(tune)
@@ -9502,7 +9208,7 @@ var TuneBuilder = function TuneBuilder(tune) {
9502
9208
  } else {
9503
9209
  for (v = 0; v < tune.lines[i].staff[s].voices.length; v++) {
9504
9210
  if (tune.lines[i].staff[s].voices[v] === undefined) tune.lines[i].staff[s].voices[v] = []; // TODO-PER: There was a part missing in the abc music. How should we recover?
9505
- else if (this.containsNotes(tune.lines[i].staff[s].voices[v])) hasAny = true;
9211
+ else if (containsNotes(tune.lines[i].staff[s].voices[v])) hasAny = true;
9506
9212
  }
9507
9213
  }
9508
9214
  }
@@ -9538,7 +9244,7 @@ var TuneBuilder = function TuneBuilder(tune) {
9538
9244
  for (s = 0; s < tune.lines[i].staff.length; s++) {
9539
9245
  var keepThis = false;
9540
9246
  for (v = 0; v < tune.lines[i].staff[s].voices.length; v++) {
9541
- if (this.containsNotesStrict(tune.lines[i].staff[s].voices[v])) {
9247
+ if (containsNotesStrict(tune.lines[i].staff[s].voices[v])) {
9542
9248
  keepThis = true;
9543
9249
  }
9544
9250
  }
@@ -9569,208 +9275,23 @@ var TuneBuilder = function TuneBuilder(tune) {
9569
9275
  }
9570
9276
 
9571
9277
  // If there are overlays, create new voices for them.
9572
- while (this.resolveOverlays()) {
9278
+ while (resolveOverlays(tune)) {
9573
9279
  // keep resolving overlays as long as any are found.
9574
9280
  }
9575
- function cleanUpSlursInLine(line, staffNum, voiceNum) {
9576
- if (!currSlur[staffNum]) currSlur[staffNum] = [];
9577
- if (!currSlur[staffNum][voiceNum]) currSlur[staffNum][voiceNum] = [];
9578
- var x;
9579
- // var lyr = null; // TODO-PER: debugging.
9580
-
9581
- var addEndSlur = function addEndSlur(obj, num, chordPos) {
9582
- if (currSlur[staffNum][voiceNum][chordPos] === undefined) {
9583
- // There isn't an exact match for note position, but we'll take any other open slur.
9584
- for (x = 0; x < currSlur[staffNum][voiceNum].length; x++) {
9585
- if (currSlur[staffNum][voiceNum][x] !== undefined) {
9586
- chordPos = x;
9587
- break;
9588
- }
9589
- }
9590
- if (currSlur[staffNum][voiceNum][chordPos] === undefined) {
9591
- var offNum = chordPos * 100 + 1;
9592
- obj.endSlur.forEach(function (x) {
9593
- if (offNum === x) --offNum;
9594
- });
9595
- currSlur[staffNum][voiceNum][chordPos] = [offNum];
9596
- }
9597
- }
9598
- var slurNum;
9599
- for (var i = 0; i < num; i++) {
9600
- slurNum = currSlur[staffNum][voiceNum][chordPos].pop();
9601
- obj.endSlur.push(slurNum);
9602
- // lyr.syllable += '<' + slurNum; // TODO-PER: debugging
9603
- }
9604
-
9605
- if (currSlur[staffNum][voiceNum][chordPos].length === 0) delete currSlur[staffNum][voiceNum][chordPos];
9606
- return slurNum;
9607
- };
9608
- var addStartSlur = function addStartSlur(obj, num, chordPos, usedNums) {
9609
- obj.startSlur = [];
9610
- if (currSlur[staffNum][voiceNum][chordPos] === undefined) {
9611
- currSlur[staffNum][voiceNum][chordPos] = [];
9612
- }
9613
- var nextNum = chordPos * 100 + 1;
9614
- for (var i = 0; i < num; i++) {
9615
- if (usedNums) {
9616
- usedNums.forEach(function (x) {
9617
- if (nextNum === x) ++nextNum;
9618
- });
9619
- usedNums.forEach(function (x) {
9620
- if (nextNum === x) ++nextNum;
9621
- });
9622
- usedNums.forEach(function (x) {
9623
- if (nextNum === x) ++nextNum;
9624
- });
9625
- }
9626
- currSlur[staffNum][voiceNum][chordPos].forEach(function (x) {
9627
- if (nextNum === x) ++nextNum;
9628
- });
9629
- currSlur[staffNum][voiceNum][chordPos].forEach(function (x) {
9630
- if (nextNum === x) ++nextNum;
9631
- });
9632
- currSlur[staffNum][voiceNum][chordPos].push(nextNum);
9633
- obj.startSlur.push({
9634
- label: nextNum
9635
- });
9636
- if (obj.dottedSlur) {
9637
- obj.startSlur[obj.startSlur.length - 1].style = 'dotted';
9638
- delete obj.dottedSlur;
9639
- }
9640
- // lyr.syllable += ' ' + nextNum + '>'; // TODO-PER:debugging
9641
- nextNum++;
9642
- }
9643
- };
9644
- for (var i = 0; i < line.length; i++) {
9645
- var el = line[i];
9646
- // if (el.lyric === undefined) // TODO-PER: debugging
9647
- // el.lyric = [{ divider: '-' }]; // TODO-PER: debugging
9648
- // lyr = el.lyric[0]; // TODO-PER: debugging
9649
- // lyr.syllable = ''; // TODO-PER: debugging
9650
- if (el.el_type === 'note') {
9651
- if (el.gracenotes) {
9652
- for (var g = 0; g < el.gracenotes.length; g++) {
9653
- if (el.gracenotes[g].endSlur) {
9654
- var gg = el.gracenotes[g].endSlur;
9655
- el.gracenotes[g].endSlur = [];
9656
- for (var ggg = 0; ggg < gg; ggg++) {
9657
- addEndSlur(el.gracenotes[g], 1, 20);
9658
- }
9659
- }
9660
- if (el.gracenotes[g].startSlur) {
9661
- x = el.gracenotes[g].startSlur;
9662
- addStartSlur(el.gracenotes[g], x, 20);
9663
- }
9664
- }
9665
- }
9666
- if (el.endSlur) {
9667
- x = el.endSlur;
9668
- el.endSlur = [];
9669
- addEndSlur(el, x, 0);
9670
- }
9671
- if (el.startSlur) {
9672
- x = el.startSlur;
9673
- addStartSlur(el, x, 0);
9674
- }
9675
- if (el.pitches) {
9676
- var usedNums = [];
9677
- for (var p = 0; p < el.pitches.length; p++) {
9678
- if (el.pitches[p].endSlur) {
9679
- var k = el.pitches[p].endSlur;
9680
- el.pitches[p].endSlur = [];
9681
- for (var j = 0; j < k; j++) {
9682
- var slurNum = addEndSlur(el.pitches[p], 1, p + 1);
9683
- usedNums.push(slurNum);
9684
- }
9685
- }
9686
- }
9687
- for (p = 0; p < el.pitches.length; p++) {
9688
- if (el.pitches[p].startSlur) {
9689
- x = el.pitches[p].startSlur;
9690
- addStartSlur(el.pitches[p], x, p + 1, usedNums);
9691
- }
9692
- }
9693
- // Correct for the weird gracenote case where ({g}a) should match.
9694
- // The end slur was already assigned to the note, and needs to be moved to the first note of the graces.
9695
- if (el.gracenotes && el.pitches[0].endSlur && el.pitches[0].endSlur[0] === 100 && el.pitches[0].startSlur) {
9696
- if (el.gracenotes[0].endSlur) el.gracenotes[0].endSlur.push(el.pitches[0].startSlur[0].label);else el.gracenotes[0].endSlur = [el.pitches[0].startSlur[0].label];
9697
- if (el.pitches[0].endSlur.length === 1) delete el.pitches[0].endSlur;else if (el.pitches[0].endSlur[0] === 100) el.pitches[0].endSlur.shift();else if (el.pitches[0].endSlur[el.pitches[0].endSlur.length - 1] === 100) el.pitches[0].endSlur.pop();
9698
- if (currSlur[staffNum][voiceNum][1].length === 1) delete currSlur[staffNum][voiceNum][1];else currSlur[staffNum][voiceNum][1].pop();
9699
- }
9700
- }
9701
- }
9702
- }
9703
- }
9704
-
9705
- // TODO-PER: This could be done faster as we go instead of as the last step.
9706
- function fixClefPlacement(el) {
9707
- parseKeyVoice.fixClef(el);
9708
- }
9709
- function wrapMusicLines(lines, barsperstaff) {
9710
- for (i = 0; i < lines.length; i++) {
9711
- if (lines[i].staff !== undefined) {
9712
- for (s = 0; s < lines[i].staff.length; s++) {
9713
- var permanentItems = [];
9714
- for (v = 0; v < lines[i].staff[s].voices.length; v++) {
9715
- var voice = lines[i].staff[s].voices[v];
9716
- var barNumThisLine = 0;
9717
- for (var n = 0; n < voice.length; n++) {
9718
- if (voice[n].el_type === 'bar') {
9719
- barNumThisLine++;
9720
- if (barNumThisLine >= barsperstaff) {
9721
- // push everything else to the next line, if there is anything else,
9722
- // and there is a next line. If there isn't a next line, create one.
9723
- if (n < voice.length - 1) {
9724
- var nextLine = getNextMusicLine(lines, i);
9725
- if (!nextLine) {
9726
- var cp = JSON.parse(JSON.stringify(lines[i]));
9727
- lines.push(parseCommon.clone(cp));
9728
- nextLine = lines[lines.length - 1];
9729
- for (var ss = 0; ss < nextLine.staff.length; ss++) {
9730
- for (var vv = 0; vv < nextLine.staff[ss].voices.length; vv++) {
9731
- nextLine.staff[ss].voices[vv] = [];
9732
- }
9733
- }
9734
- }
9735
- var startElement = n + 1;
9736
- var section = lines[i].staff[s].voices[v].slice(startElement);
9737
- lines[i].staff[s].voices[v] = lines[i].staff[s].voices[v].slice(0, startElement);
9738
- nextLine.staff[s].voices[v] = permanentItems.concat(section.concat(nextLine.staff[s].voices[v]));
9739
- return true;
9740
- }
9741
- }
9742
- } else if (!voice[n].duration) {
9743
- permanentItems.push(voice[n]);
9744
- }
9745
- }
9746
- }
9747
- }
9748
- }
9749
- }
9750
- return false;
9751
- }
9752
- function getNextMusicLine(lines, currentLine) {
9753
- currentLine++;
9754
- while (lines.length > currentLine) {
9755
- if (lines[currentLine].staff) return lines[currentLine];
9756
- currentLine++;
9757
- }
9758
- return null;
9759
- }
9760
- for (tune.lineNum = 0; tune.lineNum < tune.lines.length; tune.lineNum++) {
9761
- var staff = tune.lines[tune.lineNum].staff;
9281
+ for (var i = 0; i < tune.lines.length; i++) {
9282
+ var staff = tune.lines[i].staff;
9762
9283
  if (staff) {
9763
9284
  for (tune.staffNum = 0; tune.staffNum < staff.length; tune.staffNum++) {
9764
- if (staff[tune.staffNum].clef) fixClefPlacement(staff[tune.staffNum].clef);
9285
+ if (staff[tune.staffNum].clef) parseKeyVoice.fixClef(staff[tune.staffNum].clef);
9765
9286
  for (tune.voiceNum = 0; tune.voiceNum < staff[tune.staffNum].voices.length; tune.voiceNum++) {
9766
9287
  var voice = staff[tune.staffNum].voices[tune.voiceNum];
9767
- cleanUpSlursInLine(voice, tune.staffNum, tune.voiceNum);
9288
+ cleanUpSlursInLine(voice, tune.staffNum, tune.voiceNum, currSlur);
9768
9289
  for (var j = 0; j < voice.length; j++) {
9769
- if (voice[j].el_type === 'clef') fixClefPlacement(voice[j]);
9290
+ if (voice[j].el_type === 'clef') parseKeyVoice.fixClef(voice[j]);
9770
9291
  }
9771
9292
  if (voice.length > 0 && voice[voice.length - 1].barNumber) {
9772
9293
  // Don't hang a bar number on the last bar line: it should go on the next line.
9773
- var nextLine = getNextMusicLine(tune.lines, tune.lineNum);
9294
+ var nextLine = getNextMusicLine(tune.lines, i);
9774
9295
  if (nextLine) nextLine.staff[0].barNumber = voice[voice.length - 1].barNumber;
9775
9296
  delete voice[voice.length - 1].barNumber;
9776
9297
  }
@@ -9788,21 +9309,9 @@ var TuneBuilder = function TuneBuilder(tune) {
9788
9309
  delete tune.vskipPending;
9789
9310
  return currSlur;
9790
9311
  };
9791
- tune.reset();
9792
- this.getLastNote = function () {
9793
- if (tune.lines[tune.lineNum] && tune.lines[tune.lineNum].staff && tune.lines[tune.lineNum].staff[tune.staffNum] && tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum]) {
9794
- for (var i = tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum].length - 1; i >= 0; i--) {
9795
- var el = tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum][i];
9796
- if (el.el_type === 'note') {
9797
- return el;
9798
- }
9799
- }
9800
- }
9801
- return null;
9802
- };
9803
9312
  this.addTieToLastNote = function (dottedTie) {
9804
9313
  // TODO-PER: if this is a chord, which note?
9805
- var el = this.getLastNote();
9314
+ var el = getLastNote(tune);
9806
9315
  if (el && el.pitches && el.pitches.length > 0) {
9807
9316
  el.pitches[0].startTie = {};
9808
9317
  if (dottedTie) el.pitches[0].startTie.style = 'dotted';
@@ -9810,59 +9319,10 @@ var TuneBuilder = function TuneBuilder(tune) {
9810
9319
  }
9811
9320
  return false;
9812
9321
  };
9813
- this.getDuration = function (el) {
9814
- if (el.duration) return el.duration;
9815
- //if (el.pitches && el.pitches.length > 0) return el.pitches[0].duration;
9816
- return 0;
9817
- };
9818
- this.closeLine = function () {
9819
- if (tune.potentialStartBeam && tune.potentialEndBeam) {
9820
- tune.potentialStartBeam.startBeam = true;
9821
- tune.potentialEndBeam.endBeam = true;
9822
- }
9823
- delete tune.potentialStartBeam;
9824
- delete tune.potentialEndBeam;
9825
- };
9826
9322
  this.appendElement = function (type, startChar, endChar, hashParams) {
9827
- var This = tune;
9828
- var pushNote = function pushNote(hp) {
9829
- var currStaff = This.lines[This.lineNum].staff[This.staffNum];
9830
- if (!currStaff) {
9831
- // TODO-PER: This prevents a crash, but it drops the element. Need to figure out how to start a new line, or delay adding this.
9832
- return;
9833
- }
9834
- if (hp.pitches !== undefined) {
9835
- var mid = currStaff.workingClef.verticalPos;
9836
- hp.pitches.forEach(function (p) {
9837
- p.verticalPos = p.pitch - mid;
9838
- });
9839
- }
9840
- if (hp.gracenotes !== undefined) {
9841
- var mid2 = currStaff.workingClef.verticalPos;
9842
- hp.gracenotes.forEach(function (p) {
9843
- p.verticalPos = p.pitch - mid2;
9844
- });
9845
- }
9846
- currStaff.voices[This.voiceNum].push(hp);
9847
- };
9848
9323
  hashParams.el_type = type;
9849
9324
  if (startChar !== null) hashParams.startChar = startChar;
9850
9325
  if (endChar !== null) hashParams.endChar = endChar;
9851
- var endBeamHere = function endBeamHere() {
9852
- This.potentialStartBeam.startBeam = true;
9853
- hashParams.endBeam = true;
9854
- delete This.potentialStartBeam;
9855
- delete This.potentialEndBeam;
9856
- };
9857
- var endBeamLast = function endBeamLast() {
9858
- if (This.potentialStartBeam !== undefined && This.potentialEndBeam !== undefined) {
9859
- // Do we have a set of notes to beam?
9860
- This.potentialStartBeam.startBeam = true;
9861
- This.potentialEndBeam.endBeam = true;
9862
- }
9863
- delete This.potentialStartBeam;
9864
- delete This.potentialEndBeam;
9865
- };
9866
9326
  if (type === 'note') {
9867
9327
  // && (hashParams.rest !== undefined || hashParams.end_beam === undefined)) {
9868
9328
  // Now, add the startBeam and endBeam where it is needed.
@@ -9871,25 +9331,25 @@ var TuneBuilder = function TuneBuilder(tune) {
9871
9331
  // this.potentialEndBeam either points to null or the start beam.
9872
9332
  // If we have a beam break (note is longer than a quarter, or an end_beam is on this element), then set the beam if we have one.
9873
9333
  // reset the variables for the next notes.
9874
- var dur = self.getDuration(hashParams);
9334
+ var dur = getDuration(hashParams);
9875
9335
  if (dur >= 0.25) {
9876
9336
  // The beam ends on the note before this.
9877
- endBeamLast();
9878
- } else if (hashParams.force_end_beam_last && This.potentialStartBeam !== undefined) {
9879
- endBeamLast();
9880
- } else if (hashParams.end_beam && This.potentialStartBeam !== undefined) {
9337
+ endBeamLast(tune);
9338
+ } else if (hashParams.force_end_beam_last && tune.potentialStartBeam !== undefined) {
9339
+ endBeamLast(tune);
9340
+ } else if (hashParams.end_beam && tune.potentialStartBeam !== undefined) {
9881
9341
  // the beam is forced to end on this note, probably because of a space in the ABC
9882
- if (hashParams.rest === undefined) endBeamHere();else endBeamLast();
9342
+ if (hashParams.rest === undefined) endBeamHere(hashParams, tune);else endBeamLast(tune);
9883
9343
  } else if (hashParams.rest === undefined) {
9884
9344
  // this a short note and we aren't about to end the beam
9885
- if (This.potentialStartBeam === undefined) {
9345
+ if (tune.potentialStartBeam === undefined) {
9886
9346
  // We aren't collecting notes for a beam, so start here.
9887
9347
  if (!hashParams.end_beam) {
9888
- This.potentialStartBeam = hashParams;
9889
- delete This.potentialEndBeam;
9348
+ tune.potentialStartBeam = hashParams;
9349
+ delete tune.potentialEndBeam;
9890
9350
  }
9891
9351
  } else {
9892
- This.potentialEndBeam = hashParams; // Continue the beaming, look for the end next note.
9352
+ tune.potentialEndBeam = hashParams; // Continue the beaming, look for the end next note.
9893
9353
  }
9894
9354
  }
9895
9355
 
@@ -9897,21 +9357,32 @@ var TuneBuilder = function TuneBuilder(tune) {
9897
9357
  // if (hashParams.rest !== undefined)
9898
9358
  // {
9899
9359
  // hashParams.end_beam = true;
9900
- // var el2 = this.getLastNote();
9360
+ // var el2 = getLastNote(tune);
9901
9361
  // if (el2) el2.end_beam = true;
9902
9362
  // // TODO-PER: implement exception mentioned in the comment.
9903
9363
  // }
9904
9364
  } else {
9905
9365
  // It's not a note, so there definitely isn't beaming after it.
9906
- endBeamLast();
9366
+ endBeamLast(tune);
9907
9367
  }
9908
9368
  delete hashParams.end_beam; // We don't want this temporary variable hanging around.
9909
9369
  delete hashParams.force_end_beam_last; // We don't want this temporary variable hanging around.
9910
- pushNote(hashParams);
9370
+ if (hashParams.rest && hashParams.rest.type === 'invisible') {
9371
+ delete hashParams.decoration; // the decorations on invisible rests should be invisible, too.
9372
+ }
9373
+
9374
+ if (tune.lines.length <= tune.lineNum || tune.lines[tune.lineNum].staff.length <= tune.staffNum) {
9375
+ //console.log("pushNote IGNORED", tune.lines[tune.lineNum])
9376
+ // TODO-PER: This prevents a crash, but it drops the element. Need to figure out how to start a new line, or delay adding this.
9377
+ return false;
9378
+ }
9379
+ pushNote(self, tune, hashParams, voiceDefs, currentVoiceName);
9380
+ return true;
9911
9381
  };
9912
9382
  this.appendStartingElement = function (type, startChar, endChar, hashParams2) {
9383
+ //console.log('appendStartingElement', hashParams2)
9913
9384
  // If we're in the middle of beaming, then end the beam.
9914
- this.closeLine();
9385
+ closeLine(tune);
9915
9386
 
9916
9387
  // We only ever want implied naturals the first time.
9917
9388
  var impliedNaturals;
@@ -9922,58 +9393,54 @@ var TuneBuilder = function TuneBuilder(tune) {
9922
9393
  }
9923
9394
 
9924
9395
  // Clone the object because it will be sticking around for the next line and we don't want the extra fields in it.
9925
- var hashParams = parseCommon.clone(hashParams2);
9926
- if (tune.lines[tune.lineNum] && tune.lines[tune.lineNum].staff) {
9927
- // be sure that we are on a music type line before doing the following.
9928
- // If tune is the first item in tune staff, then we might have to initialize the staff, first.
9929
- if (tune.lines[tune.lineNum].staff.length <= tune.staffNum) {
9930
- tune.lines[tune.lineNum].staff[tune.staffNum] = {};
9931
- tune.lines[tune.lineNum].staff[tune.staffNum].clef = parseCommon.clone(tune.lines[tune.lineNum].staff[0].clef);
9932
- tune.lines[tune.lineNum].staff[tune.staffNum].key = parseCommon.clone(tune.lines[tune.lineNum].staff[0].key);
9933
- if (tune.lines[tune.lineNum].staff[0].meter) tune.lines[tune.lineNum].staff[tune.staffNum].meter = parseCommon.clone(tune.lines[tune.lineNum].staff[0].meter);
9934
- tune.lines[tune.lineNum].staff[tune.staffNum].workingClef = parseCommon.clone(tune.lines[tune.lineNum].staff[0].workingClef);
9935
- tune.lines[tune.lineNum].staff[tune.staffNum].voices = [[]];
9936
- }
9937
- // If tune is a clef type, then we replace the working clef on the line. This is kept separate from
9938
- // the clef in case there is an inline clef field. We need to know what the current position for
9939
- // the note is.
9940
- if (type === 'clef') {
9941
- tune.lines[tune.lineNum].staff[tune.staffNum].workingClef = hashParams;
9942
- }
9943
-
9944
- // These elements should not be added twice, so if the element exists on tune line without a note or bar before it, just replace the staff version.
9945
- var voice = tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum];
9946
- for (var i = 0; i < voice.length; i++) {
9947
- if (voice[i].el_type === 'note' || voice[i].el_type === 'bar') {
9948
- hashParams.el_type = type;
9949
- hashParams.startChar = startChar;
9950
- hashParams.endChar = endChar;
9951
- if (impliedNaturals) hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals);
9952
- voice.push(hashParams);
9953
- return;
9954
- }
9955
- if (voice[i].el_type === type) {
9956
- hashParams.el_type = type;
9957
- hashParams.startChar = startChar;
9958
- hashParams.endChar = endChar;
9959
- if (impliedNaturals) hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals);
9960
- voice[i] = hashParams;
9961
- return;
9962
- }
9396
+ var hashParams = Object.assign({}, hashParams2);
9397
+
9398
+ // be sure that we are on a music type line before doing the following.
9399
+ if (!tune.lines[tune.lineNum]) return;
9400
+ var staff = tune.lines[tune.lineNum].staff;
9401
+ if (!staff) return;
9402
+
9403
+ // If tune is the first item in tune staff, then we might have to initialize the staff, first.
9404
+ if (staff.length <= tune.staffNum) {
9405
+ staff[tune.staffNum] = {};
9406
+ staff[tune.staffNum].clef = Object.assign({}, staff[0].clef);
9407
+ staff[tune.staffNum].key = Object.assign({}, staff[0].key);
9408
+ if (staff[0].meter) staff[tune.staffNum].meter = Object.assign({}, staff[0].meter);
9409
+ staff[tune.staffNum].workingClef = Object.assign({}, staff[0].workingClef);
9410
+ staff[tune.staffNum].voices = [[]];
9411
+ }
9412
+ // If tune is a clef type, then we replace the working clef on the line. This is kept separate from
9413
+ // the clef in case there is an inline clef field. We need to know what the current position for
9414
+ // the note is.
9415
+ if (type === 'clef') {
9416
+ staff[tune.staffNum].workingClef = hashParams;
9417
+ }
9418
+
9419
+ // These elements should not be added twice, so if the element exists on tune line without a note or bar before it, just replace the staff version.
9420
+ var voice = staff[tune.staffNum].voices[tune.voiceNum];
9421
+ for (var i = 0; i < voice.length; i++) {
9422
+ if (voice[i].el_type === 'note' || voice[i].el_type === 'bar') {
9423
+ hashParams.el_type = type;
9424
+ hashParams.startChar = startChar;
9425
+ hashParams.endChar = endChar;
9426
+ if (impliedNaturals) hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals);
9427
+ voice.push(hashParams);
9428
+ return;
9429
+ }
9430
+ if (voice[i].el_type === type) {
9431
+ hashParams.el_type = type;
9432
+ hashParams.startChar = startChar;
9433
+ hashParams.endChar = endChar;
9434
+ if (impliedNaturals) hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals);
9435
+ voice[i] = hashParams;
9436
+ return;
9963
9437
  }
9964
- // We didn't see either that type or a note, so replace the element to the staff.
9965
- tune.lines[tune.lineNum].staff[tune.staffNum][type] = hashParams2;
9966
- }
9967
- };
9968
- this.pushLine = function (hash) {
9969
- if (tune.vskipPending) {
9970
- hash.vskip = tune.vskipPending;
9971
- delete tune.vskipPending;
9972
9438
  }
9973
- tune.lines.push(hash);
9439
+ // We didn't see either that type or a note, so replace the element to the staff.
9440
+ staff[tune.staffNum][type] = hashParams2;
9974
9441
  };
9975
9442
  this.addSubtitle = function (str, info) {
9976
- this.pushLine({
9443
+ pushLine(tune, {
9977
9444
  subtitle: {
9978
9445
  text: str,
9979
9446
  startChar: info.startChar,
@@ -9985,12 +9452,12 @@ var TuneBuilder = function TuneBuilder(tune) {
9985
9452
  tune.vskipPending = num;
9986
9453
  };
9987
9454
  this.addNewPage = function (num) {
9988
- this.pushLine({
9455
+ pushLine(tune, {
9989
9456
  newpage: num
9990
9457
  });
9991
9458
  };
9992
9459
  this.addSeparator = function (spaceAbove, spaceBelow, lineLength, info) {
9993
- this.pushLine({
9460
+ pushLine(tune, {
9994
9461
  separator: {
9995
9462
  spaceAbove: Math.round(spaceAbove),
9996
9463
  spaceBelow: Math.round(spaceBelow),
@@ -10001,7 +9468,7 @@ var TuneBuilder = function TuneBuilder(tune) {
10001
9468
  });
10002
9469
  };
10003
9470
  this.addText = function (str, info) {
10004
- this.pushLine({
9471
+ pushLine(tune, {
10005
9472
  text: {
10006
9473
  text: str,
10007
9474
  startChar: info.startChar,
@@ -10010,29 +9477,17 @@ var TuneBuilder = function TuneBuilder(tune) {
10010
9477
  });
10011
9478
  };
10012
9479
  this.addCentered = function (str) {
10013
- this.pushLine({
9480
+ pushLine(tune, {
10014
9481
  text: [{
10015
9482
  text: str,
10016
9483
  center: true
10017
9484
  }]
10018
9485
  });
10019
9486
  };
10020
- this.containsNotes = function (voice) {
10021
- for (var i = 0; i < voice.length; i++) {
10022
- if (voice[i].el_type === 'note' || voice[i].el_type === 'bar') return true;
10023
- }
10024
- return false;
10025
- };
10026
- this.containsNotesStrict = function (voice) {
10027
- for (var i = 0; i < voice.length; i++) {
10028
- if (voice[i].el_type === 'note' && (voice[i].rest === undefined || voice[i].chord !== undefined)) return true;
10029
- }
10030
- return false;
10031
- };
10032
9487
 
10033
9488
  // anyVoiceContainsNotes: function(line) {
10034
9489
  // for (var i = 0; i < line.staff.voices.length; i++) {
10035
- // if (this.containsNotes(line.staff.voices[i]))
9490
+ // if (containsNotes(line.staff.voices[i]))
10036
9491
  // return true;
10037
9492
  // }
10038
9493
  // return false;
@@ -10048,95 +9503,18 @@ var TuneBuilder = function TuneBuilder(tune) {
10048
9503
  });
10049
9504
  };
10050
9505
  this.startNewLine = function (params) {
9506
+ //console.log("startNewLine", tune.lineNum, params, voiceDefs)
10051
9507
  // If the pointed to line doesn't exist, just create that. If the line does exist, but doesn't have any music on it, just use it.
10052
9508
  // If it does exist and has music, then increment the line number. If the new element doesn't exist, create it.
10053
- var This = tune;
10054
- this.closeLine(); // Close the previous line.
10055
- var createVoice = function createVoice(params) {
10056
- var thisStaff = This.lines[This.lineNum].staff[This.staffNum];
10057
- thisStaff.voices[This.voiceNum] = [];
10058
- if (!thisStaff.title) thisStaff.title = [];
10059
- thisStaff.title[This.voiceNum] = {
10060
- name: params.name,
10061
- subname: params.subname
10062
- };
10063
- if (params.style) self.appendElement('style', null, null, {
10064
- head: params.style
10065
- });
10066
- if (params.stem) self.appendElement('stem', null, null, {
10067
- direction: params.stem
10068
- });else if (This.voiceNum > 0) {
10069
- if (thisStaff.voices[0] !== undefined) {
10070
- var found = false;
10071
- for (var i = 0; i < thisStaff.voices[0].length; i++) {
10072
- if (thisStaff.voices[0].el_type === 'stem') found = true;
10073
- }
10074
- if (!found) {
10075
- var stem = {
10076
- el_type: 'stem',
10077
- direction: 'up'
10078
- };
10079
- thisStaff.voices[0].splice(0, 0, stem);
10080
- }
10081
- }
10082
- self.appendElement('stem', null, null, {
10083
- direction: 'down'
10084
- });
10085
- }
10086
- if (params.scale) self.appendElement('scale', null, null, {
10087
- size: params.scale
10088
- });
10089
- if (params.color) self.appendElement('color', null, null, {
10090
- color: params.color
10091
- });
10092
- };
10093
- var createStaff = function createStaff(params) {
10094
- if (params.key && params.key.impliedNaturals) {
10095
- params.key.accidentals = params.key.accidentals.concat(params.key.impliedNaturals);
10096
- delete params.key.impliedNaturals;
10097
- }
10098
- This.lines[This.lineNum].staff[This.staffNum] = {
10099
- voices: [],
10100
- clef: params.clef,
10101
- key: params.key,
10102
- workingClef: params.clef
10103
- };
10104
- if (params.stafflines !== undefined) {
10105
- This.lines[This.lineNum].staff[This.staffNum].clef.stafflines = params.stafflines;
10106
- This.lines[This.lineNum].staff[This.staffNum].workingClef.stafflines = params.stafflines;
10107
- }
10108
- if (params.staffscale) {
10109
- This.lines[This.lineNum].staff[This.staffNum].staffscale = params.staffscale;
10110
- }
10111
- if (params.annotationfont) self.setLineFont("annotationfont", params.annotationfont);
10112
- if (params.gchordfont) self.setLineFont("gchordfont", params.gchordfont);
10113
- if (params.tripletfont) self.setLineFont("tripletfont", params.tripletfont);
10114
- if (params.vocalfont) self.setLineFont("vocalfont", params.vocalfont);
10115
- if (params.bracket) This.lines[This.lineNum].staff[This.staffNum].bracket = params.bracket;
10116
- if (params.brace) This.lines[This.lineNum].staff[This.staffNum].brace = params.brace;
10117
- if (params.connectBarLines) This.lines[This.lineNum].staff[This.staffNum].connectBarLines = params.connectBarLines;
10118
- if (params.barNumber) This.lines[This.lineNum].staff[This.staffNum].barNumber = params.barNumber;
10119
- createVoice(params);
10120
- // Some stuff just happens for the first voice
10121
- if (params.part) self.appendElement('part', params.part.startChar, params.part.endChar, {
10122
- title: params.part.title
10123
- });
10124
- if (params.meter !== undefined) This.lines[This.lineNum].staff[This.staffNum].meter = params.meter;
10125
- if (This.vskipPending) {
10126
- This.lines[This.lineNum].vskip = This.vskipPending;
10127
- delete This.vskipPending;
10128
- }
10129
- };
10130
- var createLine = function createLine(params) {
10131
- This.lines[This.lineNum] = {
10132
- staff: []
10133
- };
10134
- createStaff(params);
10135
- };
10136
- if (tune.lines[tune.lineNum] === undefined) createLine(params);else if (tune.lines[tune.lineNum].staff === undefined) {
9509
+ closeLine(tune); // Close the previous line.
9510
+ if (params.currentVoiceName) {
9511
+ currentVoiceName = params.currentVoiceName;
9512
+ voiceDefs[params.currentVoiceName] = params;
9513
+ }
9514
+ if (tune.lines[tune.lineNum] === undefined) createLine(self, tune, params);else if (tune.lines[tune.lineNum].staff === undefined) {
10137
9515
  tune.lineNum++;
10138
9516
  this.startNewLine(params);
10139
- } else if (tune.lines[tune.lineNum].staff[tune.staffNum] === undefined) createStaff(params);else if (tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum] === undefined) createVoice(params);else if (!this.containsNotes(tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum])) {
9517
+ } else if (tune.lines[tune.lineNum].staff[tune.staffNum] === undefined) createStaff(self, tune, params);else if (tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum] === undefined) createVoice(self, tune, params);else if (!containsNotes(tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum])) {
10140
9518
  // We don't need a new line but we might need to update parts of it.
10141
9519
  if (params.part) self.appendElement('part', params.part.startChar, params.part.endChar, {
10142
9520
  title: params.part.title
@@ -10150,21 +9528,6 @@ var TuneBuilder = function TuneBuilder(tune) {
10150
9528
  // This is called at tune start to set the current default fonts so we know whether to record a change.
10151
9529
  tune.runningFonts[type] = font;
10152
9530
  };
10153
- this.setLineFont = function (type, font) {
10154
- // If we haven't encountered the font type yet then we are using the default font so it doesn't
10155
- // need to be noted. If we have encountered it, then only record it if it is different from the last time.
10156
- if (tune.runningFonts[type]) {
10157
- var isDifferent = false;
10158
- var keys = Object.keys(font);
10159
- for (var i = 0; i < keys.length; i++) {
10160
- if (tune.runningFonts[type][keys[i]] !== font[keys[i]]) isDifferent = true;
10161
- }
10162
- if (isDifferent) {
10163
- tune.lines[tune.lineNum].staff[tune.staffNum][type] = font;
10164
- }
10165
- }
10166
- tune.runningFonts[type] = font;
10167
- };
10168
9531
  this.setBarNumberImmediate = function (barNumber) {
10169
9532
  // If tune is called right at the beginning of a line, then correct the measure number that is already written.
10170
9533
  // If tune is called at the beginning of a measure, then correct the measure number that was just created.
@@ -10194,24 +9557,31 @@ var TuneBuilder = function TuneBuilder(tune) {
10194
9557
  return true;
10195
9558
  };
10196
9559
  this.getCurrentVoice = function () {
9560
+ //console.log("getCurrentVoice", tune.lineNum)
10197
9561
  var currLine = tune.lines[tune.lineNum];
10198
9562
  if (!currLine) return null;
10199
9563
  var currStaff = currLine.staff[tune.staffNum];
10200
9564
  if (!currStaff) return null;
10201
9565
  if (currStaff.voices[tune.voiceNum] !== undefined) return currStaff.voices[tune.voiceNum];else return null;
10202
9566
  };
10203
- this.setCurrentVoice = function (staffNum, voiceNum) {
9567
+ this.setCurrentVoice = function (staffNum, voiceNum, name) {
9568
+ //console.log("setCurrentVoice", tune.lineNum, staffNum, voiceNum, name, voiceDefs)
10204
9569
  tune.staffNum = staffNum;
10205
9570
  tune.voiceNum = voiceNum;
9571
+ currentVoiceName = name;
10206
9572
  for (var i = 0; i < tune.lines.length; i++) {
10207
9573
  if (tune.lines[i].staff) {
10208
- if (tune.lines[i].staff[staffNum] === undefined || tune.lines[i].staff[staffNum].voices[voiceNum] === undefined || !this.containsNotes(tune.lines[i].staff[staffNum].voices[voiceNum])) {
9574
+ if (tune.lines[i].staff[staffNum] === undefined || tune.lines[i].staff[staffNum].voices[voiceNum] === undefined || !containsNotes(tune.lines[i].staff[staffNum].voices[voiceNum])) {
9575
+ //console.log("cv2", i, tune.lines[i].staff[staffNum])
10209
9576
  tune.lineNum = i;
10210
- return;
9577
+ if (!tune.lines[i].staff[staffNum] || !!tune.lines[i].staff[staffNum].voices[voiceNum]) return true;
9578
+ return false;
10211
9579
  }
10212
9580
  }
10213
9581
  }
9582
+ //console.log("cv3", i, tune.lineNum, tune.lines[tune.lineNum])
10214
9583
  tune.lineNum = i;
9584
+ return false;
10215
9585
  };
10216
9586
  this.addMetaText = function (key, value, info) {
10217
9587
  if (tune.metaText[key] === undefined) {
@@ -10247,7 +9617,7 @@ var TuneBuilder = function TuneBuilder(tune) {
10247
9617
  function isArrayOfStrings(arr) {
10248
9618
  if (!arr) return false;
10249
9619
  if (typeof arr === "string") return false;
10250
- var str = '';
9620
+ //var str = ''
10251
9621
  for (var i = 0; i < arr.length; i++) {
10252
9622
  if (typeof arr[i] !== 'string') return false;
10253
9623
  }
@@ -10257,32 +9627,553 @@ function simplifyMetaText(tune) {
10257
9627
  if (isArrayOfStrings(tune.metaText.notes)) tune.metaText.notes = tune.metaText.notes.join("\n");
10258
9628
  if (isArrayOfStrings(tune.metaText.history)) tune.metaText.history = tune.metaText.history.join("\n");
10259
9629
  }
10260
- function addRichTextToAnnotationsAndLyrics(tune) {
10261
- var lines = tune.lines;
9630
+
9631
+ // function addRichTextToAnnotationsAndLyrics(tune) {
9632
+ // var lines = tune.lines
9633
+ // for (var i = 0; i < lines.length; i++) {
9634
+ // if (lines[i].staff !== undefined) {
9635
+ // for (var s = 0; s < lines[i].staff.length; s++) {
9636
+ // for (var v = 0; v < lines[i].staff[s].voices.length; v++) {
9637
+ // var voice = lines[i].staff[s].voices[v];
9638
+ // for (var n = 0; n < voice.length; n++) {
9639
+ // var element = voice[n]
9640
+ // if (element.chord) {
9641
+ // for (var c = 0; c < element.chord.length; c++) {
9642
+ // element.chord[c].name = parseDirective.parseFontChangeLine(element.chord[c].name)
9643
+ // console.log(element.chord[c].name)
9644
+ // }
9645
+ // }
9646
+ // if (element.lyric) {
9647
+ // for (var l = 0; l < element.lyric.length; l++) {
9648
+ // element.lyric[l].syllable = parseDirective.parseFontChangeLine(element.lyric[l].syllable)
9649
+ // console.log(element.lyric[l].syllable)
9650
+ // }
9651
+ // }
9652
+ // }
9653
+ // }
9654
+ // }
9655
+ // }
9656
+ // }
9657
+
9658
+ // }
9659
+
9660
+ function resolveOverlays(tune) {
9661
+ var madeChanges = false;
9662
+ var durationsPerLines = [];
9663
+ for (var i = 0; i < tune.lines.length; i++) {
9664
+ var line = tune.lines[i];
9665
+ if (line.staff) {
9666
+ for (var j = 0; j < line.staff.length; j++) {
9667
+ var staff = line.staff[j];
9668
+ var overlayVoice = [];
9669
+ for (var k = 0; k < staff.voices.length; k++) {
9670
+ var voice = staff.voices[k];
9671
+ overlayVoice.push({
9672
+ hasOverlay: false,
9673
+ voice: [],
9674
+ snip: []
9675
+ });
9676
+ durationsPerLines[i] = 0;
9677
+ var durationThisBar = 0;
9678
+ var inOverlay = false;
9679
+ var overlayDuration = 0;
9680
+ var snipStart = -1;
9681
+ for (var kk = 0; kk < voice.length; kk++) {
9682
+ var event = voice[kk];
9683
+ if (event.el_type === "overlay" && !inOverlay) {
9684
+ madeChanges = true;
9685
+ inOverlay = true;
9686
+ snipStart = kk;
9687
+ overlayVoice[k].hasOverlay = true;
9688
+ if (overlayDuration === 0) overlayDuration = durationsPerLines[i];
9689
+ // If this isn't the first line, we also need invisible rests on the previous lines.
9690
+ // So, if the next voice doesn't appear in a previous line, create it
9691
+ for (var ii = 0; ii < i; ii++) {
9692
+ if (durationsPerLines[ii] && tune.lines[ii].staff && staff.voices.length >= tune.lines[ii].staff[0].voices.length) {
9693
+ tune.lines[ii].staff[0].voices.push([{
9694
+ el_type: "note",
9695
+ duration: durationsPerLines[ii],
9696
+ rest: {
9697
+ type: "invisible"
9698
+ },
9699
+ startChar: event.startChar,
9700
+ endChar: event.endChar
9701
+ }]);
9702
+ }
9703
+ }
9704
+ } else if (event.el_type === "bar") {
9705
+ if (inOverlay) {
9706
+ // delete the overlay events from this array without messing up this loop.
9707
+ inOverlay = false;
9708
+ overlayVoice[k].snip.push({
9709
+ start: snipStart,
9710
+ len: kk - snipStart
9711
+ });
9712
+ overlayVoice[k].voice.push(event); // Also end the overlay with the barline.
9713
+ } else {
9714
+ // This keeps the voices lined up: if the overlay isn't in the first measure then we need a bunch of invisible rests.
9715
+ if (durationThisBar > 0) overlayVoice[k].voice.push({
9716
+ el_type: "note",
9717
+ duration: durationThisBar,
9718
+ rest: {
9719
+ type: "invisible"
9720
+ },
9721
+ startChar: event.startChar,
9722
+ endChar: event.endChar
9723
+ });
9724
+ overlayVoice[k].voice.push(event);
9725
+ }
9726
+ durationThisBar = 0;
9727
+ } else if (event.el_type === "note") {
9728
+ if (inOverlay) {
9729
+ overlayVoice[k].voice.push(event);
9730
+ } else {
9731
+ durationThisBar += event.duration;
9732
+ durationsPerLines[i] += event.duration;
9733
+ }
9734
+ } else if (event.el_type === "scale" || event.el_type === "stem" || event.el_type === "overlay" || event.el_type === "style" || event.el_type === "transpose" || event.el_type === "color") {
9735
+ // These types of events are duplicated on the overlay layer.
9736
+ overlayVoice[k].voice.push(event);
9737
+ }
9738
+ }
9739
+ if (overlayVoice[k].hasOverlay && overlayVoice[k].snip.length === 0) {
9740
+ // there was no closing bar, so we didn't set the snip amount.
9741
+ overlayVoice[k].snip.push({
9742
+ start: snipStart,
9743
+ len: voice.length - snipStart
9744
+ });
9745
+ }
9746
+ }
9747
+ for (k = 0; k < overlayVoice.length; k++) {
9748
+ var ov = overlayVoice[k];
9749
+ if (ov.hasOverlay) {
9750
+ ov.voice.splice(0, 0, {
9751
+ el_type: "stem",
9752
+ direction: "down"
9753
+ });
9754
+ staff.voices.push(ov.voice);
9755
+ for (var kkk = ov.snip.length - 1; kkk >= 0; kkk--) {
9756
+ var snip = ov.snip[kkk];
9757
+ staff.voices[k].splice(snip.start, snip.len);
9758
+ staff.voices[k].splice(snip.start + 1, 0, {
9759
+ el_type: "stem",
9760
+ direction: "auto"
9761
+ });
9762
+ var indexOfLastBar = findLastBar(staff.voices[k], snip.start);
9763
+ staff.voices[k].splice(indexOfLastBar, 0, {
9764
+ el_type: "stem",
9765
+ direction: "up"
9766
+ });
9767
+ }
9768
+ // remove ending marks from the overlay voice so they are not repeated
9769
+ for (kkk = 0; kkk < staff.voices[staff.voices.length - 1].length; kkk++) {
9770
+ staff.voices[staff.voices.length - 1][kkk] = Object.assign({}, staff.voices[staff.voices.length - 1][kkk]);
9771
+ var el = staff.voices[staff.voices.length - 1][kkk];
9772
+ if (el.el_type === 'bar' && el.startEnding) {
9773
+ delete el.startEnding;
9774
+ }
9775
+ if (el.el_type === 'bar' && el.endEnding) delete el.endEnding;
9776
+ }
9777
+ }
9778
+ }
9779
+ }
9780
+ }
9781
+ }
9782
+ return madeChanges;
9783
+ }
9784
+ ;
9785
+ function findLastBar(voice, start) {
9786
+ for (var i = start - 1; i > 0 && voice[i].el_type !== "bar"; i--) {}
9787
+ return i;
9788
+ }
9789
+ function fixTitles(lines) {
9790
+ // We might have name and subname defined. We now know what line everything is on, so we can determine which to use.
9791
+ var firstMusicLine = true;
10262
9792
  for (var i = 0; i < lines.length; i++) {
9793
+ var line = lines[i];
9794
+ if (line.staff) {
9795
+ for (var j = 0; j < line.staff.length; j++) {
9796
+ var staff = line.staff[j];
9797
+ if (staff.title) {
9798
+ var hasATitle = false;
9799
+ for (var k = 0; k < staff.title.length; k++) {
9800
+ if (staff.title[k]) {
9801
+ staff.title[k] = firstMusicLine ? staff.title[k].name : staff.title[k].subname;
9802
+ if (staff.title[k]) hasATitle = true;else staff.title[k] = '';
9803
+ } else staff.title[k] = '';
9804
+ }
9805
+ if (!hasATitle) delete staff.title;
9806
+ }
9807
+ }
9808
+ firstMusicLine = false;
9809
+ }
9810
+ }
9811
+ }
9812
+ function cleanUpSlursInLine(line, staffNum, voiceNum, currSlur) {
9813
+ if (!currSlur[staffNum]) currSlur[staffNum] = [];
9814
+ if (!currSlur[staffNum][voiceNum]) currSlur[staffNum][voiceNum] = [];
9815
+ var x;
9816
+ // var lyr = null; // TODO-PER: debugging.
9817
+
9818
+ var addEndSlur = function addEndSlur(obj, num, chordPos) {
9819
+ if (currSlur[staffNum][voiceNum][chordPos] === undefined) {
9820
+ // There isn't an exact match for note position, but we'll take any other open slur.
9821
+ for (x = 0; x < currSlur[staffNum][voiceNum].length; x++) {
9822
+ if (currSlur[staffNum][voiceNum][x] !== undefined) {
9823
+ chordPos = x;
9824
+ break;
9825
+ }
9826
+ }
9827
+ if (currSlur[staffNum][voiceNum][chordPos] === undefined) {
9828
+ var offNum = chordPos * 100 + 1;
9829
+ obj.endSlur.forEach(function (x) {
9830
+ if (offNum === x) --offNum;
9831
+ });
9832
+ currSlur[staffNum][voiceNum][chordPos] = [offNum];
9833
+ }
9834
+ }
9835
+ var slurNum;
9836
+ for (var i = 0; i < num; i++) {
9837
+ slurNum = currSlur[staffNum][voiceNum][chordPos].pop();
9838
+ obj.endSlur.push(slurNum);
9839
+ // lyr.syllable += '<' + slurNum; // TODO-PER: debugging
9840
+ }
9841
+
9842
+ if (currSlur[staffNum][voiceNum][chordPos].length === 0) delete currSlur[staffNum][voiceNum][chordPos];
9843
+ return slurNum;
9844
+ };
9845
+ var addStartSlur = function addStartSlur(obj, num, chordPos, usedNums) {
9846
+ obj.startSlur = [];
9847
+ if (currSlur[staffNum][voiceNum][chordPos] === undefined) {
9848
+ currSlur[staffNum][voiceNum][chordPos] = [];
9849
+ }
9850
+ var nextNum = chordPos * 100 + 1;
9851
+ for (var i = 0; i < num; i++) {
9852
+ if (usedNums) {
9853
+ usedNums.forEach(function (x) {
9854
+ if (nextNum === x) ++nextNum;
9855
+ });
9856
+ usedNums.forEach(function (x) {
9857
+ if (nextNum === x) ++nextNum;
9858
+ });
9859
+ usedNums.forEach(function (x) {
9860
+ if (nextNum === x) ++nextNum;
9861
+ });
9862
+ }
9863
+ currSlur[staffNum][voiceNum][chordPos].forEach(function (x) {
9864
+ if (nextNum === x) ++nextNum;
9865
+ });
9866
+ currSlur[staffNum][voiceNum][chordPos].forEach(function (x) {
9867
+ if (nextNum === x) ++nextNum;
9868
+ });
9869
+ currSlur[staffNum][voiceNum][chordPos].push(nextNum);
9870
+ obj.startSlur.push({
9871
+ label: nextNum
9872
+ });
9873
+ if (obj.dottedSlur) {
9874
+ obj.startSlur[obj.startSlur.length - 1].style = 'dotted';
9875
+ delete obj.dottedSlur;
9876
+ }
9877
+ // lyr.syllable += ' ' + nextNum + '>'; // TODO-PER:debugging
9878
+ nextNum++;
9879
+ }
9880
+ };
9881
+ for (var i = 0; i < line.length; i++) {
9882
+ var el = line[i];
9883
+ // if (el.lyric === undefined) // TODO-PER: debugging
9884
+ // el.lyric = [{ divider: '-' }]; // TODO-PER: debugging
9885
+ // lyr = el.lyric[0]; // TODO-PER: debugging
9886
+ // lyr.syllable = ''; // TODO-PER: debugging
9887
+ if (el.el_type === 'note') {
9888
+ if (el.gracenotes) {
9889
+ for (var g = 0; g < el.gracenotes.length; g++) {
9890
+ if (el.gracenotes[g].endSlur) {
9891
+ var gg = el.gracenotes[g].endSlur;
9892
+ el.gracenotes[g].endSlur = [];
9893
+ for (var ggg = 0; ggg < gg; ggg++) {
9894
+ addEndSlur(el.gracenotes[g], 1, 20);
9895
+ }
9896
+ }
9897
+ if (el.gracenotes[g].startSlur) {
9898
+ x = el.gracenotes[g].startSlur;
9899
+ addStartSlur(el.gracenotes[g], x, 20);
9900
+ }
9901
+ }
9902
+ }
9903
+ if (el.endSlur) {
9904
+ x = el.endSlur;
9905
+ el.endSlur = [];
9906
+ addEndSlur(el, x, 0);
9907
+ }
9908
+ if (el.startSlur) {
9909
+ x = el.startSlur;
9910
+ addStartSlur(el, x, 0);
9911
+ }
9912
+ if (el.pitches) {
9913
+ var usedNums = [];
9914
+ for (var p = 0; p < el.pitches.length; p++) {
9915
+ if (el.pitches[p].endSlur) {
9916
+ var k = el.pitches[p].endSlur;
9917
+ el.pitches[p].endSlur = [];
9918
+ for (var j = 0; j < k; j++) {
9919
+ var slurNum = addEndSlur(el.pitches[p], 1, p + 1);
9920
+ usedNums.push(slurNum);
9921
+ }
9922
+ }
9923
+ }
9924
+ for (p = 0; p < el.pitches.length; p++) {
9925
+ if (el.pitches[p].startSlur) {
9926
+ x = el.pitches[p].startSlur;
9927
+ addStartSlur(el.pitches[p], x, p + 1, usedNums);
9928
+ }
9929
+ }
9930
+ // Correct for the weird gracenote case where ({g}a) should match.
9931
+ // The end slur was already assigned to the note, and needs to be moved to the first note of the graces.
9932
+ if (el.gracenotes && el.pitches[0].endSlur && el.pitches[0].endSlur[0] === 100 && el.pitches[0].startSlur) {
9933
+ if (el.gracenotes[0].endSlur) el.gracenotes[0].endSlur.push(el.pitches[0].startSlur[0].label);else el.gracenotes[0].endSlur = [el.pitches[0].startSlur[0].label];
9934
+ if (el.pitches[0].endSlur.length === 1) delete el.pitches[0].endSlur;else if (el.pitches[0].endSlur[0] === 100) el.pitches[0].endSlur.shift();else if (el.pitches[0].endSlur[el.pitches[0].endSlur.length - 1] === 100) el.pitches[0].endSlur.pop();
9935
+ if (currSlur[staffNum][voiceNum][1].length === 1) delete currSlur[staffNum][voiceNum][1];else currSlur[staffNum][voiceNum][1].pop();
9936
+ }
9937
+ }
9938
+ }
9939
+ }
9940
+ }
9941
+ function wrapMusicLines(lines, barsperstaff) {
9942
+ for (i = 0; i < lines.length; i++) {
10263
9943
  if (lines[i].staff !== undefined) {
10264
- for (var s = 0; s < lines[i].staff.length; s++) {
10265
- for (var v = 0; v < lines[i].staff[s].voices.length; v++) {
9944
+ for (s = 0; s < lines[i].staff.length; s++) {
9945
+ var permanentItems = [];
9946
+ for (v = 0; v < lines[i].staff[s].voices.length; v++) {
10266
9947
  var voice = lines[i].staff[s].voices[v];
9948
+ var barNumThisLine = 0;
10267
9949
  for (var n = 0; n < voice.length; n++) {
10268
- var element = voice[n];
10269
- if (element.chord) {
10270
- for (var c = 0; c < element.chord.length; c++) {
10271
- element.chord[c].name = parseDirective.parseFontChangeLine(element.chord[c].name);
10272
- console.log(element.chord[c].name);
10273
- }
10274
- }
10275
- if (element.lyric) {
10276
- for (var l = 0; l < element.lyric.length; l++) {
10277
- element.lyric[l].syllable = parseDirective.parseFontChangeLine(element.lyric[l].syllable);
10278
- console.log(element.lyric[l].syllable);
9950
+ if (voice[n].el_type === 'bar') {
9951
+ barNumThisLine++;
9952
+ if (barNumThisLine >= barsperstaff) {
9953
+ // push everything else to the next line, if there is anything else,
9954
+ // and there is a next line. If there isn't a next line, create one.
9955
+ if (n < voice.length - 1) {
9956
+ var nextLine = getNextMusicLine(lines, i);
9957
+ if (!nextLine) {
9958
+ var cp = JSON.parse(JSON.stringify(lines[i]));
9959
+ lines.push(Object.assign({}, cp));
9960
+ nextLine = lines[lines.length - 1];
9961
+ for (var ss = 0; ss < nextLine.staff.length; ss++) {
9962
+ for (var vv = 0; vv < nextLine.staff[ss].voices.length; vv++) {
9963
+ nextLine.staff[ss].voices[vv] = [];
9964
+ }
9965
+ }
9966
+ }
9967
+ var startElement = n + 1;
9968
+ var section = lines[i].staff[s].voices[v].slice(startElement);
9969
+ lines[i].staff[s].voices[v] = lines[i].staff[s].voices[v].slice(0, startElement);
9970
+ nextLine.staff[s].voices[v] = permanentItems.concat(section.concat(nextLine.staff[s].voices[v]));
9971
+ return true;
9972
+ }
10279
9973
  }
9974
+ } else if (!voice[n].duration) {
9975
+ permanentItems.push(voice[n]);
10280
9976
  }
10281
9977
  }
10282
9978
  }
10283
9979
  }
10284
9980
  }
10285
9981
  }
9982
+ return false;
9983
+ }
9984
+ function getNextMusicLine(lines, currentLine) {
9985
+ currentLine++;
9986
+ while (lines.length > currentLine) {
9987
+ if (lines[currentLine].staff) return lines[currentLine];
9988
+ currentLine++;
9989
+ }
9990
+ return null;
9991
+ }
9992
+ function getLastNote(tune) {
9993
+ if (!tune.lines[tune.lineNum]) return null;
9994
+ if (!tune.lines[tune.lineNum].staff) return null;
9995
+ if (!tune.lines[tune.lineNum].staff[tune.staffNum]) return null;
9996
+ var voice = tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum];
9997
+ if (!voice) return null;
9998
+ for (var i = voice.length - 1; i >= 0; i--) {
9999
+ var el = voice[i];
10000
+ if (el.el_type === 'note') {
10001
+ return el;
10002
+ }
10003
+ }
10004
+ return null;
10005
+ }
10006
+ ;
10007
+ function getDuration(el) {
10008
+ if (el.duration) return el.duration;
10009
+ return 0;
10010
+ }
10011
+ ;
10012
+ function closeLine(tune) {
10013
+ if (tune.potentialStartBeam && tune.potentialEndBeam) {
10014
+ tune.potentialStartBeam.startBeam = true;
10015
+ tune.potentialEndBeam.endBeam = true;
10016
+ }
10017
+ delete tune.potentialStartBeam;
10018
+ delete tune.potentialEndBeam;
10019
+ }
10020
+ ;
10021
+ function containsNotes(voice) {
10022
+ for (var i = 0; i < voice.length; i++) {
10023
+ if (voice[i].el_type === 'note' || voice[i].el_type === 'bar') return true;
10024
+ }
10025
+ return false;
10026
+ }
10027
+ ;
10028
+ function containsNotesStrict(voice) {
10029
+ for (var i = 0; i < voice.length; i++) {
10030
+ if (voice[i].el_type === 'note' && (voice[i].rest === undefined || voice[i].chord !== undefined)) return true;
10031
+ }
10032
+ return false;
10033
+ }
10034
+ ;
10035
+ function pushLine(tune, hash) {
10036
+ if (tune.vskipPending) {
10037
+ hash.vskip = tune.vskipPending;
10038
+ delete tune.vskipPending;
10039
+ }
10040
+ tune.lines.push(hash);
10041
+ }
10042
+ ;
10043
+ function pushNote(self, tune, hp, voiceDefs, currentVoiceName) {
10044
+ //console.log("pushNote", tune.lineNum, tune.staffNum, hp.pitches ? JSON.stringify(hp.pitches) : hp.pitches)
10045
+ var currStaff = tune.lines[tune.lineNum].staff[tune.staffNum];
10046
+ if (hp.pitches !== undefined) {
10047
+ var mid = currStaff.workingClef.verticalPos;
10048
+ hp.pitches.forEach(function (p) {
10049
+ p.verticalPos = p.pitch - mid;
10050
+ });
10051
+ }
10052
+ if (hp.gracenotes !== undefined) {
10053
+ var mid2 = currStaff.workingClef.verticalPos;
10054
+ hp.gracenotes.forEach(function (p) {
10055
+ p.verticalPos = p.pitch - mid2;
10056
+ });
10057
+ }
10058
+ if (currStaff.voices.length <= tune.voiceNum) {
10059
+ //console.log("should create?", currentVoiceName, voiceDefs)
10060
+ if (!voiceDefs[currentVoiceName]) voiceDefs[currentVoiceName] = {};
10061
+ createVoice(self, tune, voiceDefs[currentVoiceName]);
10062
+ }
10063
+ currStaff.voices[tune.voiceNum].push(hp);
10064
+ }
10065
+ function endBeamHere(hashParams, tune) {
10066
+ tune.potentialStartBeam.startBeam = true;
10067
+ hashParams.endBeam = true;
10068
+ delete tune.potentialStartBeam;
10069
+ delete tune.potentialEndBeam;
10070
+ }
10071
+ function endBeamLast(tune) {
10072
+ if (tune.potentialStartBeam !== undefined && tune.potentialEndBeam !== undefined) {
10073
+ // Do we have a set of notes to beam?
10074
+ tune.potentialStartBeam.startBeam = true;
10075
+ tune.potentialEndBeam.endBeam = true;
10076
+ }
10077
+ delete tune.potentialStartBeam;
10078
+ delete tune.potentialEndBeam;
10079
+ }
10080
+ function setLineFont(tune, type, font) {
10081
+ // If we haven't encountered the font type yet then we are using the default font so it doesn't
10082
+ // need to be noted. If we have encountered it, then only record it if it is different from the last time.
10083
+ if (tune.runningFonts[type]) {
10084
+ var isDifferent = false;
10085
+ var keys = Object.keys(font);
10086
+ for (var i = 0; i < keys.length; i++) {
10087
+ if (tune.runningFonts[type][keys[i]] !== font[keys[i]]) isDifferent = true;
10088
+ }
10089
+ if (isDifferent) {
10090
+ tune.lines[tune.lineNum].staff[tune.staffNum][type] = font;
10091
+ }
10092
+ }
10093
+ tune.runningFonts[type] = font;
10094
+ }
10095
+ function createVoice(self, tune, params) {
10096
+ //console.log("createVoice", params)
10097
+ var thisStaff = tune.lines[tune.lineNum].staff[tune.staffNum];
10098
+ thisStaff.voices[tune.voiceNum] = [];
10099
+ if (!thisStaff.title) thisStaff.title = [];
10100
+ thisStaff.title[tune.voiceNum] = {
10101
+ name: params.name,
10102
+ subname: params.subname
10103
+ };
10104
+ if (params.style) self.appendElement('style', null, null, {
10105
+ head: params.style
10106
+ });
10107
+ if (params.stem) self.appendElement('stem', null, null, {
10108
+ direction: params.stem
10109
+ });else if (tune.voiceNum > 0) {
10110
+ if (thisStaff.voices[0] !== undefined) {
10111
+ var found = false;
10112
+ for (var i = 0; i < thisStaff.voices[0].length; i++) {
10113
+ if (thisStaff.voices[0].el_type === 'stem') found = true;
10114
+ }
10115
+ if (!found) {
10116
+ var stem = {
10117
+ el_type: 'stem',
10118
+ direction: 'up'
10119
+ };
10120
+ thisStaff.voices[0].splice(0, 0, stem);
10121
+ }
10122
+ }
10123
+ self.appendElement('stem', null, null, {
10124
+ direction: 'down'
10125
+ });
10126
+ }
10127
+ if (params.scale) self.appendElement('scale', null, null, {
10128
+ size: params.scale
10129
+ });
10130
+ if (params.color) self.appendElement('color', null, null, {
10131
+ color: params.color
10132
+ });
10133
+ }
10134
+ function createStaff(self, tune, params) {
10135
+ if (params.key && params.key.impliedNaturals) {
10136
+ params.key.accidentals = params.key.accidentals.concat(params.key.impliedNaturals);
10137
+ delete params.key.impliedNaturals;
10138
+ }
10139
+ tune.lines[tune.lineNum].staff[tune.staffNum] = {
10140
+ voices: [],
10141
+ clef: params.clef,
10142
+ key: params.key,
10143
+ workingClef: params.clef
10144
+ };
10145
+ var staff = tune.lines[tune.lineNum].staff[tune.staffNum];
10146
+ if (params.stafflines !== undefined) {
10147
+ staff.clef.stafflines = params.stafflines;
10148
+ staff.workingClef.stafflines = params.stafflines;
10149
+ }
10150
+ if (params.staffscale) {
10151
+ staff.staffscale = params.staffscale;
10152
+ }
10153
+ if (params.annotationfont) setLineFont(tune, "annotationfont", params.annotationfont);
10154
+ if (params.gchordfont) setLineFont(tune, "gchordfont", params.gchordfont);
10155
+ if (params.tripletfont) setLineFont(tune, "tripletfont", params.tripletfont);
10156
+ if (params.vocalfont) setLineFont(tune, "vocalfont", params.vocalfont);
10157
+ if (params.bracket) staff.bracket = params.bracket;
10158
+ if (params.brace) staff.brace = params.brace;
10159
+ if (params.connectBarLines) staff.connectBarLines = params.connectBarLines;
10160
+ if (params.barNumber) staff.barNumber = params.barNumber;
10161
+ createVoice(self, tune, params);
10162
+ // Some stuff just happens for the first voice
10163
+ if (params.part) self.appendElement('part', params.part.startChar, params.part.endChar, {
10164
+ title: params.part.title
10165
+ });
10166
+ if (params.meter !== undefined) staff.meter = params.meter;
10167
+ if (tune.vskipPending) {
10168
+ tune.lines[tune.lineNum].vskip = tune.vskipPending;
10169
+ delete tune.vskipPending;
10170
+ }
10171
+ }
10172
+ function createLine(self, tune, params) {
10173
+ tune.lines[tune.lineNum] = {
10174
+ staff: []
10175
+ };
10176
+ createStaff(self, tune, params);
10286
10177
  }
10287
10178
  module.exports = TuneBuilder;
10288
10179
 
@@ -11452,6 +11343,7 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11452
11343
  case "chordprog":
11453
11344
  case "bassvol":
11454
11345
  case "chordvol":
11346
+ case "gchordbars":
11455
11347
  chordTrack.paramChange(element);
11456
11348
  break;
11457
11349
  default:
@@ -11581,7 +11473,8 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11581
11473
  function processVolume(beat, voiceOff) {
11582
11474
  if (voiceOff) return 0;
11583
11475
  var volume;
11584
- if (nextVolume) {
11476
+ // MAE 21 Jun 2024 - This previously wasn't allowing zero volume to be applied
11477
+ if (nextVolume != undefined) {
11585
11478
  volume = nextVolume;
11586
11479
  nextVolume = undefined;
11587
11480
  } else if (!doBeatAccents) {
@@ -12914,7 +12807,7 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
12914
12807
  if (!e) e = voices[voiceNumber].length; // If there wasn't a first ending marker, then we copy everything.
12915
12808
  // duplicate each of the elements - this has to be a deep copy.
12916
12809
  for (var z = s; z < e; z++) {
12917
- var item = parseCommon.clone(voices[voiceNumber][z]);
12810
+ var item = Object.assign({}, voices[voiceNumber][z]);
12918
12811
  if (item.pitches) item.pitches = parseCommon.cloneArray(item.pitches);
12919
12812
  voices[voiceNumber].push(item);
12920
12813
  }
@@ -13023,8 +12916,6 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13023
12916
  break;
13024
12917
  case "swing":
13025
12918
  case "gchord":
13026
- case "bassprog":
13027
- case "chordprog":
13028
12919
  case "bassvol":
13029
12920
  case "chordvol":
13030
12921
  voices[voiceNumber].push({
@@ -13032,6 +12923,23 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13032
12923
  param: elem.params[0]
13033
12924
  });
13034
12925
  break;
12926
+ case "bassprog": // MAE 22 May 2024
12927
+ case "chordprog":
12928
+ // MAE 22 May 2024
12929
+ voices[voiceNumber].push({
12930
+ el_type: elem.cmd,
12931
+ value: elem.params[0],
12932
+ octaveShift: elem.params[1]
12933
+ });
12934
+ break;
12935
+
12936
+ // MAE 23 Jun 2024
12937
+ case "gchordbars":
12938
+ voices[voiceNumber].push({
12939
+ el_type: elem.cmd,
12940
+ param: elem.params[0]
12941
+ });
12942
+ break;
13035
12943
  default:
13036
12944
  console.log("MIDI seq: midi cmd not handled: ", elem.cmd, elem);
13037
12945
  }
@@ -13304,7 +13212,7 @@ module.exports = centsToFactor;
13304
13212
  /*!**********************************!*\
13305
13213
  !*** ./src/synth/chord-track.js ***!
13306
13214
  \**********************************/
13307
- /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
13215
+ /***/ (function(module) {
13308
13216
 
13309
13217
  //
13310
13218
  // The algorithm for chords is:
@@ -13325,7 +13233,6 @@ module.exports = centsToFactor;
13325
13233
  //
13326
13234
  // If there is any note in the melody that has a rhythm head, then assume the melody controls the rhythm, so there is no chord added for that entire measure.
13327
13235
 
13328
- var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/abc_common.js");
13329
13236
  var ChordTrack = function ChordTrack(numVoices, chordsOff, midiOptions, meter) {
13330
13237
  this.chordTrack = [];
13331
13238
  this.chordTrackFinished = false;
@@ -13340,11 +13247,23 @@ var ChordTrack = function ChordTrack(numVoices, chordsOff, midiOptions, meter) {
13340
13247
  this.lastBarTime = 0;
13341
13248
  this.meter = meter;
13342
13249
  this.tempoChangeFactor = 1;
13343
- this.bassInstrument = midiOptions.bassprog && midiOptions.bassprog.length === 1 ? midiOptions.bassprog[0] : 0;
13344
- this.chordInstrument = midiOptions.chordprog && midiOptions.chordprog.length === 1 ? midiOptions.chordprog[0] : 0;
13250
+
13251
+ // MAE 17 Jun 2024 - To allow for bass and chord instrument octave shifts
13252
+ this.bassInstrument = midiOptions.bassprog && midiOptions.bassprog.length >= 1 ? midiOptions.bassprog[0] : 0;
13253
+ this.chordInstrument = midiOptions.chordprog && midiOptions.chordprog.length >= 1 ? midiOptions.chordprog[0] : 0;
13254
+
13255
+ // MAE For octave shifted bass and chords
13256
+ this.bassOctaveShift = midiOptions.bassprog && midiOptions.bassprog.length === 2 ? midiOptions.bassprog[1] : 0;
13257
+ this.chordOctaveShift = midiOptions.chordprog && midiOptions.chordprog.length === 2 ? midiOptions.chordprog[1] : 0;
13345
13258
  this.boomVolume = midiOptions.bassvol && midiOptions.bassvol.length === 1 ? midiOptions.bassvol[0] : 64;
13346
13259
  this.chickVolume = midiOptions.chordvol && midiOptions.chordvol.length === 1 ? midiOptions.chordvol[0] : 48;
13347
- this.overridePattern = midiOptions.gchord ? parseGChord(midiOptions.gchord[0]) : undefined;
13260
+
13261
+ // This allows for an initial %%MIDI gchord with no string
13262
+ if (midiOptions.gchord && midiOptions.gchord.length > 0) {
13263
+ this.overridePattern = parseGChord(midiOptions.gchord[0]);
13264
+ } else {
13265
+ this.overridePattern = undefined;
13266
+ }
13348
13267
  };
13349
13268
  ChordTrack.prototype.setMeter = function (meter) {
13350
13269
  this.meter = meter;
@@ -13364,7 +13283,7 @@ ChordTrack.prototype.setRhythmHead = function (isRhythmHead, elem) {
13364
13283
  if (isRhythmHead) {
13365
13284
  if (this.lastChord && this.lastChord.chick) {
13366
13285
  for (var i2 = 0; i2 < this.lastChord.chick.length; i2++) {
13367
- var note2 = parseCommon.clone(elem.pitches[0]);
13286
+ var note2 = Object.assign({}, elem.pitches[0]);
13368
13287
  note2.actualPitch = this.lastChord.chick[i2];
13369
13288
  ePitches.push(note2);
13370
13289
  }
@@ -13385,13 +13304,29 @@ ChordTrack.prototype.gChordOn = function (element) {
13385
13304
  ChordTrack.prototype.paramChange = function (element) {
13386
13305
  switch (element.el_type) {
13387
13306
  case "gchord":
13388
- this.overridePattern = parseGChord(element.param);
13307
+ // Skips gchord elements that don't have pattern strings
13308
+ if (element.param && element.param.length > 0) {
13309
+ this.overridePattern = parseGChord(element.param);
13310
+
13311
+ // Generate a default duration scale based on the pattern
13312
+ //this.gchordduration = generateDefaultDurationScale(element.param);
13313
+ } else this.overridePattern = undefined;
13389
13314
  break;
13390
13315
  case "bassprog":
13391
- this.bassInstrument = element.param;
13316
+ this.bassInstrument = element.value;
13317
+ if (element.octaveShift != undefined && element.octaveShift != null) {
13318
+ this.bassOctaveShift = element.octaveShift;
13319
+ } else {
13320
+ this.bassOctaveShift = 0;
13321
+ }
13392
13322
  break;
13393
13323
  case "chordprog":
13394
- this.chordInstrument = element.param;
13324
+ this.chordInstrument = element.value;
13325
+ if (element.octaveShift != undefined && element.octaveShift != null) {
13326
+ this.chordOctaveShift = element.octaveShift;
13327
+ } else {
13328
+ this.chordOctaveShift = 0;
13329
+ }
13395
13330
  break;
13396
13331
  case "bassvol":
13397
13332
  this.boomVolume = element.param;
@@ -13459,22 +13394,28 @@ ChordTrack.prototype.interpretChord = function (name) {
13459
13394
  chordTranspose -= 12;
13460
13395
  }
13461
13396
  bass += chordTranspose;
13397
+
13398
+ // MAE 17 Jun 2024 - Supporting octave shifted bass and chords
13399
+ var unshiftedBass = bass;
13400
+ bass += this.bassOctaveShift * 12;
13462
13401
  var bass2 = bass - 5; // The alternating bass is a 4th below
13463
13402
  var chick;
13464
13403
  if (name.length === 1) chick = this.chordNotes(bass, '');
13465
13404
  var remaining = name.substring(1);
13466
13405
  var acc = remaining.substring(0, 1);
13467
13406
  if (acc === 'b' || acc === '♭') {
13407
+ unshiftedBass--;
13468
13408
  bass--;
13469
13409
  bass2--;
13470
13410
  remaining = remaining.substring(1);
13471
13411
  } else if (acc === '#' || acc === '♯') {
13412
+ unshiftedBass++;
13472
13413
  bass++;
13473
13414
  bass2++;
13474
13415
  remaining = remaining.substring(1);
13475
13416
  }
13476
13417
  var arr = remaining.split('/');
13477
- chick = this.chordNotes(bass, arr[0]);
13418
+ chick = this.chordNotes(unshiftedBass, arr[0]);
13478
13419
  // If the 5th is altered then the bass is altered. Normally the bass is 7 from the root, so adjust if it isn't.
13479
13420
  if (chick.length >= 3) {
13480
13421
  var fifth = chick[2] - chick[0];
@@ -13491,6 +13432,9 @@ ChordTrack.prototype.interpretChord = function (name) {
13491
13432
  '♭': -1
13492
13433
  }[bassAcc] || 0;
13493
13434
  bass = this.basses[arr[1].substring(0, 1)] + bassShift + chordTranspose;
13435
+
13436
+ // MAE 22 May 2024 - Supporting octave shifted bass and chords
13437
+ bass += this.bassOctaveShift * 12;
13494
13438
  bass2 = bass;
13495
13439
  }
13496
13440
  }
@@ -13506,6 +13450,9 @@ ChordTrack.prototype.chordNotes = function (bass, modifier) {
13506
13450
  if (modifier.slice(0, 2).toLowerCase() === 'ma' || modifier[0] === 'M') intervals = this.chordIntervals.M;else if (modifier[0] === 'm' || modifier[0] === '-') intervals = this.chordIntervals.m;else intervals = this.chordIntervals.M;
13507
13451
  }
13508
13452
  bass += 12; // the chord is an octave above the bass note.
13453
+
13454
+ // MAE 22 May 2024 - For chick octave shift
13455
+ bass += this.chordOctaveShift * 12;
13509
13456
  var notes = [];
13510
13457
  for (var i = 0; i < intervals.length; i++) {
13511
13458
  notes.push(bass + intervals[i]);
@@ -13577,8 +13524,8 @@ ChordTrack.prototype.resolveChords = function (startTime, endTime) {
13577
13524
  if (p > 0 && currentChordsExpanded[p - 1] && currentChordsExpanded[p] && currentChordsExpanded[p - 1].boom !== currentChordsExpanded[p].boom) firstBoom = true;
13578
13525
  var type = thisPattern[p];
13579
13526
  var isBoom = type.indexOf('boom') >= 0;
13580
- // If we changed chords at a time when we're not expecting a bass note, then add an extra bass note in.
13581
- var newBass = !isBoom && p !== 0 && (!currentChordsExpanded[p - 1] || currentChordsExpanded[p - 1].boom !== currentChordsExpanded[p].boom);
13527
+ // If we changed chords at a time when we're not expecting a bass note, then add an extra bass note in if the first thing in the pattern is a bass note.
13528
+ var newBass = !isBoom && p !== 0 && thisPattern[0].indexOf('boom') >= 0 && (!currentChordsExpanded[p - 1] || currentChordsExpanded[p - 1].boom !== currentChordsExpanded[p].boom);
13582
13529
  var pitches = resolvePitch(currentChordsExpanded[p], type, firstBoom, newBass);
13583
13530
  if (isBoom) firstBoom = false;
13584
13531
  for (var oo = 0; oo < pitches.length; oo++) {
@@ -13619,8 +13566,9 @@ function resolvePitch(currentChord, type, firstBoom, newBass) {
13619
13566
  var ret = [];
13620
13567
  if (!currentChord) return ret;
13621
13568
  if (type.indexOf('boom') >= 0) ret.push(firstBoom ? currentChord.boom : currentChord.boom2);else if (newBass) ret.push(currentChord.boom);
13569
+ var numChordNotes = currentChord.chick.length;
13622
13570
  if (type.indexOf('chick') >= 0) {
13623
- for (var i = 0; i < currentChord.chick.length; i++) {
13571
+ for (var i = 0; i < numChordNotes; i++) {
13624
13572
  ret.push(currentChord.chick[i]);
13625
13573
  }
13626
13574
  }
@@ -13632,13 +13580,13 @@ function resolvePitch(currentChord, type, firstBoom, newBass) {
13632
13580
  ret.push(currentChord.chick[1]);
13633
13581
  break;
13634
13582
  case 'SOL':
13635
- ret.push(currentChord.chick[2]);
13583
+ ret.push(extractNote(currentChord, 2));
13636
13584
  break;
13637
13585
  case 'TI':
13638
- currentChord.chick.length > 3 ? ret.push(currentChord.chick[2]) : ret.push(currentChord.chick[0] + 12);
13586
+ ret.push(extractNote(currentChord, 3));
13639
13587
  break;
13640
13588
  case 'TOP':
13641
- currentChord.chick.length > 4 ? ret.push(currentChord.chick[2]) : ret.push(currentChord.chick[1] + 12);
13589
+ ret.push(extractNote(currentChord, 4));
13642
13590
  break;
13643
13591
  case 'do':
13644
13592
  ret.push(currentChord.chick[0] + 12);
@@ -13647,17 +13595,24 @@ function resolvePitch(currentChord, type, firstBoom, newBass) {
13647
13595
  ret.push(currentChord.chick[1] + 12);
13648
13596
  break;
13649
13597
  case 'sol':
13650
- ret.push(currentChord.chick[2] + 12);
13598
+ ret.push(extractNote(currentChord, 2) + 12);
13651
13599
  break;
13652
13600
  case 'ti':
13653
- currentChord.chick.length > 3 ? ret.push(currentChord.chick[2] + 12) : ret.push(currentChord.chick[0] + 24);
13601
+ ret.push(extractNote(currentChord, 3) + 12);
13654
13602
  break;
13655
13603
  case 'top':
13656
- currentChord.chick.length > 4 ? ret.push(currentChord.chick[2] + 12) : ret.push(currentChord.chick[1] + 24);
13604
+ ret.push(extractNote(currentChord, 4) + 12);
13657
13605
  break;
13658
13606
  }
13659
13607
  return ret;
13660
13608
  }
13609
+ function extractNote(chord, index) {
13610
+ // This creates an arpeggio note no matter how many notes are in the chord - if it runs out of notes it continues in the next octave
13611
+ var octave = Math.floor(index / chord.chick.length);
13612
+ var note = chord.chick[index % chord.chick.length];
13613
+ //console.log(chord.chick, {index, octave, note}, index % chord.chick.length)
13614
+ return note + octave * 12;
13615
+ }
13661
13616
  function parseGChord(gchord) {
13662
13617
  // TODO-PER: The spec is more complicated than this but for now this will not try to do anything with error cases like the wrong number of beats.
13663
13618
  var pattern = [];
@@ -13854,7 +13809,11 @@ ChordTrack.prototype.chordIntervals = {
13854
13809
  'maj7#5#11': [0, 4, 8, 11, 18],
13855
13810
  '9(#5)': [0, 4, 8, 10, 14],
13856
13811
  '13(#5)': [0, 4, 8, 10, 14, 21],
13857
- '13#5': [0, 4, 8, 10, 14, 21]
13812
+ '13#5': [0, 4, 8, 10, 14, 21],
13813
+ // MAE Power chords added 10 April 2024
13814
+ '5': [0, 7],
13815
+ '5(8)': [0, 7, 12],
13816
+ '5add8': [0, 7, 12]
13858
13817
  };
13859
13818
  ChordTrack.prototype.rhythmPatterns = {
13860
13819
  "2/2": ['boom', '', '', '', 'chick', '', '', ''],
@@ -13866,8 +13825,12 @@ ChordTrack.prototype.rhythmPatterns = {
13866
13825
  "5/4": ['boom', '', 'chick', '', 'chick', '', 'boom', '', 'chick', ''],
13867
13826
  "6/4": ['boom', '', 'chick', '', 'boom', '', 'chick', '', 'boom', '', 'chick', ''],
13868
13827
  "3/8": ['boom', '', 'chick'],
13828
+ "5/8": ['boom', 'chick', 'chick', 'boom', 'chick'],
13869
13829
  "6/8": ['boom', '', 'chick', 'boom', '', 'chick'],
13830
+ "7/8": ['boom', 'chick', 'chick', 'boom', 'chick', 'boom', 'chick'],
13870
13831
  "9/8": ['boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick'],
13832
+ "10/8": ['boom', 'chick', 'chick', 'boom', 'chick', 'chick', 'boom', 'chick', 'boom', 'chick'],
13833
+ "11/8": ['boom', 'chick', 'chick', 'boom', 'chick', 'chick', 'boom', 'chick', 'boom', 'chick', 'chick'],
13871
13834
  "12/8": ['boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick']
13872
13835
  };
13873
13836
 
@@ -13955,7 +13918,6 @@ module.exports = createNoteMap;
13955
13918
  var supportsAudio = __webpack_require__(/*! ./supports-audio */ "./src/synth/supports-audio.js");
13956
13919
  var registerAudioContext = __webpack_require__(/*! ./register-audio-context */ "./src/synth/register-audio-context.js");
13957
13920
  var activeAudioContext = __webpack_require__(/*! ./active-audio-context */ "./src/synth/active-audio-context.js");
13958
- var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/abc_common.js");
13959
13921
  var loopImage = __webpack_require__(/*! ./images/loop.svg.js */ "./src/synth/images/loop.svg.js");
13960
13922
  var playImage = __webpack_require__(/*! ./images/play.svg.js */ "./src/synth/images/play.svg.js");
13961
13923
  var pauseImage = __webpack_require__(/*! ./images/pause.svg.js */ "./src/synth/images/pause.svg.js");
@@ -13971,7 +13933,7 @@ function CreateSynthControl(parent, options) {
13971
13933
  } else if (!(parent instanceof HTMLElement)) throw new Error("The first parameter must be a valid element or selector in the DOM.");
13972
13934
  self.parent = parent;
13973
13935
  self.options = {};
13974
- if (options) self.options = parseCommon.clone(options);
13936
+ if (options) self.options = Object.assign({}, options);
13975
13937
 
13976
13938
  // This can be called in the following cases:
13977
13939
  // AC already registered and not suspended
@@ -14261,7 +14223,10 @@ function CreateSynth() {
14261
14223
  self.sequenceCallback = params.sequenceCallback;
14262
14224
  self.callbackContext = params.callbackContext;
14263
14225
  self.onEnded = params.onEnded;
14264
- self.meterFraction = options.visualObj.getMeterFraction();
14226
+ self.meterFraction = options.visualObj ? options.visualObj.getMeterFraction() : {
14227
+ den: 1
14228
+ }; // If we are given a sequence instead of a regular visual obj, then don't do the swing
14229
+
14265
14230
  var allNotes = {};
14266
14231
  var cached = [];
14267
14232
  var errorNotes = [];
@@ -15639,115 +15604,315 @@ function SynthController() {
15639
15604
  return Promise.resolve();
15640
15605
  });
15641
15606
  }
15642
- self.seek(startPercent);
15643
- return Promise.resolve();
15644
- });
15645
- }
15646
- return Promise.resolve();
15647
- };
15648
- self.onWarp = function (ev) {
15649
- var newWarp = ev.target.value;
15650
- return self.setWarp(newWarp);
15651
- };
15652
- self.setProgress = function (percent, totalTime) {
15653
- self.percent = percent;
15654
- if (self.control) self.control.setProgress(percent, totalTime);
15655
- };
15656
- self.finished = function () {
15657
- self.timer.reset();
15658
- if (self.isLooping) {
15659
- self.timer.start(0);
15660
- self.midiBuffer.finished();
15661
- self.midiBuffer.start();
15662
- return "continue";
15663
- } else {
15664
- self.timer.stop();
15665
- if (self.isStarted) {
15666
- if (self.control) self.control.pushPlay(false);
15667
- self.isStarted = false;
15668
- self.midiBuffer.finished();
15669
- if (self.cursorControl && self.cursorControl.onFinished && typeof self.cursorControl.onFinished === 'function') self.cursorControl.onFinished();
15670
- self.setProgress(0, 1);
15607
+ self.seek(startPercent);
15608
+ return Promise.resolve();
15609
+ });
15610
+ }
15611
+ return Promise.resolve();
15612
+ };
15613
+ self.onWarp = function (ev) {
15614
+ var newWarp = ev.target.value;
15615
+ return self.setWarp(newWarp);
15616
+ };
15617
+ self.setProgress = function (percent, totalTime) {
15618
+ self.percent = percent;
15619
+ if (self.control) self.control.setProgress(percent, totalTime);
15620
+ };
15621
+ self.finished = function () {
15622
+ self.timer.reset();
15623
+ if (self.isLooping) {
15624
+ self.timer.start(0);
15625
+ self.midiBuffer.finished();
15626
+ self.midiBuffer.start();
15627
+ return "continue";
15628
+ } else {
15629
+ self.timer.stop();
15630
+ if (self.isStarted) {
15631
+ if (self.control) self.control.pushPlay(false);
15632
+ self.isStarted = false;
15633
+ self.midiBuffer.finished();
15634
+ if (self.cursorControl && self.cursorControl.onFinished && typeof self.cursorControl.onFinished === 'function') self.cursorControl.onFinished();
15635
+ self.setProgress(0, 1);
15636
+ }
15637
+ }
15638
+ };
15639
+ self.beatCallback = function (beatNumber, totalBeats, totalTime, position) {
15640
+ var percent = beatNumber / totalBeats;
15641
+ self.setProgress(percent, totalTime);
15642
+ if (self.cursorControl && self.cursorControl.onBeat && typeof self.cursorControl.onBeat === 'function') self.cursorControl.onBeat(beatNumber, totalBeats, totalTime, position);
15643
+ };
15644
+ self.eventCallback = function (event) {
15645
+ if (event) {
15646
+ if (self.cursorControl && self.cursorControl.onEvent && typeof self.cursorControl.onEvent === 'function') self.cursorControl.onEvent(event);
15647
+ } else {
15648
+ return self.finished();
15649
+ }
15650
+ };
15651
+ self.lineEndCallback = function (lineEvent, leftEvent) {
15652
+ if (self.cursorControl && self.cursorControl.onLineEnd && typeof self.cursorControl.onLineEnd === 'function') self.cursorControl.onLineEnd(lineEvent, leftEvent);
15653
+ };
15654
+ self.getUrl = function () {
15655
+ return self.midiBuffer.download();
15656
+ };
15657
+ self.download = function (fileName) {
15658
+ var url = self.getUrl();
15659
+ var link = document.createElement('a');
15660
+ document.body.appendChild(link);
15661
+ link.setAttribute("style", "display: none;");
15662
+ link.href = url;
15663
+ link.download = fileName ? fileName : 'output.wav';
15664
+ link.click();
15665
+ window.URL.revokeObjectURL(url);
15666
+ document.body.removeChild(link);
15667
+ };
15668
+ }
15669
+ module.exports = SynthController;
15670
+
15671
+ /***/ }),
15672
+
15673
+ /***/ "./src/synth/synth-sequence.js":
15674
+ /*!*************************************!*\
15675
+ !*** ./src/synth/synth-sequence.js ***!
15676
+ \*************************************/
15677
+ /***/ (function(module) {
15678
+
15679
+ var SynthSequence = function SynthSequence() {
15680
+ var self = this;
15681
+ self.tracks = [];
15682
+ self.totalDuration = 0;
15683
+ self.currentInstrument = [];
15684
+ self.starts = [];
15685
+ self.addTrack = function () {
15686
+ self.tracks.push([]);
15687
+ self.currentInstrument.push(0);
15688
+ self.starts.push(0);
15689
+ return self.tracks.length - 1;
15690
+ };
15691
+ self.setInstrument = function (trackNumber, instrumentNumber) {
15692
+ self.tracks[trackNumber].push({
15693
+ channel: 0,
15694
+ cmd: "program",
15695
+ instrument: instrumentNumber
15696
+ });
15697
+ self.currentInstrument[trackNumber] = instrumentNumber;
15698
+ };
15699
+ self.appendNote = function (trackNumber, pitch, durationInMeasures, volume, cents) {
15700
+ var note = {
15701
+ cmd: "note",
15702
+ duration: durationInMeasures,
15703
+ gap: 0,
15704
+ instrument: self.currentInstrument[trackNumber],
15705
+ pitch: pitch,
15706
+ start: self.starts[trackNumber],
15707
+ volume: volume
15708
+ };
15709
+ if (cents) note.cents = cents;
15710
+ self.tracks[trackNumber].push(note);
15711
+ self.starts[trackNumber] += durationInMeasures;
15712
+ self.totalDuration = Math.max(self.totalDuration, self.starts[trackNumber]);
15713
+ };
15714
+ };
15715
+ module.exports = SynthSequence;
15716
+
15717
+ /***/ }),
15718
+
15719
+ /***/ "./src/tablatures/abc_tablatures.js":
15720
+ /*!******************************************!*\
15721
+ !*** ./src/tablatures/abc_tablatures.js ***!
15722
+ \******************************************/
15723
+ /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
15724
+
15725
+ /*
15726
+ * Tablature Plugins
15727
+ * tablature are defined dynamically and registered inside abcjs
15728
+ * by calling abcTablatures.register(plugin)
15729
+ * where plugin represents a plugin instance
15730
+ *
15731
+ */
15732
+
15733
+ // This is the only entry point to the tablatures. It is called both after parsing a tune and just before engraving
15734
+
15735
+ var TabString = __webpack_require__(/*! ./instruments/tab-string */ "./src/tablatures/instruments/tab-string.js");
15736
+
15737
+ /* extend the table below when adding a new instrument plugin */
15738
+
15739
+ // Existing tab classes
15740
+ var pluginTab = {
15741
+ 'violin': {
15742
+ name: 'StringTab',
15743
+ defaultTuning: ['G,', 'D', 'A', 'e'],
15744
+ isTabBig: false,
15745
+ tabSymbolOffset: 0
15746
+ },
15747
+ 'fiddle': {
15748
+ name: 'StringTab',
15749
+ defaultTuning: ['G,', 'D', 'A', 'e'],
15750
+ isTabBig: false,
15751
+ tabSymbolOffset: 0
15752
+ },
15753
+ 'mandolin': {
15754
+ name: 'StringTab',
15755
+ defaultTuning: ['G,', 'D', 'A', 'e'],
15756
+ isTabBig: false,
15757
+ tabSymbolOffset: 0
15758
+ },
15759
+ 'guitar': {
15760
+ name: 'StringTab',
15761
+ defaultTuning: ['E,', 'A,', 'D', 'G', 'B', 'e'],
15762
+ isTabBig: true,
15763
+ tabSymbolOffset: 0
15764
+ },
15765
+ 'fiveString': {
15766
+ name: 'StringTab',
15767
+ defaultTuning: ['C,', 'G,', 'D', 'A', 'e'],
15768
+ isTabBig: false,
15769
+ tabSymbolOffset: -.95
15770
+ }
15771
+ };
15772
+ var abcTablatures = {
15773
+ inited: false,
15774
+ plugins: {},
15775
+ /**
15776
+ * to be called once per plugin for registration
15777
+ * @param {*} plugin
15778
+ */
15779
+ register: function register(plugin) {
15780
+ var name = plugin.name;
15781
+ var tablature = plugin.tablature;
15782
+ this.plugins[name] = tablature;
15783
+ },
15784
+ setError: function setError(tune, msg) {
15785
+ if (tune.warnings) {
15786
+ tune.warning.push(msg);
15787
+ } else {
15788
+ tune.warnings = [msg];
15789
+ }
15790
+ },
15791
+ /**
15792
+ * handle params for current processed score
15793
+ * @param {*} tune current tune
15794
+ * @param {*} tuneNumber number in tune list
15795
+ * @param {*} params params to be processed for tablature
15796
+ * @return prepared tablatures plugin instances for current tune
15797
+ */
15798
+ preparePlugins: function preparePlugins(tune, tuneNumber, params) {
15799
+ // Called after parsing a tune and before engraving it
15800
+ if (!this.inited) {
15801
+ // TODO-PER: I don't think this is needed - the plugin array can be hard coded, right?
15802
+ this.register(new TabString());
15803
+ this.inited = true;
15804
+ }
15805
+ var returned = null;
15806
+ var nbPlugins = 0;
15807
+ if (params.tablature) {
15808
+ // validate requested plugins
15809
+ var tabs = params.tablature;
15810
+ returned = [];
15811
+ for (var ii = 0; ii < tabs.length; ii++) {
15812
+ var args = tabs[ii];
15813
+ var instrument = args['instrument'];
15814
+ if (instrument == null) {
15815
+ this.setError(tune, "tablature 'instrument' is missing");
15816
+ return returned;
15817
+ }
15818
+ var tabName = pluginTab[instrument];
15819
+ var plugin = null;
15820
+ if (tabName) {
15821
+ plugin = this.plugins[tabName.name];
15822
+ }
15823
+ if (plugin) {
15824
+ if (params.visualTranspose != 0) {
15825
+ // populate transposition request to tabs
15826
+ args.visualTranspose = params.visualTranspose;
15827
+ }
15828
+ args.abcSrc = params.tablature.abcSrc;
15829
+ var pluginInstance = {
15830
+ classz: plugin,
15831
+ tuneNumber: tuneNumber,
15832
+ params: args,
15833
+ instance: null,
15834
+ tabType: tabName
15835
+ };
15836
+ // proceed with tab plugin init
15837
+ // plugin.init(tune, tuneNumber, args, ii);
15838
+ returned.push(pluginInstance);
15839
+ nbPlugins++;
15840
+ } else if (instrument === '') {
15841
+ // create a placeholder - there is no tab for this staff
15842
+ returned.push(null);
15843
+ } else {
15844
+ // unknown tab plugin
15845
+ //this.emit_error('Undefined tablature plugin: ' + tabName)
15846
+ this.setError(tune, 'Undefined tablature plugin: ' + instrument);
15847
+ return returned;
15848
+ }
15671
15849
  }
15672
15850
  }
15673
- };
15674
- self.beatCallback = function (beatNumber, totalBeats, totalTime, position) {
15675
- var percent = beatNumber / totalBeats;
15676
- self.setProgress(percent, totalTime);
15677
- if (self.cursorControl && self.cursorControl.onBeat && typeof self.cursorControl.onBeat === 'function') self.cursorControl.onBeat(beatNumber, totalBeats, totalTime, position);
15678
- };
15679
- self.eventCallback = function (event) {
15680
- if (event) {
15681
- if (self.cursorControl && self.cursorControl.onEvent && typeof self.cursorControl.onEvent === 'function') self.cursorControl.onEvent(event);
15682
- } else {
15683
- return self.finished();
15684
- }
15685
- };
15686
- self.lineEndCallback = function (lineEvent, leftEvent) {
15687
- if (self.cursorControl && self.cursorControl.onLineEnd && typeof self.cursorControl.onLineEnd === 'function') self.cursorControl.onLineEnd(lineEvent, leftEvent);
15688
- };
15689
- self.getUrl = function () {
15690
- return self.midiBuffer.download();
15691
- };
15692
- self.download = function (fileName) {
15693
- var url = self.getUrl();
15694
- var link = document.createElement('a');
15695
- document.body.appendChild(link);
15696
- link.setAttribute("style", "display: none;");
15697
- link.href = url;
15698
- link.download = fileName ? fileName : 'output.wav';
15699
- link.click();
15700
- window.URL.revokeObjectURL(url);
15701
- document.body.removeChild(link);
15702
- };
15703
- }
15704
- module.exports = SynthController;
15851
+ return returned;
15852
+ },
15853
+ /**
15854
+ * Call requested plugin
15855
+ * @param {*} renderer
15856
+ * @param {*} abcTune
15857
+ */
15858
+ layoutTablatures: function layoutTablatures(renderer, abcTune) {
15859
+ var tabs = abcTune.tablatures;
15705
15860
 
15706
- /***/ }),
15861
+ // chack tabs request for each staffs
15862
+ var staffLineCount = 0;
15707
15863
 
15708
- /***/ "./src/synth/synth-sequence.js":
15709
- /*!*************************************!*\
15710
- !*** ./src/synth/synth-sequence.js ***!
15711
- \*************************************/
15712
- /***/ (function(module) {
15864
+ // Clear the suppression flag
15865
+ if (tabs && tabs.length > 0) {
15866
+ var nTabs = tabs.length;
15867
+ for (var kk = 0; kk < nTabs; ++kk) {
15868
+ if (tabs[kk] && tabs[kk].params.firstStaffOnly) {
15869
+ tabs[kk].params.suppress = false;
15870
+ }
15871
+ }
15872
+ }
15873
+ for (var ii = 0; ii < abcTune.lines.length; ii++) {
15874
+ var line = abcTune.lines[ii];
15875
+ if (line.staff) {
15876
+ staffLineCount++;
15877
+ }
15713
15878
 
15714
- var SynthSequence = function SynthSequence() {
15715
- var self = this;
15716
- self.tracks = [];
15717
- self.totalDuration = 0;
15718
- self.currentInstrument = [];
15719
- self.starts = [];
15720
- self.addTrack = function () {
15721
- self.tracks.push([]);
15722
- self.currentInstrument.push(0);
15723
- self.starts.push(0);
15724
- return self.tracks.length - 1;
15725
- };
15726
- self.setInstrument = function (trackNumber, instrumentNumber) {
15727
- self.tracks[trackNumber].push({
15728
- channel: 0,
15729
- cmd: "program",
15730
- instrument: instrumentNumber
15731
- });
15732
- self.currentInstrument[trackNumber] = instrumentNumber;
15733
- };
15734
- self.appendNote = function (trackNumber, pitch, durationInMeasures, volume, cents) {
15735
- var note = {
15736
- cmd: "note",
15737
- duration: durationInMeasures,
15738
- gap: 0,
15739
- instrument: self.currentInstrument[trackNumber],
15740
- pitch: pitch,
15741
- start: self.starts[trackNumber],
15742
- volume: volume
15743
- };
15744
- if (cents) note.cents = cents;
15745
- self.tracks[trackNumber].push(note);
15746
- self.starts[trackNumber] += durationInMeasures;
15747
- self.totalDuration = Math.max(self.totalDuration, self.starts[trackNumber]);
15748
- };
15879
+ // MAE 27Nov2023
15880
+ // If tab param "firstStaffOnly", remove the tab label after the first staff
15881
+ if (staffLineCount > 1) {
15882
+ if (tabs && tabs.length > 0) {
15883
+ var nTabs = tabs.length;
15884
+ for (var kk = 0; kk < nTabs; ++kk) {
15885
+ if (tabs[kk].params.firstStaffOnly) {
15886
+ // Set the staff draw suppression flag
15887
+ tabs[kk].params.suppress = true;
15888
+ }
15889
+ }
15890
+ }
15891
+ }
15892
+ var curStaff = line.staff;
15893
+ if (curStaff) {
15894
+ var maxStaves = curStaff.length;
15895
+ for (var jj = 0; jj < curStaff.length; jj++) {
15896
+ if (tabs[jj] && jj < maxStaves) {
15897
+ // tablature requested for staff
15898
+ var tabPlugin = tabs[jj];
15899
+ if (tabPlugin.instance == null) {
15900
+ //console.log("★★★★ Tab Init line: " + ii + " staff: " + jj)
15901
+ tabPlugin.instance = new tabPlugin.classz();
15902
+ // plugin.init(tune, tuneNumber, args, ii);
15903
+ // call initer first
15904
+ tabPlugin.instance.init(abcTune, tabPlugin.tuneNumber, tabPlugin.params, tabPlugin.tabType);
15905
+ }
15906
+ // render next
15907
+ //console.log("★★★★ Tab Render line: " + ii + " staff: " + jj)
15908
+ tabPlugin.instance.render(renderer, line, jj);
15909
+ }
15910
+ }
15911
+ }
15912
+ }
15913
+ }
15749
15914
  };
15750
- module.exports = SynthSequence;
15915
+ module.exports = abcTablatures;
15751
15916
 
15752
15917
  /***/ }),
15753
15918
 
@@ -15760,14 +15925,14 @@ module.exports = SynthSequence;
15760
15925
  var _require = __webpack_require__(/*! ../../synth/note-to-midi */ "./src/synth/note-to-midi.js"),
15761
15926
  noteToMidi = _require.noteToMidi;
15762
15927
  var TabNote = __webpack_require__(/*! ./tab-note */ "./src/tablatures/instruments/tab-note.js");
15763
- var TabNotes = __webpack_require__(/*! ./tab-notes */ "./src/tablatures/instruments/tab-notes.js");
15928
+ var tabNotes = __webpack_require__(/*! ./tab-notes */ "./src/tablatures/instruments/tab-notes.js");
15764
15929
  function buildCapo(self) {
15765
15930
  var capoTuning = null;
15766
15931
  var tuning = self.tuning;
15767
15932
  if (self.capo > 0) {
15768
15933
  capoTuning = [];
15769
15934
  for (var iii = 0; iii < tuning.length; iii++) {
15770
- var curNote = new TabNote.TabNote(tuning[iii]);
15935
+ var curNote = new TabNote(tuning[iii]);
15771
15936
  for (var jjj = 0; jjj < self.capo; jjj++) {
15772
15937
  curNote = curNote.nextNote();
15773
15938
  }
@@ -15788,8 +15953,7 @@ function buildPatterns(self) {
15788
15953
  if (iii != tuning.length - 1) {
15789
15954
  nextNote = tuning[iii + 1];
15790
15955
  }
15791
- var tabNotes = new TabNotes(tuning[iii], nextNote);
15792
- var stringNotes = tabNotes.build();
15956
+ var stringNotes = tabNotes(tuning[iii], nextNote);
15793
15957
  if (stringNotes.error) {
15794
15958
  return stringNotes;
15795
15959
  }
@@ -15838,7 +16002,7 @@ function handleChordNotes(self, notes) {
15838
16002
  var retNotes = [];
15839
16003
  for (var iiii = 0; iiii < notes.length; iiii++) {
15840
16004
  if (notes[iiii].endTie) continue;
15841
- var note = new TabNote.TabNote(notes[iiii].name, self.clefTranspose);
16005
+ var note = new TabNote(notes[iiii].name, self.clefTranspose);
15842
16006
  note.checkKeyAccidentals(self.accidentals, self.measureAccidentals);
15843
16007
  var curPos = toNumber(self, note);
15844
16008
  retNotes.push(curPos);
@@ -15930,7 +16094,7 @@ StringPatterns.prototype.notesToNumber = function (notes, graces) {
15930
16094
  }
15931
16095
  } else {
15932
16096
  if (!notes[0].endTie) {
15933
- note = new TabNote.TabNote(notes[0].name, this.clefTranspose);
16097
+ note = new TabNote(notes[0].name, this.clefTranspose);
15934
16098
  note.checkKeyAccidentals(this.accidentals, this.measureAccidentals);
15935
16099
  number = toNumber(this, note);
15936
16100
  if (number) {
@@ -15947,7 +16111,7 @@ StringPatterns.prototype.notesToNumber = function (notes, graces) {
15947
16111
  if (graces) {
15948
16112
  retGraces = [];
15949
16113
  for (var iiii = 0; iiii < graces.length; iiii++) {
15950
- note = new TabNote.TabNote(graces[iiii].name, this.clefTranspose);
16114
+ note = new TabNote(graces[iiii].name, this.clefTranspose);
15951
16115
  note.checkKeyAccidentals(this.accidentals, this.measureAccidentals);
15952
16116
  number = toNumber(this, note);
15953
16117
  if (number) {
@@ -15974,8 +16138,7 @@ StringPatterns.prototype.toString = function () {
15974
16138
  return arr.join('');
15975
16139
  };
15976
16140
  StringPatterns.prototype.tabInfos = function (plugin) {
15977
- var _super = plugin._super;
15978
- var name = _super.params.label;
16141
+ var name = plugin.params.label;
15979
16142
  if (name) {
15980
16143
  var tunePos = name.indexOf('%T');
15981
16144
  var tuning = "";
@@ -15993,8 +16156,7 @@ StringPatterns.prototype.tabInfos = function (plugin) {
15993
16156
 
15994
16157
  // MAE 27 Nov 2023
15995
16158
  StringPatterns.prototype.suppress = function (plugin) {
15996
- var _super = plugin._super;
15997
- var suppress = _super.params.suppress;
16159
+ var suppress = plugin.params.suppress;
15998
16160
  if (suppress) {
15999
16161
  return true;
16000
16162
  }
@@ -16010,9 +16172,10 @@ StringPatterns.prototype.suppress = function (plugin) {
16010
16172
  * @param {*} highestNote
16011
16173
  */
16012
16174
  function StringPatterns(plugin) {
16175
+ //console.log("INIT StringPatterns constructor")
16013
16176
  var tuning = plugin.tuning;
16014
16177
  var capo = plugin.capo;
16015
- var highestNote = plugin._super.params.highestNote;
16178
+ var highestNote = plugin.params.highestNote;
16016
16179
  this.linePitch = plugin.linePitch;
16017
16180
  this.highestNote = "a'";
16018
16181
  if (highestNote) {
@@ -16036,7 +16199,7 @@ function StringPatterns(plugin) {
16036
16199
  }
16037
16200
  this.strings = buildPatterns(this);
16038
16201
  if (this.strings.error) {
16039
- plugin._super.setError(this.strings.error);
16202
+ plugin.setError(this.strings.error);
16040
16203
  plugin.inError = true;
16041
16204
  return;
16042
16205
  }
@@ -16060,6 +16223,7 @@ module.exports = StringPatterns;
16060
16223
  */
16061
16224
 
16062
16225
  function StringTablature(numLines, lineSpace) {
16226
+ //console.log("INIT StringTablature constructor")
16063
16227
  this.numLines = numLines;
16064
16228
  this.lineSpace = lineSpace;
16065
16229
  this.verticalSize = this.numLines * this.lineSpace;
@@ -16076,6 +16240,7 @@ function StringTablature(numLines, lineSpace) {
16076
16240
  * @param {} line
16077
16241
  */
16078
16242
  StringTablature.prototype.bypass = function (line) {
16243
+ //console.log("RENDER StringTablature bypass")
16079
16244
  var voices = line.staffGroup.voices;
16080
16245
  if (voices.length > 0) {
16081
16246
  if (voices[0].isPercussion) return true;
@@ -16083,6 +16248,7 @@ StringTablature.prototype.bypass = function (line) {
16083
16248
  return false;
16084
16249
  };
16085
16250
  StringTablature.prototype.setRelative = function (child, relative, first) {
16251
+ //console.log("RENDER StringTablature setRelative")
16086
16252
  switch (child.type) {
16087
16253
  case 'bar':
16088
16254
  relative.pitch = this.bar.pitch;
@@ -16123,8 +16289,9 @@ var _require = __webpack_require__(/*! ../../synth/note-to-midi */ "./src/synth/
16123
16289
  * Note structure for Tabs
16124
16290
  *
16125
16291
  */
16126
- var notes = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
16292
+
16127
16293
  function TabNote(note, clefTranspose) {
16294
+ //console.log("INIT/RENDER TabNote constructor")
16128
16295
  var pitch = noteToMidi(note);
16129
16296
  if (clefTranspose) pitch += clefTranspose;
16130
16297
  var newNote = midiToNote(pitch);
@@ -16206,12 +16373,15 @@ function cloneNote(self) {
16206
16373
  return newTabNote;
16207
16374
  }
16208
16375
  TabNote.prototype.sameNoteAs = function (note) {
16376
+ //console.log("INIT TabNote sameNoteAs")
16209
16377
  return note.pitch === this.pitch;
16210
16378
  };
16211
16379
  TabNote.prototype.isLowerThan = function (note) {
16380
+ //console.log("INIT TabNote isLowerThan")
16212
16381
  return note.pitch > this.pitch;
16213
16382
  };
16214
16383
  TabNote.prototype.checkKeyAccidentals = function (accidentals, measureAccidentals) {
16384
+ //console.log("RENDER TabNote checkKeyAccidentals")
16215
16385
  if (this.isAltered || this.natural) return;
16216
16386
  if (measureAccidentals[this.name.toUpperCase()]) {
16217
16387
  switch (measureAccidentals[this.name.toUpperCase()]) {
@@ -16256,6 +16426,7 @@ TabNote.prototype.checkKeyAccidentals = function (accidentals, measureAccidental
16256
16426
  }
16257
16427
  };
16258
16428
  TabNote.prototype.getAccidentalEquiv = function () {
16429
+ //console.log("TabNote getAccidentalEquiv")
16259
16430
  var cloned = cloneNote(this);
16260
16431
  if (cloned.isSharp || cloned.isKeySharp) {
16261
16432
  cloned = cloned.nextNote();
@@ -16271,14 +16442,17 @@ TabNote.prototype.getAccidentalEquiv = function () {
16271
16442
  return cloned;
16272
16443
  };
16273
16444
  TabNote.prototype.nextNote = function () {
16445
+ //console.log("INIT TabNote nextNote")
16274
16446
  var note = midiToNote(this.pitch + 1 + this.pitchAltered);
16275
16447
  return new TabNote(note);
16276
16448
  };
16277
16449
  TabNote.prototype.prevNote = function () {
16450
+ //console.log("TabNote prevNote")
16278
16451
  var note = midiToNote(this.pitch - 1 + this.pitchAltered);
16279
16452
  return new TabNote(note);
16280
16453
  };
16281
16454
  TabNote.prototype.emitNoAccidentals = function () {
16455
+ //console.log("TabNote emitNoAccidentals")
16282
16456
  var returned = this.name;
16283
16457
  if (this.isLower) {
16284
16458
  returned = returned.toLowerCase();
@@ -16292,6 +16466,7 @@ TabNote.prototype.emitNoAccidentals = function () {
16292
16466
  return returned;
16293
16467
  };
16294
16468
  TabNote.prototype.emit = function () {
16469
+ //console.log("INIT/RENDER TabNote emit")
16295
16470
  var returned = this.name;
16296
16471
  if (this.isSharp || this.isKeySharp) {
16297
16472
  returned = '^' + returned;
@@ -16326,10 +16501,7 @@ TabNote.prototype.emit = function () {
16326
16501
  }
16327
16502
  return returned;
16328
16503
  };
16329
- module.exports = {
16330
- 'TabNote': TabNote,
16331
- 'notes': notes
16332
- };
16504
+ module.exports = TabNote;
16333
16505
 
16334
16506
  /***/ }),
16335
16507
 
@@ -16340,14 +16512,11 @@ module.exports = {
16340
16512
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
16341
16513
 
16342
16514
  var TabNote = __webpack_require__(/*! ./tab-note */ "./src/tablatures/instruments/tab-note.js");
16343
- var notes = TabNote.notes;
16344
- function TabNotes(fromNote, toNote) {
16345
- this.fromN = new TabNote.TabNote(fromNote);
16346
- this.toN = new TabNote.TabNote(toNote);
16347
- }
16348
- TabNotes.prototype.build = function () {
16349
- var fromN = this.fromN;
16350
- var toN = this.toN;
16515
+ var notes = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
16516
+ function tabNotes(fromNote, toNote) {
16517
+ //console.log("INIT TabNotes")
16518
+ var fromN = new TabNote(fromNote);
16519
+ var toN = new TabNote(toNote);
16351
16520
  // check that toN is not lower than fromN
16352
16521
  if (toN.isLowerThan(fromN)) {
16353
16522
  var from = fromN.emit();
@@ -16371,35 +16540,8 @@ TabNotes.prototype.build = function () {
16371
16540
  }
16372
16541
  }
16373
16542
  return buildReturned;
16374
- };
16375
- module.exports = TabNotes;
16376
-
16377
- /***/ }),
16378
-
16379
- /***/ "./src/tablatures/instruments/tab-string-patterns.js":
16380
- /*!***********************************************************!*\
16381
- !*** ./src/tablatures/instruments/tab-string-patterns.js ***!
16382
- \***********************************************************/
16383
- /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
16384
-
16385
- var StringPatterns = __webpack_require__(/*! ./string-patterns */ "./src/tablatures/instruments/string-patterns.js");
16386
- function TabStringPatterns(plugin, defaultTuning) {
16387
- this.tuning = plugin._super.params.tuning;
16388
- if (!this.tuning) {
16389
- this.tuning = defaultTuning;
16390
- }
16391
- plugin.tuning = this.tuning;
16392
- this.strings = new StringPatterns(plugin);
16393
16543
  }
16394
- TabStringPatterns.prototype.notesToNumber = function (notes, graces) {
16395
- var converter = this.strings;
16396
- return converter.notesToNumber(notes, graces);
16397
- };
16398
- TabStringPatterns.prototype.stringToPitch = function (stringNumber) {
16399
- var converter = this.strings;
16400
- return converter.stringToPitch(stringNumber);
16401
- };
16402
- module.exports = TabStringPatterns;
16544
+ module.exports = tabNotes;
16403
16545
 
16404
16546
  /***/ }),
16405
16547
 
@@ -16410,20 +16552,22 @@ module.exports = TabStringPatterns;
16410
16552
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
16411
16553
 
16412
16554
  var StringTablature = __webpack_require__(/*! ./string-tablature */ "./src/tablatures/instruments/string-tablature.js");
16413
- var TabCommon = __webpack_require__(/*! ../tab-common */ "./src/tablatures/tab-common.js");
16414
- var TabRenderer = __webpack_require__(/*! ../tab-renderer */ "./src/tablatures/tab-renderer.js");
16415
- var TabStringPatterns = __webpack_require__(/*! ./tab-string-patterns */ "./src/tablatures/instruments/tab-string-patterns.js");
16555
+ var tabRenderer = __webpack_require__(/*! ../render/tab-renderer */ "./src/tablatures/render/tab-renderer.js");
16556
+ var StringPatterns = __webpack_require__(/*! ./string-patterns */ "./src/tablatures/instruments/string-patterns.js");
16416
16557
 
16417
16558
  /**
16418
16559
  * upon init mainly store provided instances for later usage
16419
16560
  * @param {*} abcTune the parsed tune AST tree
16420
- * @param {*} tuneNumber the parsed tune AST tree
16561
+ * @param {*} tuneNumber the parsed tune AST tree
16421
16562
  * @param {*} params complementary args provided to Tablature Plugin
16422
16563
  */
16423
- Plugin.prototype.init = function (abcTune, tuneNumber, params, staffNumber, tabSettings) {
16424
- var _super = new TabCommon(abcTune, tuneNumber, params);
16564
+ Plugin.prototype.init = function (abcTune, tuneNumber, params, tabSettings) {
16565
+ //console.log("INIT AbcStringTab Plugin.init")
16566
+ this.tune = abcTune;
16567
+ this.params = params;
16568
+ this.tuneNumber = tuneNumber;
16569
+ this.inError = false;
16425
16570
  this.abcTune = abcTune;
16426
- this._super = _super;
16427
16571
  this.linePitch = 3;
16428
16572
  this.nbLines = tabSettings.defaultTuning.length;
16429
16573
  this.isTabBig = tabSettings.isTabBig;
@@ -16432,14 +16576,30 @@ Plugin.prototype.init = function (abcTune, tuneNumber, params, staffNumber, tabS
16432
16576
  this.transpose = params.visualTranspose;
16433
16577
  this.hideTabSymbol = params.hideTabSymbol;
16434
16578
  this.tablature = new StringTablature(this.nbLines, this.linePitch);
16435
- var semantics = new TabStringPatterns(this, tabSettings.defaultTuning);
16436
- this.semantics = semantics;
16579
+ var tuning = params.tuning;
16580
+ if (!tuning) {
16581
+ tuning = tabSettings.defaultTuning;
16582
+ }
16583
+ this.tuning = tuning;
16584
+ this.semantics = new StringPatterns(this);
16585
+ };
16586
+ Plugin.prototype.setError = function (error) {
16587
+ //console.log("Plugin setError")
16588
+ if (error) {
16589
+ this.error = error;
16590
+ this.inError = true;
16591
+ if (this.tune.warnings) {
16592
+ this.tune.warnings.push(error);
16593
+ } else {
16594
+ this.tune.warnings = [error];
16595
+ }
16596
+ }
16437
16597
  };
16438
16598
  Plugin.prototype.render = function (renderer, line, staffIndex) {
16439
- if (this._super.inError) return;
16599
+ //console.log("RENDER AbcStringTab Plugin.render")
16600
+ if (this.inError) return;
16440
16601
  if (this.tablature.bypass(line)) return;
16441
- var rndrer = new TabRenderer(this, renderer, line, staffIndex);
16442
- rndrer.doLayout();
16602
+ tabRenderer(this, renderer, line, staffIndex);
16443
16603
  };
16444
16604
  function Plugin() {}
16445
16605
 
@@ -16456,17 +16616,17 @@ module.exports = AbcStringTab;
16456
16616
 
16457
16617
  /***/ }),
16458
16618
 
16459
- /***/ "./src/tablatures/tab-absolute-elements.js":
16460
- /*!*************************************************!*\
16461
- !*** ./src/tablatures/tab-absolute-elements.js ***!
16462
- \*************************************************/
16619
+ /***/ "./src/tablatures/render/tab-absolute-elements.js":
16620
+ /*!********************************************************!*\
16621
+ !*** ./src/tablatures/render/tab-absolute-elements.js ***!
16622
+ \********************************************************/
16463
16623
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
16464
16624
 
16465
16625
  /**
16466
16626
  * Tablature Absolute elements factory
16467
16627
  */
16468
- var AbsoluteElement = __webpack_require__(/*! ../write/creation/elements/absolute-element */ "./src/write/creation/elements/absolute-element.js");
16469
- var RelativeElement = __webpack_require__(/*! ../write/creation/elements/relative-element */ "./src/write/creation/elements/relative-element.js");
16628
+ var AbsoluteElement = __webpack_require__(/*! ../../write/creation/elements/absolute-element */ "./src/write/creation/elements/absolute-element.js");
16629
+ var RelativeElement = __webpack_require__(/*! ../../write/creation/elements/relative-element */ "./src/write/creation/elements/relative-element.js");
16470
16630
  function isObject(a) {
16471
16631
  return a != null && a.constructor === Object;
16472
16632
  }
@@ -16553,6 +16713,7 @@ function lyricsDim(abs) {
16553
16713
  return null;
16554
16714
  }
16555
16715
  function TabAbsoluteElements() {
16716
+ //console.log("RENDER TabAbsoluteElements constructor")
16556
16717
  this.accidentals = null;
16557
16718
  }
16558
16719
  function getInitialStaffSize(staffGroup) {
@@ -16611,7 +16772,7 @@ function graceInRest(absElem) {
16611
16772
  function convertToNumber(plugin, pitches, graceNotes) {
16612
16773
  var tabPos = plugin.semantics.notesToNumber(pitches, graceNotes);
16613
16774
  if (tabPos.error) {
16614
- plugin._super.setError(tabPos.error);
16775
+ plugin.setError(tabPos.error);
16615
16776
  return tabPos; // give up on error here
16616
16777
  }
16617
16778
 
@@ -16644,6 +16805,7 @@ function buildGraceRelativesForRest(plugin, abs, absChild, graceNotes, tabVoice)
16644
16805
  * @param {*} staffAbsolute
16645
16806
  */
16646
16807
  TabAbsoluteElements.prototype.build = function (plugin, staffAbsolute, tabVoice, voiceIndex, staffIndex, keySig, tabVoiceIndex) {
16808
+ //console.log("RENDER TabAbsoluteElements build")
16647
16809
  var staffSize = getInitialStaffSize(staffAbsolute);
16648
16810
  var source = staffAbsolute[staffIndex + voiceIndex];
16649
16811
  var dest = staffAbsolute[tabVoiceIndex];
@@ -16664,17 +16826,17 @@ TabAbsoluteElements.prototype.build = function (plugin, staffAbsolute, tabVoice,
16664
16826
  // }
16665
16827
  if (absChild.isClef) {
16666
16828
  dest.children.push(buildTabAbsolute(plugin, absX, relX));
16667
- if (absChild.abcelem.type.indexOf('-8') >= 0) plugin.semantics.strings.clefTranspose = -12;
16668
- if (absChild.abcelem.type.indexOf('+8') >= 0) plugin.semantics.strings.clefTranspose = 12;
16829
+ if (absChild.abcelem.type.indexOf('-8') >= 0) plugin.semantics.clefTranspose = -12;
16830
+ if (absChild.abcelem.type.indexOf('+8') >= 0) plugin.semantics.clefTranspose = 12;
16669
16831
  }
16670
16832
  switch (absChild.type) {
16671
16833
  case 'staff-extra key-signature':
16672
16834
  // refresh key accidentals
16673
16835
  this.accidentals = absChild.abcelem.accidentals;
16674
- plugin.semantics.strings.accidentals = this.accidentals;
16836
+ plugin.semantics.accidentals = this.accidentals;
16675
16837
  break;
16676
16838
  case 'bar':
16677
- plugin.semantics.strings.measureAccidentals = {};
16839
+ plugin.semantics.measureAccidentals = {};
16678
16840
  var lastBar = false;
16679
16841
  if (ii === source.children.length - 1) {
16680
16842
  // used for final line bar drawing
@@ -16774,50 +16936,16 @@ module.exports = TabAbsoluteElements;
16774
16936
 
16775
16937
  /***/ }),
16776
16938
 
16777
- /***/ "./src/tablatures/tab-common.js":
16778
- /*!**************************************!*\
16779
- !*** ./src/tablatures/tab-common.js ***!
16780
- \**************************************/
16781
- /***/ (function(module) {
16782
-
16783
- /**
16784
- *
16785
- * Common Class/Method available for all instruments
16786
- *
16787
- */
16788
-
16789
- function TabCommon(abcTune, tuneNumber, params) {
16790
- this.tune = abcTune;
16791
- this.params = params;
16792
- this.tuneNumber = tuneNumber;
16793
- this.inError = false;
16794
- }
16795
- TabCommon.prototype.setError = function (error) {
16796
- var tune = this.tune;
16797
- if (error) {
16798
- this.error = error;
16799
- this.inError = true;
16800
- if (tune.warnings) {
16801
- tune.warnings.push(error);
16802
- } else {
16803
- tune.warnings = [error];
16804
- }
16805
- }
16806
- };
16807
- module.exports = TabCommon;
16808
-
16809
- /***/ }),
16810
-
16811
- /***/ "./src/tablatures/tab-renderer.js":
16812
- /*!****************************************!*\
16813
- !*** ./src/tablatures/tab-renderer.js ***!
16814
- \****************************************/
16939
+ /***/ "./src/tablatures/render/tab-renderer.js":
16940
+ /*!***********************************************!*\
16941
+ !*** ./src/tablatures/render/tab-renderer.js ***!
16942
+ \***********************************************/
16815
16943
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
16816
16944
 
16817
16945
  /* eslint-disable no-debugger */
16818
- var VoiceElement = __webpack_require__(/*! ../write/creation/elements/voice-element */ "./src/write/creation/elements/voice-element.js");
16819
- var TabAbsoluteElements = __webpack_require__(/*! ./tab-absolute-elements */ "./src/tablatures/tab-absolute-elements.js");
16820
- var spacing = __webpack_require__(/*! ../write/helpers/spacing */ "./src/write/helpers/spacing.js");
16946
+ var VoiceElement = __webpack_require__(/*! ../../write/creation/elements/voice-element */ "./src/write/creation/elements/voice-element.js");
16947
+ var TabAbsoluteElements = __webpack_require__(/*! ./tab-absolute-elements */ "./src/tablatures/render/tab-absolute-elements.js");
16948
+ var spacing = __webpack_require__(/*! ../../write/helpers/spacing */ "./src/write/helpers/spacing.js");
16821
16949
  function initSpecialY() {
16822
16950
  return {
16823
16951
  tempoHeightAbove: 0,
@@ -16846,12 +16974,11 @@ function getLyricHeight(voice) {
16846
16974
  return maxLyricHeight; // add spacing
16847
16975
  }
16848
16976
 
16849
- function buildTabName(self, dest) {
16850
- var stringSemantics = self.plugin.semantics.strings;
16851
- var controller = self.renderer.controller;
16852
- var textSize = controller.getTextSize;
16853
- var tabName = stringSemantics.tabInfos(self.plugin);
16854
- var suppress = stringSemantics.suppress(self.plugin);
16977
+ function buildTabName(plugin, renderer, dest) {
16978
+ var stringSemantics = plugin.semantics;
16979
+ var textSize = renderer.controller.getTextSize;
16980
+ var tabName = stringSemantics.tabInfos(plugin);
16981
+ var suppress = stringSemantics.suppress(plugin);
16855
16982
  var doDraw = true;
16856
16983
  if (suppress) {
16857
16984
  doDraw = false;
@@ -16869,27 +16996,6 @@ function buildTabName(self, dest) {
16869
16996
  }
16870
16997
  return 0;
16871
16998
  }
16872
-
16873
- /**
16874
- * Laying out tabs
16875
- * @param {*} renderer
16876
- * @param {*} line
16877
- * @param {*} staffIndex
16878
- * @param {*} tablatureLayout
16879
- */
16880
- function TabRenderer(plugin, renderer, line, staffIndex) {
16881
- this.renderer = renderer;
16882
- this.plugin = plugin;
16883
- this.line = line;
16884
- this.absolutes = new TabAbsoluteElements();
16885
- this.staffIndex = staffIndex;
16886
- this.tabStaff = {
16887
- clef: {
16888
- type: 'TAB'
16889
- }
16890
- };
16891
- this.tabSize = plugin.linePitch * plugin.nbLines;
16892
- }
16893
16999
  function islastTabInStaff(index, staffGroup) {
16894
17000
  if (staffGroup[index].isTabStaff) {
16895
17001
  if (index === staffGroup.length - 1) return true;
@@ -16935,8 +17041,7 @@ function isMultiVoiceSingleStaff(staffs, parent) {
16935
17041
  }
16936
17042
  return false;
16937
17043
  }
16938
- function getNextTabPos(self, staffGroup) {
16939
- var tabIndex = self.staffIndex;
17044
+ function getNextTabPos(tabIndex, staffGroup) {
16940
17045
  var startIndex = 0;
16941
17046
  var handledVoices = 0;
16942
17047
  var inProgress = true;
@@ -16985,30 +17090,38 @@ function checkVoiceKeySig(voices, ii) {
16985
17090
  }
16986
17091
  return voices[ii - 1].children[0];
16987
17092
  }
16988
- TabRenderer.prototype.doLayout = function () {
16989
- var staffs = this.line.staff;
17093
+ function tabRenderer(plugin, renderer, line, staffIndex) {
17094
+ //console.log("RENDER tabRenderer")
17095
+ var absolutes = new TabAbsoluteElements();
17096
+ var tabStaff = {
17097
+ clef: {
17098
+ type: 'TAB'
17099
+ }
17100
+ };
17101
+ var tabSize = plugin.linePitch * plugin.nbLines;
17102
+ var staffs = line.staff;
16990
17103
  if (staffs) {
16991
17104
  // give up on staffline=0 in key
16992
17105
  var firstStaff = staffs[0];
16993
17106
  if (firstStaff) {
16994
17107
  if (firstStaff.clef) {
16995
17108
  if (firstStaff.clef.stafflines == 0) {
16996
- this.plugin._super.setError("No tablatures when stafflines=0");
17109
+ plugin.setError("No tablatures when stafflines=0");
16997
17110
  return;
16998
17111
  }
16999
17112
  }
17000
17113
  }
17001
- staffs.splice(staffs.length, 0, this.tabStaff);
17114
+ staffs.splice(staffs.length, 0, tabStaff);
17002
17115
  }
17003
- var staffGroup = this.line.staffGroup;
17116
+ var staffGroup = line.staffGroup;
17004
17117
  var voices = staffGroup.voices;
17005
17118
  var firstVoice = voices[0];
17006
17119
  // take lyrics into account if any
17007
17120
  var lyricsHeight = getLyricHeight(firstVoice);
17008
17121
  var padd = 3;
17009
- var prevIndex = this.staffIndex;
17122
+ var prevIndex = staffIndex;
17010
17123
  var previousStaff = staffGroup.staffs[prevIndex];
17011
- var tabTop = this.tabSize + padd - previousStaff.bottom - lyricsHeight;
17124
+ var tabTop = tabSize + padd - previousStaff.bottom - lyricsHeight;
17012
17125
  if (previousStaff.isTabStaff) {
17013
17126
  tabTop = previousStaff.top;
17014
17127
  }
@@ -17016,44 +17129,44 @@ TabRenderer.prototype.doLayout = function () {
17016
17129
  bottom: -1,
17017
17130
  isTabStaff: true,
17018
17131
  specialY: initSpecialY(),
17019
- lines: this.plugin.nbLines,
17020
- linePitch: this.plugin.linePitch,
17132
+ lines: plugin.nbLines,
17133
+ linePitch: plugin.linePitch,
17021
17134
  dy: 0.15,
17022
17135
  top: tabTop
17023
17136
  };
17024
- var nextTabPos = getNextTabPos(this, staffGroup.staffs);
17137
+ var nextTabPos = getNextTabPos(staffIndex, staffGroup.staffs);
17025
17138
  if (nextTabPos === -1) return;
17026
17139
  staffGroupInfos.parentIndex = nextTabPos - 1;
17027
17140
  staffGroup.staffs.splice(nextTabPos, 0, staffGroupInfos);
17028
17141
  // staffGroup.staffs.push(staffGroupInfos);
17029
- staffGroup.height += this.tabSize + padd;
17142
+ staffGroup.height += tabSize + padd;
17030
17143
  var parentStaff = getLastStaff(staffGroup.staffs, nextTabPos);
17031
17144
  var nbVoices = 1;
17032
17145
  if (isMultiVoiceSingleStaff(staffGroup.staffs, parentStaff)) {
17033
17146
  nbVoices = parentStaff.voices.length;
17034
17147
  }
17035
17148
  // build from staff
17036
- this.tabStaff.voices = [];
17149
+ tabStaff.voices = [];
17037
17150
  for (var ii = 0; ii < nbVoices; ii++) {
17038
17151
  var tabVoice = new VoiceElement(0, 0);
17039
17152
  if (ii > 0) tabVoice.duplicate = true;
17040
- var nameHeight = buildTabName(this, tabVoice) / spacing.STEP;
17153
+ var nameHeight = buildTabName(plugin, renderer, tabVoice) / spacing.STEP;
17041
17154
  nameHeight = Math.max(nameHeight, 1); // If there is no label for the tab line, then there needs to be a little padding
17042
17155
  // This was pushing down the top staff by the tab label height
17043
- //staffGroup.staffs[this.staffIndex].top += nameHeight;
17044
- staffGroup.staffs[this.staffIndex].top += 1;
17156
+ //staffGroup.staffs[staffIndex].top += nameHeight;
17157
+ staffGroup.staffs[staffIndex].top += 1;
17045
17158
  staffGroup.height += nameHeight;
17046
17159
  tabVoice.staff = staffGroupInfos;
17047
17160
  var tabVoiceIndex = voices.length;
17048
17161
  voices.splice(voices.length, 0, tabVoice);
17049
- var keySig = checkVoiceKeySig(voices, ii + this.staffIndex);
17050
- this.tabStaff.voices[ii] = [];
17051
- this.absolutes.build(this.plugin, voices, this.tabStaff.voices[ii], ii, this.staffIndex, keySig, tabVoiceIndex);
17162
+ var keySig = checkVoiceKeySig(voices, ii + staffIndex);
17163
+ tabStaff.voices[ii] = [];
17164
+ absolutes.build(plugin, voices, tabStaff.voices[ii], ii, staffIndex, keySig, tabVoiceIndex);
17052
17165
  }
17053
17166
  linkStaffAndTabs(staffGroup.staffs); // crossreference tabs and staff
17054
- };
17167
+ }
17055
17168
 
17056
- module.exports = TabRenderer;
17169
+ module.exports = tabRenderer;
17057
17170
 
17058
17171
  /***/ }),
17059
17172
 
@@ -17987,7 +18100,7 @@ AbstractEngraver.prototype.createNote = function (elem, nostem, isSingleLineStaf
17987
18100
  if (elem.decoration) {
17988
18101
  // TODO-PER: nostem is true if this is beamed. In that case we don't know where to place the decoration yet so just make a guess. This should be refactored to not place decorations until after the beams are determined.
17989
18102
  // This should probably be combined with moveDecorations()
17990
- var bottom = nostem ? Math.min(-3, abselem.bottom - 6) : abselem.bottom;
18103
+ var bottom = nostem && dir !== 'up' ? Math.min(-3, abselem.bottom - 6) : abselem.bottom;
17991
18104
  this.decoration.createDecoration(voice, elem.decoration, abselem.top, notehead ? notehead.w : 0, abselem, roomtaken, dir, bottom, elem.positioning, this.hasVocals, this.accentAbove);
17992
18105
  }
17993
18106
  if (elem.barNumber) {
@@ -23516,7 +23629,7 @@ var Classes = __webpack_require__(/*! ./helpers/classes */ "./src/write/helpers/
23516
23629
  var GetFontAndAttr = __webpack_require__(/*! ./helpers/get-font-and-attr */ "./src/write/helpers/get-font-and-attr.js");
23517
23630
  var GetTextSize = __webpack_require__(/*! ./helpers/get-text-size */ "./src/write/helpers/get-text-size.js");
23518
23631
  var draw = __webpack_require__(/*! ./draw/draw */ "./src/write/draw/draw.js");
23519
- var tablatures = __webpack_require__(/*! ../api/abc_tablatures */ "./src/api/abc_tablatures.js");
23632
+ var tablatures = __webpack_require__(/*! ../tablatures/abc_tablatures */ "./src/tablatures/abc_tablatures.js");
23520
23633
  var findSelectableElement = __webpack_require__(/*! ./interactive/find-selectable-element */ "./src/write/interactive/find-selectable-element.js");
23521
23634
 
23522
23635
  /**
@@ -26376,7 +26489,7 @@ module.exports = Svg;
26376
26489
  \********************/
26377
26490
  /***/ (function(module) {
26378
26491
 
26379
- var version = '6.4.0';
26492
+ var version = '6.4.2';
26380
26493
  module.exports = version;
26381
26494
 
26382
26495
  /***/ })