abcjs 6.3.0 → 6.4.1

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 (46) hide show
  1. package/README.md +4 -0
  2. package/RELEASE.md +52 -0
  3. package/dist/abcjs-basic-min.js +2 -2
  4. package/dist/abcjs-basic.js +1031 -622
  5. package/dist/abcjs-basic.js.map +1 -1
  6. package/dist/abcjs-plugin-min.js +2 -2
  7. package/index.js +1 -0
  8. package/package.json +1 -1
  9. package/src/api/tune-metrics.js +18 -0
  10. package/src/data/abc_tune.js +13 -2
  11. package/src/edit/abc_editarea.js +4 -1
  12. package/src/parse/abc_parse.js +2 -0
  13. package/src/parse/abc_parse_directive.js +6 -0
  14. package/src/synth/abc_midi_flattener.js +40 -462
  15. package/src/synth/abc_midi_sequencer.js +25 -10
  16. package/src/synth/chord-track.js +565 -0
  17. package/src/synth/create-note-map.js +2 -1
  18. package/src/synth/create-synth.js +91 -42
  19. package/src/test/abc_parser_lint.js +1 -0
  20. package/src/write/creation/abstract-engraver.js +4 -1
  21. package/src/write/creation/decoration.js +3 -2
  22. package/src/write/creation/elements/tie-element.js +23 -0
  23. package/src/write/draw/draw.js +1 -1
  24. package/src/write/engraver-controller.js +9 -5
  25. package/src/write/interactive/create-analysis.js +50 -0
  26. package/src/write/interactive/find-selectable-element.js +24 -0
  27. package/src/write/interactive/selection.js +5 -45
  28. package/src/write/layout/layout-in-grid.js +83 -0
  29. package/src/write/layout/layout.js +29 -24
  30. package/src/write/layout/set-upper-and-lower-elements.js +2 -0
  31. package/src/write/layout/staff-group.js +2 -2
  32. package/src/write/layout/voice-elements.js +1 -1
  33. package/src/write/layout/voice.js +1 -1
  34. package/src/write/renderer.js +3 -0
  35. package/types/index.d.ts +96 -32
  36. package/version.js +1 -1
  37. package/abc2xml_239/abc2xml.html +0 -769
  38. package/abc2xml_239/abc2xml.py +0 -2248
  39. package/abc2xml_239/abc2xml_changelog.html +0 -124
  40. package/abc2xml_239/lazy-river.abc +0 -26
  41. package/abc2xml_239/lazy-river.xml +0 -3698
  42. package/abc2xml_239/mean-to-me.abc +0 -22
  43. package/abc2xml_239/mean-to-me.xml +0 -2954
  44. package/abc2xml_239/pyparsing.py +0 -3672
  45. package/abc2xml_239/pyparsing.pyc +0 -0
  46. package/temp.txt +0 -50
@@ -54,6 +54,7 @@ Object.keys(tuneBook).forEach(function (key) {
54
54
  abcjs[key] = tuneBook[key];
55
55
  });
56
56
  abcjs.renderAbc = __webpack_require__(/*! ./src/api/abc_tunebook_svg */ "./src/api/abc_tunebook_svg.js");
57
+ abcjs.tuneMetrics = __webpack_require__(/*! ./src/api/tune-metrics */ "./src/api/tune-metrics.js");
57
58
  abcjs.TimingCallbacks = __webpack_require__(/*! ./src/api/abc_timing_callbacks */ "./src/api/abc_timing_callbacks.js");
58
59
  var glyphs = __webpack_require__(/*! ./src/write/creation/glyphs */ "./src/write/creation/glyphs.js");
59
60
  abcjs.setGlyph = glyphs.setSymbol;
@@ -1088,6 +1089,32 @@ module.exports = renderAbc;
1088
1089
 
1089
1090
  /***/ }),
1090
1091
 
1092
+ /***/ "./src/api/tune-metrics.js":
1093
+ /*!*********************************!*\
1094
+ !*** ./src/api/tune-metrics.js ***!
1095
+ \*********************************/
1096
+ /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
1097
+
1098
+ var tunebook = __webpack_require__(/*! ./abc_tunebook */ "./src/api/abc_tunebook.js");
1099
+ var EngraverController = __webpack_require__(/*! ../write/engraver-controller */ "./src/write/engraver-controller.js");
1100
+ var tuneMetrics = function tuneMetrics(abc, params) {
1101
+ function callback(div, tune, tuneNumber, abcString) {
1102
+ div = document.createElement("div");
1103
+ div.setAttribute("style", "visibility: hidden;");
1104
+ document.body.appendChild(div);
1105
+ var engraver_controller = new EngraverController(div, params);
1106
+ var widths = engraver_controller.getMeasureWidths(tune);
1107
+ div.parentNode.removeChild(div);
1108
+ return {
1109
+ sections: widths
1110
+ };
1111
+ }
1112
+ return tunebook.renderEngine(callback, "*", abc, params);
1113
+ };
1114
+ module.exports = tuneMetrics;
1115
+
1116
+ /***/ }),
1117
+
1091
1118
  /***/ "./src/const/key-accidentals.js":
1092
1119
  /*!**************************************!*\
1093
1120
  !*** ./src/const/key-accidentals.js ***!
@@ -1666,9 +1693,12 @@ var Tune = function Tune() {
1666
1693
  eventHash["event" + voiceTimeMilliseconds].measureStart = true;
1667
1694
  nextIsBar = false;
1668
1695
  }
1669
- if (isTiedToNext) isTiedState = voiceTimeMilliseconds;
1696
+ // TODO-PER: There doesn't seem to be a harm in letting ties be two different notes and it fixes a bug when a tie goes to a new line. If there aren't other problems with this change, then the variable can be removed completely.
1697
+ // if (isTiedToNext)
1698
+ // isTiedState = voiceTimeMilliseconds;
1670
1699
  }
1671
1700
  }
1701
+
1672
1702
  return {
1673
1703
  isTiedState: isTiedState,
1674
1704
  duration: realDuration / timeDivider,
@@ -1896,6 +1926,14 @@ var Tune = function Tune() {
1896
1926
  this.deline = function (options) {
1897
1927
  return delineTune(this.lines, options);
1898
1928
  };
1929
+ this.findSelectableElement = function (target) {
1930
+ if (this.engraver && this.engraver.selectables) return this.engraver.findSelectableElement(target);
1931
+ return null;
1932
+ };
1933
+ this.getSelectableArray = function () {
1934
+ if (this.engraver && this.engraver.selectables) return this.engraver.selectables;
1935
+ return [];
1936
+ };
1899
1937
  };
1900
1938
  module.exports = Tune;
1901
1939
 
@@ -2151,7 +2189,7 @@ try {
2151
2189
  // if we aren't in a browser, this code will crash, but it is not needed then either.
2152
2190
  }
2153
2191
  var EditArea = function EditArea(textareaid) {
2154
- this.textarea = document.getElementById(textareaid);
2192
+ if (typeof textareaid === "string") this.textarea = document.getElementById(textareaid);else this.textarea = textareaid;
2155
2193
  this.initialText = this.textarea.value;
2156
2194
  this.isDragging = false;
2157
2195
  };
@@ -2779,7 +2817,9 @@ var Parse = function Parse() {
2779
2817
  setupEvents: tune.setupEvents,
2780
2818
  setTiming: tune.setTiming,
2781
2819
  setUpAudio: tune.setUpAudio,
2782
- deline: tune.deline
2820
+ deline: tune.deline,
2821
+ findSelectableElement: tune.findSelectableElement,
2822
+ getSelectableArray: tune.getSelectableArray
2783
2823
  };
2784
2824
  if (tune.lineBreaks) t.lineBreaks = tune.lineBreaks;
2785
2825
  if (tune.visualTranspose) t.visualTranspose = tune.visualTranspose;
@@ -4353,6 +4393,7 @@ var parseDirective = {};
4353
4393
  case "pageheight":
4354
4394
  case "pagewidth":
4355
4395
  case "rightmargin":
4396
+ case "stafftopmargin":
4356
4397
  case "staffsep":
4357
4398
  case "staffwidth":
4358
4399
  case "subtitlespace":
@@ -4753,6 +4794,10 @@ var parseDirective = {};
4753
4794
  if (tokens.length !== 1 || tokens[0].type !== 'number') warn("Directive \"" + cmd + "\" requires a number as a parameter.");
4754
4795
  tune.formatting.fontboxpadding = tokens[0].floatt;
4755
4796
  break;
4797
+ case "stafftopmargin":
4798
+ if (tokens.length !== 1 || tokens[0].type !== 'number') warn("Directive \"" + cmd + "\" requires a number as a parameter.");
4799
+ tune.formatting.stafftopmargin = tokens[0].floatt;
4800
+ break;
4756
4801
  case "stretchlast":
4757
4802
  var sl = parseStretchLast(tokens);
4758
4803
  if (sl.value !== undefined) tune.formatting.stretchlast = sl.value;
@@ -11209,7 +11254,7 @@ module.exports = strTranspose;
11209
11254
  // It also extracts guitar chords to a separate voice and resolves their rhythm.
11210
11255
 
11211
11256
  var flatten;
11212
- var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/abc_common.js");
11257
+ var ChordTrack = __webpack_require__(/*! ./chord-track */ "./src/synth/chord-track.js");
11213
11258
  var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pitches-to-perc.js");
11214
11259
  (function () {
11215
11260
  "use strict";
@@ -11229,25 +11274,13 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11229
11274
  var lastNoteDurationPosition;
11230
11275
  var currentTrackName;
11231
11276
  var lastEventTime;
11277
+ var chordTrack;
11232
11278
  var meter = {
11233
11279
  num: 4,
11234
11280
  den: 4
11235
11281
  };
11236
- var chordTrack;
11237
- var chordSourceTrack;
11238
- var chordTrackFinished;
11239
- var chordChannel;
11240
- var bassInstrument = 0;
11241
- var chordInstrument = 0;
11242
11282
  var drumInstrument = 128;
11243
- var boomVolume = 64;
11244
- var chickVolume = 48;
11245
- var currentChords;
11246
- var lastChord;
11247
- var chordLastBar;
11248
11283
  var lastBarTime;
11249
- var gChordTacet = false;
11250
- var hasRhythmHead = false;
11251
11284
  var doBeatAccents = true;
11252
11285
  var stressBeat1 = 105;
11253
11286
  var stressBeatDown = 95;
@@ -11285,25 +11318,10 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11285
11318
  currentTrackName = undefined;
11286
11319
  lastEventTime = 0;
11287
11320
  percmap = percmap_;
11288
-
11289
- // For resolving chords.
11290
11321
  meter = {
11291
11322
  num: 4,
11292
11323
  den: 4
11293
11324
  };
11294
- chordTrack = [];
11295
- chordSourceTrack = false;
11296
- chordChannel = voices.length; // first free channel for chords
11297
- chordTrackFinished = false;
11298
- currentChords = [];
11299
- bassInstrument = midiOptions.bassprog && midiOptions.bassprog.length === 1 ? midiOptions.bassprog[0] : 0;
11300
- chordInstrument = midiOptions.chordprog && midiOptions.chordprog.length === 1 ? midiOptions.chordprog[0] : 0;
11301
- boomVolume = midiOptions.bassvol && midiOptions.bassvol.length === 1 ? midiOptions.bassvol[0] : 64;
11302
- chickVolume = midiOptions.chordvol && midiOptions.chordvol.length === 1 ? midiOptions.chordvol[0] : 48;
11303
- lastChord = undefined;
11304
- chordLastBar = undefined;
11305
- gChordTacet = options.chordsOff ? true : false;
11306
- hasRhythmHead = false;
11307
11325
  doBeatAccents = true;
11308
11326
  stressBeat1 = 105;
11309
11327
  stressBeatDown = 95;
@@ -11320,10 +11338,19 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11320
11338
  drumBars = 1;
11321
11339
  if (voices.length > 0 && voices[0].length > 0) pickupLength = voices[0][0].pickupLength;
11322
11340
 
11341
+ // For resolving chords.
11342
+ if (options.bassprog !== undefined && !midiOptions.bassprog) midiOptions.bassprog = [options.bassprog];
11343
+ if (options.bassvol !== undefined && !midiOptions.bassvol) midiOptions.bassvol = [options.bassvol];
11344
+ if (options.chordprog !== undefined && !midiOptions.chordprog) midiOptions.chordprog = [options.chordprog];
11345
+ if (options.chordvol !== undefined && !midiOptions.chordvol) midiOptions.chordvol = [options.chordvol];
11346
+ if (options.gchord !== undefined && !midiOptions.gchord) midiOptions.gchord = [options.gchord];
11347
+ chordTrack = new ChordTrack(voices.length, options.chordsOff, midiOptions, meter);
11348
+
11323
11349
  // First adjust the input to resolve ties, set the starting time for each note, etc. That will make the rest of the logic easier
11324
11350
  preProcess(voices, options);
11325
11351
  for (var i = 0; i < voices.length; i++) {
11326
11352
  transpose = 0;
11353
+ chordTrack.setTranspose(transpose);
11327
11354
  lastNoteDurationPosition = -1;
11328
11355
  var voice = voices[i];
11329
11356
  currentTrack = [{
@@ -11333,6 +11360,7 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11333
11360
  }];
11334
11361
  currentTrackName = undefined;
11335
11362
  lastBarTime = 0;
11363
+ chordTrack.setLastBarTime(0);
11336
11364
  var voiceOff = false;
11337
11365
  if (options.voicesOff === true) voiceOff = true;else if (options.voicesOff && options.voicesOff.length && options.voicesOff.indexOf(i) >= 0) voiceOff = true;
11338
11366
  for (var j = 0; j < voice.length; j++) {
@@ -11346,8 +11374,7 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11346
11374
  };
11347
11375
  break;
11348
11376
  case "note":
11349
- var setChordTrack = writeNote(element, voiceOff);
11350
- if (setChordTrack) chordSourceTrack = i;
11377
+ writeNote(element, voiceOff);
11351
11378
  break;
11352
11379
  case "key":
11353
11380
  accidentals = setKeySignature(element);
@@ -11355,27 +11382,27 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11355
11382
  case "meter":
11356
11383
  if (!startingMeter) startingMeter = element;
11357
11384
  meter = element;
11385
+ chordTrack.setMeter(meter);
11358
11386
  beatFraction = getBeatFraction(meter);
11359
11387
  alignDrumToMeter();
11360
11388
  break;
11361
11389
  case "tempo":
11362
11390
  if (!startingTempo) startingTempo = element.qpm;else tempoChangeFactor = element.qpm ? startingTempo / element.qpm : 1;
11391
+ chordTrack.setTempoChangeFactor(tempoChangeFactor);
11363
11392
  break;
11364
11393
  case "transpose":
11365
11394
  transpose = element.transpose;
11395
+ chordTrack.setTranspose(transpose);
11366
11396
  break;
11367
11397
  case "bar":
11368
- if (chordTrack.length > 0 && (chordSourceTrack === false || i === chordSourceTrack)) {
11369
- resolveChords(lastBarTime, timeToRealTime(element.time));
11370
- currentChords = [];
11371
- }
11398
+ chordTrack.barEnd(element);
11372
11399
  barAccidentals = [];
11373
11400
  if (i === 0)
11374
11401
  // Only write the drum part on the first voice so that it is not duplicated.
11375
11402
  writeDrum(voices.length + 1);
11376
- hasRhythmHead = false; // decide whether there are rhythm heads each measure.
11377
- chordLastBar = lastChord;
11403
+ chordTrack.setRhythmHead(false); // decide whether there are rhythm heads each measure.
11378
11404
  lastBarTime = timeToRealTime(element.time);
11405
+ chordTrack.setLastBarTime(lastBarTime);
11379
11406
  break;
11380
11407
  case "bagpipes":
11381
11408
  bagpipes = true;
@@ -11402,8 +11429,8 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11402
11429
  drumDefinition = normalizeDrumDefinition(element.params);
11403
11430
  alignDrumToMeter();
11404
11431
  break;
11405
- case "gchord":
11406
- if (!options.chordsOff) gChordTacet = element.tacet;
11432
+ case "gchordOn":
11433
+ chordTrack.gChordOn(element);
11407
11434
  break;
11408
11435
  case "beat":
11409
11436
  stressBeat1 = element.beats[0];
@@ -11420,6 +11447,13 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11420
11447
  case "beataccents":
11421
11448
  doBeatAccents = element.value;
11422
11449
  break;
11450
+ case "gchord":
11451
+ case "bassprog":
11452
+ case "chordprog":
11453
+ case "bassvol":
11454
+ case "chordvol":
11455
+ chordTrack.paramChange(element);
11456
+ break;
11423
11457
  default:
11424
11458
  // This should never happen
11425
11459
  console.log("MIDI creation. Unknown el_type: " + element.el_type + "\n"); // jshint ignore:line
@@ -11429,16 +11463,14 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11429
11463
  if (currentTrack[0].instrument === undefined) currentTrack[0].instrument = instrument ? instrument : 0;
11430
11464
  if (currentTrackName) currentTrack.unshift(currentTrackName);
11431
11465
  tracks.push(currentTrack);
11432
- if (!chordTrackEmpty())
11433
- // Don't do chords on more than one track, so turn off chord detection after we create it.
11434
- chordTrackFinished = true;
11466
+ chordTrack.finish();
11435
11467
  if (drumTrack.length > 0)
11436
11468
  // Don't do drums on more than one track, so turn off drum after we create it.
11437
11469
  drumTrackFinished = true;
11438
11470
  }
11439
11471
  // See if any notes are octaves played at the same time. If so, raise the pitch of the higher one.
11440
11472
  if (options.detuneOctave) findOctaves(tracks, parseInt(options.detuneOctave, 10));
11441
- if (!chordTrackEmpty()) tracks.push(chordTrack);
11473
+ chordTrack.addTrack(tracks);
11442
11474
  if (drumTrack.length > 0) tracks.push(drumTrack);
11443
11475
  return {
11444
11476
  tempo: startingTempo,
@@ -11455,13 +11487,6 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11455
11487
  }
11456
11488
  }
11457
11489
  }
11458
- function chordTrackEmpty() {
11459
- var isEmpty = true;
11460
- for (var i = 0; i < chordTrack.length && isEmpty; i++) {
11461
- if (chordTrack[i].cmd === 'note') isEmpty = false;
11462
- }
11463
- return isEmpty;
11464
- }
11465
11490
  function timeToRealTime(time) {
11466
11491
  return time / 1000000;
11467
11492
  }
@@ -11549,48 +11574,6 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11549
11574
  }
11550
11575
  return 0.25;
11551
11576
  }
11552
- //
11553
- // The algorithm for chords is:
11554
- // - The chords are done in a separate track.
11555
- // - If there are notes before the first chord, then put that much silence to start the track.
11556
- // - The pattern of chord expression depends on the meter, and how many chords are in a measure.
11557
- // - There is a possibility that a measure will have an incorrect number of beats, if that is the case, then
11558
- // start the pattern anew on the next measure number.
11559
- // - If a chord root is not A-G, then ignore it as if the chord wasn't there at all.
11560
- // - If a chord modification isn't in our supported list, change it to a major triad.
11561
- //
11562
- // - If there is only one chord in a measure:
11563
- // - If 2/4, play root chord
11564
- // - If cut time, play root(1) chord(3)
11565
- // - If 3/4, play root chord chord
11566
- // - If 4/4 or common time, play root chord fifth chord
11567
- // - If 6/8, play root(1) chord(3) fifth(4) chord(6)
11568
- // - For any other meter, play the full chord on each beat. (TODO-PER: expand this as more support is added.)
11569
- //
11570
- // - If there is a chord specified that is not on a beat, move it earlier to the previous beat, unless there is already a chord on that beat.
11571
- // - Otherwise, move it later, unless there is already a chord on that beat.
11572
- // - Otherwise, ignore it. (TODO-PER: expand this as more support is added.)
11573
- //
11574
- // - If there is a chord on the second beat, play a chord for the first beat instead of a bass note.
11575
- // - Likewise, if there is a chord on the fourth beat of 4/4, play a chord on the third beat instead of a bass note.
11576
- //
11577
- // If there is any note in the melody that has a rhythm head, then assume the melody controls the rhythm, so that is
11578
- // the same as a break.
11579
- var breakSynonyms = ['break', '(break)', 'no chord', 'n.c.', 'tacet'];
11580
- function findChord(elem) {
11581
- if (gChordTacet) return 'break';
11582
-
11583
- // TODO-PER: Just using the first chord if there are more than one.
11584
- if (chordTrackFinished || !elem.chord || elem.chord.length === 0) return null;
11585
-
11586
- // Return the first annotation that is a regular chord: that is, it is in the default place or is a recognized "tacet" phrase.
11587
- for (var i = 0; i < elem.chord.length; i++) {
11588
- var ch = elem.chord[i];
11589
- if (ch.position === 'default') return ch.name;
11590
- if (breakSynonyms.indexOf(ch.name.toLowerCase()) >= 0) return 'break';
11591
- }
11592
- return null;
11593
- }
11594
11577
  function calcBeat(measureStart, beatLength, currTime) {
11595
11578
  var distanceFromStart = currTime - measureStart;
11596
11579
  return distanceFromStart / beatLength;
@@ -11606,7 +11589,7 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11606
11589
  } else if (pickupLength > beat) {
11607
11590
  volume = stressBeatUp;
11608
11591
  } else {
11609
- var barLength = meter.num / meter.den;
11592
+ //var barLength = meter.num / meter.den;
11610
11593
  var barBeat = calcBeat(lastBarTime, getBeatFraction(meter), beat);
11611
11594
  if (barBeat === 0) volume = stressBeat1;else if (parseInt(barBeat, 10) === barBeat) volume = stressBeatDown;else volume = stressBeatUp;
11612
11595
  }
@@ -11618,34 +11601,6 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11618
11601
  if (volume > 127) volume = 127;
11619
11602
  return voiceOff ? 0 : volume;
11620
11603
  }
11621
- function processChord(elem) {
11622
- var firstChord = false;
11623
- var chord = findChord(elem);
11624
- if (chord) {
11625
- var c = interpretChord(chord);
11626
- // If this isn't a recognized chord, just completely ignore it.
11627
- if (c) {
11628
- // If we ever have a chord in this voice, then we add the chord track.
11629
- // However, if there are chords on more than one voice, then just use the first voice.
11630
- if (chordTrack.length === 0) {
11631
- firstChord = true;
11632
- chordTrack.push({
11633
- cmd: 'program',
11634
- channel: chordChannel,
11635
- instrument: chordInstrument
11636
- });
11637
- }
11638
- lastChord = c;
11639
- var barBeat = calcBeat(lastBarTime, getBeatFraction(meter), timeToRealTime(elem.time));
11640
- currentChords.push({
11641
- chord: lastChord,
11642
- beat: barBeat,
11643
- start: timeToRealTime(elem.time)
11644
- });
11645
- }
11646
- }
11647
- return firstChord;
11648
- }
11649
11604
  function findNoteModifications(elem, velocity) {
11650
11605
  var ret = {};
11651
11606
  if (elem.decoration) {
@@ -11830,9 +11785,10 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11830
11785
  // If there are guitar chords, then they are put in a separate track, but they have the same format.
11831
11786
  //
11832
11787
 
11833
- var trackStartingIndex = currentTrack.length;
11788
+ //var trackStartingIndex = currentTrack.length;
11789
+
11834
11790
  var velocity = processVolume(timeToRealTime(elem.time), voiceOff);
11835
- var setChordTrack = processChord(elem);
11791
+ chordTrack.processChord(elem);
11836
11792
 
11837
11793
  // if there are grace notes, then also play them.
11838
11794
  // I'm not sure there is an exact rule for the length of the notes. My rule, unless I find
@@ -11884,15 +11840,7 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11884
11840
  // TODO-PER: Can also make a different sound on style=x and style=harmonic
11885
11841
  var ePitches = elem.pitches;
11886
11842
  if (elem.style === "rhythm") {
11887
- hasRhythmHead = true;
11888
- if (lastChord && lastChord.chick) {
11889
- ePitches = [];
11890
- for (var i2 = 0; i2 < lastChord.chick.length; i2++) {
11891
- var note2 = parseCommon.clone(elem.pitches[0]);
11892
- note2.actualPitch = lastChord.chick[i2];
11893
- ePitches.push(note2);
11894
- }
11895
- }
11843
+ ePitches = chordTrack.setRhythmHead(true, elem);
11896
11844
  }
11897
11845
  if (elem.elem) elem.elem.midiPitches = [];
11898
11846
  for (var i = 0; i < ePitches.length; i++) {
@@ -11944,7 +11892,6 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
11944
11892
  }
11945
11893
  var realDur = getRealDuration(elem);
11946
11894
  lastEventTime = Math.max(lastEventTime, timeToRealTime(elem.time) + durationRounded(realDur));
11947
- return setChordTrack;
11948
11895
  }
11949
11896
  function getRealDuration(elem) {
11950
11897
  if (elem.pitches && elem.pitches.length > 0 && elem.pitches[0]) return elem.pitches[0].duration;
@@ -12093,349 +12040,6 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
12093
12040
  if (pitch < 0) pitch += 7;
12094
12041
  return pitch;
12095
12042
  }
12096
- var basses = {
12097
- 'A': 33,
12098
- 'B': 35,
12099
- 'C': 36,
12100
- 'D': 38,
12101
- 'E': 40,
12102
- 'F': 41,
12103
- 'G': 43
12104
- };
12105
- function interpretChord(name) {
12106
- // chords have the format:
12107
- // [root][acc][modifier][/][bass][acc]
12108
- // (The chord might be surrounded by parens. Just ignore them.)
12109
- // root must be present and must be from A-G.
12110
- // acc is optional and can be # or b
12111
- // The modifier can be a wide variety of things, like "maj7". As they are discovered, more are supported here.
12112
- // If there is a slash, then there is a bass note, which can be from A-G, with an optional acc.
12113
- // If the root is unrecognized, then "undefined" is returned and there is no chord.
12114
- // If the modifier is unrecognized, a major triad is returned.
12115
- // If the bass notes is unrecognized, it is ignored.
12116
- if (name.length === 0) return undefined;
12117
- if (name === 'break') return {
12118
- chick: []
12119
- };
12120
- var root = name.substring(0, 1);
12121
- if (root === '(') {
12122
- name = name.substring(1, name.length - 2);
12123
- if (name.length === 0) return undefined;
12124
- root = name.substring(0, 1);
12125
- }
12126
- var bass = basses[root];
12127
- if (!bass)
12128
- // If the bass note isn't listed, then this was an unknown root. Only A-G are accepted.
12129
- return undefined;
12130
- // Don't transpose the chords more than an octave.
12131
- var chordTranspose = transpose;
12132
- while (chordTranspose < -8) {
12133
- chordTranspose += 12;
12134
- }
12135
- while (chordTranspose > 8) {
12136
- chordTranspose -= 12;
12137
- }
12138
- bass += chordTranspose;
12139
- var bass2 = bass - 5; // The alternating bass is a 4th below
12140
- var chick;
12141
- if (name.length === 1) chick = chordNotes(bass, '');
12142
- var remaining = name.substring(1);
12143
- var acc = remaining.substring(0, 1);
12144
- if (acc === 'b' || acc === '♭') {
12145
- bass--;
12146
- bass2--;
12147
- remaining = remaining.substring(1);
12148
- } else if (acc === '#' || acc === '♯') {
12149
- bass++;
12150
- bass2++;
12151
- remaining = remaining.substring(1);
12152
- }
12153
- var arr = remaining.split('/');
12154
- chick = chordNotes(bass, arr[0]);
12155
- // If the 5th is altered then the bass is altered. Normally the bass is 7 from the root, so adjust if it isn't.
12156
- if (chick.length >= 3) {
12157
- var fifth = chick[2] - chick[0];
12158
- bass2 = bass2 + fifth - 7;
12159
- }
12160
- if (arr.length === 2) {
12161
- var explicitBass = basses[arr[1].substring(0, 1)];
12162
- if (explicitBass) {
12163
- var bassAcc = arr[1].substring(1);
12164
- var bassShift = {
12165
- '#': 1,
12166
- '♯': 1,
12167
- 'b': -1,
12168
- '♭': -1
12169
- }[bassAcc] || 0;
12170
- bass = basses[arr[1].substring(0, 1)] + bassShift + chordTranspose;
12171
- bass2 = bass;
12172
- }
12173
- }
12174
- return {
12175
- boom: bass,
12176
- boom2: bass2,
12177
- chick: chick
12178
- };
12179
- }
12180
- var chordIntervals = {
12181
- // diminished (all flat 5 chords)
12182
- 'dim': [0, 3, 6],
12183
- '°': [0, 3, 6],
12184
- '˚': [0, 3, 6],
12185
- 'dim7': [0, 3, 6, 9],
12186
- '°7': [0, 3, 6, 9],
12187
- '˚7': [0, 3, 6, 9],
12188
- 'ø7': [0, 3, 6, 10],
12189
- 'm7(b5)': [0, 3, 6, 10],
12190
- 'm7b5': [0, 3, 6, 10],
12191
- 'm7♭5': [0, 3, 6, 10],
12192
- '-7(b5)': [0, 3, 6, 10],
12193
- '-7b5': [0, 3, 6, 10],
12194
- '7b5': [0, 4, 6, 10],
12195
- '7(b5)': [0, 4, 6, 10],
12196
- '7♭5': [0, 4, 6, 10],
12197
- '7(b9,b5)': [0, 4, 6, 10, 13],
12198
- '7b9,b5': [0, 4, 6, 10, 13],
12199
- '7(#9,b5)': [0, 4, 6, 10, 15],
12200
- '7#9b5': [0, 4, 6, 10, 15],
12201
- 'maj7(b5)': [0, 4, 6, 11],
12202
- 'maj7b5': [0, 4, 6, 11],
12203
- '13(b5)': [0, 4, 6, 10, 14, 21],
12204
- '13b5': [0, 4, 6, 10, 14, 21],
12205
- // minor (all normal 5, minor 3 chords)
12206
- 'm': [0, 3, 7],
12207
- '-': [0, 3, 7],
12208
- 'm6': [0, 3, 7, 9],
12209
- '-6': [0, 3, 7, 9],
12210
- 'm7': [0, 3, 7, 10],
12211
- '-7': [0, 3, 7, 10],
12212
- '-(b6)': [0, 3, 7, 8],
12213
- '-b6': [0, 3, 7, 8],
12214
- '-6/9': [0, 3, 7, 9, 14],
12215
- '-7(b9)': [0, 3, 7, 10, 13],
12216
- '-7b9': [0, 3, 7, 10, 13],
12217
- '-maj7': [0, 3, 7, 11],
12218
- '-9+7': [0, 3, 7, 11, 13],
12219
- '-11': [0, 3, 7, 11, 14, 17],
12220
- 'm11': [0, 3, 7, 11, 14, 17],
12221
- '-maj9': [0, 3, 7, 11, 14],
12222
- '-∆9': [0, 3, 7, 11, 14],
12223
- 'mM9': [0, 3, 7, 11, 14],
12224
- // major (all normal 5, major 3 chords)
12225
- 'M': [0, 4, 7],
12226
- '6': [0, 4, 7, 9],
12227
- '6/9': [0, 4, 7, 9, 14],
12228
- '6add9': [0, 4, 7, 9, 14],
12229
- '69': [0, 4, 7, 9, 14],
12230
- '7': [0, 4, 7, 10],
12231
- '9': [0, 4, 7, 10, 14],
12232
- '11': [0, 7, 10, 14, 17],
12233
- '13': [0, 4, 7, 10, 14, 21],
12234
- '7b9': [0, 4, 7, 10, 13],
12235
- '7♭9': [0, 4, 7, 10, 13],
12236
- '7(b9)': [0, 4, 7, 10, 13],
12237
- '7(#9)': [0, 4, 7, 10, 15],
12238
- '7#9': [0, 4, 7, 10, 15],
12239
- '(13)': [0, 4, 7, 10, 14, 21],
12240
- '7(9,13)': [0, 4, 7, 10, 14, 21],
12241
- '7(#9,b13)': [0, 4, 7, 10, 15, 20],
12242
- '7(#11)': [0, 4, 7, 10, 14, 18],
12243
- '7#11': [0, 4, 7, 10, 14, 18],
12244
- '7(b13)': [0, 4, 7, 10, 20],
12245
- '7b13': [0, 4, 7, 10, 20],
12246
- '9(#11)': [0, 4, 7, 10, 14, 18],
12247
- '9#11': [0, 4, 7, 10, 14, 18],
12248
- '13(#11)': [0, 4, 7, 10, 18, 21],
12249
- '13#11': [0, 4, 7, 10, 18, 21],
12250
- 'maj7': [0, 4, 7, 11],
12251
- '∆7': [0, 4, 7, 11],
12252
- 'Δ7': [0, 4, 7, 11],
12253
- 'maj9': [0, 4, 7, 11, 14],
12254
- 'maj7(9)': [0, 4, 7, 11, 14],
12255
- 'maj7(11)': [0, 4, 7, 11, 17],
12256
- 'maj7(#11)': [0, 4, 7, 11, 18],
12257
- 'maj7(13)': [0, 4, 7, 14, 21],
12258
- 'maj7(9,13)': [0, 4, 7, 11, 14, 21],
12259
- '7sus4': [0, 5, 7, 10],
12260
- 'm7sus4': [0, 3, 7, 10, 17],
12261
- 'sus4': [0, 5, 7],
12262
- 'sus2': [0, 2, 7],
12263
- '7sus2': [0, 2, 7, 10],
12264
- '9sus4': [0, 5, 7, 10, 14],
12265
- '13sus4': [0, 5, 7, 10, 14, 21],
12266
- // augmented (all sharp 5 chords)
12267
- 'aug7': [0, 4, 8, 10],
12268
- '+7': [0, 4, 8, 10],
12269
- '+': [0, 4, 8],
12270
- '7#5': [0, 4, 8, 10],
12271
- '7♯5': [0, 4, 8, 10],
12272
- '7+5': [0, 4, 8, 10],
12273
- '9#5': [0, 4, 8, 10, 14],
12274
- '9♯5': [0, 4, 8, 10, 14],
12275
- '9+5': [0, 4, 8, 10, 14],
12276
- '-7(#5)': [0, 3, 8, 10],
12277
- '-7#5': [0, 3, 8, 10],
12278
- '7(#5)': [0, 4, 8, 10],
12279
- '7(b9,#5)': [0, 4, 8, 10, 13],
12280
- '7b9#5': [0, 4, 8, 10, 13],
12281
- 'maj7(#5)': [0, 4, 8, 11],
12282
- 'maj7#5': [0, 4, 8, 11],
12283
- 'maj7(#5,#11)': [0, 4, 8, 11, 18],
12284
- 'maj7#5#11': [0, 4, 8, 11, 18],
12285
- '9(#5)': [0, 4, 8, 10, 14],
12286
- '13(#5)': [0, 4, 8, 10, 14, 21],
12287
- '13#5': [0, 4, 8, 10, 14, 21]
12288
- };
12289
- function chordNotes(bass, modifier) {
12290
- var intervals = chordIntervals[modifier];
12291
- if (!intervals) {
12292
- if (modifier.slice(0, 2).toLowerCase() === 'ma' || modifier[0] === 'M') intervals = chordIntervals.M;else if (modifier[0] === 'm' || modifier[0] === '-') intervals = chordIntervals.m;else intervals = chordIntervals.M;
12293
- }
12294
- bass += 12; // the chord is an octave above the bass note.
12295
- var notes = [];
12296
- for (var i = 0; i < intervals.length; i++) {
12297
- notes.push(bass + intervals[i]);
12298
- }
12299
- return notes;
12300
- }
12301
- function writeBoom(boom, beatLength, volume, beat, noteLength) {
12302
- // undefined means there is a stop time.
12303
- if (boom !== undefined) chordTrack.push({
12304
- cmd: 'note',
12305
- pitch: boom,
12306
- volume: volume,
12307
- start: lastBarTime + beat * durationRounded(beatLength),
12308
- duration: durationRounded(noteLength),
12309
- gap: 0,
12310
- instrument: bassInstrument
12311
- });
12312
- }
12313
- function writeChick(chick, beatLength, volume, beat, noteLength) {
12314
- for (var c = 0; c < chick.length; c++) {
12315
- chordTrack.push({
12316
- cmd: 'note',
12317
- pitch: chick[c],
12318
- volume: volume,
12319
- start: lastBarTime + beat * durationRounded(beatLength),
12320
- duration: durationRounded(noteLength),
12321
- gap: 0,
12322
- instrument: chordInstrument
12323
- });
12324
- }
12325
- }
12326
- var rhythmPatterns = {
12327
- "2/2": ['boom', 'chick'],
12328
- "2/4": ['boom', 'chick'],
12329
- "3/4": ['boom', 'chick', 'chick'],
12330
- "4/4": ['boom', 'chick', 'boom2', 'chick'],
12331
- "5/4": ['boom', 'chick', 'chick', 'boom2', 'chick'],
12332
- "6/8": ['boom', '', 'chick', 'boom2', '', 'chick'],
12333
- "9/8": ['boom', '', 'chick', 'boom2', '', 'chick', 'boom2', '', 'chick'],
12334
- "12/8": ['boom', '', 'chick', 'boom2', '', 'chick', 'boom', '', 'chick', 'boom2', '', 'chick']
12335
- };
12336
- function resolveChords(startTime, endTime) {
12337
- var num = meter.num;
12338
- var den = meter.den;
12339
- var beatLength = 1 / den;
12340
- var noteLength = beatLength / 2;
12341
- var pattern = rhythmPatterns[num + '/' + den];
12342
- var thisMeasureLength = parseInt(num, 10) / parseInt(den, 10);
12343
- var portionOfAMeasure = thisMeasureLength - (endTime - startTime) / tempoChangeFactor;
12344
- if (Math.abs(portionOfAMeasure) < 0.00001) portionOfAMeasure = false;
12345
- if (!pattern || portionOfAMeasure) {
12346
- // If it is an unsupported meter, or this isn't a full bar, just chick on each beat.
12347
- pattern = [];
12348
- var beatsPresent = (endTime - startTime) / tempoChangeFactor / beatLength;
12349
- for (var p = 0; p < beatsPresent; p++) {
12350
- pattern.push("chick");
12351
- }
12352
- }
12353
- //console.log(startTime, pattern, currentChords, lastChord, portionOfAMeasure)
12354
-
12355
- if (currentChords.length === 0) {
12356
- // there wasn't a new chord this measure, so use the last chord declared.
12357
- currentChords.push({
12358
- beat: 0,
12359
- chord: lastChord
12360
- });
12361
- }
12362
- if (currentChords[0].beat !== 0 && lastChord) {
12363
- // this is the case where there is a chord declared in the measure, but not on its first beat.
12364
- if (chordLastBar) currentChords.unshift({
12365
- beat: 0,
12366
- chord: chordLastBar
12367
- });
12368
- }
12369
- if (currentChords.length === 1) {
12370
- for (var m = currentChords[0].beat; m < pattern.length; m++) {
12371
- if (!hasRhythmHead) {
12372
- switch (pattern[m]) {
12373
- case 'boom':
12374
- writeBoom(currentChords[0].chord.boom, beatLength, boomVolume, m, noteLength);
12375
- break;
12376
- case 'boom2':
12377
- writeBoom(currentChords[0].chord.boom2, beatLength, boomVolume, m, noteLength);
12378
- break;
12379
- case 'chick':
12380
- writeChick(currentChords[0].chord.chick, beatLength, chickVolume, m, noteLength);
12381
- break;
12382
- }
12383
- }
12384
- }
12385
- return;
12386
- }
12387
-
12388
- // If we are here it is because more than one chord was declared in the measure, so we have to sort out what chord goes where.
12389
-
12390
- // First, normalize the chords on beats.
12391
- var mult = beatLength === 0.125 ? 3 : 1; // If this is a compound meter then the beats in the currentChords is 1/3 of the true beat
12392
- var beats = {};
12393
- for (var i = 0; i < currentChords.length; i++) {
12394
- var cc = currentChords[i];
12395
- var b = Math.round(cc.beat * mult);
12396
- beats['' + b] = cc;
12397
- }
12398
-
12399
- // - If there is a chord on the second beat, play a chord for the first beat instead of a bass note.
12400
- // - Likewise, if there is a chord on the fourth beat of 4/4, play a chord on the third beat instead of a bass note.
12401
- for (var m2 = 0; m2 < pattern.length; m2++) {
12402
- var thisChord;
12403
- if (beats['' + m2]) thisChord = beats['' + m2];
12404
- var lastBoom;
12405
- if (!hasRhythmHead && thisChord) {
12406
- switch (pattern[m2]) {
12407
- case 'boom':
12408
- if (beats['' + (m2 + 1)])
12409
- // If there is not a chord change on the next beat, play a bass note.
12410
- writeChick(thisChord.chord.chick, beatLength, chickVolume, m2, noteLength);else {
12411
- writeBoom(thisChord.chord.boom, beatLength, boomVolume, m2, noteLength);
12412
- lastBoom = thisChord.chord.boom;
12413
- }
12414
- break;
12415
- case 'boom2':
12416
- if (beats['' + (m2 + 1)]) writeChick(thisChord.chord.chick, beatLength, chickVolume, m2, noteLength);else {
12417
- // If there is the same root as the last chord, use the alternating bass, otherwise play the root.
12418
- if (lastBoom === thisChord.chord.boom) {
12419
- writeBoom(thisChord.chord.boom2, beatLength, boomVolume, m2, noteLength);
12420
- lastBoom = undefined;
12421
- } else {
12422
- writeBoom(thisChord.chord.boom, beatLength, boomVolume, m2, noteLength);
12423
- lastBoom = thisChord.chord.boom;
12424
- }
12425
- }
12426
- break;
12427
- case 'chick':
12428
- writeChick(thisChord.chord.chick, beatLength, chickVolume, m2, noteLength);
12429
- break;
12430
- case '':
12431
- if (beats['' + m2])
12432
- // If there is an explicit chord on this beat, play it.
12433
- writeChick(thisChord.chord.chick, beatLength, chickVolume, m2, noteLength);
12434
- break;
12435
- }
12436
- }
12437
- }
12438
- }
12439
12043
  function normalizeDrumDefinition(params) {
12440
12044
  // Be very strict with the drum definition. If anything is not perfect,
12441
12045
  // just turn the drums off.
@@ -12928,6 +12532,7 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
12928
12532
  var drumBars = options.drumBars || 1;
12929
12533
  var drumIntro = options.drumIntro || 0;
12930
12534
  var drumOn = drumPattern !== "";
12535
+ var drumOffAfterIntro = !!options.drumOff;
12931
12536
  var style = []; // The note head style for each voice.
12932
12537
  var rhythmHeadThisBar = false; // Rhythm notation was detected.
12933
12538
  var crescendoSize = 50; // how much to increase or decrease volume when crescendo/diminuendo is encountered.
@@ -13381,13 +12986,13 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13381
12986
  break;
13382
12987
  case "gchordoff":
13383
12988
  voices[voiceNumber].push({
13384
- el_type: 'gchord',
12989
+ el_type: 'gchordOn',
13385
12990
  tacet: true
13386
12991
  });
13387
12992
  break;
13388
12993
  case "gchordon":
13389
12994
  voices[voiceNumber].push({
13390
- el_type: 'gchord',
12995
+ el_type: 'gchordOn',
13391
12996
  tacet: false
13392
12997
  });
13393
12998
  break;
@@ -13410,15 +13015,21 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13410
13015
  });
13411
13016
  break;
13412
13017
  case "vol":
13018
+ case "volinc":
13413
13019
  voices[voiceNumber].push({
13414
- el_type: 'vol',
13020
+ el_type: elem.cmd,
13415
13021
  volume: elem.params[0]
13416
13022
  });
13417
13023
  break;
13418
- case "volinc":
13024
+ case "swing":
13025
+ case "gchord":
13026
+ case "bassprog":
13027
+ case "chordprog":
13028
+ case "bassvol":
13029
+ case "chordvol":
13419
13030
  voices[voiceNumber].push({
13420
- el_type: 'volinc',
13421
- volume: elem.params[0]
13031
+ el_type: elem.cmd,
13032
+ param: elem.params[0]
13422
13033
  });
13423
13034
  break;
13424
13035
  default:
@@ -13460,16 +13071,19 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13460
13071
  if (voices[vv].length > insertPoint) {
13461
13072
  for (var w = 0; w < drumIntro; w++) {
13462
13073
  // If it is the last measure of intro, subtract the pickups.
13463
- if (pickups === 0 || w < drumIntro - 1) voices[vv].splice(insertPoint, 0, {
13464
- el_type: "note",
13465
- rest: {
13466
- type: "rest"
13467
- },
13468
- duration: measureLength
13469
- }, {
13470
- el_type: "bar"
13471
- });else {
13074
+ if (pickups === 0 || w < drumIntro - 1) {
13472
13075
  voices[vv].splice(insertPoint, 0, {
13076
+ el_type: "note",
13077
+ rest: {
13078
+ type: "rest"
13079
+ },
13080
+ duration: measureLength
13081
+ }, {
13082
+ el_type: "bar"
13083
+ });
13084
+ insertPoint += 2;
13085
+ } else {
13086
+ voices[vv].splice(insertPoint++, 0, {
13473
13087
  el_type: "note",
13474
13088
  rest: {
13475
13089
  type: "rest"
@@ -13478,6 +13092,19 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13478
13092
  });
13479
13093
  }
13480
13094
  }
13095
+ if (drumOffAfterIntro) {
13096
+ drumOn = false;
13097
+ voices[vv].splice(insertPoint++, 0, {
13098
+ el_type: 'drum',
13099
+ params: {
13100
+ pattern: drumPattern,
13101
+ bars: drumBars,
13102
+ intro: drumIntro,
13103
+ on: drumOn
13104
+ }
13105
+ });
13106
+ drumOffAfterIntro = false;
13107
+ }
13481
13108
  }
13482
13109
  }
13483
13110
  }
@@ -13673,6 +13300,589 @@ module.exports = centsToFactor;
13673
13300
 
13674
13301
  /***/ }),
13675
13302
 
13303
+ /***/ "./src/synth/chord-track.js":
13304
+ /*!**********************************!*\
13305
+ !*** ./src/synth/chord-track.js ***!
13306
+ \**********************************/
13307
+ /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
13308
+
13309
+ //
13310
+ // The algorithm for chords is:
13311
+ // - The chords are done in a separate track.
13312
+ // - If there are notes before the first chord, then put that much silence to start the track.
13313
+ // - The pattern of chord expression depends on the meter, and how many chords are in a measure.
13314
+ // - There is a possibility that a measure will have an incorrect number of beats, if that is the case, then
13315
+ // start the pattern anew on the next measure number.
13316
+ // - If a chord root is not A-G, then ignore it as if the chord wasn't there at all.
13317
+ // - If a chord modification isn't in our supported list, change it to a major triad.
13318
+ //
13319
+ // - There is a standard pattern of boom-chick for each time sig, or it can be overridden.
13320
+ // - For any unrecognized meter, play the full chord on each beat.
13321
+ //
13322
+ // - If there is a chord specified that is not on a beat, move it earlier to the previous beat, unless there is already a chord on that beat.
13323
+ // - Otherwise, move it later, unless there is already a chord on that beat.
13324
+ // - Otherwise, ignore it. (TODO-PER: expand this as more support is added.)
13325
+ //
13326
+ // 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
+
13328
+ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/abc_common.js");
13329
+ var ChordTrack = function ChordTrack(numVoices, chordsOff, midiOptions, meter) {
13330
+ this.chordTrack = [];
13331
+ this.chordTrackFinished = false;
13332
+ this.chordChannel = numVoices; // first free channel for chords
13333
+ this.currentChords = [];
13334
+ this.lastChord;
13335
+ this.chordLastBar;
13336
+ this.chordsOff = !!chordsOff;
13337
+ this.gChordTacet = this.chordsOff;
13338
+ this.hasRhythmHead = false;
13339
+ this.transpose = 0;
13340
+ this.lastBarTime = 0;
13341
+ this.meter = meter;
13342
+ 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;
13345
+ this.boomVolume = midiOptions.bassvol && midiOptions.bassvol.length === 1 ? midiOptions.bassvol[0] : 64;
13346
+ this.chickVolume = midiOptions.chordvol && midiOptions.chordvol.length === 1 ? midiOptions.chordvol[0] : 48;
13347
+ this.overridePattern = midiOptions.gchord ? parseGChord(midiOptions.gchord[0]) : undefined;
13348
+ };
13349
+ ChordTrack.prototype.setMeter = function (meter) {
13350
+ this.meter = meter;
13351
+ };
13352
+ ChordTrack.prototype.setTempoChangeFactor = function (tempoChangeFactor) {
13353
+ this.tempoChangeFactor = tempoChangeFactor;
13354
+ };
13355
+ ChordTrack.prototype.setLastBarTime = function (lastBarTime) {
13356
+ this.lastBarTime = lastBarTime;
13357
+ };
13358
+ ChordTrack.prototype.setTranspose = function (transpose) {
13359
+ this.transpose = transpose;
13360
+ };
13361
+ ChordTrack.prototype.setRhythmHead = function (isRhythmHead, elem) {
13362
+ this.hasRhythmHead = isRhythmHead;
13363
+ var ePitches = [];
13364
+ if (isRhythmHead) {
13365
+ if (this.lastChord && this.lastChord.chick) {
13366
+ for (var i2 = 0; i2 < this.lastChord.chick.length; i2++) {
13367
+ var note2 = parseCommon.clone(elem.pitches[0]);
13368
+ note2.actualPitch = this.lastChord.chick[i2];
13369
+ ePitches.push(note2);
13370
+ }
13371
+ }
13372
+ }
13373
+ return ePitches;
13374
+ };
13375
+ ChordTrack.prototype.barEnd = function (element) {
13376
+ if (this.chordTrack.length > 0 && !this.chordTrackFinished) {
13377
+ this.resolveChords(this.lastBarTime, timeToRealTime(element.time));
13378
+ this.currentChords = [];
13379
+ }
13380
+ this.chordLastBar = this.lastChord;
13381
+ };
13382
+ ChordTrack.prototype.gChordOn = function (element) {
13383
+ if (!this.chordsOff) this.gChordTacet = element.tacet;
13384
+ };
13385
+ ChordTrack.prototype.paramChange = function (element) {
13386
+ switch (element.el_type) {
13387
+ case "gchord":
13388
+ this.overridePattern = parseGChord(element.param);
13389
+ break;
13390
+ case "bassprog":
13391
+ this.bassInstrument = element.param;
13392
+ break;
13393
+ case "chordprog":
13394
+ this.chordInstrument = element.param;
13395
+ break;
13396
+ case "bassvol":
13397
+ this.boomVolume = element.param;
13398
+ break;
13399
+ case "chordvol":
13400
+ this.chickVolume = element.param;
13401
+ break;
13402
+ default:
13403
+ console.log("unhandled midi param", element);
13404
+ }
13405
+ };
13406
+ ChordTrack.prototype.finish = function () {
13407
+ if (!this.chordTrackEmpty())
13408
+ // Don't do chords on more than one track, so turn off chord detection after we create it.
13409
+ this.chordTrackFinished = true;
13410
+ };
13411
+ ChordTrack.prototype.addTrack = function (tracks) {
13412
+ if (!this.chordTrackEmpty()) tracks.push(this.chordTrack);
13413
+ };
13414
+ ChordTrack.prototype.findChord = function (elem) {
13415
+ if (this.gChordTacet) return 'break';
13416
+
13417
+ // TODO-PER: Just using the first chord if there are more than one.
13418
+ if (this.chordTrackFinished || !elem.chord || elem.chord.length === 0) return null;
13419
+
13420
+ // Return the first annotation that is a regular chord: that is, it is in the default place or is a recognized "tacet" phrase.
13421
+ for (var i = 0; i < elem.chord.length; i++) {
13422
+ var ch = elem.chord[i];
13423
+ if (ch.position === 'default') return ch.name;
13424
+ if (this.breakSynonyms.indexOf(ch.name.toLowerCase()) >= 0) return 'break';
13425
+ }
13426
+ return null;
13427
+ };
13428
+ ChordTrack.prototype.interpretChord = function (name) {
13429
+ // chords have the format:
13430
+ // [root][acc][modifier][/][bass][acc]
13431
+ // (The chord might be surrounded by parens. Just ignore them.)
13432
+ // root must be present and must be from A-G.
13433
+ // acc is optional and can be # or b
13434
+ // The modifier can be a wide variety of things, like "maj7". As they are discovered, more are supported here.
13435
+ // If there is a slash, then there is a bass note, which can be from A-G, with an optional acc.
13436
+ // If the root is unrecognized, then "undefined" is returned and there is no chord.
13437
+ // If the modifier is unrecognized, a major triad is returned.
13438
+ // If the bass notes is unrecognized, it is ignored.
13439
+ if (name.length === 0) return undefined;
13440
+ if (name === 'break') return {
13441
+ chick: []
13442
+ };
13443
+ var root = name.substring(0, 1);
13444
+ if (root === '(') {
13445
+ name = name.substring(1, name.length - 2);
13446
+ if (name.length === 0) return undefined;
13447
+ root = name.substring(0, 1);
13448
+ }
13449
+ var bass = this.basses[root];
13450
+ if (!bass)
13451
+ // If the bass note isn't listed, then this was an unknown root. Only A-G are accepted.
13452
+ return undefined;
13453
+ // Don't transpose the chords more than an octave.
13454
+ var chordTranspose = this.transpose;
13455
+ while (chordTranspose < -8) {
13456
+ chordTranspose += 12;
13457
+ }
13458
+ while (chordTranspose > 8) {
13459
+ chordTranspose -= 12;
13460
+ }
13461
+ bass += chordTranspose;
13462
+ var bass2 = bass - 5; // The alternating bass is a 4th below
13463
+ var chick;
13464
+ if (name.length === 1) chick = this.chordNotes(bass, '');
13465
+ var remaining = name.substring(1);
13466
+ var acc = remaining.substring(0, 1);
13467
+ if (acc === 'b' || acc === '♭') {
13468
+ bass--;
13469
+ bass2--;
13470
+ remaining = remaining.substring(1);
13471
+ } else if (acc === '#' || acc === '♯') {
13472
+ bass++;
13473
+ bass2++;
13474
+ remaining = remaining.substring(1);
13475
+ }
13476
+ var arr = remaining.split('/');
13477
+ chick = this.chordNotes(bass, arr[0]);
13478
+ // 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
+ if (chick.length >= 3) {
13480
+ var fifth = chick[2] - chick[0];
13481
+ bass2 = bass2 + fifth - 7;
13482
+ }
13483
+ if (arr.length === 2) {
13484
+ var explicitBass = this.basses[arr[1].substring(0, 1)];
13485
+ if (explicitBass) {
13486
+ var bassAcc = arr[1].substring(1);
13487
+ var bassShift = {
13488
+ '#': 1,
13489
+ '♯': 1,
13490
+ 'b': -1,
13491
+ '♭': -1
13492
+ }[bassAcc] || 0;
13493
+ bass = this.basses[arr[1].substring(0, 1)] + bassShift + chordTranspose;
13494
+ bass2 = bass;
13495
+ }
13496
+ }
13497
+ return {
13498
+ boom: bass,
13499
+ boom2: bass2,
13500
+ chick: chick
13501
+ };
13502
+ };
13503
+ ChordTrack.prototype.chordNotes = function (bass, modifier) {
13504
+ var intervals = this.chordIntervals[modifier];
13505
+ if (!intervals) {
13506
+ 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
+ }
13508
+ bass += 12; // the chord is an octave above the bass note.
13509
+ var notes = [];
13510
+ for (var i = 0; i < intervals.length; i++) {
13511
+ notes.push(bass + intervals[i]);
13512
+ }
13513
+ return notes;
13514
+ };
13515
+ ChordTrack.prototype.writeNote = function (note, beatLength, volume, beat, noteLength, instrument) {
13516
+ // undefined means there is a stop time.
13517
+ if (note !== undefined) this.chordTrack.push({
13518
+ cmd: 'note',
13519
+ pitch: note,
13520
+ volume: volume,
13521
+ start: this.lastBarTime + beat * durationRounded(beatLength, this.tempoChangeFactor),
13522
+ duration: durationRounded(noteLength, this.tempoChangeFactor),
13523
+ gap: 0,
13524
+ instrument: instrument
13525
+ });
13526
+ };
13527
+ ChordTrack.prototype.chordTrackEmpty = function () {
13528
+ var isEmpty = true;
13529
+ for (var i = 0; i < this.chordTrack.length && isEmpty; i++) {
13530
+ if (this.chordTrack[i].cmd === 'note') isEmpty = false;
13531
+ }
13532
+ return isEmpty;
13533
+ };
13534
+ ChordTrack.prototype.resolveChords = function (startTime, endTime) {
13535
+ // If there is a rhythm head anywhere in the measure then don't add a separate rhythm track
13536
+ if (this.hasRhythmHead) return;
13537
+ var num = this.meter.num;
13538
+ var den = this.meter.den;
13539
+ var beatLength = 1 / den;
13540
+ var noteLength = beatLength / 2;
13541
+ var thisMeasureLength = parseInt(num, 10) / parseInt(den, 10);
13542
+ var portionOfAMeasure = thisMeasureLength - (endTime - startTime) / this.tempoChangeFactor;
13543
+ if (Math.abs(portionOfAMeasure) < 0.00001) portionOfAMeasure = 0;
13544
+
13545
+ // there wasn't a new chord this measure, so use the last chord declared.
13546
+ // also the case where there is a chord declared in the measure, but not on its first beat.
13547
+ if (this.currentChords.length === 0 || this.currentChords[0].beat !== 0) {
13548
+ this.currentChords.unshift({
13549
+ beat: 0,
13550
+ chord: this.chordLastBar
13551
+ });
13552
+ }
13553
+
13554
+ //console.log(this.currentChords)
13555
+ var currentChordsExpanded = expandCurrentChords(this.currentChords, 8 * num / den, beatLength);
13556
+ //console.log(currentChordsExpanded)
13557
+ var thisPattern = this.overridePattern ? this.overridePattern : this.rhythmPatterns[num + '/' + den];
13558
+ if (portionOfAMeasure) {
13559
+ thisPattern = [];
13560
+ var beatsPresent = (endTime - startTime) / this.tempoChangeFactor * 8;
13561
+ for (var p = 0; p < beatsPresent / 2; p += 2) {
13562
+ thisPattern.push("chick");
13563
+ thisPattern.push("");
13564
+ }
13565
+ }
13566
+ if (!thisPattern) {
13567
+ thisPattern = [];
13568
+ for (var p = 0; p < 8 * num / den / 2; p++) {
13569
+ thisPattern.push('chick');
13570
+ thisPattern.push("");
13571
+ }
13572
+ }
13573
+ var firstBoom = true;
13574
+ // If the pattern is overridden, it might be longer than the length of a measure. If so, then ignore the rest of it
13575
+ var minLength = Math.min(thisPattern.length, currentChordsExpanded.length);
13576
+ for (var p = 0; p < minLength; p++) {
13577
+ if (p > 0 && currentChordsExpanded[p - 1] && currentChordsExpanded[p] && currentChordsExpanded[p - 1].boom !== currentChordsExpanded[p].boom) firstBoom = true;
13578
+ var type = thisPattern[p];
13579
+ var isBoom = type.indexOf('boom') >= 0;
13580
+ // If we changed chords at a time when we're not expecting a bass note, then add an extra bass note in if the first thing in the pattern is a bass note.
13581
+ var newBass = !isBoom && p !== 0 && thisPattern[0].indexOf('boom') >= 0 && (!currentChordsExpanded[p - 1] || currentChordsExpanded[p - 1].boom !== currentChordsExpanded[p].boom);
13582
+ var pitches = resolvePitch(currentChordsExpanded[p], type, firstBoom, newBass);
13583
+ if (isBoom) firstBoom = false;
13584
+ for (var oo = 0; oo < pitches.length; oo++) {
13585
+ this.writeNote(pitches[oo], 0.125, isBoom || newBass ? this.boomVolume : this.chickVolume, p, noteLength, isBoom || newBass ? this.bassInstrument : this.chordInstrument);
13586
+ 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
+ }
13588
+ }
13589
+
13590
+ return;
13591
+ };
13592
+ ChordTrack.prototype.processChord = function (elem) {
13593
+ if (this.chordTrackFinished) return;
13594
+ var chord = this.findChord(elem);
13595
+ if (chord) {
13596
+ var c = this.interpretChord(chord);
13597
+ // If this isn't a recognized chord, just completely ignore it.
13598
+ if (c) {
13599
+ // If we ever have a chord in this voice, then we add the chord track.
13600
+ // However, if there are chords on more than one voice, then just use the first voice.
13601
+ if (this.chordTrack.length === 0) {
13602
+ this.chordTrack.push({
13603
+ cmd: 'program',
13604
+ channel: this.chordChannel,
13605
+ instrument: this.chordInstrument
13606
+ });
13607
+ }
13608
+ this.lastChord = c;
13609
+ var barBeat = calcBeat(this.lastBarTime, timeToRealTime(elem.time));
13610
+ this.currentChords.push({
13611
+ chord: this.lastChord,
13612
+ beat: barBeat,
13613
+ start: timeToRealTime(elem.time)
13614
+ });
13615
+ }
13616
+ }
13617
+ };
13618
+ function resolvePitch(currentChord, type, firstBoom, newBass) {
13619
+ var ret = [];
13620
+ if (!currentChord) return ret;
13621
+ if (type.indexOf('boom') >= 0) ret.push(firstBoom ? currentChord.boom : currentChord.boom2);else if (newBass) ret.push(currentChord.boom);
13622
+ if (type.indexOf('chick') >= 0) {
13623
+ for (var i = 0; i < currentChord.chick.length; i++) {
13624
+ ret.push(currentChord.chick[i]);
13625
+ }
13626
+ }
13627
+ switch (type) {
13628
+ case 'DO':
13629
+ ret.push(currentChord.chick[0]);
13630
+ break;
13631
+ case 'MI':
13632
+ ret.push(currentChord.chick[1]);
13633
+ break;
13634
+ case 'SOL':
13635
+ ret.push(currentChord.chick[2]);
13636
+ break;
13637
+ case 'TI':
13638
+ currentChord.chick.length > 3 ? ret.push(currentChord.chick[2]) : ret.push(currentChord.chick[0] + 12);
13639
+ break;
13640
+ case 'TOP':
13641
+ currentChord.chick.length > 4 ? ret.push(currentChord.chick[2]) : ret.push(currentChord.chick[1] + 12);
13642
+ break;
13643
+ case 'do':
13644
+ ret.push(currentChord.chick[0] + 12);
13645
+ break;
13646
+ case 'mi':
13647
+ ret.push(currentChord.chick[1] + 12);
13648
+ break;
13649
+ case 'sol':
13650
+ ret.push(currentChord.chick[2] + 12);
13651
+ break;
13652
+ case 'ti':
13653
+ currentChord.chick.length > 3 ? ret.push(currentChord.chick[2] + 12) : ret.push(currentChord.chick[0] + 24);
13654
+ break;
13655
+ case 'top':
13656
+ currentChord.chick.length > 4 ? ret.push(currentChord.chick[2] + 12) : ret.push(currentChord.chick[1] + 24);
13657
+ break;
13658
+ }
13659
+ return ret;
13660
+ }
13661
+ function parseGChord(gchord) {
13662
+ // 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
+ var pattern = [];
13664
+ for (var i = 0; i < gchord.length; i++) {
13665
+ var ch = gchord[i];
13666
+ switch (ch) {
13667
+ case 'z':
13668
+ pattern.push('');
13669
+ break;
13670
+ case '2':
13671
+ pattern.push('');
13672
+ break;
13673
+ // TODO-PER: This should extend the last note, but that's a small effect
13674
+ case 'c':
13675
+ pattern.push('chick');
13676
+ break;
13677
+ case 'b':
13678
+ pattern.push('boom&chick');
13679
+ break;
13680
+ case 'f':
13681
+ pattern.push('boom');
13682
+ break;
13683
+ case 'G':
13684
+ pattern.push('DO');
13685
+ break;
13686
+ case 'H':
13687
+ pattern.push('MI');
13688
+ break;
13689
+ case 'I':
13690
+ pattern.push('SOL');
13691
+ break;
13692
+ case 'J':
13693
+ pattern.push('TI');
13694
+ break;
13695
+ case 'K':
13696
+ pattern.push('TOP');
13697
+ break;
13698
+ case 'g':
13699
+ pattern.push('do');
13700
+ break;
13701
+ case 'h':
13702
+ pattern.push('mi');
13703
+ break;
13704
+ case 'i':
13705
+ pattern.push('sol');
13706
+ break;
13707
+ case 'j':
13708
+ pattern.push('ti');
13709
+ break;
13710
+ case 'k':
13711
+ pattern.push('top');
13712
+ break;
13713
+ }
13714
+ }
13715
+ return pattern;
13716
+ }
13717
+
13718
+ // This returns an array that has a chord for each 1/8th note position in the current measure
13719
+ function expandCurrentChords(currentChords, num8thNotes, beatLength) {
13720
+ beatLength = beatLength * 8; // this is expressed as a fraction, so that 0.25 is a quarter notes. We want it to be the number of 8th notes
13721
+ var chords = [];
13722
+ if (currentChords.length === 0) return chords;
13723
+ var currentChord = currentChords[0].chord;
13724
+ for (var i = 1; i < currentChords.length; i++) {
13725
+ var current = currentChords[i];
13726
+ while (chords.length < current.beat) {
13727
+ chords.push(currentChord);
13728
+ }
13729
+ currentChord = current.chord;
13730
+ }
13731
+ while (chords.length < num8thNotes) {
13732
+ chords.push(currentChord);
13733
+ }
13734
+ return chords;
13735
+ }
13736
+ function calcBeat(measureStart, currTime) {
13737
+ var distanceFromStart = currTime - measureStart;
13738
+ return distanceFromStart * 8;
13739
+ }
13740
+ ChordTrack.prototype.breakSynonyms = ['break', '(break)', 'no chord', 'n.c.', 'tacet'];
13741
+ ChordTrack.prototype.basses = {
13742
+ 'A': 33,
13743
+ 'B': 35,
13744
+ 'C': 36,
13745
+ 'D': 38,
13746
+ 'E': 40,
13747
+ 'F': 41,
13748
+ 'G': 43
13749
+ };
13750
+ ChordTrack.prototype.chordIntervals = {
13751
+ // diminished (all flat 5 chords)
13752
+ 'dim': [0, 3, 6],
13753
+ '°': [0, 3, 6],
13754
+ '˚': [0, 3, 6],
13755
+ 'dim7': [0, 3, 6, 9],
13756
+ '°7': [0, 3, 6, 9],
13757
+ '˚7': [0, 3, 6, 9],
13758
+ 'ø7': [0, 3, 6, 10],
13759
+ 'm7(b5)': [0, 3, 6, 10],
13760
+ 'm7b5': [0, 3, 6, 10],
13761
+ 'm7♭5': [0, 3, 6, 10],
13762
+ '-7(b5)': [0, 3, 6, 10],
13763
+ '-7b5': [0, 3, 6, 10],
13764
+ '7b5': [0, 4, 6, 10],
13765
+ '7(b5)': [0, 4, 6, 10],
13766
+ '7♭5': [0, 4, 6, 10],
13767
+ '7(b9,b5)': [0, 4, 6, 10, 13],
13768
+ '7b9,b5': [0, 4, 6, 10, 13],
13769
+ '7(#9,b5)': [0, 4, 6, 10, 15],
13770
+ '7#9b5': [0, 4, 6, 10, 15],
13771
+ 'maj7(b5)': [0, 4, 6, 11],
13772
+ 'maj7b5': [0, 4, 6, 11],
13773
+ '13(b5)': [0, 4, 6, 10, 14, 21],
13774
+ '13b5': [0, 4, 6, 10, 14, 21],
13775
+ // minor (all normal 5, minor 3 chords)
13776
+ 'm': [0, 3, 7],
13777
+ '-': [0, 3, 7],
13778
+ 'm6': [0, 3, 7, 9],
13779
+ '-6': [0, 3, 7, 9],
13780
+ 'm7': [0, 3, 7, 10],
13781
+ '-7': [0, 3, 7, 10],
13782
+ '-(b6)': [0, 3, 7, 8],
13783
+ '-b6': [0, 3, 7, 8],
13784
+ '-6/9': [0, 3, 7, 9, 14],
13785
+ '-7(b9)': [0, 3, 7, 10, 13],
13786
+ '-7b9': [0, 3, 7, 10, 13],
13787
+ '-maj7': [0, 3, 7, 11],
13788
+ '-9+7': [0, 3, 7, 11, 13],
13789
+ '-11': [0, 3, 7, 11, 14, 17],
13790
+ 'm11': [0, 3, 7, 11, 14, 17],
13791
+ '-maj9': [0, 3, 7, 11, 14],
13792
+ '-∆9': [0, 3, 7, 11, 14],
13793
+ 'mM9': [0, 3, 7, 11, 14],
13794
+ // major (all normal 5, major 3 chords)
13795
+ 'M': [0, 4, 7],
13796
+ '6': [0, 4, 7, 9],
13797
+ '6/9': [0, 4, 7, 9, 14],
13798
+ '6add9': [0, 4, 7, 9, 14],
13799
+ '69': [0, 4, 7, 9, 14],
13800
+ '7': [0, 4, 7, 10],
13801
+ '9': [0, 4, 7, 10, 14],
13802
+ '11': [0, 7, 10, 14, 17],
13803
+ '13': [0, 4, 7, 10, 14, 21],
13804
+ '7b9': [0, 4, 7, 10, 13],
13805
+ '7♭9': [0, 4, 7, 10, 13],
13806
+ '7(b9)': [0, 4, 7, 10, 13],
13807
+ '7(#9)': [0, 4, 7, 10, 15],
13808
+ '7#9': [0, 4, 7, 10, 15],
13809
+ '(13)': [0, 4, 7, 10, 14, 21],
13810
+ '7(9,13)': [0, 4, 7, 10, 14, 21],
13811
+ '7(#9,b13)': [0, 4, 7, 10, 15, 20],
13812
+ '7(#11)': [0, 4, 7, 10, 14, 18],
13813
+ '7#11': [0, 4, 7, 10, 14, 18],
13814
+ '7(b13)': [0, 4, 7, 10, 20],
13815
+ '7b13': [0, 4, 7, 10, 20],
13816
+ '9(#11)': [0, 4, 7, 10, 14, 18],
13817
+ '9#11': [0, 4, 7, 10, 14, 18],
13818
+ '13(#11)': [0, 4, 7, 10, 18, 21],
13819
+ '13#11': [0, 4, 7, 10, 18, 21],
13820
+ 'maj7': [0, 4, 7, 11],
13821
+ '∆7': [0, 4, 7, 11],
13822
+ 'Δ7': [0, 4, 7, 11],
13823
+ 'maj9': [0, 4, 7, 11, 14],
13824
+ 'maj7(9)': [0, 4, 7, 11, 14],
13825
+ 'maj7(11)': [0, 4, 7, 11, 17],
13826
+ 'maj7(#11)': [0, 4, 7, 11, 18],
13827
+ 'maj7(13)': [0, 4, 7, 14, 21],
13828
+ 'maj7(9,13)': [0, 4, 7, 11, 14, 21],
13829
+ '7sus4': [0, 5, 7, 10],
13830
+ 'm7sus4': [0, 3, 7, 10, 17],
13831
+ 'sus4': [0, 5, 7],
13832
+ 'sus2': [0, 2, 7],
13833
+ '7sus2': [0, 2, 7, 10],
13834
+ '9sus4': [0, 5, 7, 10, 14],
13835
+ '13sus4': [0, 5, 7, 10, 14, 21],
13836
+ // augmented (all sharp 5 chords)
13837
+ 'aug7': [0, 4, 8, 10],
13838
+ '+7': [0, 4, 8, 10],
13839
+ '+': [0, 4, 8],
13840
+ '7#5': [0, 4, 8, 10],
13841
+ '7♯5': [0, 4, 8, 10],
13842
+ '7+5': [0, 4, 8, 10],
13843
+ '9#5': [0, 4, 8, 10, 14],
13844
+ '9♯5': [0, 4, 8, 10, 14],
13845
+ '9+5': [0, 4, 8, 10, 14],
13846
+ '-7(#5)': [0, 3, 8, 10],
13847
+ '-7#5': [0, 3, 8, 10],
13848
+ '7(#5)': [0, 4, 8, 10],
13849
+ '7(b9,#5)': [0, 4, 8, 10, 13],
13850
+ '7b9#5': [0, 4, 8, 10, 13],
13851
+ 'maj7(#5)': [0, 4, 8, 11],
13852
+ 'maj7#5': [0, 4, 8, 11],
13853
+ 'maj7(#5,#11)': [0, 4, 8, 11, 18],
13854
+ 'maj7#5#11': [0, 4, 8, 11, 18],
13855
+ '9(#5)': [0, 4, 8, 10, 14],
13856
+ '13(#5)': [0, 4, 8, 10, 14, 21],
13857
+ '13#5': [0, 4, 8, 10, 14, 21]
13858
+ };
13859
+ ChordTrack.prototype.rhythmPatterns = {
13860
+ "2/2": ['boom', '', '', '', 'chick', '', '', ''],
13861
+ "3/2": ['boom', '', '', '', 'chick', '', '', '', 'chick', '', '', ''],
13862
+ "4/2": ['boom', '', '', '', 'chick', '', '', '', 'boom', '', '', '', 'chick', '', '', ''],
13863
+ "2/4": ['boom', '', 'chick', ''],
13864
+ "3/4": ['boom', '', 'chick', '', 'chick', ''],
13865
+ "4/4": ['boom', '', 'chick', '', 'boom', '', 'chick', ''],
13866
+ "5/4": ['boom', '', 'chick', '', 'chick', '', 'boom', '', 'chick', ''],
13867
+ "6/4": ['boom', '', 'chick', '', 'boom', '', 'chick', '', 'boom', '', 'chick', ''],
13868
+ "3/8": ['boom', '', 'chick'],
13869
+ "6/8": ['boom', '', 'chick', 'boom', '', 'chick'],
13870
+ "9/8": ['boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick'],
13871
+ "12/8": ['boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick']
13872
+ };
13873
+
13874
+ // TODO-PER: these are repeated in flattener. Can it be shared?
13875
+
13876
+ function timeToRealTime(time) {
13877
+ return time / 1000000;
13878
+ }
13879
+ function durationRounded(duration, tempoChangeFactor) {
13880
+ return Math.round(duration * tempoChangeFactor * 1000000) / 1000000;
13881
+ }
13882
+ module.exports = ChordTrack;
13883
+
13884
+ /***/ }),
13885
+
13676
13886
  /***/ "./src/synth/create-note-map.js":
13677
13887
  /*!**************************************!*\
13678
13888
  !*** ./src/synth/create-note-map.js ***!
@@ -13699,13 +13909,14 @@ var createNoteMap = function createNoteMap(sequence) {
13699
13909
  // ev contains:
13700
13910
  // {"cmd":"note","pitch":72,"volume":95,"start":0.125,"duration":0.25,"instrument":0,"gap":0}
13701
13911
  // where start and duration are in whole notes, gap is in 1/1920 of a second (i.e. MIDI ticks)
13912
+ var inst = ev.instrument !== undefined ? instrumentIndexToName[ev.instrument] : currentInstrument;
13702
13913
  if (ev.duration > 0) {
13703
13914
  var gap = ev.gap ? ev.gap : 0;
13704
13915
  var len = ev.duration;
13705
13916
  gap = Math.min(gap, len * 2 / 3);
13706
13917
  var obj = {
13707
13918
  pitch: ev.pitch,
13708
- instrument: currentInstrument,
13919
+ instrument: inst,
13709
13920
  start: Math.round(ev.start * 1000000) / 1000000,
13710
13921
  end: Math.round((ev.start + len - gap) * 1000000) / 1000000,
13711
13922
  volume: ev.volume
@@ -13968,12 +14179,13 @@ function CreateSynth() {
13968
14179
  self.audioBuffers = []; // cache of the buffers so starting play can be fast.
13969
14180
  self.duration = undefined; // the duration of the tune in seconds.
13970
14181
  self.isRunning = false; // whether there is currently a sound buffer running.
13971
- // self.options = undefined
14182
+ self.options = undefined;
14183
+ self.pickupLength = 0;
13972
14184
 
13973
14185
  // Load and cache all needed sounds
13974
14186
  self.init = function (options) {
13975
14187
  if (!options) options = {};
13976
- // self.options = options
14188
+ if (options.options) self.options = options.options;
13977
14189
  registerAudioContext(options.audioContext); // This works no matter what - if there is already an ac it is a nop; if the context is not passed in, then it creates one.
13978
14190
  var startTime = activeAudioContext().currentTime;
13979
14191
  self.debugCallback = options.debugCallback;
@@ -14042,12 +14254,17 @@ function CreateSynth() {
14042
14254
  self.flattened = options.visualObj.setUpAudio(params);
14043
14255
  var meter = options.visualObj.getMeterFraction();
14044
14256
  if (meter.den) self.meterSize = options.visualObj.getMeterFraction().num / options.visualObj.getMeterFraction().den;
14257
+ self.pickupLength = options.visualObj.getPickupLength();
14045
14258
  } else if (options.sequence) self.flattened = options.sequence;else return Promise.reject(new Error("Must pass in either a visualObj or a sequence"));
14046
14259
  self.millisecondsPerMeasure = options.millisecondsPerMeasure ? options.millisecondsPerMeasure : options.visualObj ? options.visualObj.millisecondsPerMeasure(self.flattened.tempo) : 1000;
14047
14260
  self.beatsPerMeasure = options.visualObj ? options.visualObj.getBeatsPerMeasure() : 4;
14048
14261
  self.sequenceCallback = params.sequenceCallback;
14049
14262
  self.callbackContext = params.callbackContext;
14050
14263
  self.onEnded = params.onEnded;
14264
+ self.meterFraction = options.visualObj ? options.visualObj.getMeterFraction() : {
14265
+ den: 1
14266
+ }; // If we are given a sequence instead of a regular visual obj, then don't do the swing
14267
+
14051
14268
  var allNotes = {};
14052
14269
  var cached = [];
14053
14270
  var errorNotes = [];
@@ -14058,14 +14275,15 @@ function CreateSynth() {
14058
14275
  if (event.pitch !== undefined) {
14059
14276
  var pitchNumber = event.pitch;
14060
14277
  var noteName = pitchToNoteName[pitchNumber];
14278
+ var inst = event.instrument !== undefined ? instrumentIndexToName[event.instrument] : currentInstrument;
14061
14279
  if (noteName) {
14062
- if (!allNotes[currentInstrument]) allNotes[currentInstrument] = {};
14063
- if (!soundsCache[currentInstrument] || !soundsCache[currentInstrument][noteName]) allNotes[currentInstrument][noteName] = true;else {
14064
- var label2 = currentInstrument + ":" + noteName;
14280
+ if (!allNotes[inst]) allNotes[inst] = {};
14281
+ if (!soundsCache[inst] || !soundsCache[inst][noteName]) allNotes[inst][noteName] = true;else {
14282
+ var label2 = inst + ":" + noteName;
14065
14283
  if (cached.indexOf(label2) < 0) cached.push(label2);
14066
14284
  }
14067
14285
  } else {
14068
- var label = currentInstrument + ":" + noteName;
14286
+ var label = inst + ":" + noteName;
14069
14287
  console.log("Can't find note: ", pitchNumber, label);
14070
14288
  if (errorNotes.indexOf(label) < 0) errorNotes.push(label);
14071
14289
  }
@@ -14206,8 +14424,7 @@ function CreateSynth() {
14206
14424
  // There might be a previous run that needs to be turned off.
14207
14425
  self.stop();
14208
14426
  var noteMapTracks = createNoteMap(self.flattened);
14209
- // if (self.options.swing)
14210
- // addSwing(noteMapTracks, self.options.swing, self.beatsPerMeasure)
14427
+ if (self.options.swing) addSwing(noteMapTracks, self.options.swing, self.meterFraction, self.pickupLength);
14211
14428
  if (self.sequenceCallback) self.sequenceCallback(noteMapTracks, self.callbackContext);
14212
14429
  var panDistances = setPan(noteMapTracks.length, self.pan);
14213
14430
 
@@ -14420,41 +14637,65 @@ function CreateSynth() {
14420
14637
  };
14421
14638
  }
14422
14639
  };
14423
-
14424
- // // this is a first attempt at adding a little bit of swing to the output, but the algorithm isn't correct.
14425
- // function addSwing(noteMapTracks, swing, beatsPerMeasure) {
14426
- // console.log("addSwing", noteMapTracks, swing, beatsPerMeasure)
14427
- // // Swing should be between -0.9 and 0.9. Make sure the input is between them.
14428
- // // Then that is the percentage to add to the first beat, so a negative number moves the second beat earlier.
14429
- // // A value of zero is the same as no swing at all.
14430
- // // This only works when there are an even number of beats in a measure.
14431
- // if (beatsPerMeasure % 2 !== 0)
14432
- // return;
14433
- // swing = parseFloat(swing)
14434
- // if (isNaN(swing))
14435
- // return
14436
- // if (swing < -0.9)
14437
- // swing = -0.9
14438
- // if (swing > 0.9)
14439
- // swing = 0.9
14440
- // var beatLength = (1 / beatsPerMeasure)*2
14441
- // swing = beatLength * swing
14442
- // for (var t = 0; t < noteMapTracks.length; t++) {
14443
- // var track = noteMapTracks[t];
14444
- // for (var i = 0; i < track.length; i++) {
14445
- // var event = track[i];
14446
- // if (event.start % beatLength) {
14447
- // // This is the off beat
14448
- // event.start += swing;
14449
- // } else {
14450
- // // This is the beat
14451
- // event.end += swing;
14452
- // }
14453
- // }
14454
- // }
14455
- // }
14640
+ function addSwing(noteMapTracks, swing, meterFraction, pickupLength) {
14641
+ // we can only swing in X/4 and X/8 meters.
14642
+ if (meterFraction.den != 4 && meterFraction.den != 8) return;
14643
+ swing = parseFloat(swing);
14644
+
14645
+ // 50 (or less) is no swing,
14646
+ if (isNaN(swing) || swing <= 50) return;
14647
+
14648
+ // 66 is triplet swing 2:1, and
14649
+ // 60 is swing with a ratio of 3:2.
14650
+ // 75 is the maximum swing where the first eight is played as a dotted eight and the second as a sixteenth.
14651
+ if (swing > 75) swing = 75;
14652
+
14653
+ // convert the swing percentage to a percentage of increase for the first half of the beat
14654
+ swing = swing / 50 - 1;
14655
+
14656
+ // The volume of the swung notes is increased by this factor
14657
+ // could be also in the settings. Try out values such 0.1, 0.2
14658
+ var volumeIncrease = 0.0;
14659
+
14660
+ // the beatLength in X/8 meters
14661
+ var beatLength = 0.25;
14662
+
14663
+ // in X/8 meters the 16s swing so the beatLength is halved
14664
+ if (meterFraction.den === 8) beatLength = beatLength / 2;
14665
+
14666
+ // duration of a half beat
14667
+ var halfbeatLength = beatLength / 2;
14668
+
14669
+ // the extra duration of the first swung notes and the delay of the second notes
14670
+ var swingDuration = halfbeatLength * swing;
14671
+ for (var t = 0; t < noteMapTracks.length; t++) {
14672
+ var track = noteMapTracks[t];
14673
+ for (var i = 0; i < track.length; i++) {
14674
+ var event = track[i];
14675
+ if (
14676
+ // is halfbeat
14677
+ (event.start - pickupLength) % halfbeatLength == 0 && (event.start - pickupLength) % beatLength != 0 && (
14678
+ // the previous note is on the beat or before OR there is no previous note
14679
+ i == 0 || track[i - 1].start <= track[i].start - halfbeatLength) && (
14680
+ // the next note is on the beat or after OR there is no next note
14681
+ i == track.length - 1 || track[i + 1].start >= track[i].start + halfbeatLength)) {
14682
+ var oldEventStart = event.start;
14683
+ event.start += swingDuration;
14684
+
14685
+ // Increase volume of swung notes
14686
+ event.volume *= 1 + volumeIncrease;
14687
+
14688
+ // if there is a previous note ending at the start of this note, extend its end
14689
+ // and decrease its volume
14690
+ if (i > 0 && track[i - 1].end == oldEventStart) {
14691
+ track[i - 1].end = event.start;
14692
+ track[i - 1].volume *= 1 - volumeIncrease;
14693
+ }
14694
+ }
14695
+ }
14696
+ }
14697
+ }
14456
14698
  }
14457
-
14458
14699
  module.exports = CreateSynth;
14459
14700
 
14460
14701
  /***/ }),
@@ -17747,7 +17988,10 @@ AbstractEngraver.prototype.createNote = function (elem, nostem, isSingleLineStaf
17747
17988
  roomtaken += this.addGraceNotes(elem, voice, abselem, notehead, this.stemHeight * this.voiceScale, this.isBagpipes, roomtaken);
17748
17989
  }
17749
17990
  if (elem.decoration) {
17750
- this.decoration.createDecoration(voice, elem.decoration, abselem.top, notehead ? notehead.w : 0, abselem, roomtaken, dir, abselem.bottom, elem.positioning, this.hasVocals, this.accentAbove);
17991
+ // 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
+ // This should probably be combined with moveDecorations()
17993
+ var bottom = nostem ? Math.min(-3, abselem.bottom - 6) : abselem.bottom;
17994
+ this.decoration.createDecoration(voice, elem.decoration, abselem.top, notehead ? notehead.w : 0, abselem, roomtaken, dir, bottom, elem.positioning, this.hasVocals, this.accentAbove);
17751
17995
  }
17752
17996
  if (elem.barNumber) {
17753
17997
  abselem.addFixed(new RelativeElement(elem.barNumber, -10, 0, 0, {
@@ -18742,7 +18986,8 @@ var stackedDecoration = function stackedDecoration(decoration, width, abselem, y
18742
18986
  y = placement === 'above' ? y + height / 2 : y - height / 2; // Center the element vertically.
18743
18987
  abselem.addFixedX(new RelativeElement(symbol, deltaX, glyphs.getSymbolWidth(symbol), y, {
18744
18988
  klass: 'ornament',
18745
- thickness: glyphs.symbolHeightInPitches(symbol)
18989
+ thickness: glyphs.symbolHeightInPitches(symbol),
18990
+ position: placement
18746
18991
  }));
18747
18992
  incrementPlacement(placement, height);
18748
18993
  }
@@ -18939,7 +19184,8 @@ Decoration.prototype.createDecoration = function (voice, decoration, pitch, widt
18939
19184
  // yPos is an object containing 'above' and 'below'. That is the placement of the next symbol on either side.
18940
19185
 
18941
19186
  yPos.above = Math.max(yPos.above, this.minTop);
18942
- var hasOne = stackedDecoration(decoration, width, abselem, yPos, positioning.ornamentPosition, this.minTop, this.minBottom, accentAbove);
19187
+ yPos.below = Math.min(yPos.below, minPitch);
19188
+ var hasOne = stackedDecoration(decoration, width, abselem, yPos, positioning.ornamentPosition, this.minTop, minPitch, accentAbove);
18943
19189
  //if (hasOne) {
18944
19190
  // abselem.top = Math.max(yPos.above + 3, abselem.top); // TODO-PER: Not sure why we need this fudge factor.
18945
19191
  //}
@@ -20208,6 +20454,23 @@ TieElem.prototype.calcSlurY = function () {
20208
20454
  this.endY = midPoint;
20209
20455
  this.endX += Math.round(this.anchor2.w / 2); // When going to the middle of the stem, bump the line to the right a little bit to make it look right.
20210
20456
  } else this.endY = this.above && beamInterferes ? this.anchor2.highestVert : this.anchor2.pitch;
20457
+ if (this.anchor1.scalex === 1) {
20458
+ // Need a way to tell if this is a grace note - if so then keep the slur as close as possible. TODO-PER-HACK: this should be more declaratively determined.
20459
+ var hasBeam1 = !!this.anchor1.parent.beam;
20460
+ var hasBeam2 = !!this.anchor2.parent.beam;
20461
+ if (hasBeam1) {
20462
+ var isLastInBeam = this.anchor1.parent === this.anchor1.parent.beam.elems[this.anchor1.parent.beam.elems.length - 1];
20463
+ if (!isLastInBeam) {
20464
+ if (this.above) this.startY = this.anchor1.parent.fixed.t;else this.startY = this.anchor1.parent.fixed.b;
20465
+ }
20466
+ }
20467
+ if (hasBeam2) {
20468
+ var isFirstInBeam = this.anchor2.parent === this.anchor2.parent.beam.elems[0];
20469
+ if (!isFirstInBeam) {
20470
+ if (this.above) this.endY = this.anchor2.parent.fixed.t;else this.endY = this.anchor2.parent.fixed.b;
20471
+ }
20472
+ }
20473
+ }
20211
20474
  } else if (this.anchor1) {
20212
20475
  this.startY = this.endY = this.anchor1.pitch;
20213
20476
  } else if (this.anchor2) {
@@ -21507,7 +21770,7 @@ function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, se
21507
21770
  classes.incrLine();
21508
21771
  var abcLine = abcTune.lines[line];
21509
21772
  if (abcLine.staff) {
21510
- if (classes.shouldAddClasses) groupClasses.klass = "abcjs-staff l" + classes.lineNumber;
21773
+ if (classes.shouldAddClasses) groupClasses.klass = "abcjs-staff-wrapper abcjs-l" + classes.lineNumber;
21511
21774
  renderer.paper.openGroup(groupClasses);
21512
21775
  if (abcLine.vskip) {
21513
21776
  renderer.moveY(abcLine.vskip);
@@ -23257,6 +23520,7 @@ var GetFontAndAttr = __webpack_require__(/*! ./helpers/get-font-and-attr */ "./s
23257
23520
  var GetTextSize = __webpack_require__(/*! ./helpers/get-text-size */ "./src/write/helpers/get-text-size.js");
23258
23521
  var draw = __webpack_require__(/*! ./draw/draw */ "./src/write/draw/draw.js");
23259
23522
  var tablatures = __webpack_require__(/*! ../api/abc_tablatures */ "./src/api/abc_tablatures.js");
23523
+ var findSelectableElement = __webpack_require__(/*! ./interactive/find-selectable-element */ "./src/write/interactive/find-selectable-element.js");
23260
23524
 
23261
23525
  /**
23262
23526
  * @class
@@ -23272,6 +23536,7 @@ var tablatures = __webpack_require__(/*! ../api/abc_tablatures */ "./src/api/abc
23272
23536
  */
23273
23537
  var EngraverController = function EngraverController(paper, params) {
23274
23538
  params = params || {};
23539
+ this.findSelectableElement = findSelectableElement;
23275
23540
  this.oneSvgPerLine = params.oneSvgPerLine;
23276
23541
  this.selectionColor = params.selectionColor;
23277
23542
  this.dragColor = params.dragColor ? params.dragColor : params.selectionColor;
@@ -23280,6 +23545,7 @@ var EngraverController = function EngraverController(paper, params) {
23280
23545
  this.responsive = params.responsive;
23281
23546
  this.space = 3 * spacing.SPACE;
23282
23547
  this.initialClef = params.initialClef;
23548
+ this.timeBasedLayout = params.timeBasedLayout;
23283
23549
  this.expandToWidest = !!params.expandToWidest;
23284
23550
  this.scale = params.scale ? parseFloat(params.scale) : 0;
23285
23551
  this.classes = new Classes({
@@ -23359,7 +23625,7 @@ EngraverController.prototype.getMeasureWidths = function (abcTune) {
23359
23625
  this.constructTuneElements(abcTune);
23360
23626
  // layout() sets the x-coordinate of the abcTune element here:
23361
23627
  // abcTune.lines[0].staffGroup.voices[0].children[0].x
23362
- layout(this.renderer, abcTune, 0, this.space);
23628
+ layout(this.renderer, abcTune, 0, this.space, this.timeBasedLayout);
23363
23629
  var ret = [];
23364
23630
  var section;
23365
23631
  var needNewSection = true;
@@ -23414,6 +23680,7 @@ EngraverController.prototype.setupTune = function (abcTune, tuneNumber) {
23414
23680
  percmap: abcTune.formatting.percmap,
23415
23681
  initialClef: this.initialClef,
23416
23682
  jazzchords: this.jazzchords,
23683
+ timeBasedLayout: this.timeBasedLayout,
23417
23684
  accentAbove: this.accentAbove,
23418
23685
  germanAlphabet: this.germanAlphabet
23419
23686
  });
@@ -23472,7 +23739,7 @@ EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOf
23472
23739
  //Set the top text now that we know the width
23473
23740
 
23474
23741
  // Do all the positioning, both horizontally and vertically
23475
- var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest);
23742
+ var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest, this.timeBasedLayout);
23476
23743
 
23477
23744
  //Set the top text now that we know the width
23478
23745
  if (this.expandToWidest && maxWidth > this.width + 1) {
@@ -23516,14 +23783,14 @@ EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOf
23516
23783
  this.selectables = ret.selectables;
23517
23784
  if (this.oneSvgPerLine) {
23518
23785
  var div = this.renderer.paper.svg.parentNode;
23519
- this.svgs = splitSvgIntoLines(this.renderer, div, abcTune.metaText.title, this.responsive);
23786
+ this.svgs = splitSvgIntoLines(this.renderer, div, abcTune.metaText.title, this.responsive, scale);
23520
23787
  } else {
23521
23788
  this.svgs = [this.renderer.paper.svg];
23522
23789
  }
23523
23790
  setupSelection(this, this.svgs);
23524
23791
  this.jazzchords = origJazzChords;
23525
23792
  };
23526
- function splitSvgIntoLines(renderer, output, title, responsive) {
23793
+ function splitSvgIntoLines(renderer, output, title, responsive, scale) {
23527
23794
  // Each line is a top level <g> in the svg. To split it into separate
23528
23795
  // svgs iterate through each of those and put them in a new svg. Since
23529
23796
  // they are placed absolutely, the viewBox needs to be manipulated to
@@ -23546,7 +23813,7 @@ function splitSvgIntoLines(renderer, output, title, responsive) {
23546
23813
  var height = box.height + gapBetweenLines;
23547
23814
  var wrapper = document.createElement("div");
23548
23815
  var divStyles = "overflow: hidden;";
23549
- if (responsive !== 'resize') divStyles += "height:" + height + "px;";
23816
+ if (responsive !== 'resize') divStyles += "height:" + height * scale + "px;";
23550
23817
  wrapper.setAttribute("style", divStyles);
23551
23818
  var svg = duplicateSvg(source);
23552
23819
  var fullTitle = "Sheet Music for \"" + title + "\" section " + (i + 1);
@@ -23873,6 +24140,98 @@ module.exports = spacing;
23873
24140
 
23874
24141
  /***/ }),
23875
24142
 
24143
+ /***/ "./src/write/interactive/create-analysis.js":
24144
+ /*!**************************************************!*\
24145
+ !*** ./src/write/interactive/create-analysis.js ***!
24146
+ \**************************************************/
24147
+ /***/ (function(module) {
24148
+
24149
+ function findNumber(klass, match, target, name) {
24150
+ if (klass.indexOf(match) === 0) {
24151
+ var value = klass.replace(match, '');
24152
+ var num = parseInt(value, 10);
24153
+ if ('' + num === value) target[name] = num;
24154
+ }
24155
+ }
24156
+ function createAnalysis(target, ev) {
24157
+ var classes = [];
24158
+ if (target.absEl.elemset) {
24159
+ var classObj = {};
24160
+ for (var j = 0; j < target.absEl.elemset.length; j++) {
24161
+ var es = target.absEl.elemset[j];
24162
+ if (es) {
24163
+ var klass = es.getAttribute("class").split(' ');
24164
+ for (var k = 0; k < klass.length; k++) {
24165
+ classObj[klass[k]] = true;
24166
+ }
24167
+ }
24168
+ }
24169
+ for (var kk = 0; kk < Object.keys(classObj).length; kk++) {
24170
+ classes.push(Object.keys(classObj)[kk]);
24171
+ }
24172
+ }
24173
+ var analysis = {};
24174
+ for (var ii = 0; ii < classes.length; ii++) {
24175
+ findNumber(classes[ii], "abcjs-v", analysis, "voice");
24176
+ findNumber(classes[ii], "abcjs-l", analysis, "line");
24177
+ findNumber(classes[ii], "abcjs-m", analysis, "measure");
24178
+ }
24179
+ if (target.staffPos) analysis.staffPos = target.staffPos;
24180
+ var closest = ev.target;
24181
+ while (closest && closest.dataset && !closest.dataset.name && closest.tagName.toLowerCase() !== 'svg') {
24182
+ closest = closest.parentNode;
24183
+ }
24184
+ var parent = ev.target;
24185
+ while (parent && parent.dataset && !parent.dataset.index && parent.tagName.toLowerCase() !== 'svg') {
24186
+ parent = parent.parentNode;
24187
+ }
24188
+ if (parent && parent.dataset) {
24189
+ analysis.name = parent.dataset.name;
24190
+ analysis.clickedName = closest.dataset.name;
24191
+ analysis.parentClasses = parent.classList;
24192
+ }
24193
+ if (closest && closest.classList) analysis.clickedClasses = closest.classList;
24194
+ analysis.selectableElement = target.svgEl;
24195
+ return {
24196
+ classes: classes,
24197
+ analysis: analysis
24198
+ };
24199
+ }
24200
+ module.exports = createAnalysis;
24201
+
24202
+ /***/ }),
24203
+
24204
+ /***/ "./src/write/interactive/find-selectable-element.js":
24205
+ /*!**********************************************************!*\
24206
+ !*** ./src/write/interactive/find-selectable-element.js ***!
24207
+ \**********************************************************/
24208
+ /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
24209
+
24210
+ var createAnalysis = __webpack_require__(/*! ./create-analysis */ "./src/write/interactive/create-analysis.js");
24211
+ function findSelectableElement(event) {
24212
+ var selectable = event;
24213
+ while (selectable && selectable.attributes && selectable.tagName.toLowerCase() !== 'svg' && !selectable.attributes.selectable) {
24214
+ selectable = selectable.parentNode;
24215
+ }
24216
+ if (selectable && selectable.attributes && selectable.attributes.selectable) {
24217
+ var index = selectable.attributes['data-index'].nodeValue;
24218
+ if (index) {
24219
+ index = parseInt(index, 10);
24220
+ if (index >= 0 && index < this.selectables.length) {
24221
+ var element = this.selectables[index];
24222
+ var ret = createAnalysis(element, event);
24223
+ ret.index = index;
24224
+ ret.element = element;
24225
+ return ret;
24226
+ }
24227
+ }
24228
+ }
24229
+ return null;
24230
+ }
24231
+ module.exports = findSelectableElement;
24232
+
24233
+ /***/ }),
24234
+
23876
24235
  /***/ "./src/write/interactive/highlight.js":
23877
24236
  /*!********************************************!*\
23878
24237
  !*** ./src/write/interactive/highlight.js ***!
@@ -23896,6 +24255,7 @@ module.exports = highlight;
23896
24255
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
23897
24256
 
23898
24257
  var spacing = __webpack_require__(/*! ../helpers/spacing */ "./src/write/helpers/spacing.js");
24258
+ var createAnalysis = __webpack_require__(/*! ./create-analysis */ "./src/write/interactive/create-analysis.js");
23899
24259
  function setupSelection(engraver, svgs) {
23900
24260
  engraver.rangeHighlight = rangeHighlight;
23901
24261
  if (engraver.dragging) {
@@ -24201,44 +24561,9 @@ function setSelection(dragIndex) {
24201
24561
  }
24202
24562
  }
24203
24563
  function notifySelect(target, dragStep, dragMax, dragIndex, ev) {
24204
- var classes = [];
24205
- if (target.absEl.elemset) {
24206
- var classObj = {};
24207
- for (var j = 0; j < target.absEl.elemset.length; j++) {
24208
- var es = target.absEl.elemset[j];
24209
- if (es) {
24210
- var klass = es.getAttribute("class").split(' ');
24211
- for (var k = 0; k < klass.length; k++) {
24212
- classObj[klass[k]] = true;
24213
- }
24214
- }
24215
- }
24216
- for (var kk = 0; kk < Object.keys(classObj).length; kk++) {
24217
- classes.push(Object.keys(classObj)[kk]);
24218
- }
24219
- }
24220
- var analysis = {};
24221
- for (var ii = 0; ii < classes.length; ii++) {
24222
- findNumber(classes[ii], "abcjs-v", analysis, "voice");
24223
- findNumber(classes[ii], "abcjs-l", analysis, "line");
24224
- findNumber(classes[ii], "abcjs-m", analysis, "measure");
24225
- }
24226
- if (target.staffPos) analysis.staffPos = target.staffPos;
24227
- var closest = ev.target;
24228
- while (closest && closest.dataset && !closest.dataset.name && closest.tagName.toLowerCase() !== 'svg') {
24229
- closest = closest.parentNode;
24230
- }
24231
- var parent = ev.target;
24232
- while (parent && parent.dataset && !parent.dataset.index && parent.tagName.toLowerCase() !== 'svg') {
24233
- parent = parent.parentNode;
24234
- }
24235
- if (parent && parent.dataset) {
24236
- analysis.name = parent.dataset.name;
24237
- analysis.clickedName = closest.dataset.name;
24238
- analysis.parentClasses = parent.classList;
24239
- }
24240
- if (closest && closest.classList) analysis.clickedClasses = closest.classList;
24241
- analysis.selectableElement = target.svgEl;
24564
+ var ret = createAnalysis(target, ev);
24565
+ var classes = ret.classes;
24566
+ var analysis = ret.analysis;
24242
24567
  for (var i = 0; i < this.listeners.length; i++) {
24243
24568
  this.listeners[i](target.absEl.abcelem, target.absEl.tuneNumber, classes.join(' '), analysis, {
24244
24569
  step: dragStep,
@@ -24248,13 +24573,6 @@ function notifySelect(target, dragStep, dragMax, dragIndex, ev) {
24248
24573
  }, ev);
24249
24574
  }
24250
24575
  }
24251
- function findNumber(klass, match, target, name) {
24252
- if (klass.indexOf(match) === 0) {
24253
- var value = klass.replace(match, '');
24254
- var num = parseInt(value, 10);
24255
- if ('' + num === value) target[name] = num;
24256
- }
24257
- }
24258
24576
  function clearSelection() {
24259
24577
  for (var i = 0; i < this.selected.length; i++) {
24260
24578
  this.selected[i].unhighlight(undefined, this.renderer.foregroundColor);
@@ -24609,6 +24927,94 @@ module.exports = getLeftEdgeOfStaff;
24609
24927
 
24610
24928
  /***/ }),
24611
24929
 
24930
+ /***/ "./src/write/layout/layout-in-grid.js":
24931
+ /*!********************************************!*\
24932
+ !*** ./src/write/layout/layout-in-grid.js ***!
24933
+ \********************************************/
24934
+ /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
24935
+
24936
+ var getLeftEdgeOfStaff = __webpack_require__(/*! ./get-left-edge-of-staff */ "./src/write/layout/get-left-edge-of-staff.js");
24937
+ function layoutInGrid(renderer, staffGroup, timeBasedLayout) {
24938
+ var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
24939
+ var ret = getTotalDuration(staffGroup, timeBasedLayout.minPadding);
24940
+ var totalDuration = ret.totalDuration;
24941
+ var minSpacing = ret.minSpacing;
24942
+ var totalWidth = minSpacing * totalDuration;
24943
+ if (timeBasedLayout.minWidth) totalWidth = Math.max(totalWidth, timeBasedLayout.minWidth);
24944
+ var leftAlignPadding = timeBasedLayout.minPadding ? timeBasedLayout.minPadding / 2 : 2; // If the padding isn't specified still give it some
24945
+
24946
+ staffGroup.startx = leftEdge;
24947
+ staffGroup.w = totalWidth + leftEdge;
24948
+ for (var i = 0; i < staffGroup.voices.length; i++) {
24949
+ var voice = staffGroup.voices[i];
24950
+ voice.startx = leftEdge;
24951
+ voice.w = totalWidth + leftEdge;
24952
+ var x = leftEdge;
24953
+ var afterFixedLeft = false;
24954
+ var durationUnit = 0;
24955
+ for (var j = 0; j < voice.children.length; j++) {
24956
+ var child = voice.children[j];
24957
+ if (!afterFixedLeft) {
24958
+ if (child.duration !== 0) {
24959
+ // We got to the first music element on the line
24960
+ afterFixedLeft = true;
24961
+ durationUnit = (totalWidth + leftEdge - x) / totalDuration;
24962
+ staffGroup.gridStart = x;
24963
+ } else {
24964
+ // We are still doing the preliminary stuff - clef, time sig, etc.
24965
+ child.x = x;
24966
+ x += child.w + child.minspacing;
24967
+ }
24968
+ }
24969
+ if (afterFixedLeft) {
24970
+ if (timeBasedLayout.align === 'center') child.x = x + child.duration * durationUnit / 2 - child.w / 2;else {
24971
+ // left align with padding - but no padding for barlines, they should be right aligned.
24972
+ // TODO-PER: it looks better to move bar lines one pixel to right. Not sure why.
24973
+ if (child.duration === 0) {
24974
+ child.x = x + 1 - child.w;
24975
+ } else {
24976
+ // child.extraw has the width of the accidentals - push the note to the right to take that into consideration. It will be 0 if there is nothing to the left.
24977
+ child.x = x + leftAlignPadding - child.extraw;
24978
+ }
24979
+ }
24980
+ x += child.duration * durationUnit;
24981
+ }
24982
+ for (var k = 0; k < child.children.length; k++) {
24983
+ var grandchild = child.children[k];
24984
+ // some elements don't have a dx - Tempo, for instance
24985
+ var dx = grandchild.dx ? grandchild.dx : 0;
24986
+ grandchild.x = child.x + dx;
24987
+ }
24988
+ }
24989
+ staffGroup.gridEnd = x;
24990
+ }
24991
+ return totalWidth;
24992
+ }
24993
+ function getTotalDuration(staffGroup, timeBasedLayout) {
24994
+ var maxSpacing = 0;
24995
+ var maxCount = 0;
24996
+ for (var i = 0; i < staffGroup.voices.length; i++) {
24997
+ var count = 0;
24998
+ var voice = staffGroup.voices[i];
24999
+ for (var j = 0; j < voice.children.length; j++) {
25000
+ var element = voice.children[j];
25001
+ count += element.duration;
25002
+ if (element.duration) {
25003
+ var width = (element.w + timeBasedLayout) / element.duration;
25004
+ maxSpacing = Math.max(maxSpacing, width);
25005
+ }
25006
+ }
25007
+ maxCount = Math.max(maxCount, count);
25008
+ }
25009
+ return {
25010
+ totalDuration: maxCount,
25011
+ minSpacing: maxSpacing
25012
+ };
25013
+ }
25014
+ module.exports = layoutInGrid;
25015
+
25016
+ /***/ }),
25017
+
24612
25018
  /***/ "./src/write/layout/layout.js":
24613
25019
  /*!************************************!*\
24614
25020
  !*** ./src/write/layout/layout.js ***!
@@ -24619,7 +25025,12 @@ var layoutVoice = __webpack_require__(/*! ./voice */ "./src/write/layout/voice.j
24619
25025
  var setUpperAndLowerElements = __webpack_require__(/*! ./set-upper-and-lower-elements */ "./src/write/layout/set-upper-and-lower-elements.js");
24620
25026
  var layoutStaffGroup = __webpack_require__(/*! ./staff-group */ "./src/write/layout/staff-group.js");
24621
25027
  var getLeftEdgeOfStaff = __webpack_require__(/*! ./get-left-edge-of-staff */ "./src/write/layout/get-left-edge-of-staff.js");
24622
- var layout = function layout(renderer, abctune, width, space, expandToWidest) {
25028
+ var layoutInGrid = __webpack_require__(/*! ./layout-in-grid */ "./src/write/layout/layout-in-grid.js");
25029
+
25030
+ // This sets the "x" attribute on all the children in abctune.lines
25031
+ // It also sets the "w" and "startx" attributes on "voices"
25032
+ // It also sets the "w" and "startx" attributes on "voices.children"
25033
+ var layout = function layout(renderer, abctune, width, space, expandToWidest, timeBasedLayout) {
24623
25034
  var i;
24624
25035
  var abcLine;
24625
25036
  // Adjust the x-coordinates to their absolute positions
@@ -24628,7 +25039,8 @@ var layout = function layout(renderer, abctune, width, space, expandToWidest) {
24628
25039
  abcLine = abctune.lines[i];
24629
25040
  if (abcLine.staff) {
24630
25041
  // console.log("=== line", i)
24631
- var thisWidth = setXSpacing(renderer, maxWidth, space, abcLine.staffGroup, abctune.formatting, i === abctune.lines.length - 1, false);
25042
+ var thisWidth;
25043
+ if (timeBasedLayout !== undefined) thisWidth = layoutInGrid(renderer, abcLine.staffGroup, timeBasedLayout);else thisWidth = setXSpacing(renderer, maxWidth, space, abcLine.staffGroup, abctune.formatting, i === abctune.lines.length - 1, false);
24632
25044
  // console.log(thisWidth, maxWidth)
24633
25045
  if (Math.round(thisWidth) > Math.round(maxWidth)) {
24634
25046
  // to take care of floating point weirdness
@@ -24663,40 +25075,34 @@ var layout = function layout(renderer, abctune, width, space, expandToWidest) {
24663
25075
  var setXSpacing = function setXSpacing(renderer, width, space, staffGroup, formatting, isLastLine, debug) {
24664
25076
  var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
24665
25077
  var newspace = space;
25078
+ //dumpGroup("before", staffGroup)
24666
25079
  for (var it = 0; it < 8; it++) {
24667
25080
  // TODO-PER: shouldn't need multiple passes, but each pass gets it closer to the right spacing. (Only affects long lines: normal lines break out of this loop quickly.)
24668
25081
  // console.log("iteration", it)
24669
- // dumpGroup("before", staffGroup)
24670
- var ret = layoutStaffGroup(newspace, renderer, debug, staffGroup, leftEdge);
24671
- // dumpGroup("after",staffGroup)
25082
+ var ret = layoutStaffGroup(newspace, renderer.minPadding, debug, staffGroup, leftEdge);
24672
25083
  newspace = calcHorizontalSpacing(isLastLine, formatting.stretchlast, width + renderer.padding.left, staffGroup.w, newspace, ret.spacingUnits, ret.minSpace, renderer.padding.left + renderer.padding.right);
24673
25084
  if (debug) console.log("setXSpace", it, staffGroup.w, newspace, staffGroup.minspace);
24674
25085
  if (newspace === null) break;
24675
25086
  }
25087
+ //dumpGroup("after",staffGroup)
24676
25088
  centerWholeRests(staffGroup.voices);
24677
25089
  return staffGroup.w - leftEdge;
24678
25090
  };
24679
-
24680
- // function dumpGroup(label, staffGroup) {
24681
- // var output = {
24682
- // line: staffGroup.line,
24683
- // w: staffGroup.w,
24684
- // voice: {
24685
- // i: staffGroup.voices[0].i,
24686
- // minx: staffGroup.voices[0].minx,
24687
- // nextx: staffGroup.voices[0].nextx,
24688
- // spacingduration: staffGroup.voices[0].spacingduration,
24689
- // w: staffGroup.voices[0].w,
24690
- // children: [],
24691
- // }
24692
- // }
24693
- // for (var i = 0; i < staffGroup.voices[0].children.length; i++) {
24694
- // var child = staffGroup.voices[0].children[i]
24695
- // output.voice.children.push({ fixedW: child.fixed.w, w: child.w, x: child.x, type: child.type })
24696
- // }
24697
- // console.log(label,output)
24698
- // }
24699
-
25091
+ function replacer(key, value) {
25092
+ // Filtering out properties
25093
+ if (key === 'parent') {
25094
+ return 'parent';
25095
+ }
25096
+ if (key === 'beam') {
25097
+ return 'beam';
25098
+ }
25099
+ return value;
25100
+ }
25101
+ function dumpGroup(label, staffGroup) {
25102
+ console.log("=================== " + label + " =========================");
25103
+ console.log(staffGroup);
25104
+ console.log(JSON.stringify(staffGroup, replacer, "\t"));
25105
+ }
24700
25106
  function calcHorizontalSpacing(isLastLine, stretchLast, targetWidth, lineWidth, spacing, spacingUnits, minSpace, padding) {
24701
25107
  if (isLastLine) {
24702
25108
  if (stretchLast === undefined) {
@@ -24827,6 +25233,7 @@ var setUpperAndLowerElements = function setUpperAndLowerElements(renderer, staff
24827
25233
  var addedSpace = minSpacingInPitches - forcedSpacingBetween;
24828
25234
  if (addedSpace > 0) staff.top += addedSpace;
24829
25235
  }
25236
+ staff.top += renderer.spacing.staffTopMargin / spacing.STEP;
24830
25237
  lastStaffBottom = 2 - staff.bottom; // the staff starts at position 2 and the bottom variable is negative. Therefore to find out how large the bottom is, we reverse the sign of the bottom, and add the 2 in.
24831
25238
 
24832
25239
  // Now we need a little margin on the top, so we'll just throw that in.
@@ -24985,7 +25392,7 @@ function checkLastBarX(voices) {
24985
25392
  }
24986
25393
  }
24987
25394
  }
24988
- var layoutStaffGroup = function layoutStaffGroup(spacing, renderer, debug, staffGroup, leftEdge) {
25395
+ var layoutStaffGroup = function layoutStaffGroup(spacing, minPadding, debug, staffGroup, leftEdge) {
24989
25396
  var epsilon = 0.0000001; // Fudging for inexactness of floating point math.
24990
25397
  var spacingunits = 0; // number of times we will have ended up using the spacing distance (as opposed to fixed width distances)
24991
25398
  var minspace = 1000; // a big number to start off with - used to find out what the smallest space between two notes is -- GD 2014.1.7
@@ -25041,7 +25448,7 @@ var layoutStaffGroup = function layoutStaffGroup(spacing, renderer, debug, staff
25041
25448
  if (v.voicenumber === 0) lastTopVoice = i;
25042
25449
  var topVoice = lastTopVoice !== undefined && currentvoices[lastTopVoice].voicenumber !== v.voicenumber ? currentvoices[lastTopVoice] : undefined;
25043
25450
  if (!isSameStaff(v, topVoice)) topVoice = undefined;
25044
- var voicechildx = layoutVoiceElements.layoutOneItem(x, spacing, v, renderer.minPadding, topVoice);
25451
+ var voicechildx = layoutVoiceElements.layoutOneItem(x, spacing, v, minPadding, topVoice);
25045
25452
  var dx = voicechildx - x;
25046
25453
  if (dx > 0) {
25047
25454
  x = voicechildx; //update x
@@ -25267,7 +25674,7 @@ VoiceElement.shiftRight = function (dx, voice) {
25267
25674
 
25268
25675
  // call when spacingduration has been updated
25269
25676
  VoiceElement.updateNextX = function (x, spacing, voice) {
25270
- voice.nextx = x + spacing * Math.sqrt(voice.spacingduration * 8);
25677
+ voice.nextx = x + spacing * this.getSpacingUnits(voice);
25271
25678
  };
25272
25679
  VoiceElement.updateIndices = function (voice) {
25273
25680
  if (!this.layoutEnded(voice)) {
@@ -25332,7 +25739,7 @@ function moveDecorations(beam) {
25332
25739
  var top = yAtNote(child, beam);
25333
25740
  for (var i = 0; i < child.children.length; i++) {
25334
25741
  var el = child.children[i];
25335
- if (el.klass === 'ornament') {
25742
+ if (el.klass === 'ornament' && el.position !== 'below') {
25336
25743
  if (el.bottom - padding < top) {
25337
25744
  var distance = top - el.bottom + padding; // Find the distance that it needs to move and add a little margin so the element doesn't touch the beam.
25338
25745
  el.bottom += distance;
@@ -25536,6 +25943,7 @@ Renderer.prototype.initVerticalSpace = function () {
25536
25943
  // Set the slur height factor.
25537
25944
  staffSeparation: 61.33,
25538
25945
  // Do not put a staff system closer than <unit> from the previous system.
25946
+ staffTopMargin: 0,
25539
25947
  stemHeight: 26.67 + 10,
25540
25948
  // Set the stem height.
25541
25949
  subtitle: 3.78,
@@ -25587,6 +25995,7 @@ Renderer.prototype.setVerticalSpace = function (formatting) {
25587
25995
  if (formatting.musicspace !== undefined) this.spacing.music = formatting.musicspace * 4 / 3;
25588
25996
  if (formatting.titlespace !== undefined) this.spacing.title = formatting.titlespace * 4 / 3;
25589
25997
  if (formatting.sysstaffsep !== undefined) this.spacing.systemStaffSeparation = formatting.sysstaffsep * 4 / 3;
25998
+ if (formatting.stafftopmargin !== undefined) this.spacing.staffTopMargin = formatting.stafftopmargin * 4 / 3;
25590
25999
  if (formatting.subtitlespace !== undefined) this.spacing.subtitle = formatting.subtitlespace * 4 / 3;
25591
26000
  if (formatting.topspace !== undefined) this.spacing.top = formatting.topspace * 4 / 3;
25592
26001
  if (formatting.vocalspace !== undefined) this.spacing.vocal = formatting.vocalspace * 4 / 3;
@@ -25970,7 +26379,7 @@ module.exports = Svg;
25970
26379
  \********************/
25971
26380
  /***/ (function(module) {
25972
26381
 
25973
- var version = '6.3.0';
26382
+ var version = '6.4.1';
25974
26383
  module.exports = version;
25975
26384
 
25976
26385
  /***/ })