abcjs 6.4.1 → 6.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE.md +1 -1
  2. package/RELEASE.md +46 -0
  3. package/dist/abcjs-basic-min.js +2 -2
  4. package/dist/abcjs-basic-min.js.LICENSE +1 -1
  5. package/dist/abcjs-basic.js +1271 -1179
  6. package/dist/abcjs-basic.js.map +1 -1
  7. package/dist/abcjs-plugin-min.js +2 -2
  8. package/dist/abcjs-plugin-min.js.LICENSE +1 -1
  9. package/index.js +1 -1
  10. package/license.js +1 -1
  11. package/package.json +1 -1
  12. package/plugin.js +1 -1
  13. package/src/api/abc_tunebook.js +1 -2
  14. package/src/api/abc_tunebook_svg.js +0 -1
  15. package/src/data/abc_tune.js +2 -0
  16. package/src/midi/abc_midi_create.js +22 -7
  17. package/src/parse/abc_common.js +3 -11
  18. package/src/parse/abc_parse.js +1 -1
  19. package/src/parse/abc_parse_directive.js +44 -3
  20. package/src/parse/abc_parse_header.js +6 -4
  21. package/src/parse/abc_parse_key_voice.js +10 -6
  22. package/src/parse/abc_parse_music.js +54 -22
  23. package/src/parse/tune-builder.js +675 -643
  24. package/src/synth/abc_midi_flattener.js +3 -1
  25. package/src/synth/abc_midi_sequencer.js +18 -3
  26. package/src/synth/chord-track.js +90 -18
  27. package/src/synth/create-synth-control.js +1 -2
  28. package/src/tablatures/abc_tablatures.js +184 -0
  29. package/src/tablatures/instruments/string-patterns.js +266 -268
  30. package/src/tablatures/instruments/string-tablature.js +38 -35
  31. package/src/tablatures/instruments/tab-note.js +186 -181
  32. package/src/tablatures/instruments/tab-notes.js +30 -35
  33. package/src/tablatures/instruments/tab-string.js +43 -25
  34. package/src/tablatures/render/tab-absolute-elements.js +303 -0
  35. package/src/tablatures/render/tab-renderer.js +244 -0
  36. package/src/test/abc_parser_lint.js +2 -3
  37. package/src/write/creation/abstract-engraver.js +1 -1
  38. package/src/write/creation/elements/tie-element.js +26 -0
  39. package/src/write/engraver-controller.js +1 -1
  40. package/src/write/layout/set-upper-and-lower-elements.js +8 -0
  41. package/test.js +1 -1
  42. package/types/index.d.ts +2 -2
  43. package/version.js +1 -1
  44. package/src/api/abc_tablatures.js +0 -184
  45. package/src/tablatures/instruments/tab-string-patterns.js +0 -23
  46. package/src/tablatures/tab-absolute-elements.js +0 -301
  47. package/src/tablatures/tab-common.js +0 -29
  48. package/src/tablatures/tab-renderer.js +0 -259
@@ -18,7 +18,7 @@ return /******/ (function() { // webpackBootstrap
18
18
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
19
19
 
20
20
  /**!
21
- Copyright (c) 2009-2023 Paul Rosen and Gregory Dyke
21
+ Copyright (c) 2009-2024 Paul Rosen and Gregory Dyke
22
22
 
23
23
  Permission is hereby granted, free of charge, to any person obtaining a copy
24
24
  of this software and associated documentation files (the "Software"), to deal
@@ -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();
@@ -817,7 +617,6 @@ var tunebook = {};
817
617
  }
818
618
  }
819
619
  }
820
-
821
620
  return staves;
822
621
  }
823
622
  function measuresParser(staff, tune) {
@@ -955,8 +754,6 @@ var Tune = __webpack_require__(/*! ../data/abc_tune */ "./src/data/abc_tune.js")
955
754
  var EngraverController = __webpack_require__(/*! ../write/engraver-controller */ "./src/write/engraver-controller.js");
956
755
  var Parse = __webpack_require__(/*! ../parse/abc_parse */ "./src/parse/abc_parse.js");
957
756
  var wrap = __webpack_require__(/*! ../parse/wrap_lines */ "./src/parse/wrap_lines.js");
958
- // var tablatures = require('./abc_tablatures');
959
-
960
757
  var resizeDivs = {};
961
758
  function resizeOuter() {
962
759
  var width = window.innerWidth;
@@ -1543,7 +1340,6 @@ var Tune = function Tune() {
1543
1340
  };
1544
1341
  return this.meter; // TODO-PER: is this saved value used anywhere? A get function shouldn't change state.
1545
1342
  };
1546
-
1547
1343
  this.getKeySignature = function () {
1548
1344
  for (var i = 0; i < this.lines.length; i++) {
1549
1345
  var line = this.lines[i];
@@ -1698,7 +1494,6 @@ var Tune = function Tune() {
1698
1494
  // isTiedState = voiceTimeMilliseconds;
1699
1495
  }
1700
1496
  }
1701
-
1702
1497
  return {
1703
1498
  isTiedState: isTiedState,
1704
1499
  duration: realDuration / timeDivider,
@@ -1869,6 +1664,7 @@ var Tune = function Tune() {
1869
1664
  }
1870
1665
  this.getBpm = function (tempo) {
1871
1666
  var bpm;
1667
+ if (!tempo) tempo = this.metaText ? this.metaText.tempo : null;
1872
1668
  if (tempo) {
1873
1669
  bpm = tempo.bpm;
1874
1670
  var beatLength = this.getBeatLength();
@@ -2630,9 +2426,21 @@ var create;
2630
2426
  if (title && title.length > 128) title = title.substring(0, 124) + '...';
2631
2427
  var key = abcTune.getKeySignature();
2632
2428
  var time = abcTune.getMeterFraction();
2633
- var beatsPerSecond = commands.tempo / 60;
2429
+
2430
+ // MAE 7 July 2024 - Fix for */8 meter tempos
2431
+ var tempo = commands.tempo;
2432
+ var beatsPerSecond = tempo / 60;
2433
+
2434
+ // Fix tempo for */8 meters
2435
+ if (time.den == 8) {
2436
+ // Compute the tempo based on the actual milliseconds per measure, scaled by the number of eight notes and halved to get tempo in bpm.
2437
+ var msPerMeasure = abcTune.millisecondsPerMeasure();
2438
+ tempo = 60000 / (msPerMeasure / time.num) / 2;
2439
+ beatsPerSecond = tempo / 60;
2440
+ }
2441
+
2634
2442
  //var beatLength = abcTune.getBeatLength();
2635
- midi.setGlobalInfo(commands.tempo, title, key, time);
2443
+ midi.setGlobalInfo(tempo, title, key, time);
2636
2444
  for (var i = 0; i < commands.tracks.length; i++) {
2637
2445
  midi.startTrack();
2638
2446
  var notePlacement = {};
@@ -2718,27 +2526,20 @@ module.exports = create;
2718
2526
  \*********************************/
2719
2527
  /***/ (function(module) {
2720
2528
 
2721
- // abc_parse.js: parses a string representing ABC Music Notation into a usable internal structure.
2529
+ // abc_common.js: Some common utility functions.
2722
2530
 
2723
2531
  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
2532
  parseCommon.cloneArray = function (source) {
2732
2533
  var destination = [];
2733
2534
  for (var i = 0; i < source.length; i++) {
2734
- destination.push(parseCommon.clone(source[i]));
2535
+ destination.push(Object.assign({}, source[i]));
2735
2536
  }
2736
2537
  return destination;
2737
2538
  };
2738
2539
  parseCommon.cloneHashOfHash = function (source) {
2739
2540
  var destination = {};
2740
2541
  for (var property in source) {
2741
- if (source.hasOwnProperty(property)) destination[property] = parseCommon.clone(source[property]);
2542
+ if (source.hasOwnProperty(property)) destination[property] = Object.assign({}, source[property]);
2742
2543
  }
2743
2544
  return destination;
2744
2545
  };
@@ -3230,7 +3031,7 @@ var Parse = function Parse() {
3230
3031
  });
3231
3032
  for (var i = 0; i < nextVoice.length; i++) {
3232
3033
  var element = nextVoice[i];
3233
- var hint = parseCommon.clone(element);
3034
+ var hint = Object.assign({}, element);
3234
3035
  voice.push(hint);
3235
3036
  if (element.el_type === 'bar') return;
3236
3037
  }
@@ -3318,7 +3119,6 @@ var Parse = function Parse() {
3318
3119
  multilineVars.lineBreaks = switches.lineBreaks;
3319
3120
  //multilineVars.continueall = true;
3320
3121
  }
3321
-
3322
3122
  header.reset(tokenizer, warn, multilineVars, tune);
3323
3123
  try {
3324
3124
  if (switches.format) {
@@ -3408,7 +3208,6 @@ var bookParser = function bookParser(book) {
3408
3208
  });
3409
3209
  pos += tune.length + 1; // We also lost a newline when splitting, so count that.
3410
3210
  });
3411
-
3412
3211
  if (tunes.length > 1 && !parseCommon.startsWith(tunes[0].abc, 'X:')) {
3413
3212
  // If there is only one tune, the X: might be missing, otherwise assume the top of the file is "intertune"
3414
3213
  // There could be file-wide directives in this, if so, we need to insert it into each tune. We can probably get away with
@@ -4157,7 +3956,7 @@ var parseDirective = {};
4157
3956
  };
4158
3957
  var midiCmdParam0 = ["nobarlines", "barlines", "beataccents", "nobeataccents", "droneon", "droneoff", "drumon", "drumoff", "fermatafixed", "fermataproportional", "gchordon", "gchordoff", "controlcombo", "temperamentnormal", "noportamento"];
4159
3958
  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"];
3959
+ var midiCmdParam1Integer = ["bassvol", "chordvol", "c", "channel", "beatmod", "deltaloudness", "drumbars", "gracedivider", "makechordchannels", "randomchordattack", "chordattack", "stressmodel", "transpose", "rtranspose", "vol", "volinc", "gchordbars"];
4161
3960
  var midiCmdParam1Integer1OptionalInteger = ["program"];
4162
3961
  var midiCmdParam2Integer = ["ratio", "snt", "bendvelocity", "pitchbend", "control", "temperamentlinear"];
4163
3962
  var midiCmdParam4Integer = ["beat"];
@@ -4165,6 +3964,7 @@ var parseDirective = {};
4165
3964
  var midiCmdParam1String1Integer = ["portamento"];
4166
3965
  var midiCmdParamFraction = ["expand", "grace", "trim"];
4167
3966
  var midiCmdParam1StringVariableIntegers = ["drum", "chordname"];
3967
+ var midiCmdParam1Integer1OptionalString = ["bassprog", "chordprog"];
4168
3968
  var parseMidiCommand = function parseMidiCommand(midi, tune, restOfString) {
4169
3969
  var midi_cmd = midi.shift().token;
4170
3970
  var midi_params = [];
@@ -4256,6 +4056,34 @@ var parseDirective = {};
4256
4056
  midi_params.push(p.intt);
4257
4057
  }
4258
4058
  }
4059
+ } else if (midiCmdParam1Integer1OptionalString.indexOf(midi_cmd) >= 0) {
4060
+ // ONE INT PARAMETER, ONE OPTIONAL string
4061
+ 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 {
4062
+ midi_params.push(midi[0].intt);
4063
+
4064
+ // Currently only bassprog and chordprog with optional octave shifts use this path
4065
+ if (midi.length === 2) {
4066
+ var cmd = midi[1].token;
4067
+ if (cmd.indexOf("octave=") != -1) {
4068
+ cmd = cmd.replace("octave=", "");
4069
+ cmd = parseInt(cmd);
4070
+ if (!isNaN(cmd)) {
4071
+ // Limit range from -1 to 3 octaves
4072
+ if (cmd < -1) {
4073
+ warn("Expected octave= in MIDI " + midi_cmd + ' to be >= -1 (recv:' + cmd + ')');
4074
+ cmd = -1;
4075
+ }
4076
+ if (cmd > 3) {
4077
+ warn("Expected octave= in MIDI " + midi_cmd + ' to be <= 3 (recv:' + cmd + ')');
4078
+ cmd = 3;
4079
+ }
4080
+ midi_params.push(cmd);
4081
+ } else warn("Expected octave value in MIDI" + midi_cmd);
4082
+ } else {
4083
+ warn("Expected octave= in MIDI" + midi_cmd);
4084
+ }
4085
+ }
4086
+ }
4259
4087
  }
4260
4088
  if (tuneBuilder.hasBeginMusic()) tuneBuilder.appendElement('midi', -1, -1, {
4261
4089
  cmd: midi_cmd,
@@ -4952,7 +4780,6 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
4952
4780
  }
4953
4781
  return ret; // just to suppress warning
4954
4782
  };
4955
-
4956
4783
  var parseFraction = function parseFraction() {
4957
4784
  // handles this much: parseNum slash decimal
4958
4785
  var ret = parseNum();
@@ -4983,7 +4810,6 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
4983
4810
  //var tok = tokens.shift();
4984
4811
  //if (tok.token !== '+') throw "Extra characters in M: line";
4985
4812
  }
4986
-
4987
4813
  if (multilineVars.havent_set_length === true) {
4988
4814
  multilineVars.default_length = totalLength < 0.75 ? 0.0625 : 0.125;
4989
4815
  multilineVars.havent_set_length = false;
@@ -5201,6 +5027,7 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
5201
5027
  }
5202
5028
  };
5203
5029
  this.letter_to_inline_header = function (line, i, startLine) {
5030
+ var needsNewLine = false;
5204
5031
  var ws = tokenizer.eatWhiteSpace(line, i);
5205
5032
  i += ws;
5206
5033
  if (line.length >= i + 5 && line[i] === '[' && line[i + 2] === ':') {
@@ -5247,9 +5074,9 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
5247
5074
  break;
5248
5075
  case "[V:":
5249
5076
  if (e > 0) {
5250
- parseKeyVoice.parseVoice(line, i + 3, e);
5077
+ needsNewLine = parseKeyVoice.parseVoice(line, i + 3, e);
5251
5078
  //startNewLine();
5252
- return [e - i + 1 + ws, line[i + 1], line.substring(i + 3, e)];
5079
+ return [e - i + 1 + ws, line[i + 1], line.substring(i + 3, e), needsNewLine];
5253
5080
  }
5254
5081
  break;
5255
5082
  case "[r:":
@@ -5258,10 +5085,10 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
5258
5085
  // TODO: complain about unhandled header
5259
5086
  }
5260
5087
  }
5261
-
5262
5088
  return [0];
5263
5089
  };
5264
5090
  this.letter_to_body_header = function (line, i) {
5091
+ var needsNewLine = false;
5265
5092
  if (line.length >= i + 3) {
5266
5093
  switch (line.substring(i, i + 2)) {
5267
5094
  case "I:":
@@ -5292,14 +5119,13 @@ var ParseHeader = function ParseHeader(tokenizer, warn, multilineVars, tune, tun
5292
5119
  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
5120
  return [e, line[i], parseCommon.strip(line.substring(i + 2))];
5294
5121
  case "V:":
5295
- parseKeyVoice.parseVoice(line, i + 2, line.length);
5122
+ needsNewLine = parseKeyVoice.parseVoice(line, i + 2, line.length);
5296
5123
  // startNewLine();
5297
- return [line.length, line[i], parseCommon.strip(line.substring(i + 2))];
5124
+ return [line.length, line[i], parseCommon.strip(line.substring(i + 2)), needsNewLine];
5298
5125
  default:
5299
5126
  // TODO: complain about unhandled header
5300
5127
  }
5301
5128
  }
5302
-
5303
5129
  return [0];
5304
5130
  };
5305
5131
  var metaTextHeaders = {
@@ -5432,7 +5258,6 @@ module.exports = ParseHeader;
5432
5258
  \******************************************/
5433
5259
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
5434
5260
 
5435
- var parseCommon = __webpack_require__(/*! ./abc_common */ "./src/parse/abc_common.js");
5436
5261
  var parseDirective = __webpack_require__(/*! ./abc_parse_directive */ "./src/parse/abc_parse_directive.js");
5437
5262
  var transpose = __webpack_require__(/*! ./abc_transpose */ "./src/parse/abc_transpose.js");
5438
5263
  var parseKeyVoice = {};
@@ -5683,7 +5508,7 @@ var parseKeyVoice = {};
5683
5508
  mode: key.mode
5684
5509
  };
5685
5510
  key.accidentals.forEach(function (k) {
5686
- ret.accidentals.push(parseCommon.clone(k));
5511
+ ret.accidentals.push(Object.assign({}, k));
5687
5512
  });
5688
5513
  return ret;
5689
5514
  };
@@ -5749,7 +5574,7 @@ var parseKeyVoice = {};
5749
5574
  }
5750
5575
  };
5751
5576
  parseKeyVoice.fixKey = function (clef, key) {
5752
- var fixedKey = parseCommon.clone(key);
5577
+ var fixedKey = Object.assign({}, key);
5753
5578
  parseKeyVoice.addPosToKey(clef, fixedKey);
5754
5579
  return fixedKey;
5755
5580
  };
@@ -5767,7 +5592,6 @@ var parseKeyVoice = {};
5767
5592
  str: str.substring(i)
5768
5593
  }; // We get the note in the middle of the staff. We want the note that appears as the first ledger line below the staff.
5769
5594
  };
5770
-
5771
5595
  var normalizeAccidentals = function normalizeAccidentals(accs) {
5772
5596
  for (var i = 0; i < accs.length; i++) {
5773
5597
  if (accs[i].note === 'b') accs[i].note = 'B';else if (accs[i].note === 'a') accs[i].note = 'A';else if (accs[i].note === 'F') accs[i].note = 'f';else if (accs[i].note === 'E') accs[i].note = 'e';else if (accs[i].note === 'D') accs[i].note = 'd';else if (accs[i].note === 'C') accs[i].note = 'c';else if (accs[i].note === 'G' && accs[i].acc === 'sharp') accs[i].note = 'g';else if (accs[i].note === 'g' && accs[i].acc === 'flat') accs[i].note = 'G';
@@ -6180,8 +6004,12 @@ var parseKeyVoice = {};
6180
6004
  return ret;
6181
6005
  };
6182
6006
  var setCurrentVoice = function setCurrentVoice(id) {
6183
- multilineVars.currentVoice = multilineVars.voices[id];
6184
- tuneBuilder.setCurrentVoice(multilineVars.currentVoice.staffNum, multilineVars.currentVoice.index);
6007
+ var currentVoice = multilineVars.voices[id];
6008
+ if (multilineVars.currentVoice) {
6009
+ if (multilineVars.currentVoice.index === currentVoice.index && multilineVars.currentVoice.staffNum === currentVoice.staffNum) return; // there was no change so don't reset it.
6010
+ }
6011
+ multilineVars.currentVoice = currentVoice;
6012
+ return tuneBuilder.setCurrentVoice(currentVoice.staffNum, currentVoice.index, id);
6185
6013
  };
6186
6014
  parseKeyVoice.parseVoice = function (line, i, e) {
6187
6015
  //First truncate the string to the first non-space character after V: through either the
@@ -6395,7 +6223,6 @@ var parseKeyVoice = {};
6395
6223
  // console.log("parse voice", token, tune.metaText.title);
6396
6224
  }
6397
6225
  }
6398
-
6399
6226
  start += tokenizer.eatWhiteSpace(line, start);
6400
6227
  }
6401
6228
 
@@ -6433,7 +6260,7 @@ var parseKeyVoice = {};
6433
6260
  if (staffInfo.subname) {
6434
6261
  if (s.subname) s.subname.push(staffInfo.subname);else s.subname = [staffInfo.subname];
6435
6262
  }
6436
- setCurrentVoice(id);
6263
+ return setCurrentVoice(id);
6437
6264
  };
6438
6265
  })();
6439
6266
  module.exports = parseKeyVoice;
@@ -6446,7 +6273,6 @@ module.exports = parseKeyVoice;
6446
6273
  \**************************************/
6447
6274
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
6448
6275
 
6449
- var parseCommon = __webpack_require__(/*! ./abc_common */ "./src/parse/abc_common.js");
6450
6276
  var parseKeyVoice = __webpack_require__(/*! ./abc_parse_key_voice */ "./src/parse/abc_parse_key_voice.js");
6451
6277
  var transpose = __webpack_require__(/*! ./abc_transpose */ "./src/parse/abc_transpose.js");
6452
6278
  var tokenizer;
@@ -6571,7 +6397,6 @@ MusicParser.prototype.parseMusic = function (line) {
6571
6397
  // delayStartNewLine = true;
6572
6398
  // TODO-PER: Handle inline headers
6573
6399
  }
6574
-
6575
6400
  var overlayLevel = 0;
6576
6401
  while (i < line.length) {
6577
6402
  var startI = i;
@@ -6579,6 +6404,7 @@ MusicParser.prototype.parseMusic = function (line) {
6579
6404
  var retInlineHeader = header.letter_to_inline_header(line, i, delayStartNewLine);
6580
6405
  if (retInlineHeader[0] > 0) {
6581
6406
  i += retInlineHeader[0];
6407
+ //console.log("inline header", retInlineHeader)
6582
6408
  if (retInlineHeader[1] === 'V') delayStartNewLine = true; // fixes bug on this: c[V:2]d
6583
6409
  // TODO-PER: Handle inline headers
6584
6410
  //multilineVars.start_new_line = false;
@@ -6858,6 +6684,9 @@ MusicParser.prototype.parseMusic = function (line) {
6858
6684
  if (ch === '-' || ch === ')' || ch === ' ' || ch === '<' || ch === '>') i--; // Subtracting one because one is automatically added below
6859
6685
  else postChordDone = true;
6860
6686
  break;
6687
+ case '0':
6688
+ chordDuration = 0;
6689
+ break;
6861
6690
  default:
6862
6691
  postChordDone = true;
6863
6692
  break;
@@ -6947,7 +6776,11 @@ MusicParser.prototype.parseMusic = function (line) {
6947
6776
  if (!el.rest || el.rest.type !== 'spacer') warn("Duration not representable: " + line.substring(startI, i), line, i);
6948
6777
  }
6949
6778
  multilineVars.addFormattingOptions(el, tune.formatting, 'note');
6950
- tuneBuilder.appendElement('note', startOfLine + startI, startOfLine + i, el);
6779
+ var succeeded = tuneBuilder.appendElement('note', startOfLine + startI, startOfLine + i, el);
6780
+ if (!succeeded) {
6781
+ this.startNewLine();
6782
+ tuneBuilder.appendElement('note', startOfLine + startI, startOfLine + i, el);
6783
+ }
6951
6784
  multilineVars.measureNotEmpty = true;
6952
6785
  el = {};
6953
6786
  }
@@ -6991,15 +6824,30 @@ var letter_to_chord = function letter_to_chord(line, i) {
6991
6824
  chord[1] = chord[1].substring(1);
6992
6825
  chord[2] = 'right';
6993
6826
  } else if (chord[0] > 0 && chord[1].length > 0 && chord[1][0] === '@') {
6994
- // @-15,5.7
6827
+ // @-15,5.7
6995
6828
  chord[1] = chord[1].substring(1);
6996
6829
  var x = tokenizer.getFloat(chord[1]);
6997
- if (x.digits === 0) warn("Missing first position in absolutely positioned annotation.", line, i);
6830
+ if (x.digits === 0) {
6831
+ warn("Missing first position in absolutely positioned annotation.", line, i);
6832
+ chord[1] = chord[1].replace("@", "");
6833
+ chord[2] = 'above';
6834
+ return chord;
6835
+ }
6998
6836
  chord[1] = chord[1].substring(x.digits);
6999
- if (chord[1][0] !== ',') warn("Missing comma absolutely positioned annotation.", line, i);
6837
+ if (chord[1][0] !== ',') {
6838
+ warn("Missing comma absolutely positioned annotation.", line, i);
6839
+ chord[1] = chord[1].replace("@", "");
6840
+ chord[2] = 'above';
6841
+ return chord;
6842
+ }
7000
6843
  chord[1] = chord[1].substring(1);
7001
6844
  var y = tokenizer.getFloat(chord[1]);
7002
- if (y.digits === 0) warn("Missing second position in absolutely positioned annotation.", line, i);
6845
+ if (y.digits === 0) {
6846
+ warn("Missing second position in absolutely positioned annotation.", line, i);
6847
+ chord[1] = chord[1].replace("@", "");
6848
+ chord[2] = 'above';
6849
+ return chord;
6850
+ }
7003
6851
  chord[1] = chord[1].substring(y.digits);
7004
6852
  var ws = tokenizer.skipWhiteSpace(chord[1]);
7005
6853
  chord[1] = chord[1].substring(ws);
@@ -7049,7 +6897,10 @@ var letter_to_grace = function letter_to_grace(line, i) {
7049
6897
  // 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
6898
  note.duration = note.duration / (multilineVars.default_length * 8);
7051
6899
  if (acciaccatura) note.acciaccatura = true;
7052
- gracenotes.push(note);
6900
+ if (note.rest) {
6901
+ // don't allow rests inside gracenotes
6902
+ warn("Rests not allowed as grace notes '" + gra[1][ii] + "' while parsing grace note", line, i);
6903
+ } else gracenotes.push(note);
7053
6904
  if (inTie) {
7054
6905
  note.endTie = true;
7055
6906
  inTie = false;
@@ -7280,7 +7131,7 @@ MusicParser.prototype.startNewLine = function () {
7280
7131
  endChar: -1
7281
7132
  };
7282
7133
  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);
7134
+ params.clef = multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].clef !== undefined ? Object.assign({}, multilineVars.staves[multilineVars.currentVoice.staffNum].clef) : Object.assign({}, multilineVars.clef);
7284
7135
  var scoreTranspose = multilineVars.currentVoice ? multilineVars.currentVoice.scoreTranspose : 0;
7285
7136
  params.key = parseKeyVoice.standardKey(multilineVars.key.root + multilineVars.key.acc + multilineVars.key.mode, multilineVars.key.root, multilineVars.key.acc, scoreTranspose);
7286
7137
  params.key.mode = multilineVars.key.mode;
@@ -7334,6 +7185,11 @@ MusicParser.prototype.startNewLine = function () {
7334
7185
  if (multilineVars.currentVoice.color) params.color = multilineVars.currentVoice.color;
7335
7186
  if (multilineVars.currentVoice.style) params.style = multilineVars.currentVoice.style;
7336
7187
  if (multilineVars.currentVoice.transpose) params.clef.transpose = multilineVars.currentVoice.transpose;
7188
+ params.currentVoice = multilineVars.currentVoice;
7189
+ var voices = Object.keys(multilineVars.voices);
7190
+ for (var mv = 0; mv < voices.length; mv++) {
7191
+ if (params.currentVoice.staffNum === multilineVars.voices[voices[mv]].staffNum && params.currentVoice.index === multilineVars.voices[voices[mv]].index) params.currentVoiceName = voices[mv];
7192
+ }
7337
7193
  }
7338
7194
  var isFirstVoice = multilineVars.currentVoice === undefined || multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0;
7339
7195
  if (multilineVars.barNumbers === 0 && isFirstVoice && multilineVars.currBarNumber !== 1) params.barNumber = multilineVars.currBarNumber;
@@ -7721,7 +7577,6 @@ var Tokenizer = function Tokenizer(lines, multilineVars) {
7721
7577
  }
7722
7578
  return str.length; // It must have been all white space
7723
7579
  };
7724
-
7725
7580
  var finished = function finished(str, i) {
7726
7581
  return i >= str.length;
7727
7582
  };
@@ -7782,7 +7637,6 @@ var Tokenizer = function Tokenizer(lines, multilineVars) {
7782
7637
  // case 'f':return {len: i+1, token: 'F'};
7783
7638
  // case 'g':return {len: i+1, token: 'G'};
7784
7639
  }
7785
-
7786
7640
  return {
7787
7641
  len: 0
7788
7642
  };
@@ -8614,7 +8468,6 @@ var Tokenizer = function Tokenizer(lines, multilineVars) {
8614
8468
 
8615
8469
  // More chars: IJ ij Ď ď Đ đ Ĝ ĝ Ğ ğ Ġ ġ Ģ ģ Ĥ ĥ Ħ ħ Ĵ ĵ Ķ ķ ĸ Ĺ ĺ Ļ ļ Ľ ľ Ŀ ŀ Ł ł Ń ń Ņ ņ Ň ň ʼn Ŋ ŋ Ŕ ŕ Ŗ ŗ Ř ř Ś ś Ŝ ŝ Ş ş Š Ţ ţ Ť ť Ŧ ŧ Ŵ ŵ Ź ź Ż ż Ž
8616
8470
  };
8617
-
8618
8471
  var charMap1 = {
8619
8472
  "#": "♯",
8620
8473
  "b": "♭",
@@ -9306,7 +9159,6 @@ function transposeChordName(chord, steps, preferFlats, freeGCchord) {
9306
9159
  }
9307
9160
  } else chord += bass; // Don't know what to do so do nothing
9308
9161
  }
9309
-
9310
9162
  if (extra2) chord += extra2;
9311
9163
  return chord;
9312
9164
  }
@@ -9321,166 +9173,19 @@ module.exports = transposeChordName;
9321
9173
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
9322
9174
 
9323
9175
  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");
9176
+ //var parseCommon = require('../parse/abc_common');
9177
+ //var parseDirective = require('./abc_parse_directive');
9178
+
9326
9179
  var TuneBuilder = function TuneBuilder(tune) {
9327
9180
  var self = this;
9181
+ var voiceDefs = {};
9182
+ var currentVoiceName = '';
9183
+ tune.reset();
9328
9184
  this.setVisualTranspose = function (visualTranspose) {
9329
9185
  if (visualTranspose) tune.visualTranspose = visualTranspose;
9330
9186
  };
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
9187
  this.cleanUp = function (barsperstaff, staffnonote, currSlur) {
9483
- this.closeLine(); // Close the last line.
9188
+ closeLine(tune); // Close the last line.
9484
9189
  delete tune.runningFonts;
9485
9190
  simplifyMetaText(tune);
9486
9191
  //addRichTextToAnnotationsAndLyrics(tune)
@@ -9502,7 +9207,7 @@ var TuneBuilder = function TuneBuilder(tune) {
9502
9207
  } else {
9503
9208
  for (v = 0; v < tune.lines[i].staff[s].voices.length; v++) {
9504
9209
  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;
9210
+ else if (containsNotes(tune.lines[i].staff[s].voices[v])) hasAny = true;
9506
9211
  }
9507
9212
  }
9508
9213
  }
@@ -9538,7 +9243,7 @@ var TuneBuilder = function TuneBuilder(tune) {
9538
9243
  for (s = 0; s < tune.lines[i].staff.length; s++) {
9539
9244
  var keepThis = false;
9540
9245
  for (v = 0; v < tune.lines[i].staff[s].voices.length; v++) {
9541
- if (this.containsNotesStrict(tune.lines[i].staff[s].voices[v])) {
9246
+ if (containsNotesStrict(tune.lines[i].staff[s].voices[v])) {
9542
9247
  keepThis = true;
9543
9248
  }
9544
9249
  }
@@ -9569,208 +9274,23 @@ var TuneBuilder = function TuneBuilder(tune) {
9569
9274
  }
9570
9275
 
9571
9276
  // If there are overlays, create new voices for them.
9572
- while (this.resolveOverlays()) {
9277
+ while (resolveOverlays(tune)) {
9573
9278
  // keep resolving overlays as long as any are found.
9574
9279
  }
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;
9280
+ for (var i = 0; i < tune.lines.length; i++) {
9281
+ var staff = tune.lines[i].staff;
9762
9282
  if (staff) {
9763
9283
  for (tune.staffNum = 0; tune.staffNum < staff.length; tune.staffNum++) {
9764
- if (staff[tune.staffNum].clef) fixClefPlacement(staff[tune.staffNum].clef);
9284
+ if (staff[tune.staffNum].clef) parseKeyVoice.fixClef(staff[tune.staffNum].clef);
9765
9285
  for (tune.voiceNum = 0; tune.voiceNum < staff[tune.staffNum].voices.length; tune.voiceNum++) {
9766
9286
  var voice = staff[tune.staffNum].voices[tune.voiceNum];
9767
- cleanUpSlursInLine(voice, tune.staffNum, tune.voiceNum);
9287
+ cleanUpSlursInLine(voice, tune.staffNum, tune.voiceNum, currSlur);
9768
9288
  for (var j = 0; j < voice.length; j++) {
9769
- if (voice[j].el_type === 'clef') fixClefPlacement(voice[j]);
9289
+ if (voice[j].el_type === 'clef') parseKeyVoice.fixClef(voice[j]);
9770
9290
  }
9771
9291
  if (voice.length > 0 && voice[voice.length - 1].barNumber) {
9772
9292
  // 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);
9293
+ var nextLine = getNextMusicLine(tune.lines, i);
9774
9294
  if (nextLine) nextLine.staff[0].barNumber = voice[voice.length - 1].barNumber;
9775
9295
  delete voice[voice.length - 1].barNumber;
9776
9296
  }
@@ -9788,21 +9308,9 @@ var TuneBuilder = function TuneBuilder(tune) {
9788
9308
  delete tune.vskipPending;
9789
9309
  return currSlur;
9790
9310
  };
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
9311
  this.addTieToLastNote = function (dottedTie) {
9804
9312
  // TODO-PER: if this is a chord, which note?
9805
- var el = this.getLastNote();
9313
+ var el = getLastNote(tune);
9806
9314
  if (el && el.pitches && el.pitches.length > 0) {
9807
9315
  el.pitches[0].startTie = {};
9808
9316
  if (dottedTie) el.pitches[0].startTie.style = 'dotted';
@@ -9810,59 +9318,10 @@ var TuneBuilder = function TuneBuilder(tune) {
9810
9318
  }
9811
9319
  return false;
9812
9320
  };
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
9321
  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
9322
  hashParams.el_type = type;
9849
9323
  if (startChar !== null) hashParams.startChar = startChar;
9850
9324
  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
9325
  if (type === 'note') {
9867
9326
  // && (hashParams.rest !== undefined || hashParams.end_beam === undefined)) {
9868
9327
  // Now, add the startBeam and endBeam where it is needed.
@@ -9871,25 +9330,25 @@ var TuneBuilder = function TuneBuilder(tune) {
9871
9330
  // this.potentialEndBeam either points to null or the start beam.
9872
9331
  // 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
9332
  // reset the variables for the next notes.
9874
- var dur = self.getDuration(hashParams);
9333
+ var dur = getDuration(hashParams);
9875
9334
  if (dur >= 0.25) {
9876
9335
  // 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) {
9336
+ endBeamLast(tune);
9337
+ } else if (hashParams.force_end_beam_last && tune.potentialStartBeam !== undefined) {
9338
+ endBeamLast(tune);
9339
+ } else if (hashParams.end_beam && tune.potentialStartBeam !== undefined) {
9881
9340
  // 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();
9341
+ if (hashParams.rest === undefined) endBeamHere(hashParams, tune);else endBeamLast(tune);
9883
9342
  } else if (hashParams.rest === undefined) {
9884
9343
  // this a short note and we aren't about to end the beam
9885
- if (This.potentialStartBeam === undefined) {
9344
+ if (tune.potentialStartBeam === undefined) {
9886
9345
  // We aren't collecting notes for a beam, so start here.
9887
9346
  if (!hashParams.end_beam) {
9888
- This.potentialStartBeam = hashParams;
9889
- delete This.potentialEndBeam;
9347
+ tune.potentialStartBeam = hashParams;
9348
+ delete tune.potentialEndBeam;
9890
9349
  }
9891
9350
  } else {
9892
- This.potentialEndBeam = hashParams; // Continue the beaming, look for the end next note.
9351
+ tune.potentialEndBeam = hashParams; // Continue the beaming, look for the end next note.
9893
9352
  }
9894
9353
  }
9895
9354
 
@@ -9897,21 +9356,31 @@ var TuneBuilder = function TuneBuilder(tune) {
9897
9356
  // if (hashParams.rest !== undefined)
9898
9357
  // {
9899
9358
  // hashParams.end_beam = true;
9900
- // var el2 = this.getLastNote();
9359
+ // var el2 = getLastNote(tune);
9901
9360
  // if (el2) el2.end_beam = true;
9902
9361
  // // TODO-PER: implement exception mentioned in the comment.
9903
9362
  // }
9904
9363
  } else {
9905
9364
  // It's not a note, so there definitely isn't beaming after it.
9906
- endBeamLast();
9365
+ endBeamLast(tune);
9907
9366
  }
9908
9367
  delete hashParams.end_beam; // We don't want this temporary variable hanging around.
9909
9368
  delete hashParams.force_end_beam_last; // We don't want this temporary variable hanging around.
9910
- pushNote(hashParams);
9369
+ if (hashParams.rest && hashParams.rest.type === 'invisible') {
9370
+ delete hashParams.decoration; // the decorations on invisible rests should be invisible, too.
9371
+ }
9372
+ if (tune.lines.length <= tune.lineNum || tune.lines[tune.lineNum].staff.length <= tune.staffNum) {
9373
+ //console.log("pushNote IGNORED", tune.lines[tune.lineNum])
9374
+ // 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.
9375
+ return false;
9376
+ }
9377
+ pushNote(self, tune, hashParams, voiceDefs, currentVoiceName);
9378
+ return true;
9911
9379
  };
9912
9380
  this.appendStartingElement = function (type, startChar, endChar, hashParams2) {
9381
+ //console.log('appendStartingElement', hashParams2)
9913
9382
  // If we're in the middle of beaming, then end the beam.
9914
- this.closeLine();
9383
+ closeLine(tune);
9915
9384
 
9916
9385
  // We only ever want implied naturals the first time.
9917
9386
  var impliedNaturals;
@@ -9922,58 +9391,54 @@ var TuneBuilder = function TuneBuilder(tune) {
9922
9391
  }
9923
9392
 
9924
9393
  // 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
- }
9394
+ var hashParams = Object.assign({}, hashParams2);
9395
+
9396
+ // be sure that we are on a music type line before doing the following.
9397
+ if (!tune.lines[tune.lineNum]) return;
9398
+ var staff = tune.lines[tune.lineNum].staff;
9399
+ if (!staff) return;
9400
+
9401
+ // If tune is the first item in tune staff, then we might have to initialize the staff, first.
9402
+ if (staff.length <= tune.staffNum) {
9403
+ staff[tune.staffNum] = {};
9404
+ staff[tune.staffNum].clef = Object.assign({}, staff[0].clef);
9405
+ staff[tune.staffNum].key = Object.assign({}, staff[0].key);
9406
+ if (staff[0].meter) staff[tune.staffNum].meter = Object.assign({}, staff[0].meter);
9407
+ staff[tune.staffNum].workingClef = Object.assign({}, staff[0].workingClef);
9408
+ staff[tune.staffNum].voices = [[]];
9409
+ }
9410
+ // If tune is a clef type, then we replace the working clef on the line. This is kept separate from
9411
+ // the clef in case there is an inline clef field. We need to know what the current position for
9412
+ // the note is.
9413
+ if (type === 'clef') {
9414
+ staff[tune.staffNum].workingClef = hashParams;
9415
+ }
9416
+
9417
+ // 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.
9418
+ var voice = staff[tune.staffNum].voices[tune.voiceNum];
9419
+ for (var i = 0; i < voice.length; i++) {
9420
+ if (voice[i].el_type === 'note' || voice[i].el_type === 'bar') {
9421
+ hashParams.el_type = type;
9422
+ hashParams.startChar = startChar;
9423
+ hashParams.endChar = endChar;
9424
+ if (impliedNaturals) hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals);
9425
+ voice.push(hashParams);
9426
+ return;
9427
+ }
9428
+ if (voice[i].el_type === type) {
9429
+ hashParams.el_type = type;
9430
+ hashParams.startChar = startChar;
9431
+ hashParams.endChar = endChar;
9432
+ if (impliedNaturals) hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals);
9433
+ voice[i] = hashParams;
9434
+ return;
9963
9435
  }
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
9436
  }
9973
- tune.lines.push(hash);
9437
+ // We didn't see either that type or a note, so replace the element to the staff.
9438
+ staff[tune.staffNum][type] = hashParams2;
9974
9439
  };
9975
9440
  this.addSubtitle = function (str, info) {
9976
- this.pushLine({
9441
+ pushLine(tune, {
9977
9442
  subtitle: {
9978
9443
  text: str,
9979
9444
  startChar: info.startChar,
@@ -9985,12 +9450,12 @@ var TuneBuilder = function TuneBuilder(tune) {
9985
9450
  tune.vskipPending = num;
9986
9451
  };
9987
9452
  this.addNewPage = function (num) {
9988
- this.pushLine({
9453
+ pushLine(tune, {
9989
9454
  newpage: num
9990
9455
  });
9991
9456
  };
9992
9457
  this.addSeparator = function (spaceAbove, spaceBelow, lineLength, info) {
9993
- this.pushLine({
9458
+ pushLine(tune, {
9994
9459
  separator: {
9995
9460
  spaceAbove: Math.round(spaceAbove),
9996
9461
  spaceBelow: Math.round(spaceBelow),
@@ -10001,7 +9466,7 @@ var TuneBuilder = function TuneBuilder(tune) {
10001
9466
  });
10002
9467
  };
10003
9468
  this.addText = function (str, info) {
10004
- this.pushLine({
9469
+ pushLine(tune, {
10005
9470
  text: {
10006
9471
  text: str,
10007
9472
  startChar: info.startChar,
@@ -10010,29 +9475,17 @@ var TuneBuilder = function TuneBuilder(tune) {
10010
9475
  });
10011
9476
  };
10012
9477
  this.addCentered = function (str) {
10013
- this.pushLine({
9478
+ pushLine(tune, {
10014
9479
  text: [{
10015
9480
  text: str,
10016
9481
  center: true
10017
9482
  }]
10018
9483
  });
10019
9484
  };
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
9485
 
10033
9486
  // anyVoiceContainsNotes: function(line) {
10034
9487
  // for (var i = 0; i < line.staff.voices.length; i++) {
10035
- // if (this.containsNotes(line.staff.voices[i]))
9488
+ // if (containsNotes(line.staff.voices[i]))
10036
9489
  // return true;
10037
9490
  // }
10038
9491
  // return false;
@@ -10048,95 +9501,18 @@ var TuneBuilder = function TuneBuilder(tune) {
10048
9501
  });
10049
9502
  };
10050
9503
  this.startNewLine = function (params) {
9504
+ //console.log("startNewLine", tune.lineNum, params, voiceDefs)
10051
9505
  // 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
9506
  // 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) {
9507
+ closeLine(tune); // Close the previous line.
9508
+ if (params.currentVoiceName) {
9509
+ currentVoiceName = params.currentVoiceName;
9510
+ voiceDefs[params.currentVoiceName] = params;
9511
+ }
9512
+ if (tune.lines[tune.lineNum] === undefined) createLine(self, tune, params);else if (tune.lines[tune.lineNum].staff === undefined) {
10137
9513
  tune.lineNum++;
10138
9514
  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])) {
9515
+ } 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
9516
  // We don't need a new line but we might need to update parts of it.
10141
9517
  if (params.part) self.appendElement('part', params.part.startChar, params.part.endChar, {
10142
9518
  title: params.part.title
@@ -10150,21 +9526,6 @@ var TuneBuilder = function TuneBuilder(tune) {
10150
9526
  // This is called at tune start to set the current default fonts so we know whether to record a change.
10151
9527
  tune.runningFonts[type] = font;
10152
9528
  };
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
9529
  this.setBarNumberImmediate = function (barNumber) {
10169
9530
  // If tune is called right at the beginning of a line, then correct the measure number that is already written.
10170
9531
  // If tune is called at the beginning of a measure, then correct the measure number that was just created.
@@ -10194,24 +9555,31 @@ var TuneBuilder = function TuneBuilder(tune) {
10194
9555
  return true;
10195
9556
  };
10196
9557
  this.getCurrentVoice = function () {
9558
+ //console.log("getCurrentVoice", tune.lineNum)
10197
9559
  var currLine = tune.lines[tune.lineNum];
10198
9560
  if (!currLine) return null;
10199
9561
  var currStaff = currLine.staff[tune.staffNum];
10200
9562
  if (!currStaff) return null;
10201
9563
  if (currStaff.voices[tune.voiceNum] !== undefined) return currStaff.voices[tune.voiceNum];else return null;
10202
9564
  };
10203
- this.setCurrentVoice = function (staffNum, voiceNum) {
9565
+ this.setCurrentVoice = function (staffNum, voiceNum, name) {
9566
+ //console.log("setCurrentVoice", tune.lineNum, staffNum, voiceNum, name, voiceDefs)
10204
9567
  tune.staffNum = staffNum;
10205
9568
  tune.voiceNum = voiceNum;
9569
+ currentVoiceName = name;
10206
9570
  for (var i = 0; i < tune.lines.length; i++) {
10207
9571
  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])) {
9572
+ if (tune.lines[i].staff[staffNum] === undefined || tune.lines[i].staff[staffNum].voices[voiceNum] === undefined || !containsNotes(tune.lines[i].staff[staffNum].voices[voiceNum])) {
9573
+ //console.log("cv2", i, tune.lines[i].staff[staffNum])
10209
9574
  tune.lineNum = i;
10210
- return;
9575
+ if (!tune.lines[i].staff[staffNum] || !!tune.lines[i].staff[staffNum].voices[voiceNum]) return true;
9576
+ return false;
10211
9577
  }
10212
9578
  }
10213
9579
  }
9580
+ //console.log("cv3", i, tune.lineNum, tune.lines[tune.lineNum])
10214
9581
  tune.lineNum = i;
9582
+ return false;
10215
9583
  };
10216
9584
  this.addMetaText = function (key, value, info) {
10217
9585
  if (tune.metaText[key] === undefined) {
@@ -10247,7 +9615,7 @@ var TuneBuilder = function TuneBuilder(tune) {
10247
9615
  function isArrayOfStrings(arr) {
10248
9616
  if (!arr) return false;
10249
9617
  if (typeof arr === "string") return false;
10250
- var str = '';
9618
+ //var str = ''
10251
9619
  for (var i = 0; i < arr.length; i++) {
10252
9620
  if (typeof arr[i] !== 'string') return false;
10253
9621
  }
@@ -10257,33 +9625,553 @@ function simplifyMetaText(tune) {
10257
9625
  if (isArrayOfStrings(tune.metaText.notes)) tune.metaText.notes = tune.metaText.notes.join("\n");
10258
9626
  if (isArrayOfStrings(tune.metaText.history)) tune.metaText.history = tune.metaText.history.join("\n");
10259
9627
  }
10260
- function addRichTextToAnnotationsAndLyrics(tune) {
10261
- var lines = tune.lines;
10262
- for (var i = 0; i < lines.length; i++) {
10263
- 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++) {
10266
- var voice = lines[i].staff[s].voices[v];
10267
- 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);
10279
- }
10280
- }
10281
- }
10282
- }
9628
+
9629
+ // function addRichTextToAnnotationsAndLyrics(tune) {
9630
+ // var lines = tune.lines
9631
+ // for (var i = 0; i < lines.length; i++) {
9632
+ // if (lines[i].staff !== undefined) {
9633
+ // for (var s = 0; s < lines[i].staff.length; s++) {
9634
+ // for (var v = 0; v < lines[i].staff[s].voices.length; v++) {
9635
+ // var voice = lines[i].staff[s].voices[v];
9636
+ // for (var n = 0; n < voice.length; n++) {
9637
+ // var element = voice[n]
9638
+ // if (element.chord) {
9639
+ // for (var c = 0; c < element.chord.length; c++) {
9640
+ // element.chord[c].name = parseDirective.parseFontChangeLine(element.chord[c].name)
9641
+ // console.log(element.chord[c].name)
9642
+ // }
9643
+ // }
9644
+ // if (element.lyric) {
9645
+ // for (var l = 0; l < element.lyric.length; l++) {
9646
+ // element.lyric[l].syllable = parseDirective.parseFontChangeLine(element.lyric[l].syllable)
9647
+ // console.log(element.lyric[l].syllable)
9648
+ // }
9649
+ // }
9650
+ // }
9651
+ // }
9652
+ // }
9653
+ // }
9654
+ // }
9655
+
9656
+ // }
9657
+
9658
+ function resolveOverlays(tune) {
9659
+ var madeChanges = false;
9660
+ var durationsPerLines = [];
9661
+ for (var i = 0; i < tune.lines.length; i++) {
9662
+ var line = tune.lines[i];
9663
+ if (line.staff) {
9664
+ for (var j = 0; j < line.staff.length; j++) {
9665
+ var staff = line.staff[j];
9666
+ var overlayVoice = [];
9667
+ for (var k = 0; k < staff.voices.length; k++) {
9668
+ var voice = staff.voices[k];
9669
+ overlayVoice.push({
9670
+ hasOverlay: false,
9671
+ voice: [],
9672
+ snip: []
9673
+ });
9674
+ durationsPerLines[i] = 0;
9675
+ var durationThisBar = 0;
9676
+ var inOverlay = false;
9677
+ var overlayDuration = 0;
9678
+ var snipStart = -1;
9679
+ for (var kk = 0; kk < voice.length; kk++) {
9680
+ var event = voice[kk];
9681
+ if (event.el_type === "overlay" && !inOverlay) {
9682
+ madeChanges = true;
9683
+ inOverlay = true;
9684
+ snipStart = kk;
9685
+ overlayVoice[k].hasOverlay = true;
9686
+ if (overlayDuration === 0) overlayDuration = durationsPerLines[i];
9687
+ // If this isn't the first line, we also need invisible rests on the previous lines.
9688
+ // So, if the next voice doesn't appear in a previous line, create it
9689
+ for (var ii = 0; ii < i; ii++) {
9690
+ if (durationsPerLines[ii] && tune.lines[ii].staff && staff.voices.length >= tune.lines[ii].staff[0].voices.length) {
9691
+ tune.lines[ii].staff[0].voices.push([{
9692
+ el_type: "note",
9693
+ duration: durationsPerLines[ii],
9694
+ rest: {
9695
+ type: "invisible"
9696
+ },
9697
+ startChar: event.startChar,
9698
+ endChar: event.endChar
9699
+ }]);
9700
+ }
9701
+ }
9702
+ } else if (event.el_type === "bar") {
9703
+ if (inOverlay) {
9704
+ // delete the overlay events from this array without messing up this loop.
9705
+ inOverlay = false;
9706
+ overlayVoice[k].snip.push({
9707
+ start: snipStart,
9708
+ len: kk - snipStart
9709
+ });
9710
+ overlayVoice[k].voice.push(event); // Also end the overlay with the barline.
9711
+ } else {
9712
+ // This keeps the voices lined up: if the overlay isn't in the first measure then we need a bunch of invisible rests.
9713
+ if (durationThisBar > 0) overlayVoice[k].voice.push({
9714
+ el_type: "note",
9715
+ duration: durationThisBar,
9716
+ rest: {
9717
+ type: "invisible"
9718
+ },
9719
+ startChar: event.startChar,
9720
+ endChar: event.endChar
9721
+ });
9722
+ overlayVoice[k].voice.push(event);
9723
+ }
9724
+ durationThisBar = 0;
9725
+ } else if (event.el_type === "note") {
9726
+ if (inOverlay) {
9727
+ overlayVoice[k].voice.push(event);
9728
+ } else {
9729
+ durationThisBar += event.duration;
9730
+ durationsPerLines[i] += event.duration;
9731
+ }
9732
+ } 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") {
9733
+ // These types of events are duplicated on the overlay layer.
9734
+ overlayVoice[k].voice.push(event);
9735
+ }
9736
+ }
9737
+ if (overlayVoice[k].hasOverlay && overlayVoice[k].snip.length === 0) {
9738
+ // there was no closing bar, so we didn't set the snip amount.
9739
+ overlayVoice[k].snip.push({
9740
+ start: snipStart,
9741
+ len: voice.length - snipStart
9742
+ });
9743
+ }
9744
+ }
9745
+ for (k = 0; k < overlayVoice.length; k++) {
9746
+ var ov = overlayVoice[k];
9747
+ if (ov.hasOverlay) {
9748
+ ov.voice.splice(0, 0, {
9749
+ el_type: "stem",
9750
+ direction: "down"
9751
+ });
9752
+ staff.voices.push(ov.voice);
9753
+ for (var kkk = ov.snip.length - 1; kkk >= 0; kkk--) {
9754
+ var snip = ov.snip[kkk];
9755
+ staff.voices[k].splice(snip.start, snip.len);
9756
+ staff.voices[k].splice(snip.start + 1, 0, {
9757
+ el_type: "stem",
9758
+ direction: "auto"
9759
+ });
9760
+ var indexOfLastBar = findLastBar(staff.voices[k], snip.start);
9761
+ staff.voices[k].splice(indexOfLastBar, 0, {
9762
+ el_type: "stem",
9763
+ direction: "up"
9764
+ });
9765
+ }
9766
+ // remove ending marks from the overlay voice so they are not repeated
9767
+ for (kkk = 0; kkk < staff.voices[staff.voices.length - 1].length; kkk++) {
9768
+ staff.voices[staff.voices.length - 1][kkk] = Object.assign({}, staff.voices[staff.voices.length - 1][kkk]);
9769
+ var el = staff.voices[staff.voices.length - 1][kkk];
9770
+ if (el.el_type === 'bar' && el.startEnding) {
9771
+ delete el.startEnding;
9772
+ }
9773
+ if (el.el_type === 'bar' && el.endEnding) delete el.endEnding;
9774
+ }
9775
+ }
9776
+ }
9777
+ }
9778
+ }
9779
+ }
9780
+ return madeChanges;
9781
+ }
9782
+ ;
9783
+ function findLastBar(voice, start) {
9784
+ for (var i = start - 1; i > 0 && voice[i].el_type !== "bar"; i--) {}
9785
+ return i;
9786
+ }
9787
+ function fixTitles(lines) {
9788
+ // We might have name and subname defined. We now know what line everything is on, so we can determine which to use.
9789
+ var firstMusicLine = true;
9790
+ for (var i = 0; i < lines.length; i++) {
9791
+ var line = lines[i];
9792
+ if (line.staff) {
9793
+ for (var j = 0; j < line.staff.length; j++) {
9794
+ var staff = line.staff[j];
9795
+ if (staff.title) {
9796
+ var hasATitle = false;
9797
+ for (var k = 0; k < staff.title.length; k++) {
9798
+ if (staff.title[k]) {
9799
+ staff.title[k] = firstMusicLine ? staff.title[k].name : staff.title[k].subname;
9800
+ if (staff.title[k]) hasATitle = true;else staff.title[k] = '';
9801
+ } else staff.title[k] = '';
9802
+ }
9803
+ if (!hasATitle) delete staff.title;
9804
+ }
9805
+ }
9806
+ firstMusicLine = false;
9807
+ }
9808
+ }
9809
+ }
9810
+ function cleanUpSlursInLine(line, staffNum, voiceNum, currSlur) {
9811
+ if (!currSlur[staffNum]) currSlur[staffNum] = [];
9812
+ if (!currSlur[staffNum][voiceNum]) currSlur[staffNum][voiceNum] = [];
9813
+ var x;
9814
+ // var lyr = null; // TODO-PER: debugging.
9815
+
9816
+ var addEndSlur = function addEndSlur(obj, num, chordPos) {
9817
+ if (currSlur[staffNum][voiceNum][chordPos] === undefined) {
9818
+ // There isn't an exact match for note position, but we'll take any other open slur.
9819
+ for (x = 0; x < currSlur[staffNum][voiceNum].length; x++) {
9820
+ if (currSlur[staffNum][voiceNum][x] !== undefined) {
9821
+ chordPos = x;
9822
+ break;
9823
+ }
9824
+ }
9825
+ if (currSlur[staffNum][voiceNum][chordPos] === undefined) {
9826
+ var offNum = chordPos * 100 + 1;
9827
+ obj.endSlur.forEach(function (x) {
9828
+ if (offNum === x) --offNum;
9829
+ });
9830
+ currSlur[staffNum][voiceNum][chordPos] = [offNum];
9831
+ }
9832
+ }
9833
+ var slurNum;
9834
+ for (var i = 0; i < num; i++) {
9835
+ slurNum = currSlur[staffNum][voiceNum][chordPos].pop();
9836
+ obj.endSlur.push(slurNum);
9837
+ // lyr.syllable += '<' + slurNum; // TODO-PER: debugging
9838
+ }
9839
+ if (currSlur[staffNum][voiceNum][chordPos].length === 0) delete currSlur[staffNum][voiceNum][chordPos];
9840
+ return slurNum;
9841
+ };
9842
+ var addStartSlur = function addStartSlur(obj, num, chordPos, usedNums) {
9843
+ obj.startSlur = [];
9844
+ if (currSlur[staffNum][voiceNum][chordPos] === undefined) {
9845
+ currSlur[staffNum][voiceNum][chordPos] = [];
9846
+ }
9847
+ var nextNum = chordPos * 100 + 1;
9848
+ for (var i = 0; i < num; i++) {
9849
+ if (usedNums) {
9850
+ usedNums.forEach(function (x) {
9851
+ if (nextNum === x) ++nextNum;
9852
+ });
9853
+ usedNums.forEach(function (x) {
9854
+ if (nextNum === x) ++nextNum;
9855
+ });
9856
+ usedNums.forEach(function (x) {
9857
+ if (nextNum === x) ++nextNum;
9858
+ });
9859
+ }
9860
+ currSlur[staffNum][voiceNum][chordPos].forEach(function (x) {
9861
+ if (nextNum === x) ++nextNum;
9862
+ });
9863
+ currSlur[staffNum][voiceNum][chordPos].forEach(function (x) {
9864
+ if (nextNum === x) ++nextNum;
9865
+ });
9866
+ currSlur[staffNum][voiceNum][chordPos].push(nextNum);
9867
+ obj.startSlur.push({
9868
+ label: nextNum
9869
+ });
9870
+ if (obj.dottedSlur) {
9871
+ obj.startSlur[obj.startSlur.length - 1].style = 'dotted';
9872
+ delete obj.dottedSlur;
9873
+ }
9874
+ // lyr.syllable += ' ' + nextNum + '>'; // TODO-PER:debugging
9875
+ nextNum++;
9876
+ }
9877
+ };
9878
+ for (var i = 0; i < line.length; i++) {
9879
+ var el = line[i];
9880
+ // if (el.lyric === undefined) // TODO-PER: debugging
9881
+ // el.lyric = [{ divider: '-' }]; // TODO-PER: debugging
9882
+ // lyr = el.lyric[0]; // TODO-PER: debugging
9883
+ // lyr.syllable = ''; // TODO-PER: debugging
9884
+ if (el.el_type === 'note') {
9885
+ if (el.gracenotes) {
9886
+ for (var g = 0; g < el.gracenotes.length; g++) {
9887
+ if (el.gracenotes[g].endSlur) {
9888
+ var gg = el.gracenotes[g].endSlur;
9889
+ el.gracenotes[g].endSlur = [];
9890
+ for (var ggg = 0; ggg < gg; ggg++) {
9891
+ addEndSlur(el.gracenotes[g], 1, 20);
9892
+ }
9893
+ }
9894
+ if (el.gracenotes[g].startSlur) {
9895
+ x = el.gracenotes[g].startSlur;
9896
+ addStartSlur(el.gracenotes[g], x, 20);
9897
+ }
9898
+ }
9899
+ }
9900
+ if (el.endSlur) {
9901
+ x = el.endSlur;
9902
+ el.endSlur = [];
9903
+ addEndSlur(el, x, 0);
9904
+ }
9905
+ if (el.startSlur) {
9906
+ x = el.startSlur;
9907
+ addStartSlur(el, x, 0);
9908
+ }
9909
+ if (el.pitches) {
9910
+ var usedNums = [];
9911
+ for (var p = 0; p < el.pitches.length; p++) {
9912
+ if (el.pitches[p].endSlur) {
9913
+ var k = el.pitches[p].endSlur;
9914
+ el.pitches[p].endSlur = [];
9915
+ for (var j = 0; j < k; j++) {
9916
+ var slurNum = addEndSlur(el.pitches[p], 1, p + 1);
9917
+ usedNums.push(slurNum);
9918
+ }
9919
+ }
9920
+ }
9921
+ for (p = 0; p < el.pitches.length; p++) {
9922
+ if (el.pitches[p].startSlur) {
9923
+ x = el.pitches[p].startSlur;
9924
+ addStartSlur(el.pitches[p], x, p + 1, usedNums);
9925
+ }
9926
+ }
9927
+ // Correct for the weird gracenote case where ({g}a) should match.
9928
+ // The end slur was already assigned to the note, and needs to be moved to the first note of the graces.
9929
+ if (el.gracenotes && el.pitches[0].endSlur && el.pitches[0].endSlur[0] === 100 && el.pitches[0].startSlur) {
9930
+ 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];
9931
+ 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();
9932
+ if (currSlur[staffNum][voiceNum][1].length === 1) delete currSlur[staffNum][voiceNum][1];else currSlur[staffNum][voiceNum][1].pop();
9933
+ }
9934
+ }
9935
+ }
9936
+ }
9937
+ }
9938
+ function wrapMusicLines(lines, barsperstaff) {
9939
+ for (i = 0; i < lines.length; i++) {
9940
+ if (lines[i].staff !== undefined) {
9941
+ for (s = 0; s < lines[i].staff.length; s++) {
9942
+ var permanentItems = [];
9943
+ for (v = 0; v < lines[i].staff[s].voices.length; v++) {
9944
+ var voice = lines[i].staff[s].voices[v];
9945
+ var barNumThisLine = 0;
9946
+ for (var n = 0; n < voice.length; n++) {
9947
+ if (voice[n].el_type === 'bar') {
9948
+ barNumThisLine++;
9949
+ if (barNumThisLine >= barsperstaff) {
9950
+ // push everything else to the next line, if there is anything else,
9951
+ // and there is a next line. If there isn't a next line, create one.
9952
+ if (n < voice.length - 1) {
9953
+ var nextLine = getNextMusicLine(lines, i);
9954
+ if (!nextLine) {
9955
+ var cp = JSON.parse(JSON.stringify(lines[i]));
9956
+ lines.push(Object.assign({}, cp));
9957
+ nextLine = lines[lines.length - 1];
9958
+ for (var ss = 0; ss < nextLine.staff.length; ss++) {
9959
+ for (var vv = 0; vv < nextLine.staff[ss].voices.length; vv++) {
9960
+ nextLine.staff[ss].voices[vv] = [];
9961
+ }
9962
+ }
9963
+ }
9964
+ var startElement = n + 1;
9965
+ var section = lines[i].staff[s].voices[v].slice(startElement);
9966
+ lines[i].staff[s].voices[v] = lines[i].staff[s].voices[v].slice(0, startElement);
9967
+ nextLine.staff[s].voices[v] = permanentItems.concat(section.concat(nextLine.staff[s].voices[v]));
9968
+ return true;
9969
+ }
9970
+ }
9971
+ } else if (!voice[n].duration) {
9972
+ permanentItems.push(voice[n]);
9973
+ }
9974
+ }
9975
+ }
9976
+ }
9977
+ }
9978
+ }
9979
+ return false;
9980
+ }
9981
+ function getNextMusicLine(lines, currentLine) {
9982
+ currentLine++;
9983
+ while (lines.length > currentLine) {
9984
+ if (lines[currentLine].staff) return lines[currentLine];
9985
+ currentLine++;
9986
+ }
9987
+ return null;
9988
+ }
9989
+ function getLastNote(tune) {
9990
+ if (!tune.lines[tune.lineNum]) return null;
9991
+ if (!tune.lines[tune.lineNum].staff) return null;
9992
+ if (!tune.lines[tune.lineNum].staff[tune.staffNum]) return null;
9993
+ var voice = tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum];
9994
+ if (!voice) return null;
9995
+ for (var i = voice.length - 1; i >= 0; i--) {
9996
+ var el = voice[i];
9997
+ if (el.el_type === 'note') {
9998
+ return el;
9999
+ }
10000
+ }
10001
+ return null;
10002
+ }
10003
+ ;
10004
+ function getDuration(el) {
10005
+ if (el.duration) return el.duration;
10006
+ return 0;
10007
+ }
10008
+ ;
10009
+ function closeLine(tune) {
10010
+ if (tune.potentialStartBeam && tune.potentialEndBeam) {
10011
+ tune.potentialStartBeam.startBeam = true;
10012
+ tune.potentialEndBeam.endBeam = true;
10013
+ }
10014
+ delete tune.potentialStartBeam;
10015
+ delete tune.potentialEndBeam;
10016
+ }
10017
+ ;
10018
+ function containsNotes(voice) {
10019
+ for (var i = 0; i < voice.length; i++) {
10020
+ if (voice[i].el_type === 'note' || voice[i].el_type === 'bar') return true;
10021
+ }
10022
+ return false;
10023
+ }
10024
+ ;
10025
+ function containsNotesStrict(voice) {
10026
+ for (var i = 0; i < voice.length; i++) {
10027
+ if (voice[i].el_type === 'note' && (voice[i].rest === undefined || voice[i].chord !== undefined)) return true;
10028
+ }
10029
+ return false;
10030
+ }
10031
+ ;
10032
+ function pushLine(tune, hash) {
10033
+ if (tune.vskipPending) {
10034
+ hash.vskip = tune.vskipPending;
10035
+ delete tune.vskipPending;
10036
+ }
10037
+ tune.lines.push(hash);
10038
+ }
10039
+ ;
10040
+ function pushNote(self, tune, hp, voiceDefs, currentVoiceName) {
10041
+ //console.log("pushNote", tune.lineNum, tune.staffNum, hp.pitches ? JSON.stringify(hp.pitches) : hp.pitches)
10042
+ var currStaff = tune.lines[tune.lineNum].staff[tune.staffNum];
10043
+ if (hp.pitches !== undefined) {
10044
+ var mid = currStaff.workingClef.verticalPos;
10045
+ hp.pitches.forEach(function (p) {
10046
+ p.verticalPos = p.pitch - mid;
10047
+ });
10048
+ }
10049
+ if (hp.gracenotes !== undefined) {
10050
+ var mid2 = currStaff.workingClef.verticalPos;
10051
+ hp.gracenotes.forEach(function (p) {
10052
+ p.verticalPos = p.pitch - mid2;
10053
+ });
10054
+ }
10055
+ if (currStaff.voices.length <= tune.voiceNum) {
10056
+ //console.log("should create?", currentVoiceName, voiceDefs)
10057
+ if (!voiceDefs[currentVoiceName]) voiceDefs[currentVoiceName] = {};
10058
+ createVoice(self, tune, voiceDefs[currentVoiceName]);
10059
+ }
10060
+ currStaff.voices[tune.voiceNum].push(hp);
10061
+ }
10062
+ function endBeamHere(hashParams, tune) {
10063
+ tune.potentialStartBeam.startBeam = true;
10064
+ hashParams.endBeam = true;
10065
+ delete tune.potentialStartBeam;
10066
+ delete tune.potentialEndBeam;
10067
+ }
10068
+ function endBeamLast(tune) {
10069
+ if (tune.potentialStartBeam !== undefined && tune.potentialEndBeam !== undefined) {
10070
+ // Do we have a set of notes to beam?
10071
+ tune.potentialStartBeam.startBeam = true;
10072
+ tune.potentialEndBeam.endBeam = true;
10073
+ }
10074
+ delete tune.potentialStartBeam;
10075
+ delete tune.potentialEndBeam;
10076
+ }
10077
+ function setLineFont(tune, type, font) {
10078
+ // If we haven't encountered the font type yet then we are using the default font so it doesn't
10079
+ // need to be noted. If we have encountered it, then only record it if it is different from the last time.
10080
+ if (tune.runningFonts[type]) {
10081
+ var isDifferent = false;
10082
+ var keys = Object.keys(font);
10083
+ for (var i = 0; i < keys.length; i++) {
10084
+ if (tune.runningFonts[type][keys[i]] !== font[keys[i]]) isDifferent = true;
10085
+ }
10086
+ if (isDifferent) {
10087
+ tune.lines[tune.lineNum].staff[tune.staffNum][type] = font;
10088
+ }
10089
+ }
10090
+ tune.runningFonts[type] = font;
10091
+ }
10092
+ function createVoice(self, tune, params) {
10093
+ //console.log("createVoice", params)
10094
+ var thisStaff = tune.lines[tune.lineNum].staff[tune.staffNum];
10095
+ thisStaff.voices[tune.voiceNum] = [];
10096
+ if (!thisStaff.title) thisStaff.title = [];
10097
+ thisStaff.title[tune.voiceNum] = {
10098
+ name: params.name,
10099
+ subname: params.subname
10100
+ };
10101
+ if (params.style) self.appendElement('style', null, null, {
10102
+ head: params.style
10103
+ });
10104
+ if (params.stem) self.appendElement('stem', null, null, {
10105
+ direction: params.stem
10106
+ });else if (tune.voiceNum > 0) {
10107
+ if (thisStaff.voices[0] !== undefined) {
10108
+ var found = false;
10109
+ for (var i = 0; i < thisStaff.voices[0].length; i++) {
10110
+ if (thisStaff.voices[0].el_type === 'stem') found = true;
10111
+ }
10112
+ if (!found) {
10113
+ var stem = {
10114
+ el_type: 'stem',
10115
+ direction: 'up'
10116
+ };
10117
+ thisStaff.voices[0].splice(0, 0, stem);
10283
10118
  }
10284
10119
  }
10120
+ self.appendElement('stem', null, null, {
10121
+ direction: 'down'
10122
+ });
10123
+ }
10124
+ if (params.scale) self.appendElement('scale', null, null, {
10125
+ size: params.scale
10126
+ });
10127
+ if (params.color) self.appendElement('color', null, null, {
10128
+ color: params.color
10129
+ });
10130
+ }
10131
+ function createStaff(self, tune, params) {
10132
+ if (params.key && params.key.impliedNaturals) {
10133
+ params.key.accidentals = params.key.accidentals.concat(params.key.impliedNaturals);
10134
+ delete params.key.impliedNaturals;
10135
+ }
10136
+ tune.lines[tune.lineNum].staff[tune.staffNum] = {
10137
+ voices: [],
10138
+ clef: params.clef,
10139
+ key: params.key,
10140
+ workingClef: params.clef
10141
+ };
10142
+ var staff = tune.lines[tune.lineNum].staff[tune.staffNum];
10143
+ if (params.stafflines !== undefined) {
10144
+ staff.clef.stafflines = params.stafflines;
10145
+ staff.workingClef.stafflines = params.stafflines;
10146
+ }
10147
+ if (params.staffscale) {
10148
+ staff.staffscale = params.staffscale;
10149
+ }
10150
+ if (params.annotationfont) setLineFont(tune, "annotationfont", params.annotationfont);
10151
+ if (params.gchordfont) setLineFont(tune, "gchordfont", params.gchordfont);
10152
+ if (params.tripletfont) setLineFont(tune, "tripletfont", params.tripletfont);
10153
+ if (params.vocalfont) setLineFont(tune, "vocalfont", params.vocalfont);
10154
+ if (params.bracket) staff.bracket = params.bracket;
10155
+ if (params.brace) staff.brace = params.brace;
10156
+ if (params.connectBarLines) staff.connectBarLines = params.connectBarLines;
10157
+ if (params.barNumber) staff.barNumber = params.barNumber;
10158
+ createVoice(self, tune, params);
10159
+ // Some stuff just happens for the first voice
10160
+ if (params.part) self.appendElement('part', params.part.startChar, params.part.endChar, {
10161
+ title: params.part.title
10162
+ });
10163
+ if (params.meter !== undefined) staff.meter = params.meter;
10164
+ if (tune.vskipPending) {
10165
+ tune.lines[tune.lineNum].vskip = tune.vskipPending;
10166
+ delete tune.vskipPending;
10285
10167
  }
10286
10168
  }
10169
+ function createLine(self, tune, params) {
10170
+ tune.lines[tune.lineNum] = {
10171
+ staff: []
10172
+ };
10173
+ createStaff(self, tune, params);
10174
+ }
10287
10175
  module.exports = TuneBuilder;
10288
10176
 
10289
10177
  /***/ }),
@@ -11197,7 +11085,6 @@ var strTranspose;
11197
11085
  return 0;
11198
11086
  // this should never happen
11199
11087
  }
11200
-
11201
11088
  case '_':
11202
11089
  switch (thisAccidental) {
11203
11090
  case '__':
@@ -11214,7 +11101,6 @@ var strTranspose;
11214
11101
  return 0;
11215
11102
  // this should never happen
11216
11103
  }
11217
-
11218
11104
  case '^':
11219
11105
  switch (thisAccidental) {
11220
11106
  case '__':
@@ -11232,11 +11118,9 @@ var strTranspose;
11232
11118
  // this should never happen
11233
11119
  }
11234
11120
  }
11235
-
11236
11121
  return 0; // this should never happen
11237
11122
  }
11238
11123
  })();
11239
-
11240
11124
  module.exports = strTranspose;
11241
11125
 
11242
11126
  /***/ }),
@@ -11452,6 +11336,7 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11452
11336
  case "chordprog":
11453
11337
  case "bassvol":
11454
11338
  case "chordvol":
11339
+ case "gchordbars":
11455
11340
  chordTrack.paramChange(element);
11456
11341
  break;
11457
11342
  default:
@@ -11560,7 +11445,6 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11560
11445
  // console.log(JSON.stringify(voices))
11561
11446
  }
11562
11447
  }
11563
-
11564
11448
  function getBeatFraction(meter) {
11565
11449
  switch (parseInt(meter.den, 10)) {
11566
11450
  case 2:
@@ -11581,7 +11465,8 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11581
11465
  function processVolume(beat, voiceOff) {
11582
11466
  if (voiceOff) return 0;
11583
11467
  var volume;
11584
- if (nextVolume) {
11468
+ // MAE 21 Jun 2024 - This previously wasn't allowing zero volume to be applied
11469
+ if (nextVolume != undefined) {
11585
11470
  volume = nextVolume;
11586
11471
  nextVolume = undefined;
11587
11472
  } else if (!doBeatAccents) {
@@ -12335,7 +12220,6 @@ var rendererFactory;
12335
12220
  this.track += this.noteOnAndChannel;
12336
12221
  this.track += "%" + pitch.toString(16) + toHex(loudness, 2); //note
12337
12222
  };
12338
-
12339
12223
  Midi.prototype.endNote = function (pitch) {
12340
12224
  this.track += toDurationHex(this.silencelength); // only need to shift by amount of silence (if there is any)
12341
12225
  this.silencelength = 0;
@@ -12349,7 +12233,6 @@ var rendererFactory;
12349
12233
  this.track += this.noteOffAndChannel;
12350
12234
  this.track += "%" + pitch.toString(16) + "%00"; //end note
12351
12235
  };
12352
-
12353
12236
  Midi.prototype.addRest = function (length) {
12354
12237
  this.silencelength += length;
12355
12238
  if (this.silencelength < 0) this.silencelength = 0;
@@ -12386,7 +12269,6 @@ var rendererFactory;
12386
12269
  }
12387
12270
  return "%00%FF" + cmdType + toHex(nameArray.length / 3, 2) + nameArray; // Each byte is represented by three chars "%XX", so divide by 3 to get the length.
12388
12271
  }
12389
-
12390
12272
  function keySignature(key) {
12391
12273
  //00 FF 5902 03 00 - key signature
12392
12274
  if (!key || !key.accidentals) return "";
@@ -12914,7 +12796,7 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
12914
12796
  if (!e) e = voices[voiceNumber].length; // If there wasn't a first ending marker, then we copy everything.
12915
12797
  // duplicate each of the elements - this has to be a deep copy.
12916
12798
  for (var z = s; z < e; z++) {
12917
- var item = parseCommon.clone(voices[voiceNumber][z]);
12799
+ var item = Object.assign({}, voices[voiceNumber][z]);
12918
12800
  if (item.pitches) item.pitches = parseCommon.cloneArray(item.pitches);
12919
12801
  voices[voiceNumber].push(item);
12920
12802
  }
@@ -13023,8 +12905,6 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13023
12905
  break;
13024
12906
  case "swing":
13025
12907
  case "gchord":
13026
- case "bassprog":
13027
- case "chordprog":
13028
12908
  case "bassvol":
13029
12909
  case "chordvol":
13030
12910
  voices[voiceNumber].push({
@@ -13032,6 +12912,23 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13032
12912
  param: elem.params[0]
13033
12913
  });
13034
12914
  break;
12915
+ case "bassprog": // MAE 22 May 2024
12916
+ case "chordprog":
12917
+ // MAE 22 May 2024
12918
+ voices[voiceNumber].push({
12919
+ el_type: elem.cmd,
12920
+ value: elem.params[0],
12921
+ octaveShift: elem.params[1]
12922
+ });
12923
+ break;
12924
+
12925
+ // MAE 23 Jun 2024
12926
+ case "gchordbars":
12927
+ voices[voiceNumber].push({
12928
+ el_type: elem.cmd,
12929
+ param: elem.params[0]
12930
+ });
12931
+ break;
13035
12932
  default:
13036
12933
  console.log("MIDI seq: midi cmd not handled: ", elem.cmd, elem);
13037
12934
  }
@@ -13161,7 +13058,6 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13161
13058
  }
13162
13059
  }
13163
13060
  }
13164
-
13165
13061
  function chordVoiceOffThisBar(voices) {
13166
13062
  for (var i = 0; i < voices.length; i++) {
13167
13063
  var voice = voices[i];
@@ -13304,7 +13200,7 @@ module.exports = centsToFactor;
13304
13200
  /*!**********************************!*\
13305
13201
  !*** ./src/synth/chord-track.js ***!
13306
13202
  \**********************************/
13307
- /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
13203
+ /***/ (function(module) {
13308
13204
 
13309
13205
  //
13310
13206
  // The algorithm for chords is:
@@ -13325,7 +13221,6 @@ module.exports = centsToFactor;
13325
13221
  //
13326
13222
  // 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
13223
 
13328
- var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/abc_common.js");
13329
13224
  var ChordTrack = function ChordTrack(numVoices, chordsOff, midiOptions, meter) {
13330
13225
  this.chordTrack = [];
13331
13226
  this.chordTrackFinished = false;
@@ -13340,11 +13235,23 @@ var ChordTrack = function ChordTrack(numVoices, chordsOff, midiOptions, meter) {
13340
13235
  this.lastBarTime = 0;
13341
13236
  this.meter = meter;
13342
13237
  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;
13238
+
13239
+ // MAE 17 Jun 2024 - To allow for bass and chord instrument octave shifts
13240
+ this.bassInstrument = midiOptions.bassprog && midiOptions.bassprog.length >= 1 ? midiOptions.bassprog[0] : 0;
13241
+ this.chordInstrument = midiOptions.chordprog && midiOptions.chordprog.length >= 1 ? midiOptions.chordprog[0] : 0;
13242
+
13243
+ // MAE For octave shifted bass and chords
13244
+ this.bassOctaveShift = midiOptions.bassprog && midiOptions.bassprog.length === 2 ? midiOptions.bassprog[1] : 0;
13245
+ this.chordOctaveShift = midiOptions.chordprog && midiOptions.chordprog.length === 2 ? midiOptions.chordprog[1] : 0;
13345
13246
  this.boomVolume = midiOptions.bassvol && midiOptions.bassvol.length === 1 ? midiOptions.bassvol[0] : 64;
13346
13247
  this.chickVolume = midiOptions.chordvol && midiOptions.chordvol.length === 1 ? midiOptions.chordvol[0] : 48;
13347
- this.overridePattern = midiOptions.gchord ? parseGChord(midiOptions.gchord[0]) : undefined;
13248
+
13249
+ // This allows for an initial %%MIDI gchord with no string
13250
+ if (midiOptions.gchord && midiOptions.gchord.length > 0) {
13251
+ this.overridePattern = parseGChord(midiOptions.gchord[0]);
13252
+ } else {
13253
+ this.overridePattern = undefined;
13254
+ }
13348
13255
  };
13349
13256
  ChordTrack.prototype.setMeter = function (meter) {
13350
13257
  this.meter = meter;
@@ -13364,7 +13271,7 @@ ChordTrack.prototype.setRhythmHead = function (isRhythmHead, elem) {
13364
13271
  if (isRhythmHead) {
13365
13272
  if (this.lastChord && this.lastChord.chick) {
13366
13273
  for (var i2 = 0; i2 < this.lastChord.chick.length; i2++) {
13367
- var note2 = parseCommon.clone(elem.pitches[0]);
13274
+ var note2 = Object.assign({}, elem.pitches[0]);
13368
13275
  note2.actualPitch = this.lastChord.chick[i2];
13369
13276
  ePitches.push(note2);
13370
13277
  }
@@ -13385,13 +13292,29 @@ ChordTrack.prototype.gChordOn = function (element) {
13385
13292
  ChordTrack.prototype.paramChange = function (element) {
13386
13293
  switch (element.el_type) {
13387
13294
  case "gchord":
13388
- this.overridePattern = parseGChord(element.param);
13295
+ // Skips gchord elements that don't have pattern strings
13296
+ if (element.param && element.param.length > 0) {
13297
+ this.overridePattern = parseGChord(element.param);
13298
+
13299
+ // Generate a default duration scale based on the pattern
13300
+ //this.gchordduration = generateDefaultDurationScale(element.param);
13301
+ } else this.overridePattern = undefined;
13389
13302
  break;
13390
13303
  case "bassprog":
13391
- this.bassInstrument = element.param;
13304
+ this.bassInstrument = element.value;
13305
+ if (element.octaveShift != undefined && element.octaveShift != null) {
13306
+ this.bassOctaveShift = element.octaveShift;
13307
+ } else {
13308
+ this.bassOctaveShift = 0;
13309
+ }
13392
13310
  break;
13393
13311
  case "chordprog":
13394
- this.chordInstrument = element.param;
13312
+ this.chordInstrument = element.value;
13313
+ if (element.octaveShift != undefined && element.octaveShift != null) {
13314
+ this.chordOctaveShift = element.octaveShift;
13315
+ } else {
13316
+ this.chordOctaveShift = 0;
13317
+ }
13395
13318
  break;
13396
13319
  case "bassvol":
13397
13320
  this.boomVolume = element.param;
@@ -13459,22 +13382,36 @@ ChordTrack.prototype.interpretChord = function (name) {
13459
13382
  chordTranspose -= 12;
13460
13383
  }
13461
13384
  bass += chordTranspose;
13385
+
13386
+ // MAE 31 Aug 2024 - For visual transpose backup range issue
13387
+ // If transposed below A or above G, bring it back in the normal backup range
13388
+ if (bass < 33) {
13389
+ bass += 12;
13390
+ } else if (bass > 44) {
13391
+ bass -= 12;
13392
+ }
13393
+
13394
+ // MAE 17 Jun 2024 - Supporting octave shifted bass and chords
13395
+ var unshiftedBass = bass;
13396
+ bass += this.bassOctaveShift * 12;
13462
13397
  var bass2 = bass - 5; // The alternating bass is a 4th below
13463
13398
  var chick;
13464
13399
  if (name.length === 1) chick = this.chordNotes(bass, '');
13465
13400
  var remaining = name.substring(1);
13466
13401
  var acc = remaining.substring(0, 1);
13467
13402
  if (acc === 'b' || acc === '♭') {
13403
+ unshiftedBass--;
13468
13404
  bass--;
13469
13405
  bass2--;
13470
13406
  remaining = remaining.substring(1);
13471
13407
  } else if (acc === '#' || acc === '♯') {
13408
+ unshiftedBass++;
13472
13409
  bass++;
13473
13410
  bass2++;
13474
13411
  remaining = remaining.substring(1);
13475
13412
  }
13476
13413
  var arr = remaining.split('/');
13477
- chick = this.chordNotes(bass, arr[0]);
13414
+ chick = this.chordNotes(unshiftedBass, arr[0]);
13478
13415
  // 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
13416
  if (chick.length >= 3) {
13480
13417
  var fifth = chick[2] - chick[0];
@@ -13491,6 +13428,9 @@ ChordTrack.prototype.interpretChord = function (name) {
13491
13428
  '♭': -1
13492
13429
  }[bassAcc] || 0;
13493
13430
  bass = this.basses[arr[1].substring(0, 1)] + bassShift + chordTranspose;
13431
+
13432
+ // MAE 22 May 2024 - Supporting octave shifted bass and chords
13433
+ bass += this.bassOctaveShift * 12;
13494
13434
  bass2 = bass;
13495
13435
  }
13496
13436
  }
@@ -13506,6 +13446,9 @@ ChordTrack.prototype.chordNotes = function (bass, modifier) {
13506
13446
  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
13447
  }
13508
13448
  bass += 12; // the chord is an octave above the bass note.
13449
+
13450
+ // MAE 22 May 2024 - For chick octave shift
13451
+ bass += this.chordOctaveShift * 12;
13509
13452
  var notes = [];
13510
13453
  for (var i = 0; i < intervals.length; i++) {
13511
13454
  notes.push(bass + intervals[i]);
@@ -13586,7 +13529,6 @@ ChordTrack.prototype.resolveChords = function (startTime, endTime) {
13586
13529
  if (newBass) newBass = false;else isBoom = false; // only the first note in a chord is a bass note. This handles the case where bass and chord are played at the same time.
13587
13530
  }
13588
13531
  }
13589
-
13590
13532
  return;
13591
13533
  };
13592
13534
  ChordTrack.prototype.processChord = function (elem) {
@@ -13619,8 +13561,9 @@ function resolvePitch(currentChord, type, firstBoom, newBass) {
13619
13561
  var ret = [];
13620
13562
  if (!currentChord) return ret;
13621
13563
  if (type.indexOf('boom') >= 0) ret.push(firstBoom ? currentChord.boom : currentChord.boom2);else if (newBass) ret.push(currentChord.boom);
13564
+ var numChordNotes = currentChord.chick.length;
13622
13565
  if (type.indexOf('chick') >= 0) {
13623
- for (var i = 0; i < currentChord.chick.length; i++) {
13566
+ for (var i = 0; i < numChordNotes; i++) {
13624
13567
  ret.push(currentChord.chick[i]);
13625
13568
  }
13626
13569
  }
@@ -13632,13 +13575,13 @@ function resolvePitch(currentChord, type, firstBoom, newBass) {
13632
13575
  ret.push(currentChord.chick[1]);
13633
13576
  break;
13634
13577
  case 'SOL':
13635
- ret.push(currentChord.chick[2]);
13578
+ ret.push(extractNote(currentChord, 2));
13636
13579
  break;
13637
13580
  case 'TI':
13638
- currentChord.chick.length > 3 ? ret.push(currentChord.chick[2]) : ret.push(currentChord.chick[0] + 12);
13581
+ ret.push(extractNote(currentChord, 3));
13639
13582
  break;
13640
13583
  case 'TOP':
13641
- currentChord.chick.length > 4 ? ret.push(currentChord.chick[2]) : ret.push(currentChord.chick[1] + 12);
13584
+ ret.push(extractNote(currentChord, 4));
13642
13585
  break;
13643
13586
  case 'do':
13644
13587
  ret.push(currentChord.chick[0] + 12);
@@ -13647,17 +13590,24 @@ function resolvePitch(currentChord, type, firstBoom, newBass) {
13647
13590
  ret.push(currentChord.chick[1] + 12);
13648
13591
  break;
13649
13592
  case 'sol':
13650
- ret.push(currentChord.chick[2] + 12);
13593
+ ret.push(extractNote(currentChord, 2) + 12);
13651
13594
  break;
13652
13595
  case 'ti':
13653
- currentChord.chick.length > 3 ? ret.push(currentChord.chick[2] + 12) : ret.push(currentChord.chick[0] + 24);
13596
+ ret.push(extractNote(currentChord, 3) + 12);
13654
13597
  break;
13655
13598
  case 'top':
13656
- currentChord.chick.length > 4 ? ret.push(currentChord.chick[2] + 12) : ret.push(currentChord.chick[1] + 24);
13599
+ ret.push(extractNote(currentChord, 4) + 12);
13657
13600
  break;
13658
13601
  }
13659
13602
  return ret;
13660
13603
  }
13604
+ function extractNote(chord, index) {
13605
+ // 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
13606
+ var octave = Math.floor(index / chord.chick.length);
13607
+ var note = chord.chick[index % chord.chick.length];
13608
+ //console.log(chord.chick, {index, octave, note}, index % chord.chick.length)
13609
+ return note + octave * 12;
13610
+ }
13661
13611
  function parseGChord(gchord) {
13662
13612
  // 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
13613
  var pattern = [];
@@ -13854,7 +13804,11 @@ ChordTrack.prototype.chordIntervals = {
13854
13804
  'maj7#5#11': [0, 4, 8, 11, 18],
13855
13805
  '9(#5)': [0, 4, 8, 10, 14],
13856
13806
  '13(#5)': [0, 4, 8, 10, 14, 21],
13857
- '13#5': [0, 4, 8, 10, 14, 21]
13807
+ '13#5': [0, 4, 8, 10, 14, 21],
13808
+ // MAE Power chords added 10 April 2024
13809
+ '5': [0, 7],
13810
+ '5(8)': [0, 7, 12],
13811
+ '5add8': [0, 7, 12]
13858
13812
  };
13859
13813
  ChordTrack.prototype.rhythmPatterns = {
13860
13814
  "2/2": ['boom', '', '', '', 'chick', '', '', ''],
@@ -13866,8 +13820,12 @@ ChordTrack.prototype.rhythmPatterns = {
13866
13820
  "5/4": ['boom', '', 'chick', '', 'chick', '', 'boom', '', 'chick', ''],
13867
13821
  "6/4": ['boom', '', 'chick', '', 'boom', '', 'chick', '', 'boom', '', 'chick', ''],
13868
13822
  "3/8": ['boom', '', 'chick'],
13823
+ "5/8": ['boom', 'chick', 'chick', 'boom', 'chick'],
13869
13824
  "6/8": ['boom', '', 'chick', 'boom', '', 'chick'],
13825
+ "7/8": ['boom', 'chick', 'chick', 'boom', 'chick', 'boom', 'chick'],
13870
13826
  "9/8": ['boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick'],
13827
+ "10/8": ['boom', 'chick', 'chick', 'boom', 'chick', 'chick', 'boom', 'chick', 'boom', 'chick'],
13828
+ "11/8": ['boom', 'chick', 'chick', 'boom', 'chick', 'chick', 'boom', 'chick', 'boom', 'chick', 'chick'],
13871
13829
  "12/8": ['boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick']
13872
13830
  };
13873
13831
 
@@ -13955,7 +13913,6 @@ module.exports = createNoteMap;
13955
13913
  var supportsAudio = __webpack_require__(/*! ./supports-audio */ "./src/synth/supports-audio.js");
13956
13914
  var registerAudioContext = __webpack_require__(/*! ./register-audio-context */ "./src/synth/register-audio-context.js");
13957
13915
  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
13916
  var loopImage = __webpack_require__(/*! ./images/loop.svg.js */ "./src/synth/images/loop.svg.js");
13960
13917
  var playImage = __webpack_require__(/*! ./images/play.svg.js */ "./src/synth/images/play.svg.js");
13961
13918
  var pauseImage = __webpack_require__(/*! ./images/pause.svg.js */ "./src/synth/images/pause.svg.js");
@@ -13971,7 +13928,7 @@ function CreateSynthControl(parent, options) {
13971
13928
  } else if (!(parent instanceof HTMLElement)) throw new Error("The first parameter must be a valid element or selector in the DOM.");
13972
13929
  self.parent = parent;
13973
13930
  self.options = {};
13974
- if (options) self.options = parseCommon.clone(options);
13931
+ if (options) self.options = Object.assign({}, options);
13975
13932
 
13976
13933
  // This can be called in the following cases:
13977
13934
  // AC already registered and not suspended
@@ -14627,7 +14584,6 @@ function CreateSynth() {
14627
14584
  self.directSource[trackNum].buffer = audioBuffer; // tell the source which sound to play
14628
14585
  self.directSource[trackNum].connect(activeAudioContext().destination); // connect the source to the context's destination (the speakers)
14629
14586
  });
14630
-
14631
14587
  self.directSource.forEach(function (source) {
14632
14588
  source.start(0, seconds);
14633
14589
  });
@@ -15685,72 +15641,272 @@ function SynthController() {
15685
15641
  } else {
15686
15642
  return self.finished();
15687
15643
  }
15688
- };
15689
- self.lineEndCallback = function (lineEvent, leftEvent) {
15690
- if (self.cursorControl && self.cursorControl.onLineEnd && typeof self.cursorControl.onLineEnd === 'function') self.cursorControl.onLineEnd(lineEvent, leftEvent);
15691
- };
15692
- self.getUrl = function () {
15693
- return self.midiBuffer.download();
15694
- };
15695
- self.download = function (fileName) {
15696
- var url = self.getUrl();
15697
- var link = document.createElement('a');
15698
- document.body.appendChild(link);
15699
- link.setAttribute("style", "display: none;");
15700
- link.href = url;
15701
- link.download = fileName ? fileName : 'output.wav';
15702
- link.click();
15703
- window.URL.revokeObjectURL(url);
15704
- document.body.removeChild(link);
15705
- };
15706
- }
15707
- module.exports = SynthController;
15644
+ };
15645
+ self.lineEndCallback = function (lineEvent, leftEvent) {
15646
+ if (self.cursorControl && self.cursorControl.onLineEnd && typeof self.cursorControl.onLineEnd === 'function') self.cursorControl.onLineEnd(lineEvent, leftEvent);
15647
+ };
15648
+ self.getUrl = function () {
15649
+ return self.midiBuffer.download();
15650
+ };
15651
+ self.download = function (fileName) {
15652
+ var url = self.getUrl();
15653
+ var link = document.createElement('a');
15654
+ document.body.appendChild(link);
15655
+ link.setAttribute("style", "display: none;");
15656
+ link.href = url;
15657
+ link.download = fileName ? fileName : 'output.wav';
15658
+ link.click();
15659
+ window.URL.revokeObjectURL(url);
15660
+ document.body.removeChild(link);
15661
+ };
15662
+ }
15663
+ module.exports = SynthController;
15664
+
15665
+ /***/ }),
15666
+
15667
+ /***/ "./src/synth/synth-sequence.js":
15668
+ /*!*************************************!*\
15669
+ !*** ./src/synth/synth-sequence.js ***!
15670
+ \*************************************/
15671
+ /***/ (function(module) {
15672
+
15673
+ var SynthSequence = function SynthSequence() {
15674
+ var self = this;
15675
+ self.tracks = [];
15676
+ self.totalDuration = 0;
15677
+ self.currentInstrument = [];
15678
+ self.starts = [];
15679
+ self.addTrack = function () {
15680
+ self.tracks.push([]);
15681
+ self.currentInstrument.push(0);
15682
+ self.starts.push(0);
15683
+ return self.tracks.length - 1;
15684
+ };
15685
+ self.setInstrument = function (trackNumber, instrumentNumber) {
15686
+ self.tracks[trackNumber].push({
15687
+ channel: 0,
15688
+ cmd: "program",
15689
+ instrument: instrumentNumber
15690
+ });
15691
+ self.currentInstrument[trackNumber] = instrumentNumber;
15692
+ };
15693
+ self.appendNote = function (trackNumber, pitch, durationInMeasures, volume, cents) {
15694
+ var note = {
15695
+ cmd: "note",
15696
+ duration: durationInMeasures,
15697
+ gap: 0,
15698
+ instrument: self.currentInstrument[trackNumber],
15699
+ pitch: pitch,
15700
+ start: self.starts[trackNumber],
15701
+ volume: volume
15702
+ };
15703
+ if (cents) note.cents = cents;
15704
+ self.tracks[trackNumber].push(note);
15705
+ self.starts[trackNumber] += durationInMeasures;
15706
+ self.totalDuration = Math.max(self.totalDuration, self.starts[trackNumber]);
15707
+ };
15708
+ };
15709
+ module.exports = SynthSequence;
15710
+
15711
+ /***/ }),
15712
+
15713
+ /***/ "./src/tablatures/abc_tablatures.js":
15714
+ /*!******************************************!*\
15715
+ !*** ./src/tablatures/abc_tablatures.js ***!
15716
+ \******************************************/
15717
+ /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
15718
+
15719
+ /*
15720
+ * Tablature Plugins
15721
+ * tablature are defined dynamically and registered inside abcjs
15722
+ * by calling abcTablatures.register(plugin)
15723
+ * where plugin represents a plugin instance
15724
+ *
15725
+ */
15726
+
15727
+ // This is the only entry point to the tablatures. It is called both after parsing a tune and just before engraving
15728
+
15729
+ var TabString = __webpack_require__(/*! ./instruments/tab-string */ "./src/tablatures/instruments/tab-string.js");
15730
+
15731
+ /* extend the table below when adding a new instrument plugin */
15732
+
15733
+ // Existing tab classes
15734
+ var pluginTab = {
15735
+ 'violin': {
15736
+ name: 'StringTab',
15737
+ defaultTuning: ['G,', 'D', 'A', 'e'],
15738
+ isTabBig: false,
15739
+ tabSymbolOffset: 0
15740
+ },
15741
+ 'fiddle': {
15742
+ name: 'StringTab',
15743
+ defaultTuning: ['G,', 'D', 'A', 'e'],
15744
+ isTabBig: false,
15745
+ tabSymbolOffset: 0
15746
+ },
15747
+ 'mandolin': {
15748
+ name: 'StringTab',
15749
+ defaultTuning: ['G,', 'D', 'A', 'e'],
15750
+ isTabBig: false,
15751
+ tabSymbolOffset: 0
15752
+ },
15753
+ 'guitar': {
15754
+ name: 'StringTab',
15755
+ defaultTuning: ['E,', 'A,', 'D', 'G', 'B', 'e'],
15756
+ isTabBig: true,
15757
+ tabSymbolOffset: 0
15758
+ },
15759
+ 'fiveString': {
15760
+ name: 'StringTab',
15761
+ defaultTuning: ['C,', 'G,', 'D', 'A', 'e'],
15762
+ isTabBig: false,
15763
+ tabSymbolOffset: -.95
15764
+ }
15765
+ };
15766
+ var abcTablatures = {
15767
+ inited: false,
15768
+ plugins: {},
15769
+ /**
15770
+ * to be called once per plugin for registration
15771
+ * @param {*} plugin
15772
+ */
15773
+ register: function register(plugin) {
15774
+ var name = plugin.name;
15775
+ var tablature = plugin.tablature;
15776
+ this.plugins[name] = tablature;
15777
+ },
15778
+ setError: function setError(tune, msg) {
15779
+ if (tune.warnings) {
15780
+ tune.warning.push(msg);
15781
+ } else {
15782
+ tune.warnings = [msg];
15783
+ }
15784
+ },
15785
+ /**
15786
+ * handle params for current processed score
15787
+ * @param {*} tune current tune
15788
+ * @param {*} tuneNumber number in tune list
15789
+ * @param {*} params params to be processed for tablature
15790
+ * @return prepared tablatures plugin instances for current tune
15791
+ */
15792
+ preparePlugins: function preparePlugins(tune, tuneNumber, params) {
15793
+ // Called after parsing a tune and before engraving it
15794
+ if (!this.inited) {
15795
+ // TODO-PER: I don't think this is needed - the plugin array can be hard coded, right?
15796
+ this.register(new TabString());
15797
+ this.inited = true;
15798
+ }
15799
+ var returned = null;
15800
+ var nbPlugins = 0;
15801
+ if (params.tablature) {
15802
+ // validate requested plugins
15803
+ var tabs = params.tablature;
15804
+ returned = [];
15805
+ for (var ii = 0; ii < tabs.length; ii++) {
15806
+ var args = tabs[ii];
15807
+ var instrument = args['instrument'];
15808
+ if (instrument == null) {
15809
+ this.setError(tune, "tablature 'instrument' is missing");
15810
+ return returned;
15811
+ }
15812
+ var tabName = pluginTab[instrument];
15813
+ var plugin = null;
15814
+ if (tabName) {
15815
+ plugin = this.plugins[tabName.name];
15816
+ }
15817
+ if (plugin) {
15818
+ if (params.visualTranspose != 0) {
15819
+ // populate transposition request to tabs
15820
+ args.visualTranspose = params.visualTranspose;
15821
+ }
15822
+ args.abcSrc = params.tablature.abcSrc;
15823
+ var pluginInstance = {
15824
+ classz: plugin,
15825
+ tuneNumber: tuneNumber,
15826
+ params: args,
15827
+ instance: null,
15828
+ tabType: tabName
15829
+ };
15830
+ // proceed with tab plugin init
15831
+ // plugin.init(tune, tuneNumber, args, ii);
15832
+ returned.push(pluginInstance);
15833
+ nbPlugins++;
15834
+ } else if (instrument === '') {
15835
+ // create a placeholder - there is no tab for this staff
15836
+ returned.push(null);
15837
+ } else {
15838
+ // unknown tab plugin
15839
+ //this.emit_error('Undefined tablature plugin: ' + tabName)
15840
+ this.setError(tune, 'Undefined tablature plugin: ' + instrument);
15841
+ return returned;
15842
+ }
15843
+ }
15844
+ }
15845
+ return returned;
15846
+ },
15847
+ /**
15848
+ * Call requested plugin
15849
+ * @param {*} renderer
15850
+ * @param {*} abcTune
15851
+ */
15852
+ layoutTablatures: function layoutTablatures(renderer, abcTune) {
15853
+ var tabs = abcTune.tablatures;
15708
15854
 
15709
- /***/ }),
15855
+ // chack tabs request for each staffs
15856
+ var staffLineCount = 0;
15710
15857
 
15711
- /***/ "./src/synth/synth-sequence.js":
15712
- /*!*************************************!*\
15713
- !*** ./src/synth/synth-sequence.js ***!
15714
- \*************************************/
15715
- /***/ (function(module) {
15858
+ // Clear the suppression flag
15859
+ if (tabs && tabs.length > 0) {
15860
+ var nTabs = tabs.length;
15861
+ for (var kk = 0; kk < nTabs; ++kk) {
15862
+ if (tabs[kk] && tabs[kk].params.firstStaffOnly) {
15863
+ tabs[kk].params.suppress = false;
15864
+ }
15865
+ }
15866
+ }
15867
+ for (var ii = 0; ii < abcTune.lines.length; ii++) {
15868
+ var line = abcTune.lines[ii];
15869
+ if (line.staff) {
15870
+ staffLineCount++;
15871
+ }
15716
15872
 
15717
- var SynthSequence = function SynthSequence() {
15718
- var self = this;
15719
- self.tracks = [];
15720
- self.totalDuration = 0;
15721
- self.currentInstrument = [];
15722
- self.starts = [];
15723
- self.addTrack = function () {
15724
- self.tracks.push([]);
15725
- self.currentInstrument.push(0);
15726
- self.starts.push(0);
15727
- return self.tracks.length - 1;
15728
- };
15729
- self.setInstrument = function (trackNumber, instrumentNumber) {
15730
- self.tracks[trackNumber].push({
15731
- channel: 0,
15732
- cmd: "program",
15733
- instrument: instrumentNumber
15734
- });
15735
- self.currentInstrument[trackNumber] = instrumentNumber;
15736
- };
15737
- self.appendNote = function (trackNumber, pitch, durationInMeasures, volume, cents) {
15738
- var note = {
15739
- cmd: "note",
15740
- duration: durationInMeasures,
15741
- gap: 0,
15742
- instrument: self.currentInstrument[trackNumber],
15743
- pitch: pitch,
15744
- start: self.starts[trackNumber],
15745
- volume: volume
15746
- };
15747
- if (cents) note.cents = cents;
15748
- self.tracks[trackNumber].push(note);
15749
- self.starts[trackNumber] += durationInMeasures;
15750
- self.totalDuration = Math.max(self.totalDuration, self.starts[trackNumber]);
15751
- };
15873
+ // MAE 27Nov2023
15874
+ // If tab param "firstStaffOnly", remove the tab label after the first staff
15875
+ if (staffLineCount > 1) {
15876
+ if (tabs && tabs.length > 0) {
15877
+ var nTabs = tabs.length;
15878
+ for (var kk = 0; kk < nTabs; ++kk) {
15879
+ if (tabs[kk].params.firstStaffOnly) {
15880
+ // Set the staff draw suppression flag
15881
+ tabs[kk].params.suppress = true;
15882
+ }
15883
+ }
15884
+ }
15885
+ }
15886
+ var curStaff = line.staff;
15887
+ if (curStaff) {
15888
+ var maxStaves = curStaff.length;
15889
+ for (var jj = 0; jj < curStaff.length; jj++) {
15890
+ if (tabs[jj] && jj < maxStaves) {
15891
+ // tablature requested for staff
15892
+ var tabPlugin = tabs[jj];
15893
+ if (tabPlugin.instance == null) {
15894
+ //console.log("★★★★ Tab Init line: " + ii + " staff: " + jj)
15895
+ tabPlugin.instance = new tabPlugin.classz();
15896
+ // plugin.init(tune, tuneNumber, args, ii);
15897
+ // call initer first
15898
+ tabPlugin.instance.init(abcTune, tabPlugin.tuneNumber, tabPlugin.params, tabPlugin.tabType);
15899
+ }
15900
+ // render next
15901
+ //console.log("★★★★ Tab Render line: " + ii + " staff: " + jj)
15902
+ tabPlugin.instance.render(renderer, line, jj);
15903
+ }
15904
+ }
15905
+ }
15906
+ }
15907
+ }
15752
15908
  };
15753
- module.exports = SynthSequence;
15909
+ module.exports = abcTablatures;
15754
15910
 
15755
15911
  /***/ }),
15756
15912
 
@@ -15763,14 +15919,14 @@ module.exports = SynthSequence;
15763
15919
  var _require = __webpack_require__(/*! ../../synth/note-to-midi */ "./src/synth/note-to-midi.js"),
15764
15920
  noteToMidi = _require.noteToMidi;
15765
15921
  var TabNote = __webpack_require__(/*! ./tab-note */ "./src/tablatures/instruments/tab-note.js");
15766
- var TabNotes = __webpack_require__(/*! ./tab-notes */ "./src/tablatures/instruments/tab-notes.js");
15922
+ var tabNotes = __webpack_require__(/*! ./tab-notes */ "./src/tablatures/instruments/tab-notes.js");
15767
15923
  function buildCapo(self) {
15768
15924
  var capoTuning = null;
15769
15925
  var tuning = self.tuning;
15770
15926
  if (self.capo > 0) {
15771
15927
  capoTuning = [];
15772
15928
  for (var iii = 0; iii < tuning.length; iii++) {
15773
- var curNote = new TabNote.TabNote(tuning[iii]);
15929
+ var curNote = new TabNote(tuning[iii]);
15774
15930
  for (var jjj = 0; jjj < self.capo; jjj++) {
15775
15931
  curNote = curNote.nextNote();
15776
15932
  }
@@ -15791,8 +15947,7 @@ function buildPatterns(self) {
15791
15947
  if (iii != tuning.length - 1) {
15792
15948
  nextNote = tuning[iii + 1];
15793
15949
  }
15794
- var tabNotes = new TabNotes(tuning[iii], nextNote);
15795
- var stringNotes = tabNotes.build();
15950
+ var stringNotes = tabNotes(tuning[iii], nextNote);
15796
15951
  if (stringNotes.error) {
15797
15952
  return stringNotes;
15798
15953
  }
@@ -15841,7 +15996,7 @@ function handleChordNotes(self, notes) {
15841
15996
  var retNotes = [];
15842
15997
  for (var iiii = 0; iiii < notes.length; iiii++) {
15843
15998
  if (notes[iiii].endTie) continue;
15844
- var note = new TabNote.TabNote(notes[iiii].name, self.clefTranspose);
15999
+ var note = new TabNote(notes[iiii].name, self.clefTranspose);
15845
16000
  note.checkKeyAccidentals(self.accidentals, self.measureAccidentals);
15846
16001
  var curPos = toNumber(self, note);
15847
16002
  retNotes.push(curPos);
@@ -15933,7 +16088,7 @@ StringPatterns.prototype.notesToNumber = function (notes, graces) {
15933
16088
  }
15934
16089
  } else {
15935
16090
  if (!notes[0].endTie) {
15936
- note = new TabNote.TabNote(notes[0].name, this.clefTranspose);
16091
+ note = new TabNote(notes[0].name, this.clefTranspose);
15937
16092
  note.checkKeyAccidentals(this.accidentals, this.measureAccidentals);
15938
16093
  number = toNumber(this, note);
15939
16094
  if (number) {
@@ -15950,7 +16105,7 @@ StringPatterns.prototype.notesToNumber = function (notes, graces) {
15950
16105
  if (graces) {
15951
16106
  retGraces = [];
15952
16107
  for (var iiii = 0; iiii < graces.length; iiii++) {
15953
- note = new TabNote.TabNote(graces[iiii].name, this.clefTranspose);
16108
+ note = new TabNote(graces[iiii].name, this.clefTranspose);
15954
16109
  note.checkKeyAccidentals(this.accidentals, this.measureAccidentals);
15955
16110
  number = toNumber(this, note);
15956
16111
  if (number) {
@@ -15977,8 +16132,7 @@ StringPatterns.prototype.toString = function () {
15977
16132
  return arr.join('');
15978
16133
  };
15979
16134
  StringPatterns.prototype.tabInfos = function (plugin) {
15980
- var _super = plugin._super;
15981
- var name = _super.params.label;
16135
+ var name = plugin.params.label;
15982
16136
  if (name) {
15983
16137
  var tunePos = name.indexOf('%T');
15984
16138
  var tuning = "";
@@ -15996,8 +16150,7 @@ StringPatterns.prototype.tabInfos = function (plugin) {
15996
16150
 
15997
16151
  // MAE 27 Nov 2023
15998
16152
  StringPatterns.prototype.suppress = function (plugin) {
15999
- var _super = plugin._super;
16000
- var suppress = _super.params.suppress;
16153
+ var suppress = plugin.params.suppress;
16001
16154
  if (suppress) {
16002
16155
  return true;
16003
16156
  }
@@ -16013,9 +16166,10 @@ StringPatterns.prototype.suppress = function (plugin) {
16013
16166
  * @param {*} highestNote
16014
16167
  */
16015
16168
  function StringPatterns(plugin) {
16169
+ //console.log("INIT StringPatterns constructor")
16016
16170
  var tuning = plugin.tuning;
16017
16171
  var capo = plugin.capo;
16018
- var highestNote = plugin._super.params.highestNote;
16172
+ var highestNote = plugin.params.highestNote;
16019
16173
  this.linePitch = plugin.linePitch;
16020
16174
  this.highestNote = "a'";
16021
16175
  if (highestNote) {
@@ -16039,7 +16193,7 @@ function StringPatterns(plugin) {
16039
16193
  }
16040
16194
  this.strings = buildPatterns(this);
16041
16195
  if (this.strings.error) {
16042
- plugin._super.setError(this.strings.error);
16196
+ plugin.setError(this.strings.error);
16043
16197
  plugin.inError = true;
16044
16198
  return;
16045
16199
  }
@@ -16063,6 +16217,7 @@ module.exports = StringPatterns;
16063
16217
  */
16064
16218
 
16065
16219
  function StringTablature(numLines, lineSpace) {
16220
+ //console.log("INIT StringTablature constructor")
16066
16221
  this.numLines = numLines;
16067
16222
  this.lineSpace = lineSpace;
16068
16223
  this.verticalSize = this.numLines * this.lineSpace;
@@ -16079,6 +16234,7 @@ function StringTablature(numLines, lineSpace) {
16079
16234
  * @param {} line
16080
16235
  */
16081
16236
  StringTablature.prototype.bypass = function (line) {
16237
+ //console.log("RENDER StringTablature bypass")
16082
16238
  var voices = line.staffGroup.voices;
16083
16239
  if (voices.length > 0) {
16084
16240
  if (voices[0].isPercussion) return true;
@@ -16086,6 +16242,7 @@ StringTablature.prototype.bypass = function (line) {
16086
16242
  return false;
16087
16243
  };
16088
16244
  StringTablature.prototype.setRelative = function (child, relative, first) {
16245
+ //console.log("RENDER StringTablature setRelative")
16089
16246
  switch (child.type) {
16090
16247
  case 'bar':
16091
16248
  relative.pitch = this.bar.pitch;
@@ -16126,8 +16283,9 @@ var _require = __webpack_require__(/*! ../../synth/note-to-midi */ "./src/synth/
16126
16283
  * Note structure for Tabs
16127
16284
  *
16128
16285
  */
16129
- var notes = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
16286
+
16130
16287
  function TabNote(note, clefTranspose) {
16288
+ //console.log("INIT/RENDER TabNote constructor")
16131
16289
  var pitch = noteToMidi(note);
16132
16290
  if (clefTranspose) pitch += clefTranspose;
16133
16291
  var newNote = midiToNote(pitch);
@@ -16209,12 +16367,15 @@ function cloneNote(self) {
16209
16367
  return newTabNote;
16210
16368
  }
16211
16369
  TabNote.prototype.sameNoteAs = function (note) {
16370
+ //console.log("INIT TabNote sameNoteAs")
16212
16371
  return note.pitch === this.pitch;
16213
16372
  };
16214
16373
  TabNote.prototype.isLowerThan = function (note) {
16374
+ //console.log("INIT TabNote isLowerThan")
16215
16375
  return note.pitch > this.pitch;
16216
16376
  };
16217
16377
  TabNote.prototype.checkKeyAccidentals = function (accidentals, measureAccidentals) {
16378
+ //console.log("RENDER TabNote checkKeyAccidentals")
16218
16379
  if (this.isAltered || this.natural) return;
16219
16380
  if (measureAccidentals[this.name.toUpperCase()]) {
16220
16381
  switch (measureAccidentals[this.name.toUpperCase()]) {
@@ -16259,6 +16420,7 @@ TabNote.prototype.checkKeyAccidentals = function (accidentals, measureAccidental
16259
16420
  }
16260
16421
  };
16261
16422
  TabNote.prototype.getAccidentalEquiv = function () {
16423
+ //console.log("TabNote getAccidentalEquiv")
16262
16424
  var cloned = cloneNote(this);
16263
16425
  if (cloned.isSharp || cloned.isKeySharp) {
16264
16426
  cloned = cloned.nextNote();
@@ -16274,14 +16436,17 @@ TabNote.prototype.getAccidentalEquiv = function () {
16274
16436
  return cloned;
16275
16437
  };
16276
16438
  TabNote.prototype.nextNote = function () {
16439
+ //console.log("INIT TabNote nextNote")
16277
16440
  var note = midiToNote(this.pitch + 1 + this.pitchAltered);
16278
16441
  return new TabNote(note);
16279
16442
  };
16280
16443
  TabNote.prototype.prevNote = function () {
16444
+ //console.log("TabNote prevNote")
16281
16445
  var note = midiToNote(this.pitch - 1 + this.pitchAltered);
16282
16446
  return new TabNote(note);
16283
16447
  };
16284
16448
  TabNote.prototype.emitNoAccidentals = function () {
16449
+ //console.log("TabNote emitNoAccidentals")
16285
16450
  var returned = this.name;
16286
16451
  if (this.isLower) {
16287
16452
  returned = returned.toLowerCase();
@@ -16295,6 +16460,7 @@ TabNote.prototype.emitNoAccidentals = function () {
16295
16460
  return returned;
16296
16461
  };
16297
16462
  TabNote.prototype.emit = function () {
16463
+ //console.log("INIT/RENDER TabNote emit")
16298
16464
  var returned = this.name;
16299
16465
  if (this.isSharp || this.isKeySharp) {
16300
16466
  returned = '^' + returned;
@@ -16329,10 +16495,7 @@ TabNote.prototype.emit = function () {
16329
16495
  }
16330
16496
  return returned;
16331
16497
  };
16332
- module.exports = {
16333
- 'TabNote': TabNote,
16334
- 'notes': notes
16335
- };
16498
+ module.exports = TabNote;
16336
16499
 
16337
16500
  /***/ }),
16338
16501
 
@@ -16343,14 +16506,11 @@ module.exports = {
16343
16506
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
16344
16507
 
16345
16508
  var TabNote = __webpack_require__(/*! ./tab-note */ "./src/tablatures/instruments/tab-note.js");
16346
- var notes = TabNote.notes;
16347
- function TabNotes(fromNote, toNote) {
16348
- this.fromN = new TabNote.TabNote(fromNote);
16349
- this.toN = new TabNote.TabNote(toNote);
16350
- }
16351
- TabNotes.prototype.build = function () {
16352
- var fromN = this.fromN;
16353
- var toN = this.toN;
16509
+ var notes = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
16510
+ function tabNotes(fromNote, toNote) {
16511
+ //console.log("INIT TabNotes")
16512
+ var fromN = new TabNote(fromNote);
16513
+ var toN = new TabNote(toNote);
16354
16514
  // check that toN is not lower than fromN
16355
16515
  if (toN.isLowerThan(fromN)) {
16356
16516
  var from = fromN.emit();
@@ -16374,35 +16534,8 @@ TabNotes.prototype.build = function () {
16374
16534
  }
16375
16535
  }
16376
16536
  return buildReturned;
16377
- };
16378
- module.exports = TabNotes;
16379
-
16380
- /***/ }),
16381
-
16382
- /***/ "./src/tablatures/instruments/tab-string-patterns.js":
16383
- /*!***********************************************************!*\
16384
- !*** ./src/tablatures/instruments/tab-string-patterns.js ***!
16385
- \***********************************************************/
16386
- /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
16387
-
16388
- var StringPatterns = __webpack_require__(/*! ./string-patterns */ "./src/tablatures/instruments/string-patterns.js");
16389
- function TabStringPatterns(plugin, defaultTuning) {
16390
- this.tuning = plugin._super.params.tuning;
16391
- if (!this.tuning) {
16392
- this.tuning = defaultTuning;
16393
- }
16394
- plugin.tuning = this.tuning;
16395
- this.strings = new StringPatterns(plugin);
16396
16537
  }
16397
- TabStringPatterns.prototype.notesToNumber = function (notes, graces) {
16398
- var converter = this.strings;
16399
- return converter.notesToNumber(notes, graces);
16400
- };
16401
- TabStringPatterns.prototype.stringToPitch = function (stringNumber) {
16402
- var converter = this.strings;
16403
- return converter.stringToPitch(stringNumber);
16404
- };
16405
- module.exports = TabStringPatterns;
16538
+ module.exports = tabNotes;
16406
16539
 
16407
16540
  /***/ }),
16408
16541
 
@@ -16413,20 +16546,22 @@ module.exports = TabStringPatterns;
16413
16546
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
16414
16547
 
16415
16548
  var StringTablature = __webpack_require__(/*! ./string-tablature */ "./src/tablatures/instruments/string-tablature.js");
16416
- var TabCommon = __webpack_require__(/*! ../tab-common */ "./src/tablatures/tab-common.js");
16417
- var TabRenderer = __webpack_require__(/*! ../tab-renderer */ "./src/tablatures/tab-renderer.js");
16418
- var TabStringPatterns = __webpack_require__(/*! ./tab-string-patterns */ "./src/tablatures/instruments/tab-string-patterns.js");
16549
+ var tabRenderer = __webpack_require__(/*! ../render/tab-renderer */ "./src/tablatures/render/tab-renderer.js");
16550
+ var StringPatterns = __webpack_require__(/*! ./string-patterns */ "./src/tablatures/instruments/string-patterns.js");
16419
16551
 
16420
16552
  /**
16421
16553
  * upon init mainly store provided instances for later usage
16422
16554
  * @param {*} abcTune the parsed tune AST tree
16423
- * @param {*} tuneNumber the parsed tune AST tree
16555
+ * @param {*} tuneNumber the parsed tune AST tree
16424
16556
  * @param {*} params complementary args provided to Tablature Plugin
16425
16557
  */
16426
- Plugin.prototype.init = function (abcTune, tuneNumber, params, staffNumber, tabSettings) {
16427
- var _super = new TabCommon(abcTune, tuneNumber, params);
16558
+ Plugin.prototype.init = function (abcTune, tuneNumber, params, tabSettings) {
16559
+ //console.log("INIT AbcStringTab Plugin.init")
16560
+ this.tune = abcTune;
16561
+ this.params = params;
16562
+ this.tuneNumber = tuneNumber;
16563
+ this.inError = false;
16428
16564
  this.abcTune = abcTune;
16429
- this._super = _super;
16430
16565
  this.linePitch = 3;
16431
16566
  this.nbLines = tabSettings.defaultTuning.length;
16432
16567
  this.isTabBig = tabSettings.isTabBig;
@@ -16435,14 +16570,30 @@ Plugin.prototype.init = function (abcTune, tuneNumber, params, staffNumber, tabS
16435
16570
  this.transpose = params.visualTranspose;
16436
16571
  this.hideTabSymbol = params.hideTabSymbol;
16437
16572
  this.tablature = new StringTablature(this.nbLines, this.linePitch);
16438
- var semantics = new TabStringPatterns(this, tabSettings.defaultTuning);
16439
- this.semantics = semantics;
16573
+ var tuning = params.tuning;
16574
+ if (!tuning) {
16575
+ tuning = tabSettings.defaultTuning;
16576
+ }
16577
+ this.tuning = tuning;
16578
+ this.semantics = new StringPatterns(this);
16579
+ };
16580
+ Plugin.prototype.setError = function (error) {
16581
+ //console.log("Plugin setError")
16582
+ if (error) {
16583
+ this.error = error;
16584
+ this.inError = true;
16585
+ if (this.tune.warnings) {
16586
+ this.tune.warnings.push(error);
16587
+ } else {
16588
+ this.tune.warnings = [error];
16589
+ }
16590
+ }
16440
16591
  };
16441
16592
  Plugin.prototype.render = function (renderer, line, staffIndex) {
16442
- if (this._super.inError) return;
16593
+ //console.log("RENDER AbcStringTab Plugin.render")
16594
+ if (this.inError) return;
16443
16595
  if (this.tablature.bypass(line)) return;
16444
- var rndrer = new TabRenderer(this, renderer, line, staffIndex);
16445
- rndrer.doLayout();
16596
+ tabRenderer(this, renderer, line, staffIndex);
16446
16597
  };
16447
16598
  function Plugin() {}
16448
16599
 
@@ -16459,17 +16610,17 @@ module.exports = AbcStringTab;
16459
16610
 
16460
16611
  /***/ }),
16461
16612
 
16462
- /***/ "./src/tablatures/tab-absolute-elements.js":
16463
- /*!*************************************************!*\
16464
- !*** ./src/tablatures/tab-absolute-elements.js ***!
16465
- \*************************************************/
16613
+ /***/ "./src/tablatures/render/tab-absolute-elements.js":
16614
+ /*!********************************************************!*\
16615
+ !*** ./src/tablatures/render/tab-absolute-elements.js ***!
16616
+ \********************************************************/
16466
16617
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
16467
16618
 
16468
16619
  /**
16469
16620
  * Tablature Absolute elements factory
16470
16621
  */
16471
- var AbsoluteElement = __webpack_require__(/*! ../write/creation/elements/absolute-element */ "./src/write/creation/elements/absolute-element.js");
16472
- var RelativeElement = __webpack_require__(/*! ../write/creation/elements/relative-element */ "./src/write/creation/elements/relative-element.js");
16622
+ var AbsoluteElement = __webpack_require__(/*! ../../write/creation/elements/absolute-element */ "./src/write/creation/elements/absolute-element.js");
16623
+ var RelativeElement = __webpack_require__(/*! ../../write/creation/elements/relative-element */ "./src/write/creation/elements/relative-element.js");
16473
16624
  function isObject(a) {
16474
16625
  return a != null && a.constructor === Object;
16475
16626
  }
@@ -16556,6 +16707,7 @@ function lyricsDim(abs) {
16556
16707
  return null;
16557
16708
  }
16558
16709
  function TabAbsoluteElements() {
16710
+ //console.log("RENDER TabAbsoluteElements constructor")
16559
16711
  this.accidentals = null;
16560
16712
  }
16561
16713
  function getInitialStaffSize(staffGroup) {
@@ -16614,10 +16766,9 @@ function graceInRest(absElem) {
16614
16766
  function convertToNumber(plugin, pitches, graceNotes) {
16615
16767
  var tabPos = plugin.semantics.notesToNumber(pitches, graceNotes);
16616
16768
  if (tabPos.error) {
16617
- plugin._super.setError(tabPos.error);
16769
+ plugin.setError(tabPos.error);
16618
16770
  return tabPos; // give up on error here
16619
16771
  }
16620
-
16621
16772
  if (tabPos.graces && tabPos.notes) {
16622
16773
  // add graces to last note in notes
16623
16774
  var posNote = tabPos.notes.length - 1;
@@ -16647,6 +16798,7 @@ function buildGraceRelativesForRest(plugin, abs, absChild, graceNotes, tabVoice)
16647
16798
  * @param {*} staffAbsolute
16648
16799
  */
16649
16800
  TabAbsoluteElements.prototype.build = function (plugin, staffAbsolute, tabVoice, voiceIndex, staffIndex, keySig, tabVoiceIndex) {
16801
+ //console.log("RENDER TabAbsoluteElements build")
16650
16802
  var staffSize = getInitialStaffSize(staffAbsolute);
16651
16803
  var source = staffAbsolute[staffIndex + voiceIndex];
16652
16804
  var dest = staffAbsolute[tabVoiceIndex];
@@ -16667,17 +16819,17 @@ TabAbsoluteElements.prototype.build = function (plugin, staffAbsolute, tabVoice,
16667
16819
  // }
16668
16820
  if (absChild.isClef) {
16669
16821
  dest.children.push(buildTabAbsolute(plugin, absX, relX));
16670
- if (absChild.abcelem.type.indexOf('-8') >= 0) plugin.semantics.strings.clefTranspose = -12;
16671
- if (absChild.abcelem.type.indexOf('+8') >= 0) plugin.semantics.strings.clefTranspose = 12;
16822
+ if (absChild.abcelem.type.indexOf('-8') >= 0) plugin.semantics.clefTranspose = -12;
16823
+ if (absChild.abcelem.type.indexOf('+8') >= 0) plugin.semantics.clefTranspose = 12;
16672
16824
  }
16673
16825
  switch (absChild.type) {
16674
16826
  case 'staff-extra key-signature':
16675
16827
  // refresh key accidentals
16676
16828
  this.accidentals = absChild.abcelem.accidentals;
16677
- plugin.semantics.strings.accidentals = this.accidentals;
16829
+ plugin.semantics.accidentals = this.accidentals;
16678
16830
  break;
16679
16831
  case 'bar':
16680
- plugin.semantics.strings.measureAccidentals = {};
16832
+ plugin.semantics.measureAccidentals = {};
16681
16833
  var lastBar = false;
16682
16834
  if (ii === source.children.length - 1) {
16683
16835
  // used for final line bar drawing
@@ -16777,50 +16929,16 @@ module.exports = TabAbsoluteElements;
16777
16929
 
16778
16930
  /***/ }),
16779
16931
 
16780
- /***/ "./src/tablatures/tab-common.js":
16781
- /*!**************************************!*\
16782
- !*** ./src/tablatures/tab-common.js ***!
16783
- \**************************************/
16784
- /***/ (function(module) {
16785
-
16786
- /**
16787
- *
16788
- * Common Class/Method available for all instruments
16789
- *
16790
- */
16791
-
16792
- function TabCommon(abcTune, tuneNumber, params) {
16793
- this.tune = abcTune;
16794
- this.params = params;
16795
- this.tuneNumber = tuneNumber;
16796
- this.inError = false;
16797
- }
16798
- TabCommon.prototype.setError = function (error) {
16799
- var tune = this.tune;
16800
- if (error) {
16801
- this.error = error;
16802
- this.inError = true;
16803
- if (tune.warnings) {
16804
- tune.warnings.push(error);
16805
- } else {
16806
- tune.warnings = [error];
16807
- }
16808
- }
16809
- };
16810
- module.exports = TabCommon;
16811
-
16812
- /***/ }),
16813
-
16814
- /***/ "./src/tablatures/tab-renderer.js":
16815
- /*!****************************************!*\
16816
- !*** ./src/tablatures/tab-renderer.js ***!
16817
- \****************************************/
16932
+ /***/ "./src/tablatures/render/tab-renderer.js":
16933
+ /*!***********************************************!*\
16934
+ !*** ./src/tablatures/render/tab-renderer.js ***!
16935
+ \***********************************************/
16818
16936
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
16819
16937
 
16820
16938
  /* eslint-disable no-debugger */
16821
- var VoiceElement = __webpack_require__(/*! ../write/creation/elements/voice-element */ "./src/write/creation/elements/voice-element.js");
16822
- var TabAbsoluteElements = __webpack_require__(/*! ./tab-absolute-elements */ "./src/tablatures/tab-absolute-elements.js");
16823
- var spacing = __webpack_require__(/*! ../write/helpers/spacing */ "./src/write/helpers/spacing.js");
16939
+ var VoiceElement = __webpack_require__(/*! ../../write/creation/elements/voice-element */ "./src/write/creation/elements/voice-element.js");
16940
+ var TabAbsoluteElements = __webpack_require__(/*! ./tab-absolute-elements */ "./src/tablatures/render/tab-absolute-elements.js");
16941
+ var spacing = __webpack_require__(/*! ../../write/helpers/spacing */ "./src/write/helpers/spacing.js");
16824
16942
  function initSpecialY() {
16825
16943
  return {
16826
16944
  tempoHeightAbove: 0,
@@ -16848,13 +16966,11 @@ function getLyricHeight(voice) {
16848
16966
  }
16849
16967
  return maxLyricHeight; // add spacing
16850
16968
  }
16851
-
16852
- function buildTabName(self, dest) {
16853
- var stringSemantics = self.plugin.semantics.strings;
16854
- var controller = self.renderer.controller;
16855
- var textSize = controller.getTextSize;
16856
- var tabName = stringSemantics.tabInfos(self.plugin);
16857
- var suppress = stringSemantics.suppress(self.plugin);
16969
+ function buildTabName(plugin, renderer, dest) {
16970
+ var stringSemantics = plugin.semantics;
16971
+ var textSize = renderer.controller.getTextSize;
16972
+ var tabName = stringSemantics.tabInfos(plugin);
16973
+ var suppress = stringSemantics.suppress(plugin);
16858
16974
  var doDraw = true;
16859
16975
  if (suppress) {
16860
16976
  doDraw = false;
@@ -16872,27 +16988,6 @@ function buildTabName(self, dest) {
16872
16988
  }
16873
16989
  return 0;
16874
16990
  }
16875
-
16876
- /**
16877
- * Laying out tabs
16878
- * @param {*} renderer
16879
- * @param {*} line
16880
- * @param {*} staffIndex
16881
- * @param {*} tablatureLayout
16882
- */
16883
- function TabRenderer(plugin, renderer, line, staffIndex) {
16884
- this.renderer = renderer;
16885
- this.plugin = plugin;
16886
- this.line = line;
16887
- this.absolutes = new TabAbsoluteElements();
16888
- this.staffIndex = staffIndex;
16889
- this.tabStaff = {
16890
- clef: {
16891
- type: 'TAB'
16892
- }
16893
- };
16894
- this.tabSize = plugin.linePitch * plugin.nbLines;
16895
- }
16896
16991
  function islastTabInStaff(index, staffGroup) {
16897
16992
  if (staffGroup[index].isTabStaff) {
16898
16993
  if (index === staffGroup.length - 1) return true;
@@ -16938,8 +17033,7 @@ function isMultiVoiceSingleStaff(staffs, parent) {
16938
17033
  }
16939
17034
  return false;
16940
17035
  }
16941
- function getNextTabPos(self, staffGroup) {
16942
- var tabIndex = self.staffIndex;
17036
+ function getNextTabPos(tabIndex, staffGroup) {
16943
17037
  var startIndex = 0;
16944
17038
  var handledVoices = 0;
16945
17039
  var inProgress = true;
@@ -16950,7 +17044,6 @@ function getNextTabPos(self, staffGroup) {
16950
17044
  if (!staffGroup[startIndex].isTabStaff) {
16951
17045
  nbVoices = staffGroup[startIndex].voices.length; // get number of staff voices
16952
17046
  }
16953
-
16954
17047
  if (staffGroup[startIndex].isTabStaff) {
16955
17048
  handledVoices++;
16956
17049
  if (islastTabInStaff(startIndex, staffGroup)) {
@@ -16988,30 +17081,38 @@ function checkVoiceKeySig(voices, ii) {
16988
17081
  }
16989
17082
  return voices[ii - 1].children[0];
16990
17083
  }
16991
- TabRenderer.prototype.doLayout = function () {
16992
- var staffs = this.line.staff;
17084
+ function tabRenderer(plugin, renderer, line, staffIndex) {
17085
+ //console.log("RENDER tabRenderer")
17086
+ var absolutes = new TabAbsoluteElements();
17087
+ var tabStaff = {
17088
+ clef: {
17089
+ type: 'TAB'
17090
+ }
17091
+ };
17092
+ var tabSize = plugin.linePitch * plugin.nbLines;
17093
+ var staffs = line.staff;
16993
17094
  if (staffs) {
16994
17095
  // give up on staffline=0 in key
16995
17096
  var firstStaff = staffs[0];
16996
17097
  if (firstStaff) {
16997
17098
  if (firstStaff.clef) {
16998
17099
  if (firstStaff.clef.stafflines == 0) {
16999
- this.plugin._super.setError("No tablatures when stafflines=0");
17100
+ plugin.setError("No tablatures when stafflines=0");
17000
17101
  return;
17001
17102
  }
17002
17103
  }
17003
17104
  }
17004
- staffs.splice(staffs.length, 0, this.tabStaff);
17105
+ staffs.splice(staffs.length, 0, tabStaff);
17005
17106
  }
17006
- var staffGroup = this.line.staffGroup;
17107
+ var staffGroup = line.staffGroup;
17007
17108
  var voices = staffGroup.voices;
17008
17109
  var firstVoice = voices[0];
17009
17110
  // take lyrics into account if any
17010
17111
  var lyricsHeight = getLyricHeight(firstVoice);
17011
17112
  var padd = 3;
17012
- var prevIndex = this.staffIndex;
17113
+ var prevIndex = staffIndex;
17013
17114
  var previousStaff = staffGroup.staffs[prevIndex];
17014
- var tabTop = this.tabSize + padd - previousStaff.bottom - lyricsHeight;
17115
+ var tabTop = tabSize + padd - previousStaff.bottom - lyricsHeight;
17015
17116
  if (previousStaff.isTabStaff) {
17016
17117
  tabTop = previousStaff.top;
17017
17118
  }
@@ -17019,44 +17120,43 @@ TabRenderer.prototype.doLayout = function () {
17019
17120
  bottom: -1,
17020
17121
  isTabStaff: true,
17021
17122
  specialY: initSpecialY(),
17022
- lines: this.plugin.nbLines,
17023
- linePitch: this.plugin.linePitch,
17123
+ lines: plugin.nbLines,
17124
+ linePitch: plugin.linePitch,
17024
17125
  dy: 0.15,
17025
17126
  top: tabTop
17026
17127
  };
17027
- var nextTabPos = getNextTabPos(this, staffGroup.staffs);
17128
+ var nextTabPos = getNextTabPos(staffIndex, staffGroup.staffs);
17028
17129
  if (nextTabPos === -1) return;
17029
17130
  staffGroupInfos.parentIndex = nextTabPos - 1;
17030
17131
  staffGroup.staffs.splice(nextTabPos, 0, staffGroupInfos);
17031
17132
  // staffGroup.staffs.push(staffGroupInfos);
17032
- staffGroup.height += this.tabSize + padd;
17133
+ staffGroup.height += tabSize + padd;
17033
17134
  var parentStaff = getLastStaff(staffGroup.staffs, nextTabPos);
17034
17135
  var nbVoices = 1;
17035
17136
  if (isMultiVoiceSingleStaff(staffGroup.staffs, parentStaff)) {
17036
17137
  nbVoices = parentStaff.voices.length;
17037
17138
  }
17038
17139
  // build from staff
17039
- this.tabStaff.voices = [];
17140
+ tabStaff.voices = [];
17040
17141
  for (var ii = 0; ii < nbVoices; ii++) {
17041
17142
  var tabVoice = new VoiceElement(0, 0);
17042
17143
  if (ii > 0) tabVoice.duplicate = true;
17043
- var nameHeight = buildTabName(this, tabVoice) / spacing.STEP;
17144
+ var nameHeight = buildTabName(plugin, renderer, tabVoice) / spacing.STEP;
17044
17145
  nameHeight = Math.max(nameHeight, 1); // If there is no label for the tab line, then there needs to be a little padding
17045
17146
  // This was pushing down the top staff by the tab label height
17046
- //staffGroup.staffs[this.staffIndex].top += nameHeight;
17047
- staffGroup.staffs[this.staffIndex].top += 1;
17147
+ //staffGroup.staffs[staffIndex].top += nameHeight;
17148
+ staffGroup.staffs[staffIndex].top += 1;
17048
17149
  staffGroup.height += nameHeight;
17049
17150
  tabVoice.staff = staffGroupInfos;
17050
17151
  var tabVoiceIndex = voices.length;
17051
17152
  voices.splice(voices.length, 0, tabVoice);
17052
- var keySig = checkVoiceKeySig(voices, ii + this.staffIndex);
17053
- this.tabStaff.voices[ii] = [];
17054
- this.absolutes.build(this.plugin, voices, this.tabStaff.voices[ii], ii, this.staffIndex, keySig, tabVoiceIndex);
17153
+ var keySig = checkVoiceKeySig(voices, ii + staffIndex);
17154
+ tabStaff.voices[ii] = [];
17155
+ absolutes.build(plugin, voices, tabStaff.voices[ii], ii, staffIndex, keySig, tabVoiceIndex);
17055
17156
  }
17056
17157
  linkStaffAndTabs(staffGroup.staffs); // crossreference tabs and staff
17057
- };
17058
-
17059
- module.exports = TabRenderer;
17158
+ }
17159
+ module.exports = tabRenderer;
17060
17160
 
17061
17161
  /***/ }),
17062
17162
 
@@ -17275,7 +17375,6 @@ AbstractEngraver.prototype.createABCStaff = function (staffgroup, abcstaff, temp
17275
17375
  } else {
17276
17376
  voice.duplicate = true; // bar lines and other duplicate info need not be created
17277
17377
  }
17278
-
17279
17378
  if (abcstaff.title && abcstaff.title[v]) {
17280
17379
  voice.header = abcstaff.title[v].replace(/\\n/g, "\n");
17281
17380
  voice.headerPosition = 6 + staffgroup.getTextSize.baselineToCenter(voice.header, "voicefont", 'staff-extra voice-name', v, abcstaff.voices.length) / spacing.STEP;
@@ -17289,13 +17388,11 @@ AbstractEngraver.prototype.createABCStaff = function (staffgroup, abcstaff, temp
17289
17388
  voice.addChild(clef);
17290
17389
  this.startlimitelem = clef; // limit ties here
17291
17390
  }
17292
-
17293
17391
  var keySig = createKeySignature(abcstaff.key, this.tuneNumber);
17294
17392
  if (keySig) {
17295
17393
  voice.addChild(keySig);
17296
17394
  this.startlimitelem = keySig; // limit ties here
17297
17395
  }
17298
-
17299
17396
  if (abcstaff.meter) {
17300
17397
  if (abcstaff.meter.type === 'specified') {
17301
17398
  this.measureLength = abcstaff.meter.value[0].num / abcstaff.meter.value[0].den;
@@ -17304,7 +17401,6 @@ AbstractEngraver.prototype.createABCStaff = function (staffgroup, abcstaff, temp
17304
17401
  voice.addChild(ts);
17305
17402
  this.startlimitelem = ts; // limit ties here
17306
17403
  }
17307
-
17308
17404
  if (voice.duplicate) voice.children = []; // we shouldn't reprint the above if we're reusing the same staff. We just created them to get the right spacing.
17309
17405
  var staffLines = abcstaff.clef.stafflines || abcstaff.clef.stafflines === 0 ? abcstaff.clef.stafflines : 5;
17310
17406
  staffgroup.addVoice(voice, s, staffLines);
@@ -17468,7 +17564,6 @@ AbstractEngraver.prototype.createABCElement = function (isFirstStaff, isSingleLi
17468
17564
  elemset[0] = absKey;
17469
17565
  this.startlimitelem = elemset[0]; // limit ties here
17470
17566
  }
17471
-
17472
17567
  if (voice.duplicate && elemset.length > 0) elemset[0].invisible = true;
17473
17568
  break;
17474
17569
  case "stem":
@@ -17602,7 +17697,6 @@ AbstractEngraver.prototype.addGraceNotes = function (elem, voice, abselem, noteh
17602
17697
  if (hint) gracebeam.setHint();
17603
17698
  gracebeam.mainNote = abselem; // this gives us a reference back to the note this is attached to so that the stems can be attached somewhere.
17604
17699
  }
17605
-
17606
17700
  var i;
17607
17701
  var graceoffsets = [];
17608
17702
  for (i = elem.gracenotes.length - 1; i >= 0; i--) {
@@ -17702,7 +17796,6 @@ function addRestToAbsElement(abselem, elem, duration, dot, isMultiVoice, stemdir
17702
17796
  if (duration < 0.5) restpitch = 7;else if (duration < 1) restpitch = 7; // half rest
17703
17797
  else restpitch = 5; // whole rest
17704
17798
  }
17705
-
17706
17799
  switch (elem.rest.type) {
17707
17800
  case "whole":
17708
17801
  c = chartable.rest[0];
@@ -17841,7 +17934,6 @@ AbstractEngraver.prototype.addNoteToAbcElement = function (abselem, elem, dot, s
17841
17934
  elem.pitches[p].highestVert = elem.pitches[pp - 1].verticalPos;
17842
17935
  if (getDuration(elem) < 1 && (stemdir === "up" || dir === "up")) elem.pitches[p].highestVert += 6; // If the stem is up, then compensate for the length of the stem
17843
17936
  }
17844
-
17845
17937
  if (elem.startSlur) {
17846
17938
  if (!elem.pitches[p].startSlur) elem.pitches[p].startSlur = []; //TODO possibly redundant, provided array is not optional
17847
17939
  for (i = 0; i < elem.startSlur.length; i++) {
@@ -17990,7 +18082,7 @@ AbstractEngraver.prototype.createNote = function (elem, nostem, isSingleLineStaf
17990
18082
  if (elem.decoration) {
17991
18083
  // 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.
17992
18084
  // This should probably be combined with moveDecorations()
17993
- var bottom = nostem ? Math.min(-3, abselem.bottom - 6) : abselem.bottom;
18085
+ var bottom = nostem && dir !== 'up' ? Math.min(-3, abselem.bottom - 6) : abselem.bottom;
17994
18086
  this.decoration.createDecoration(voice, elem.decoration, abselem.top, notehead ? notehead.w : 0, abselem, roomtaken, dir, bottom, elem.positioning, this.hasVocals, this.accentAbove);
17995
18087
  }
17996
18088
  if (elem.barNumber) {
@@ -18011,7 +18103,6 @@ AbstractEngraver.prototype.createNote = function (elem, nostem, isSingleLineStaf
18011
18103
  flatBeams: this.flatBeams
18012
18104
  }); // above is opposite from case of slurs
18013
18105
  }
18014
-
18015
18106
  if (elem.endTriplet && this.triplet) {
18016
18107
  this.triplet.setCloseAnchor(notehead);
18017
18108
  }
@@ -18145,7 +18236,6 @@ AbstractEngraver.prototype.createBarLine = function (voice, elem, isFirstStaff)
18145
18236
  abselem.addRight(new RelativeElement("dots.dot", dx, 1, 5));
18146
18237
  dx += 6; //2 hardcoded, twice;
18147
18238
  }
18148
-
18149
18239
  if (firstthin) {
18150
18240
  anchor = new RelativeElement(null, dx, 1, 2, {
18151
18241
  "type": "bar",
@@ -18194,7 +18284,6 @@ AbstractEngraver.prototype.createBarLine = function (voice, elem, isFirstStaff)
18194
18284
  });
18195
18285
  abselem.addRight(anchor); // 3 is hardcoded
18196
18286
  }
18197
-
18198
18287
  if (seconddots) {
18199
18288
  dx += 3; //3 hardcoded;
18200
18289
  abselem.addRight(new RelativeElement("dots.dot", dx, 1, 7));
@@ -18528,7 +18617,6 @@ var createClef = function createClef(elem, tuneNumber) {
18528
18617
  //abselem.top += 2;
18529
18618
  }
18530
18619
  }
18531
-
18532
18620
  return abselem;
18533
18621
  };
18534
18622
  function clefOffsets(clef) {
@@ -18720,7 +18808,6 @@ var createNoteHead = function createNoteHead(abselem, c, pitchelem, options) {
18720
18808
  }));
18721
18809
  extraLeft = glyphs.getSymbolWidth(symb) / 2; // TODO-PER: We need a little extra width if there is an accidental, but I'm not sure why it isn't the full width of the accidental.
18722
18810
  }
18723
-
18724
18811
  return {
18725
18812
  notehead: notehead,
18726
18813
  accidentalshiftx: accidentalshiftx,
@@ -19511,7 +19598,6 @@ BeamElem.prototype.setStemDirection = function () {
19511
19598
  var middleLine = 6; // hardcoded 6 is B
19512
19599
  this.stemsUp = this.average < middleLine; // true is up, false is down;
19513
19600
  }
19514
-
19515
19601
  delete this.count;
19516
19602
  this.total = 0;
19517
19603
  };
@@ -19525,7 +19611,6 @@ BeamElem.prototype.calcDir = function () {
19525
19611
  var middleLine = 6; // hardcoded 6 is B
19526
19612
  this.stemsUp = this.average < middleLine; // true is up, false is down;
19527
19613
  }
19528
-
19529
19614
  var dir = this.stemsUp ? 'up' : 'down';
19530
19615
  for (var i = 0; i < this.elems.length; i++) {
19531
19616
  for (var j = 0; j < this.elems[i].heads.length; j++) {
@@ -19715,7 +19800,6 @@ BraceElem.prototype.continuing = function (voice) {
19715
19800
  BraceElem.prototype.getWidth = function () {
19716
19801
  return 10; // TODO-PER: right now the drawing function doesn't vary the width at all. If it does in the future then this will change.
19717
19802
  };
19718
-
19719
19803
  BraceElem.prototype.isStartVoice = function (voice) {
19720
19804
  if (this.startVoice && this.startVoice.staff && this.startVoice.staff.voices.length > 0 && this.startVoice.staff.voices[0] === voice) return true;
19721
19805
  return false;
@@ -19740,7 +19824,6 @@ var CrescendoElem = function CrescendoElem(anchor1, anchor2, dir, positioning) {
19740
19824
  if (positioning === 'above') this.dynamicHeightAbove = 6;else this.dynamicHeightBelow = 6;
19741
19825
  this.pitch = undefined; // This will be set later
19742
19826
  };
19743
-
19744
19827
  module.exports = CrescendoElem;
19745
19828
 
19746
19829
  /***/ }),
@@ -19760,7 +19843,6 @@ var DynamicDecoration = function DynamicDecoration(anchor, dec, position) {
19760
19843
  if (position === 'below') this.volumeHeightBelow = 6;else this.volumeHeightAbove = 6;
19761
19844
  this.pitch = undefined; // This will be set later
19762
19845
  };
19763
-
19764
19846
  module.exports = DynamicDecoration;
19765
19847
 
19766
19848
  /***/ }),
@@ -19781,7 +19863,6 @@ var EndingElem = function EndingElem(text, anchor1, anchor2) {
19781
19863
  this.endingHeightAbove = 5;
19782
19864
  this.pitch = undefined; // This will be set later
19783
19865
  };
19784
-
19785
19866
  module.exports = EndingElem;
19786
19867
 
19787
19868
  /***/ }),
@@ -19887,7 +19968,6 @@ var GlissandoElem = function GlissandoElem(anchor1, anchor2) {
19887
19968
  this.anchor1 = anchor1; // must have a .x and a .parent property or be null (means starts at the "beginning" of the line - after keysig)
19888
19969
  this.anchor2 = anchor2; // must have a .x property or be null (means ends at the end of the line)
19889
19970
  };
19890
-
19891
19971
  module.exports = GlissandoElem;
19892
19972
 
19893
19973
  /***/ }),
@@ -20422,14 +20502,12 @@ TieElem.prototype.calcX = function (lineStartX, lineEndX) {
20422
20502
  if (this.anchor2) this.startX = this.anchor2.x - 20; // There is no element and no repeat mark: make a small arc
20423
20503
  else this.startX = lineStartX; // Don't have any guidance, so extend to beginning of line
20424
20504
  }
20425
-
20426
20505
  if (!this.anchor1 && this.dotted) this.startX -= 3; // The arc needs to be long enough to tell that it is dotted.
20427
20506
 
20428
20507
  if (this.anchor2) this.endX = this.anchor2.x; // The normal case where there is a starting element to attach to.
20429
20508
  else if (this.endLimitX) this.endX = this.endLimitX.x; // if there is no start element, but there is a repeat mark before the start of the line.
20430
20509
  else this.endX = lineEndX; // There is no element and no repeat mark: extend to the beginning of the line.
20431
20510
  };
20432
-
20433
20511
  TieElem.prototype.calcTieY = function () {
20434
20512
  // If the tie comes from another line, then one or both anchors will be missing.
20435
20513
  if (this.anchor1) this.startY = this.anchor1.pitch;else if (this.anchor2) this.startY = this.anchor2.pitch;else this.startY = this.above ? 14 : 0;
@@ -20492,6 +20570,30 @@ TieElem.prototype.avoidCollisionAbove = function () {
20492
20570
  if (maxInnerHeight > this.startY && maxInnerHeight > this.endY) this.startY = this.endY = maxInnerHeight - 1;
20493
20571
  }
20494
20572
  };
20573
+ TieElem.prototype.getYBounds = function () {
20574
+ var lineStartX = 10; // TODO-PER: I'm not sure where to get this number from but it probably doesn't matter much
20575
+ var lineEndX = 1000; // TODO-PER: I'm not sure where to get this number from but it probably doesn't matter much
20576
+ if (this.isTie) {
20577
+ this.calcTieDirection();
20578
+ this.calcX(lineStartX, lineEndX);
20579
+ this.calcTieY();
20580
+ } else {
20581
+ this.calcSlurDirection();
20582
+ this.calcX(lineStartX, lineEndX);
20583
+ this.calcSlurY();
20584
+ }
20585
+ var top;
20586
+ var bottom;
20587
+ // TODO-PER: It's hard to tell how far the arc is, so I'm just using 3 as the max
20588
+ if (this.above) {
20589
+ bottom = Math.min(this.startY, this.endY);
20590
+ top = bottom + 3;
20591
+ } else {
20592
+ top = Math.min(this.startY, this.endY);
20593
+ bottom = top - 3;
20594
+ }
20595
+ return [top, bottom];
20596
+ };
20495
20597
  module.exports = TieElem;
20496
20598
 
20497
20599
  /***/ }),
@@ -20542,7 +20644,6 @@ function TopText(metaText, metaTextInfo, formatting, lines, width, isPrint, padd
20542
20644
 
20543
20645
  // TopText.prototype.addTextIf = function (marginLeft, text, font, klass, marginTop, marginBottom, anchor, getTextSize, absElemType, noMove) {
20544
20646
  }
20545
-
20546
20647
  if (isPrint) this.rows.push({
20547
20648
  move: spacing.top
20548
20649
  });
@@ -21614,7 +21715,6 @@ function straightPath(renderer, xLeft, yTop, yBottom, type) {
21614
21715
  // right point
21615
21716
  -wCurve * 0.1, -hCurve * 0.3, -wCurve, -hCurve - spacing.STEP // left bottom
21616
21717
  );
21617
-
21618
21718
  return renderer.paper.path({
21619
21719
  path: pathString,
21620
21720
  stroke: renderer.foregroundColor,
@@ -22893,7 +22993,6 @@ function drawStaffGroup(renderer, params, selectables, lineNumber) {
22893
22993
  // renderer.moveY(spacing.STEP, -staff.bottom);
22894
22994
  }
22895
22995
  }
22896
-
22897
22996
  renderer.controller.classes.newMeasure();
22898
22997
 
22899
22998
  // connect all the staves together with a vertical line
@@ -23115,7 +23214,6 @@ function drawTempo(renderer, params) {
23115
23214
  // });
23116
23215
  //return [tempoGroup];
23117
23216
  }
23118
-
23119
23217
  module.exports = drawTempo;
23120
23218
 
23121
23219
  /***/ }),
@@ -23452,7 +23550,6 @@ function drawVoice(renderer, params, bartop, selectables, staffPos) {
23452
23550
  renderer.controller.classes.incrMeasure();
23453
23551
  } else drawBeam(renderer, beam, selectables); // beams must be drawn first for proper printing of triplets, slurs and ties.
23454
23552
  }
23455
-
23456
23553
  renderer.controller.classes.startMeasure();
23457
23554
  for (i = 0; i < params.otherchildren.length; i++) {
23458
23555
  child = params.otherchildren[i];
@@ -23519,7 +23616,7 @@ var Classes = __webpack_require__(/*! ./helpers/classes */ "./src/write/helpers/
23519
23616
  var GetFontAndAttr = __webpack_require__(/*! ./helpers/get-font-and-attr */ "./src/write/helpers/get-font-and-attr.js");
23520
23617
  var GetTextSize = __webpack_require__(/*! ./helpers/get-text-size */ "./src/write/helpers/get-text-size.js");
23521
23618
  var draw = __webpack_require__(/*! ./draw/draw */ "./src/write/draw/draw.js");
23522
- var tablatures = __webpack_require__(/*! ../api/abc_tablatures */ "./src/api/abc_tablatures.js");
23619
+ var tablatures = __webpack_require__(/*! ../tablatures/abc_tablatures */ "./src/tablatures/abc_tablatures.js");
23523
23620
  var findSelectableElement = __webpack_require__(/*! ./interactive/find-selectable-element */ "./src/write/interactive/find-selectable-element.js");
23524
23621
 
23525
23622
  /**
@@ -23561,7 +23658,6 @@ var EngraverController = function EngraverController(paper, params) {
23561
23658
  this.staffwidthScreen = 740; // TODO-PER: Not sure where this number comes from, but this is how it's always been.
23562
23659
  this.staffwidthPrint = 680; // The number of pixels in 8.5", after 1cm of margin has been removed.
23563
23660
  }
23564
-
23565
23661
  this.listeners = [];
23566
23662
  if (params.clickListener) this.addSelectListener(params.clickListener);
23567
23663
  this.renderer = new Renderer(paper);
@@ -24471,7 +24567,6 @@ function getMousePosition(self, ev) {
24471
24567
  clickedOn = findElementByCoord(self, x, y);
24472
24568
  //console.log("clicked near", clickedOn, x, y, printEl(ev.target));
24473
24569
  }
24474
-
24475
24570
  return {
24476
24571
  x: x,
24477
24572
  y: y,
@@ -24709,7 +24804,6 @@ function minStem(element, stemsUp, referencePitch, minStemHeight) {
24709
24804
  var elem = element.children[i];
24710
24805
  if (stemsUp && elem.top !== undefined && elem.c === "flags.ugrace") minStemHeight = Math.max(minStemHeight, elem.top - referencePitch);else if (!stemsUp && elem.bottom !== undefined && elem.c === "flags.ugrace") minStemHeight = Math.max(minStemHeight, referencePitch - elem.bottom + 7); // The extra 7 is because we are measuring the slash from the top.
24711
24806
  }
24712
-
24713
24807
  return minStemHeight;
24714
24808
  }
24715
24809
  function calcSlant(leftAveragePitch, rightAveragePitch, numStems, isFlat) {
@@ -25114,7 +25208,6 @@ function calcHorizontalSpacing(isLastLine, stretchLast, targetWidth, lineWidth,
25114
25208
  if (!stretch) return null; // don't stretch last line too much
25115
25209
  }
25116
25210
  }
25117
-
25118
25211
  if (Math.abs(targetWidth - lineWidth) < 2) return null; // if we are already near the target width, we're done.
25119
25212
  var relSpace = spacingUnits * spacing;
25120
25213
  var constSpace = lineWidth - relSpace;
@@ -25179,7 +25272,6 @@ var setUpperAndLowerElements = function setUpperAndLowerElements(renderer, staff
25179
25272
  staff.originalTop = staff.top; // This is just being stored for debugging purposes.
25180
25273
  staff.originalBottom = staff.bottom; // This is just being stored for debugging purposes.
25181
25274
  }
25182
-
25183
25275
  incTop(staff, positionY, 'lyricHeightAbove');
25184
25276
  incTop(staff, positionY, 'chordHeightAbove', staff.specialY.chordLines.above);
25185
25277
  if (staff.specialY.endingHeightAbove) {
@@ -25242,7 +25334,6 @@ var setUpperAndLowerElements = function setUpperAndLowerElements(renderer, staff
25242
25334
  }
25243
25335
  //console.log("Staff Height: ",heightInPitches,this.height);
25244
25336
  };
25245
-
25246
25337
  var margin = 1;
25247
25338
  function incTop(staff, positionY, item, count) {
25248
25339
  if (staff.specialY[item]) {
@@ -25271,6 +25362,14 @@ function setUpperAndLowerVoiceElements(positionY, voice, spacing) {
25271
25362
  case 'EndingElem':
25272
25363
  setUpperAndLowerEndingElements(positionY, abselem);
25273
25364
  break;
25365
+ case 'TieElem':
25366
+ // If a tie element is the highest or lowest thing then space might need to make room for it.
25367
+ var yBounds = abselem.getYBounds();
25368
+ voice.staff.top = Math.max(voice.staff.top, yBounds[0]);
25369
+ voice.staff.top = Math.max(voice.staff.top, yBounds[1]);
25370
+ voice.staff.bottom = Math.min(voice.staff.bottom, yBounds[0]);
25371
+ voice.staff.bottom = Math.min(voice.staff.bottom, yBounds[1]);
25372
+ break;
25274
25373
  }
25275
25374
  }
25276
25375
  }
@@ -25499,7 +25598,6 @@ function finished(voices) {
25499
25598
  function getDurationIndex(element) {
25500
25599
  return element.durationindex - (element.children[element.i] && element.children[element.i].duration > 0 ? 0 : 0.0000005); // if the ith element doesn't have a duration (is not a note), its duration index is fractionally before. This enables CLEF KEYSIG TIMESIG PART, etc. to be laid out before we get to the first note of other voices
25501
25600
  }
25502
-
25503
25601
  function isSameStaff(voice1, voice2) {
25504
25602
  if (!voice1 || !voice1.staff || !voice1.staff.voices || voice1.staff.voices.length === 0) return false;
25505
25603
  if (!voice2 || !voice2.staff || !voice2.staff.voices || voice2.staff.voices.length === 0) return false;
@@ -25597,7 +25695,6 @@ VoiceElement.beginLayout = function (startx, voice) {
25597
25695
  voice.nextx = startx; // x position where the next element of this voice should be placed assuming no other voices and no fixed width constraints
25598
25696
  voice.spacingduration = 0; // duration left to be laid out in current iteration (omitting additional spacing due to other aspects, such as bars, dots, sharps and flats)
25599
25697
  };
25600
-
25601
25698
  VoiceElement.layoutEnded = function (voice) {
25602
25699
  return voice.i >= voice.children.length;
25603
25700
  };
@@ -25663,7 +25760,6 @@ VoiceElement.layoutOneItem = function (x, spacing, voice, minPadding, firstVoice
25663
25760
 
25664
25761
  return x; // where we end up having placed the child
25665
25762
  };
25666
-
25667
25763
  VoiceElement.shiftRight = function (dx, voice) {
25668
25764
  var child = voice.children[voice.i];
25669
25765
  if (!child) return;
@@ -25826,7 +25922,6 @@ function setLane(absElems, numLanesAbove, numLanesBelow) {
25826
25922
  }
25827
25923
  }
25828
25924
  }
25829
-
25830
25925
  function yAtNote(element, beam) {
25831
25926
  beam = beam.beams[0];
25832
25927
  return getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, element.x);
@@ -25985,7 +26080,6 @@ Renderer.prototype.initVerticalSpace = function () {
25985
26080
  <float> range is 0.0 to 1.0.
25986
26081
  */
25987
26082
  };
25988
-
25989
26083
  Renderer.prototype.setVerticalSpace = function (formatting) {
25990
26084
  // conversion from pts to px 4/3
25991
26085
  if (formatting.staffsep !== undefined) this.spacing.staffSeparation = formatting.staffsep * 4 / 3;
@@ -26107,7 +26201,6 @@ Svg.prototype.insertStyles = function (styles) {
26107
26201
  this.svg.insertBefore(el, this.svg.firstChild); // prepend is not available on older browsers.
26108
26202
  // this.svg.prepend(el);
26109
26203
  };
26110
-
26111
26204
  Svg.prototype.setParentStyles = function (attr) {
26112
26205
  // This is needed to get the size right when there is scaling involved.
26113
26206
  for (var key in attr) {
@@ -26250,7 +26343,6 @@ Svg.prototype.guessWidth = function (text, attr) {
26250
26343
  height: attr['font-size'] + 2
26251
26344
  }; // Just a wild guess.
26252
26345
  }
26253
-
26254
26346
  svg.removeChild(el);
26255
26347
  return size;
26256
26348
  };
@@ -26379,7 +26471,7 @@ module.exports = Svg;
26379
26471
  \********************/
26380
26472
  /***/ (function(module) {
26381
26473
 
26382
- var version = '6.4.1';
26474
+ var version = '6.4.3';
26383
26475
  module.exports = version;
26384
26476
 
26385
26477
  /***/ })