abcjs 6.5.2 → 6.6.0

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.
@@ -213,6 +213,7 @@ var TimingCallbacks = function TimingCallbacks(target, params) {
213
213
  self.lineEndCallback = params.lineEndCallback; // This is called when the end of a line is approaching.
214
214
  self.lineEndAnticipation = params.lineEndAnticipation ? parseInt(params.lineEndAnticipation, 10) : 0; // How many milliseconds before the end should the call happen.
215
215
  self.beatSubdivisions = params.beatSubdivisions ? parseInt(params.beatSubdivisions, 10) : 1; // how many callbacks per beat is desired.
216
+ if (!self.beatSubdivisions) self.beatSubdivisions = 1;
216
217
  self.joggerTimer = null;
217
218
  self.replaceTarget = function (newTarget) {
218
219
  self.noteTimings = newTarget.setTiming(self.qpm, self.extraMeasuresAtBeginning);
@@ -235,18 +236,96 @@ var TimingCallbacks = function TimingCallbacks(target, params) {
235
236
  // noteTimings contains an array of events sorted by time. Events that happen at the same time are in the same element of the array.
236
237
  self.millisecondsPerBeat = 1000 / (self.qpm / 60) / self.beatSubdivisions;
237
238
  self.lastMoment = self.noteTimings[self.noteTimings.length - 1].milliseconds;
238
- self.totalBeats = Math.round(self.lastMoment / self.millisecondsPerBeat);
239
+
240
+ // For irregular time sigs that specify the beat divisions (that is, something like `M: 2+3/8`)
241
+ // Then the beat is not a regular pulse. To keep most of this logic easy, the beat will be specified
242
+ // as half as long, but the callback will happen according to the pattern. That is,
243
+ // for bpm=60 at the above time signature, internally the beat callback will happen at 120 bpm, but
244
+ // the callback function will be called at 2 sub beats, then 3 sub beats, etc.
245
+ // The beat number will be an integer for all of them and count up by one each time.
246
+ var meter = newTarget.getMeter();
247
+ var irregularMeter = '';
248
+ if (meter && meter.type === "specified" && meter.value && meter.value.length > 0 && meter.value[0].num.indexOf('+') > 0) irregularMeter = meter.value[0].num;
249
+ // for subdivisions = 1, then this should contain only whole numbers and the callbacks are irregular
250
+ // for subdivisions = 2, then this should be 1/8 notes. The beats should be something like: 0, 0.5, 1, 1.33, 1.66 2 (For M:2+3/8)
251
+ // etc for more subdivisions - they are just multiplied
252
+ self.beatStarts = [];
253
+ if (irregularMeter) {
254
+ var measureLength = self.noteTimings[self.noteTimings.length - 1].millisecondsPerMeasure;
255
+ var numMeasures = self.lastMoment / measureLength;
256
+ var parts = irregularMeter.split("+");
257
+ for (var i = 0; i < parts.length; i++) {
258
+ parts[i] = parseInt(parts[i], 10) / 2;
259
+ } // since we count a beat as a quarter note, but these numbers refer to 1/8 notes, we convert the beat length
260
+ var currentTs = 0;
261
+ var beatNumber = 0;
262
+ // For the input: parts = [ 1, 1.5 ] and beatSubdivisions = 2
263
+ // beatNumbers = 0 0.5 1 1.33 1.67 2 2.5 3 3.33 3.67 ...
264
+ // when part=1 then there is 1 extra sub beat and it is 0.5
265
+ // when part=1.5 then there are 2 extra sub beats at 0.33 and 0.67
266
+ //
267
+ // beatSubdivision | numFor1 | numFor1.5
268
+ // 2 | 2 | 3
269
+ // 3 | 3 | 4.5
270
+ // 4 | 4 | 6
271
+ for (var measureNumber = 0; measureNumber < numMeasures; measureNumber++) {
272
+ var measureStartTs = measureNumber * measureLength;
273
+ var subBeatCounter = 0;
274
+ for (var kk = 0; kk < parts.length; kk++) {
275
+ var beatLength = parts[kk]; // This is either 1 or 1.5 (how many quarter notes in this beat)
276
+ if (self.beatSubdivisions === 1) {
277
+ if (self.beatSubdivisions === 1) if (currentTs < self.lastMoment) {
278
+ self.beatStarts.push({
279
+ b: beatNumber,
280
+ ts: currentTs
281
+ });
282
+ }
283
+ currentTs += beatLength * self.millisecondsPerBeat;
284
+ } else {
285
+ var numDivisions = beatLength * self.beatSubdivisions;
286
+ for (var k = 0; k < Math.floor(numDivisions); k++) {
287
+ var subBeat = k / numDivisions;
288
+ var ts = Math.round(measureStartTs + subBeatCounter * self.millisecondsPerBeat);
289
+ if (ts < self.lastMoment) {
290
+ self.beatStarts.push({
291
+ b: beatNumber + subBeat,
292
+ ts: ts
293
+ });
294
+ }
295
+ subBeatCounter++;
296
+ }
297
+ }
298
+ beatNumber++;
299
+ }
300
+ }
301
+ self.beatStarts.push({
302
+ b: numMeasures * parts.length,
303
+ ts: self.lastMoment
304
+ });
305
+ self.totalBeats = self.beatStarts.length;
306
+ } else {
307
+ self.totalBeats = Math.round(self.lastMoment / self.millisecondsPerBeat);
308
+ // Add one so the last beat is the last moment
309
+ for (var j = 0; j < self.totalBeats + 1; j++) {
310
+ self.beatStarts.push({
311
+ b: j / self.beatSubdivisions,
312
+ ts: Math.round(j * self.millisecondsPerBeat)
313
+ });
314
+ }
315
+ }
316
+ //console.log({lastMoment: self.lastMoment, beatStarts: self.beatStarts})
239
317
  };
318
+
240
319
  self.replaceTarget(target);
241
320
  self.doTiming = function (timestamp) {
242
321
  // This is called 60 times a second, that is, every 16 msecs.
243
322
  //console.log("doTiming", timestamp, timestamp-self.lastTimestamp);
244
323
  if (self.lastTimestamp === timestamp) return; // If there are multiple seeks or other calls, then we can easily get multiple callbacks for the same instant.
245
324
  self.lastTimestamp = timestamp;
246
- if (!self.startTime) {
247
- self.startTime = timestamp;
248
- }
249
325
  if (!self.isPaused && self.isRunning) {
326
+ if (!self.startTime) {
327
+ self.startTime = timestamp;
328
+ }
250
329
  self.currentTime = timestamp - self.startTime;
251
330
  self.currentTime += 16; // Add a little slop because this function isn't called exactly.
252
331
  while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < self.currentTime) {
@@ -270,14 +349,16 @@ var TimingCallbacks = function TimingCallbacks(target, params) {
270
349
  }
271
350
  if (self.currentTime < self.lastMoment) {
272
351
  requestAnimationFrame(self.doTiming);
273
- if (self.currentBeat * self.millisecondsPerBeat < self.currentTime) {
352
+ if (self.currentBeat < self.beatStarts.length && self.beatStarts[self.currentBeat].ts <= self.currentTime) {
274
353
  var ret = self.doBeatCallback(timestamp);
354
+ self.currentBeat++;
275
355
  if (ret !== null) self.currentTime = ret;
276
356
  }
277
357
  } else if (self.currentBeat <= self.totalBeats) {
278
358
  // Because of timing issues (for instance, if the browser tab isn't active), the beat callbacks might not have happened when they are supposed to. To keep the client programs from having to deal with that, this will keep calling the loop until all of them have been sent.
279
359
  if (self.beatCallback) {
280
360
  var ret2 = self.doBeatCallback(timestamp);
361
+ self.currentBeat++;
281
362
  if (ret2 !== null) self.currentTime = ret2;
282
363
  requestAnimationFrame(self.doTiming);
283
364
  }
@@ -362,11 +443,13 @@ var TimingCallbacks = function TimingCallbacks(target, params) {
362
443
  };
363
444
  }
364
445
  var thisStartTime = self.startTime; // the beat callback can call seek and change the position from beneath us.
365
- self.beatCallback(self.currentBeat / self.beatSubdivisions, self.totalBeats / self.beatSubdivisions, self.lastMoment, position, debugInfo);
446
+ self.beatCallback(self.beatStarts[self.currentBeat].b, self.totalBeats / self.beatSubdivisions, self.lastMoment, position, debugInfo);
366
447
  if (thisStartTime !== self.startTime) {
367
448
  return timestamp - self.startTime;
368
- } else self.currentBeat++;
449
+ } // else
450
+ // self.currentBeat++;
369
451
  }
452
+
370
453
  return null;
371
454
  };
372
455
 
@@ -457,7 +540,6 @@ var TimingCallbacks = function TimingCallbacks(target, params) {
457
540
  if (!self.isRunning) self.pausedPercent = percent;
458
541
  var now = performance.now();
459
542
  self.startTime = now - self.currentTime;
460
- var oldEvent = self.currentEvent;
461
543
  self.currentEvent = 0;
462
544
  while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < self.currentTime) {
463
545
  self.currentEvent++;
@@ -468,11 +550,18 @@ var TimingCallbacks = function TimingCallbacks(target, params) {
468
550
  self.currentLine++;
469
551
  }
470
552
  }
553
+
554
+ //console.log({jump:self.currentTime})
471
555
  var oldBeat = self.currentBeat;
472
- self.currentBeat = Math.floor(self.currentTime / self.millisecondsPerBeat);
473
- if (self.beatCallback && oldBeat !== self.currentBeat)
556
+ for (self.currentBeat = 0; self.currentBeat < self.beatStarts.length; self.currentBeat++) {
557
+ if (self.beatStarts[self.currentBeat].ts > self.currentTime) break;
558
+ }
559
+ self.currentBeat--;
560
+ if (self.beatCallback && oldBeat !== self.currentBeat) {
474
561
  // If the movement caused the beat to change, then immediately report it to the client.
475
562
  self.doBeatCallback(self.startTime + self.currentTime);
563
+ self.currentBeat++;
564
+ }
476
565
  if (self.eventCallback && self.currentEvent >= 0 && self.noteTimings[self.currentEvent].type === 'event') self.eventCallback(self.noteTimings[self.currentEvent]);
477
566
  if (self.lineEndCallback) self.lineEndCallback(self.lineEndTimings[self.currentLine], self.noteTimings[self.currentEvent], {
478
567
  line: self.currentLine,
@@ -1024,67 +1113,71 @@ module.exports = keyAccidentals;
1024
1113
  // All these keys have the same number of accidentals
1025
1114
  var keys = {
1026
1115
  'C': {
1027
- modes: ['CMaj', 'Amin', 'Am', 'GMix', 'DDor', 'EPhr', 'FLyd', 'BLoc'],
1116
+ modes: ['CMaj', 'CIon', 'Amin', 'AAeo', 'Am', 'GMix', 'DDor', 'EPhr', 'FLyd', 'BLoc'],
1028
1117
  stepsFromC: 0
1029
1118
  },
1030
1119
  'Db': {
1031
- modes: ['DbMaj', 'Bbmin', 'Bbm', 'AbMix', 'EbDor', 'FPhr', 'GbLyd', 'CLoc'],
1120
+ modes: ['DbMaj', 'DbIon', 'Bbmin', 'BbAeo', 'Bbm', 'AbMix', 'EbDor', 'FPhr', 'GbLyd', 'CLoc'],
1032
1121
  stepsFromC: 1
1033
1122
  },
1034
1123
  'D': {
1035
- modes: ['DMaj', 'Bmin', 'Bm', 'AMix', 'EDor', 'F#Phr', 'GLyd', 'C#Loc'],
1124
+ modes: ['DMaj', 'DIon', 'Bmin', 'BAeo', 'Bm', 'AMix', 'EDor', 'F#Phr', 'GLyd', 'C#Loc'],
1036
1125
  stepsFromC: 2
1037
1126
  },
1038
1127
  'Eb': {
1039
- modes: ['EbMaj', 'Cmin', 'Cm', 'BbMix', 'FDor', 'GPhr', 'AbLyd', 'DLoc'],
1128
+ modes: ['EbMaj', 'EbIon', 'Cmin', 'CAeo', 'Cm', 'BbMix', 'FDor', 'GPhr', 'AbLyd', 'DLoc'],
1040
1129
  stepsFromC: 3
1041
1130
  },
1042
1131
  'E': {
1043
- modes: ['EMaj', 'C#min', 'C#m', 'BMix', 'F#Dor', 'G#Phr', 'ALyd', 'D#Loc'],
1132
+ modes: ['EMaj', 'EIon', 'C#min', 'C#Aeo', 'C#m', 'BMix', 'F#Dor', 'G#Phr', 'ALyd', 'D#Loc'],
1044
1133
  stepsFromC: 4
1045
1134
  },
1046
1135
  'F': {
1047
- modes: ['FMaj', 'Dmin', 'Dm', 'CMix', 'GDor', 'APhr', 'BbLyd', 'ELoc'],
1136
+ modes: ['FMaj', 'FIon', 'Dmin', 'DAeo', 'Dm', 'CMix', 'GDor', 'APhr', 'BbLyd', 'ELoc'],
1048
1137
  stepsFromC: 5
1049
1138
  },
1050
1139
  'Gb': {
1051
- modes: ['GbMaj', 'Ebmin', 'Ebm', 'DbMix', 'AbDor', 'BbPhr', 'CbLyd', 'FLoc'],
1140
+ modes: ['GbMaj', 'GbIon', 'Ebmin', 'EbAeo', 'Ebm', 'DbMix', 'AbDor', 'BbPhr', 'CbLyd', 'FLoc'],
1052
1141
  stepsFromC: 6
1053
1142
  },
1054
1143
  'G': {
1055
- modes: ['GMaj', 'Emin', 'Em', 'DMix', 'ADor', 'BPhr', 'CLyd', 'F#Loc'],
1144
+ modes: ['GMaj', 'GIon', 'Emin', 'EAeo', 'Em', 'DMix', 'ADor', 'BPhr', 'CLyd', 'F#Loc'],
1056
1145
  stepsFromC: 7
1057
1146
  },
1058
1147
  'Ab': {
1059
- modes: ['AbMaj', 'Fmin', 'Fm', 'EbMix', 'BbDor', 'CPhr', 'DbLyd', 'GLoc'],
1148
+ modes: ['AbMaj', 'AbIon', 'Fmin', 'FAeo', 'Fm', 'EbMix', 'BbDor', 'CPhr', 'DbLyd', 'GLoc'],
1060
1149
  stepsFromC: 8
1061
1150
  },
1062
1151
  'A': {
1063
- modes: ['AMaj', 'F#min', 'F#m', 'EMix', 'BDor', 'C#Phr', 'DLyd', 'G#Loc'],
1152
+ modes: ['AMaj', 'AIon', 'F#min', 'F#Aeo', 'F#m', 'EMix', 'BDor', 'C#Phr', 'DLyd', 'G#Loc'],
1064
1153
  stepsFromC: 9
1065
1154
  },
1066
1155
  'Bb': {
1067
- modes: ['BbMaj', 'Gmin', 'Gm', 'FMix', 'CDor', 'DPhr', 'EbLyd', 'ALoc'],
1156
+ modes: ['BbMaj', 'BbIon', 'Gmin', 'GAeo', 'Gm', 'FMix', 'CDor', 'DPhr', 'EbLyd', 'ALoc'],
1068
1157
  stepsFromC: 10
1069
1158
  },
1070
1159
  'B': {
1071
- modes: ['BMaj', 'G#min', 'G#m', 'F#Mix', 'C#Dor', 'D#Phr', 'ELyd', 'A#Loc'],
1160
+ modes: ['BMaj', 'BIon', 'G#min', 'G#Aeo', 'G#m', 'F#Mix', 'C#Dor', 'D#Phr', 'ELyd', 'A#Loc'],
1072
1161
  stepsFromC: 11
1073
1162
  },
1074
1163
  // Enharmonic keys
1075
1164
  'C#': {
1076
- modes: ['C#Maj', 'A#min', 'A#m', 'G#Mix', 'D#Dor', 'E#Phr', 'F#Lyd', 'B#Loc'],
1165
+ modes: ['C#Maj', 'C#Ion', 'A#min', 'A#Aeo', 'A#m', 'G#Mix', 'D#Dor', 'E#Phr', 'F#Lyd', 'B#Loc'],
1077
1166
  stepsFromC: 1
1078
1167
  },
1079
1168
  'F#': {
1080
- modes: ['F#Maj', 'D#min', 'D#m', 'C#Mix', 'G#Dor', 'A#Phr', 'BLyd', 'E#Loc'],
1169
+ modes: ['F#Maj', 'F#Ion', 'D#min', 'D#Aeo', 'D#m', 'C#Mix', 'G#Dor', 'A#Phr', 'BLyd', 'E#Loc'],
1081
1170
  stepsFromC: 6
1082
1171
  },
1083
1172
  'Cb': {
1084
- modes: ['CbMaj', 'Abmin', 'Abm', 'GbMix', 'DbDor', 'EbPhr', 'FbLyd', 'BbLoc'],
1173
+ modes: ['CbMaj', 'CbIon', 'Abmin', 'AbAeo', 'Abm', 'GbMix', 'DbDor', 'EbPhr', 'FbLyd', 'BbLoc'],
1085
1174
  stepsFromC: 11
1086
1175
  }
1087
1176
  };
1177
+ var modeNames = ['maj', 'ion', 'min', 'aeo', 'm', 'mix', 'dor', 'phr', 'lyd', 'loc'];
1178
+ function isLegalMode(mode) {
1179
+ return modeNames.indexOf(mode.toLowerCase()) >= 0;
1180
+ }
1088
1181
  var keyReverse = null;
1089
1182
  function createKeyReverse() {
1090
1183
  keyReverse = {};
@@ -1107,7 +1200,7 @@ function relativeMajor(key) {
1107
1200
  createKeyReverse();
1108
1201
  }
1109
1202
  // get the key portion itself - there might be other stuff, like extra sharps and flats, or the mode written out.
1110
- var mode = key.toLowerCase().match(/([a-g][b#]?)(maj|min|mix|dor|phr|lyd|loc|m)?/);
1203
+ var mode = key.toLowerCase().match(/([a-g][b#]?)(maj|ion|min|aeo|mix|dor|phr|lyd|loc|m)?/);
1111
1204
  if (!mode || !mode[2]) return key;
1112
1205
  mode = mode[1] + mode[2];
1113
1206
  var maj = keyReverse[mode];
@@ -1120,7 +1213,7 @@ function relativeMode(majorKey, mode) {
1120
1213
  var group = keys[majorKey];
1121
1214
  if (!group) return majorKey;
1122
1215
  if (mode === '') return majorKey;
1123
- var match = mode.toLowerCase().match(/^(maj|min|mix|dor|phr|lyd|loc|m)/);
1216
+ var match = mode.toLowerCase().match(/^(maj|ion|min|aeo|mix|dor|phr|lyd|loc|m)/);
1124
1217
  if (!match) return majorKey;
1125
1218
  var regMode = match[1];
1126
1219
  for (var i = 0; i < group.modes.length; i++) {
@@ -1148,7 +1241,8 @@ function transposeKey(key, steps) {
1148
1241
  module.exports = {
1149
1242
  relativeMajor: relativeMajor,
1150
1243
  relativeMode: relativeMode,
1151
- transposeKey: transposeKey
1244
+ transposeKey: transposeKey,
1245
+ isLegalMode: isLegalMode
1152
1246
  };
1153
1247
 
1154
1248
  /***/ }),
@@ -1239,9 +1333,12 @@ var Tune = function Tune() {
1239
1333
  this.getBeatLength = function () {
1240
1334
  // This returns a fraction: for instance 1/4 for a quarter
1241
1335
  // There are two types of meters: compound and regular. Compound meter has 3 beats counted as one.
1336
+
1337
+ // Irregular meters have the beat as an 1/8 note but the tempo as a 1/4.
1338
+ // That keeps it a similar tempo to 4/4 but that may or may not be generally intuitive, so that might need to change.
1242
1339
  var meter = this.getMeterFraction();
1243
1340
  var multiplier = 1;
1244
- if (meter.num === 6 || meter.num === 9 || meter.num === 12) multiplier = 3;else if (meter.num === 3 && meter.den === 8) multiplier = 3;
1341
+ if (meter.num === 6 || meter.num === 9 || meter.num === 12) multiplier = 3;else if (meter.num === 3 && meter.den === 8) multiplier = 3;else if (meter.den === 8 && (meter.num === 5 || meter.num === 7)) multiplier = 2;
1245
1342
  return multiplier / meter.den;
1246
1343
  };
1247
1344
  function computePickupLength(lines, barLength) {
@@ -1325,7 +1422,13 @@ var Tune = function Tune() {
1325
1422
  var den = 4;
1326
1423
  if (meter) {
1327
1424
  if (meter.type === 'specified') {
1328
- num = parseInt(meter.value[0].num, 10);
1425
+ if (meter.value && meter.value.length > 0 && meter.value[0].num.indexOf('+') > 0) {
1426
+ var parts = meter.value[0].num.split('+');
1427
+ num = 0;
1428
+ for (var i = 0; i < parts.length; i++) {
1429
+ num += parseInt(parts[i], 10);
1430
+ }
1431
+ } else num = parseInt(meter.value[0].num, 10);
1329
1432
  den = parseInt(meter.value[0].den, 10);
1330
1433
  } else if (meter.type === 'cut_time') {
1331
1434
  num = 2;
@@ -1757,7 +1860,7 @@ function delineTune(inputLines, options) {
1757
1860
  var currentTripletFont = [];
1758
1861
  var currentAnnotationFont = [];
1759
1862
  for (var i = 0; i < inputLines.length; i++) {
1760
- var inputLine = inputLines[i];
1863
+ var inputLine = cloneLine(inputLines[i]);
1761
1864
  if (inputLine.staff) {
1762
1865
  if (inMusicLine && !inputLine.vskip) {
1763
1866
  var outputLine = outputLines[outputLines.length - 1];
@@ -2451,8 +2554,8 @@ var create;
2451
2554
  var tempo = commands.tempo;
2452
2555
  var beatsPerSecond = tempo / 60;
2453
2556
 
2454
- // Fix tempo for */8 meters
2455
- if (time.den == 8) {
2557
+ // Fix tempo for compound meters
2558
+ if (time.den === 8 && time.num !== 5 && time.num !== 7) {
2456
2559
  // Compute the tempo based on the actual milliseconds per measure, scaled by the number of eight notes and halved to get tempo in bpm.
2457
2560
  var msPerMeasure = abcTune.millisecondsPerMeasure();
2458
2561
  tempo = 60000 / (msPerMeasure / time.num) / 2;
@@ -2602,6 +2705,7 @@ var ParseHeader = __webpack_require__(/*! ./abc_parse_header */ "./src/parse/abc
2602
2705
  var ParseMusic = __webpack_require__(/*! ./abc_parse_music */ "./src/parse/abc_parse_music.js");
2603
2706
  var Tokenizer = __webpack_require__(/*! ./abc_tokenizer */ "./src/parse/abc_tokenizer.js");
2604
2707
  var wrap = __webpack_require__(/*! ./wrap_lines */ "./src/parse/wrap_lines.js");
2708
+ var chordGrid = __webpack_require__(/*! ./chord-grid */ "./src/parse/chord-grid.js");
2605
2709
  var Tune = __webpack_require__(/*! ../data/abc_tune */ "./src/data/abc_tune.js");
2606
2710
  var TuneBuilder = __webpack_require__(/*! ../parse/tune-builder */ "./src/parse/tune-builder.js");
2607
2711
  var Parse = function Parse() {
@@ -2644,6 +2748,7 @@ var Parse = function Parse() {
2644
2748
  };
2645
2749
  if (tune.lineBreaks) t.lineBreaks = tune.lineBreaks;
2646
2750
  if (tune.visualTranspose) t.visualTranspose = tune.visualTranspose;
2751
+ if (tune.chordGrid) t.chordGrid = tune.chordGrid;
2647
2752
  return t;
2648
2753
  };
2649
2754
  function addPositioning(el, type, value) {
@@ -3193,6 +3298,22 @@ var Parse = function Parse() {
3193
3298
  addHintMeasures();
3194
3299
  }
3195
3300
  wrap.wrapLines(tune, multilineVars.lineBreaks, multilineVars.barNumbers);
3301
+ if (switches.chordGrid) {
3302
+ try {
3303
+ tune.chordGrid = chordGrid(tune);
3304
+ } catch (err) {
3305
+ switch (err.message) {
3306
+ case "notCommonTime":
3307
+ warn("Chord grid only works for 2/2 and 4/4 time.", 0, 0);
3308
+ break;
3309
+ case "noChords":
3310
+ warn("No chords are found in the tune.", 0, 0);
3311
+ break;
3312
+ default:
3313
+ warn(err.message, 0, 0);
3314
+ }
3315
+ }
3316
+ }
3196
3317
  };
3197
3318
  };
3198
3319
  module.exports = Parse;
@@ -9219,6 +9340,344 @@ module.exports = allNotes;
9219
9340
 
9220
9341
  /***/ }),
9221
9342
 
9343
+ /***/ "./src/parse/chord-grid.js":
9344
+ /*!*********************************!*\
9345
+ !*** ./src/parse/chord-grid.js ***!
9346
+ \*********************************/
9347
+ /***/ (function(module) {
9348
+
9349
+ // This takes a visual object and returns an object that can
9350
+ // be rotely turned into a chord grid.
9351
+ //
9352
+ // 1) It will always be 8 measures on a line, unless it is a 12 bar blues, then it will be 4 measures.
9353
+ // 2) If it is not in 4/4 it will return an error
9354
+ // 3) If there are no chords it will return an error
9355
+ // 4) It will be divided into parts with the part title and an array of measures
9356
+ // 5) |: and :| will be included in a measure
9357
+ // 6) If there are first and second endings and the chords are the same, then collapse them
9358
+ // 7) If there are first and second endings and the chords are different, use a separate line for the second ending and right justify it.
9359
+ // 8) If there is one chord per measure and it is repeated in the next measure use a % for the second measure.
9360
+ // 9) All lines are the same height, so they are tall enough to fit two lines if there lots of chords
9361
+ // 10) Chords will be printed as large as they can without overlapping, so different chords will be smaller if they are long.
9362
+ // 11) If there are two chords per measure then there is a slash between them.
9363
+ // 12) If there are three or four chords then there is a 2x2 grid with the chords reading right to left. For three chords, leave the repeated cell blank.
9364
+ // 13) Breaks are indicated by the word "break" or "N.C.". A break that extends to the next measure is indicated by three dots in the next measure.
9365
+ // 14) Ignore pickup notes
9366
+ // 15) if a part is not a multiple of 8 bars (and not 12 bars), the last line has
9367
+ // 4 squares on left and not any grid on the right.
9368
+ // 16) Annotations and some decorations get printed above the cells.
9369
+
9370
+ function chordGrid(visualObj) {
9371
+ var meter = visualObj.getMeterFraction();
9372
+ var isCommonTime = meter.num === 4 && meter.den === 4;
9373
+ var isCutTime = meter.num === 2 && meter.den === 2;
9374
+ if (!isCutTime && !isCommonTime) throw new Error("notCommonTime");
9375
+ var deline = visualObj.deline();
9376
+ var chartLines = [];
9377
+ var nonSubtitle = false;
9378
+ deline.forEach(function (section) {
9379
+ if (section.subtitle) {
9380
+ if (nonSubtitle) {
9381
+ // Don't do the subtitle if the first thing is the subtitle, but that is already printed on the top
9382
+ chartLines.push({
9383
+ type: "subtitle",
9384
+ subtitle: section.subtitle.text
9385
+ });
9386
+ }
9387
+ } else if (section.text) {
9388
+ nonSubtitle = true;
9389
+ chartLines.push({
9390
+ type: "text",
9391
+ text: section.text.text
9392
+ });
9393
+ } else if (section.staff) {
9394
+ nonSubtitle = true;
9395
+ // The first staff and the first voice in it drive everything.
9396
+ // Only part designations there will count. However, look for
9397
+ // chords in any other part. If there is not a chord defined in
9398
+ // the first part, use a chord defined in another part.
9399
+ var staves = section.staff;
9400
+ var parts = flattenVoices(staves);
9401
+ chartLines = chartLines.concat(parts);
9402
+ }
9403
+ });
9404
+ collapseIdenticalEndings(chartLines);
9405
+ addLineBreaks(chartLines);
9406
+ addPercents(chartLines);
9407
+ return chartLines;
9408
+ }
9409
+ var breakSynonyms = ['break', '(break)', 'no chord', 'n.c.', 'tacet'];
9410
+ function flattenVoices(staves) {
9411
+ var parts = [];
9412
+ var partName = "";
9413
+ var measures = [];
9414
+ var currentBar = {
9415
+ chord: ['', '', '', '']
9416
+ };
9417
+ var lastChord = "";
9418
+ var nextBarEnding = "";
9419
+ staves.forEach(function (staff, staffNum) {
9420
+ if (staff.voices) {
9421
+ staff.voices.forEach(function (voice, voiceNum) {
9422
+ var currentPartNum = 0;
9423
+ var beatNum = 0;
9424
+ var measureNum = 0;
9425
+ voice.forEach(function (element) {
9426
+ if (element.el_type === 'part') {
9427
+ if (measures.length > 0) {
9428
+ if (staffNum === 0 && voiceNum === 0) {
9429
+ parts.push({
9430
+ type: "part",
9431
+ name: partName,
9432
+ lines: [measures]
9433
+ });
9434
+ measures = [];
9435
+ // } else {
9436
+ // currentPartNum++
9437
+ // measureNum = 0
9438
+ // measures = parts[currentPartNum].lines[0]
9439
+ }
9440
+ }
9441
+
9442
+ partName = element.title;
9443
+ } else if (element.el_type === 'note') {
9444
+ addDecoration(element, currentBar);
9445
+ var intBeat = Math.floor(beatNum);
9446
+ if (element.chord && element.chord.length > 0) {
9447
+ var chord = element.chord[0]; // Use just the first chord specified - if there are multiple ones, then ignore them
9448
+ var chordName = chord.position === 'default' || breakSynonyms.indexOf(chord.name.toLowerCase()) >= 0 ? chord.name : '';
9449
+ if (chordName) {
9450
+ if (intBeat > 0 && !currentBar.chord[0])
9451
+ // Be sure there is a chord for the first beat in a measure
9452
+ currentBar.chord[0] = lastChord;
9453
+ lastChord = chordName;
9454
+ if (currentBar.chord[intBeat]) {
9455
+ // If there is already a chord on this beat put the next chord on the next beat, but don't overwrite anything.
9456
+ // This handles the case were a chord is misplaced slightly, for instance it is on the 1/8 before the beat.
9457
+ if (intBeat < 4 && !currentBar.chord[intBeat + 1]) currentBar.chord[intBeat + 1] = chordName;
9458
+ } else currentBar.chord[intBeat] = chordName;
9459
+ }
9460
+ element.chord.forEach(function (ch) {
9461
+ if (ch.position !== 'default' && breakSynonyms.indexOf(chord.name.toLowerCase()) < 0) {
9462
+ if (!currentBar.annotations) currentBar.annotations = [];
9463
+ currentBar.annotations.push(ch.name);
9464
+ }
9465
+ });
9466
+ }
9467
+ if (!element.rest || element.rest.type !== 'spacer') {
9468
+ var thisDuration = Math.floor(element.duration * 4);
9469
+ if (thisDuration > 4) {
9470
+ measureNum += Math.floor(thisDuration / 4);
9471
+ beatNum = 0;
9472
+ } else {
9473
+ var thisBeat = element.duration * 4;
9474
+ if (element.tripletMultiplier) thisBeat *= element.tripletMultiplier;
9475
+ beatNum += thisBeat;
9476
+ }
9477
+ }
9478
+ } else if (element.el_type === 'bar') {
9479
+ if (nextBarEnding) {
9480
+ currentBar.ending = nextBarEnding;
9481
+ nextBarEnding = "";
9482
+ }
9483
+ addDecoration(element, currentBar);
9484
+ if (element.type === 'bar_dbl_repeat' || element.type === 'bar_left_repeat') currentBar.hasStartRepeat = true;
9485
+ if (element.type === 'bar_dbl_repeat' || element.type === 'bar_right_repeat') currentBar.hasEndRepeat = true;
9486
+ if (element.startEnding) nextBarEnding = element.startEnding;
9487
+ if (beatNum >= 4) {
9488
+ if (currentBar.chord[0] === '') {
9489
+ // If there isn't a chord change at the beginning, repeat the last chord found
9490
+ if (currentBar.chord[1] || currentBar.chord[2] || currentBar.chord[3]) {
9491
+ currentBar.chord[0] = findLastChord(measures);
9492
+ }
9493
+ }
9494
+ if (staffNum === 0 && voiceNum === 0) measures.push(currentBar);else {
9495
+ // Add the found items of interest to the original array
9496
+ // We have the extra [0] in there because lines is an array of lines (but we just use the [0] for constructing, we split it apart at the end)
9497
+ var index = measureNum;
9498
+ var partIndex = 0;
9499
+ while (index >= parts[partIndex].lines[0].length && partIndex < parts.length) {
9500
+ index -= parts[partIndex].lines[0].length;
9501
+ partIndex++;
9502
+ }
9503
+ if (partIndex < parts.length && index < parts[partIndex].lines[0].length) {
9504
+ var bar = parts[partIndex].lines[0][index];
9505
+ if (!bar.chord[0] && currentBar.chord[0]) bar.chord[0] = currentBar.chord[0];
9506
+ if (!bar.chord[1] && currentBar.chord[1]) bar.chord[1] = currentBar.chord[1];
9507
+ if (!bar.chord[2] && currentBar.chord[2]) bar.chord[2] = currentBar.chord[2];
9508
+ if (!bar.chord[3] && currentBar.chord[3]) bar.chord[3] = currentBar.chord[3];
9509
+ if (currentBar.annotations) {
9510
+ if (!bar.annotations) bar.annotations = currentBar.annotations;else bar.annotations = bar.annotations.concat(currentBar.annotations);
9511
+ }
9512
+ }
9513
+ measureNum++;
9514
+ }
9515
+ currentBar = {
9516
+ chord: ['', '', '', '']
9517
+ };
9518
+ } else currentBar.chord = ['', '', '', ''];
9519
+ beatNum = 0;
9520
+ } else if (element.el_type === 'tempo') {
9521
+ // TODO-PER: should probably report tempo, too
9522
+ }
9523
+ });
9524
+ if (staffNum === 0 && voiceNum === 0) {
9525
+ parts.push({
9526
+ type: "part",
9527
+ name: partName,
9528
+ lines: [measures]
9529
+ });
9530
+ }
9531
+ });
9532
+ }
9533
+ });
9534
+ if (!lastChord) throw new Error("noChords");
9535
+ return parts;
9536
+ }
9537
+ function findLastChord(measures) {
9538
+ for (var m = measures.length - 1; m >= 0; m--) {
9539
+ for (var c = measures[m].chord.length - 1; c >= 0; c--) {
9540
+ if (measures[m].chord[c]) return measures[m].chord[c];
9541
+ }
9542
+ }
9543
+ }
9544
+ function collapseIdenticalEndings(chartLines) {
9545
+ chartLines.forEach(function (line) {
9546
+ if (line.type === "part") {
9547
+ var partLine = line.lines[0];
9548
+ var ending1 = partLine.findIndex(function (bar) {
9549
+ return !!bar.ending;
9550
+ });
9551
+ var ending2 = partLine.findIndex(function (bar, index) {
9552
+ return index > ending1 && !!bar.ending;
9553
+ });
9554
+ if (ending1 >= 0 && ending2 >= 0) {
9555
+ // If the endings are not the same length, don't collapse
9556
+ if (ending2 - ending1 === partLine.length - ending2) {
9557
+ var matches = true;
9558
+ for (var i = 0; i < ending2 - ending1 && matches; i++) {
9559
+ var measureLhs = partLine[ending1 + i];
9560
+ var measureRhs = partLine[ending2 + i];
9561
+ if (measureLhs.chord[0] !== measureRhs.chord[0]) matches = false;
9562
+ if (measureLhs.chord[1] !== measureRhs.chord[1]) matches = false;
9563
+ if (measureLhs.chord[2] !== measureRhs.chord[2]) matches = false;
9564
+ if (measureLhs.chord[3] !== measureRhs.chord[3]) matches = false;
9565
+ if (measureLhs.annotations && !measureRhs.annotations) matches = false;
9566
+ if (!measureLhs.annotations && measureRhs.annotations) matches = false;
9567
+ if (measureLhs.annotations && measureRhs.annotations) {
9568
+ if (measureLhs.annotations.length !== measureRhs.annotations.length) matches = false;else {
9569
+ for (var j = 0; j < measureLhs.annotations.length; j++) {
9570
+ if (measureLhs.annotations[j] !== measureRhs.annotations[j]) matches = false;
9571
+ }
9572
+ }
9573
+ }
9574
+ }
9575
+ if (matches) {
9576
+ delete partLine[ending1].ending;
9577
+ partLine.splice(ending2, partLine.length - ending2);
9578
+ }
9579
+ }
9580
+ }
9581
+ }
9582
+ });
9583
+ }
9584
+ function addLineBreaks(chartLines) {
9585
+ chartLines.forEach(function (line) {
9586
+ if (line.type === "part") {
9587
+ var newLines = [];
9588
+ var oldLines = line.lines[0];
9589
+ var is12bar = false;
9590
+ var firstEndRepeat = oldLines.findIndex(function (l) {
9591
+ return !!l.hasEndRepeat;
9592
+ });
9593
+ var length = firstEndRepeat >= 0 ? Math.min(firstEndRepeat + 1, oldLines.length) : oldLines.length;
9594
+ if (length === 12) is12bar = true;
9595
+ var barsPerLine = is12bar ? 4 : 8; // Only do 4 bars per line for 12-bar blues
9596
+ for (var i = 0; i < oldLines.length; i += barsPerLine) {
9597
+ var newLine = oldLines.slice(i, i + barsPerLine);
9598
+ var endRepeat = newLine.findIndex(function (l) {
9599
+ return !!l.hasEndRepeat;
9600
+ });
9601
+ if (endRepeat >= 0 && endRepeat < newLine.length - 1) {
9602
+ newLines.push(newLine.slice(0, endRepeat + 1));
9603
+ newLines.push(newLine.slice(endRepeat + 1));
9604
+ } else newLines.push(newLine);
9605
+ }
9606
+ // TODO-PER: The following probably doesn't handle all cases. Rethink it.
9607
+ for (var _i = 0; _i < newLines.length; _i++) {
9608
+ if (newLines[_i][0].ending) {
9609
+ var prevLine = Math.max(0, _i - 1);
9610
+ var toAdd = newLines[prevLine].length - newLines[_i].length;
9611
+ var thisLine = [];
9612
+ for (var j = 0; j < toAdd; j++) {
9613
+ thisLine.push({
9614
+ noBorder: true,
9615
+ chord: ['', '', '', '']
9616
+ });
9617
+ }
9618
+ newLines[_i] = thisLine.concat(newLines[_i]);
9619
+ }
9620
+ }
9621
+ line.lines = newLines;
9622
+ }
9623
+ });
9624
+ }
9625
+ function addPercents(chartLines) {
9626
+ chartLines.forEach(function (part) {
9627
+ if (part.lines) {
9628
+ var lastMeasureSingle = false;
9629
+ var lastChord = "";
9630
+ part.lines.forEach(function (line) {
9631
+ line.forEach(function (measure) {
9632
+ if (!measure.noBorder) {
9633
+ var chords = measure.chord;
9634
+ if (!chords[0] && !chords[1] && !chords[2] && !chords[3]) {
9635
+ // if there are no chords specified for this measure
9636
+ if (lastMeasureSingle) {
9637
+ if (lastChord) chords[0] = '%';
9638
+ } else chords[0] = lastChord;
9639
+ lastMeasureSingle = true;
9640
+ } else if (!chords[1] && !chords[2] && !chords[3]) {
9641
+ // if there is a single chord for this measure
9642
+ lastMeasureSingle = true;
9643
+ lastChord = chords[0];
9644
+ } else {
9645
+ // if the measure is complicated - in that case the next measure won't get %
9646
+ lastMeasureSingle = false;
9647
+ lastChord = chords[3] || chords[2] || chords[1];
9648
+ }
9649
+ }
9650
+ });
9651
+ });
9652
+ }
9653
+ });
9654
+ }
9655
+ function addDecoration(element, currentBar) {
9656
+ if (element.decoration) {
9657
+ // Some decorations are interesting to rhythm players
9658
+ for (var i = 0; i < element.decoration.length; i++) {
9659
+ switch (element.decoration[i]) {
9660
+ case 'fermata':
9661
+ case 'segno':
9662
+ case 'coda':
9663
+ case "D.C.":
9664
+ case "D.S.":
9665
+ case "D.C.alcoda":
9666
+ case "D.C.alfine":
9667
+ case "D.S.alcoda":
9668
+ case "D.S.alfine":
9669
+ case "fine":
9670
+ if (!currentBar.annotations) currentBar.annotations = [];
9671
+ currentBar.annotations.push(element.decoration[i]);
9672
+ break;
9673
+ }
9674
+ }
9675
+ }
9676
+ }
9677
+ module.exports = chordGrid;
9678
+
9679
+ /***/ }),
9680
+
9222
9681
  /***/ "./src/parse/transpose-chord.js":
9223
9682
  /*!**************************************!*\
9224
9683
  !*** ./src/parse/transpose-chord.js ***!
@@ -9857,7 +10316,7 @@ function resolveOverlays(tune) {
9857
10316
  } else if (event.el_type === "note") {
9858
10317
  if (inOverlay) {
9859
10318
  overlayVoice[k].voice.push(event);
9860
- } else {
10319
+ } else if (!event.rest || event.rest.type !== 'spacer') {
9861
10320
  durationThisBar += event.duration;
9862
10321
  durationsPerLines[i] += event.duration;
9863
10322
  }
@@ -10794,14 +11253,12 @@ module.exports = {
10794
11253
  \***************************/
10795
11254
  /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
10796
11255
 
10797
- function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
10798
- function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
10799
- function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
10800
11256
  var keyAccidentals = __webpack_require__(/*! ../const/key-accidentals */ "./src/const/key-accidentals.js");
10801
11257
  var _require = __webpack_require__(/*! ../const/relative-major */ "./src/const/relative-major.js"),
10802
11258
  relativeMajor = _require.relativeMajor,
10803
11259
  transposeKey = _require.transposeKey,
10804
- relativeMode = _require.relativeMode;
11260
+ relativeMode = _require.relativeMode,
11261
+ isLegalMode = _require.isLegalMode;
10805
11262
  var transposeChordName = __webpack_require__(/*! ../parse/transpose-chord */ "./src/parse/transpose-chord.js");
10806
11263
  var strTranspose;
10807
11264
  (function () {
@@ -10864,11 +11321,12 @@ var strTranspose;
10864
11321
  var match = segment.match(/^( *)([A-G])([#b]?)( ?)(\w*)/);
10865
11322
  if (match) {
10866
11323
  var start = count + 2 + match[1].length; // move past the 'K:' and optional white space
10867
- var key = match[2] + match[3] + match[4] + match[5]; // key name, accidental, optional space, and mode
11324
+ var mode = isLegalMode(match[5]) ? match[5] : '';
11325
+ var key = match[2] + match[3] + match[4] + mode; // key name, accidental, optional space, and mode
10868
11326
  var destinationKey = newKey({
10869
11327
  root: match[2],
10870
11328
  acc: match[3],
10871
- mode: match[5]
11329
+ mode: mode
10872
11330
  }, steps);
10873
11331
  var dest = destinationKey.root + destinationKey.acc + match[4] + destinationKey.mode;
10874
11332
  changes.push({
@@ -10932,12 +11390,18 @@ var strTranspose;
10932
11390
  }
10933
11391
  }
10934
11392
  if (el.el_type === 'note' && el.pitches) {
10935
- for (var j = 0; j < el.pitches.length; j++) {
10936
- var note = parseNote(el.pitches[j].name, keyRoot, keyAccidentals, measureAccidentals);
11393
+ var pitchArray = findNotes(abc, el.startChar, el.endChar);
11394
+ //console.log(pitchArray)
11395
+ for (var j = 0; j < pitchArray.length; j++) {
11396
+ var note = parseNote(pitchArray[j].note, keyRoot, keyAccidentals, measureAccidentals);
10937
11397
  if (note.acc) measureAccidentals[note.name.toUpperCase()] = note.acc;
10938
11398
  var newPitch = transposePitch(note, destinationKey, letterDistance, transposedMeasureAccidentals);
10939
11399
  if (newPitch.acc) transposedMeasureAccidentals[newPitch.upper] = newPitch.acc;
10940
- changes.push(replaceNote(abc, el.startChar, el.endChar, newPitch.acc + newPitch.name, j));
11400
+ changes.push({
11401
+ note: newPitch.acc + newPitch.name,
11402
+ start: pitchArray[j].index,
11403
+ end: pitchArray[j].index + pitchArray[j].note.length
11404
+ });
10941
11405
  }
10942
11406
  if (el.gracenotes) {
10943
11407
  for (var g = 0; g < el.gracenotes.length; g++) {
@@ -11013,6 +11477,7 @@ var strTranspose;
11013
11477
  break;
11014
11478
  }
11015
11479
  }
11480
+ var newNote;
11016
11481
  switch (adj) {
11017
11482
  case -2:
11018
11483
  acc = "__";
@@ -11031,7 +11496,7 @@ var strTranspose;
11031
11496
  break;
11032
11497
  case -3:
11033
11498
  // This requires a triple flat, so bump down the pitch and try again
11034
- var newNote = {};
11499
+ newNote = {};
11035
11500
  newNote.pitch = note.pitch - 1;
11036
11501
  newNote.oct = note.oct;
11037
11502
  newNote.name = letters[letters.indexOf(note.name) - 1];
@@ -11043,7 +11508,7 @@ var strTranspose;
11043
11508
  return transposePitch(newNote, key, letterDistance + 1, measureAccidentals);
11044
11509
  case 3:
11045
11510
  // This requires a triple sharp, so bump up the pitch and try again
11046
- var newNote = {};
11511
+ newNote = {};
11047
11512
  newNote.pitch = note.pitch + 1;
11048
11513
  newNote.oct = note.oct;
11049
11514
  newNote.name = letters[letters.indexOf(note.name) + 1];
@@ -11092,8 +11557,8 @@ var strTranspose;
11092
11557
  var regPitch = /([_^=]*)([A-Ga-g])([,']*)/;
11093
11558
  var regNote = /([_^=]*[A-Ga-g][,']*)(\d*\/*\d*)([\>\<\-\)\.\s\\]*)/;
11094
11559
  var regOptionalNote = /([_^=]*[A-Ga-g][,']*)?(\d*\/*\d*)?([\>\<\-\)]*)?/;
11095
- var regSpace = /(\s*)$/;
11096
- var regOptionalSpace = /(\s*)/;
11560
+ //var regSpace = /(\s*)$/
11561
+ //var regOptionalSpace = /(\s*)/
11097
11562
 
11098
11563
  // This the relationship of the note to the tonic and an octave. So what is returned is a distance in steps from the tonic and the amount of adjustment from
11099
11564
  // a normal scale. That is - in the key of D an F# is two steps from the tonic and no adjustment. A G# is three steps from the tonic and one half-step higher.
@@ -11121,68 +11586,100 @@ var strTranspose;
11121
11586
  courtesy: reg[1] === currentAcc
11122
11587
  };
11123
11588
  }
11124
- function replaceNote(abc, start, end, newPitch, index) {
11589
+ function findNotes(abc, start, end) {
11590
+ // TODO-PER: I thought this regex should have found all the notes and ignored the chords and decorations but it didn't: /(?:"[^"]+")*(?:![^!]+!)*([_^=]*)([A-Ga-g])([,']*)/g
11125
11591
  var note = abc.substring(start, end);
11126
- // Try single note first
11127
- var match = note.match(new RegExp(regNote.source + regSpace.source));
11128
- if (match) {
11129
- var noteLen = match[1].length;
11130
- var trailingLen = match[2].length + match[3].length + match[4].length;
11131
- var leadingLen = end - start - noteLen - trailingLen;
11132
- start += leadingLen;
11133
- end -= trailingLen;
11134
- } else {
11135
- // Match chord
11136
- var regPreBracket = /([^\[]*)/;
11137
- var regOpenBracket = /\[/;
11138
- var regCloseBracket = /\-?](\d*\/*\d*)?([\>\<\-\)]*)/;
11139
- var regChord = new RegExp(regPreBracket.source + regOpenBracket.source + "(?:" + regOptionalNote.source + "\\s*){1,8}" + regCloseBracket.source + regSpace.source);
11140
- match = note.match(regChord);
11141
- if (match) {
11142
- var beforeChordLen = match[1].length + 1; // text before + '['
11143
- var chordBody = note.slice(match[1].length + 1, note.lastIndexOf("]"));
11144
- // Collect notes inside chord
11145
- var chordNotes = [];
11146
- var regNoteWithSpace = new RegExp(regOptionalNote.source + "\\s*", "g");
11147
- var _iterator = _createForOfIteratorHelper(chordBody.matchAll(regNoteWithSpace)),
11148
- _step;
11149
- try {
11150
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
11151
- var m = _step.value;
11152
- var noteText = m[0].trim();
11153
- if (noteText !== "") {
11154
- chordNotes.push({
11155
- text: noteText,
11156
- index: m.index
11157
- });
11158
- }
11159
- }
11160
- } catch (err) {
11161
- _iterator.e(err);
11162
- } finally {
11163
- _iterator.f();
11164
- }
11165
- if (index >= chordNotes.length) {
11166
- throw new Error("Chord index out of range for chord: " + note);
11167
- }
11168
- var chosen = chordNotes[index];
11169
- // Preserve duration and tie
11170
- var mDurTie = chosen.text.match(/^(.+?)(\d+\/?\d*)?(-)?$/);
11171
- var pitchPart = mDurTie ? mDurTie[1] : chosen.text;
11172
- var durationPart = mDurTie && mDurTie[2] ? mDurTie[2] : "";
11173
- var tiePart = mDurTie && mDurTie[3] ? mDurTie[3] : "";
11174
- // Replace note keeping duration and tie
11175
- newPitch = newPitch + durationPart + tiePart;
11176
- start += beforeChordLen + chosen.index;
11177
- end = start + chosen.text.length;
11592
+
11593
+ // Since the regex will also find "c", "d", and "a" in `!coda!`, we need to filter them
11594
+ var array;
11595
+ var ignoreBlocks = [];
11596
+ var regChord = /("[^"]+")+/g;
11597
+ while ((array = regChord.exec(note)) !== null) {
11598
+ ignoreBlocks.push({
11599
+ start: regChord.lastIndex - array[0].length,
11600
+ end: regChord.lastIndex
11601
+ });
11602
+ }
11603
+ var regDec = /(![^!]+!)+/g;
11604
+ while ((array = regDec.exec(note)) !== null) {
11605
+ ignoreBlocks.push({
11606
+ start: regDec.lastIndex - array[0].length,
11607
+ end: regDec.lastIndex
11608
+ });
11609
+ }
11610
+ var ret = [];
11611
+ // Define the regex each time because it is stateful
11612
+ var regPitch = /([_^=]*)([A-Ga-g])([,']*)/g;
11613
+ while ((array = regPitch.exec(note)) !== null) {
11614
+ var found = false;
11615
+ for (var i = 0; i < ignoreBlocks.length; i++) {
11616
+ if (regPitch.lastIndex >= ignoreBlocks[i].start && regPitch.lastIndex <= ignoreBlocks[i].end) found = true;
11178
11617
  }
11618
+ if (!found) ret.push({
11619
+ note: array[0],
11620
+ index: start + regPitch.lastIndex - array[0].length
11621
+ });
11179
11622
  }
11180
- return {
11181
- start: start,
11182
- end: end,
11183
- note: newPitch
11184
- };
11623
+ return ret;
11185
11624
  }
11625
+
11626
+ // function replaceNote(abc, start, end, newPitch, oldPitch, index) {
11627
+ // var note = abc.substring(start, end);
11628
+ // // Try single note first
11629
+ // var match = note.match(new RegExp(regNote.source + regSpace.source));
11630
+ // if (match) {
11631
+ // var noteLen = match[1].length;
11632
+ // var trailingLen = match[2].length + match[3].length + match[4].length;
11633
+ // var leadingLen = end - start - noteLen - trailingLen;
11634
+ // start += leadingLen;
11635
+ // end -= trailingLen;
11636
+ // } else {
11637
+ // // Match chord
11638
+ // var regPreBracket = /([^\[]*)/;
11639
+ // var regOpenBracket = /\[/;
11640
+ // var regCloseBracket = /\-?](\d*\/*\d*)?([\>\<\-\)]*)/;
11641
+ // var regChord = new RegExp(
11642
+ // regPreBracket.source +
11643
+ // regOpenBracket.source +
11644
+ // "(?:" + regOptionalNote.source + "\\s*){1,8}" +
11645
+ // regCloseBracket.source +
11646
+ // regSpace.source
11647
+ // );
11648
+ // match = note.match(regChord);
11649
+ // if (match) {
11650
+ // var beforeChordLen = match[1].length + 1; // text before + '['
11651
+ // var chordBody = note.slice(match[1].length + 1, note.lastIndexOf("]"));
11652
+ // // Collect notes inside chord
11653
+ // var chordNotes = [];
11654
+ // var regNoteWithSpace = new RegExp(regOptionalNote.source + "\\s*", "g");
11655
+ // for (const m of chordBody.matchAll(regNoteWithSpace)) {
11656
+ // let noteText = m[0].trim();
11657
+ // if (noteText !== "") {
11658
+ // chordNotes.push({ text: noteText, index: m.index });
11659
+ // }
11660
+ // }
11661
+ // if (index >= chordNotes.length) {
11662
+ // throw new Error("Chord index out of range for chord: " + note);
11663
+ // }
11664
+ // var chosen = chordNotes[index];
11665
+ // // Preserve duration and tie
11666
+ // let mDurTie = chosen.text.match(/^(.+?)(\d+\/?\d*)?(-)?$/);
11667
+ // let pitchPart = mDurTie ? mDurTie[1] : chosen.text;
11668
+ // let durationPart = mDurTie && mDurTie[2] ? mDurTie[2] : "";
11669
+ // let tiePart = mDurTie && mDurTie[3] ? mDurTie[3] : "";
11670
+ // // Replace note keeping duration and tie
11671
+ // newPitch = newPitch + durationPart + tiePart;
11672
+ // start += beforeChordLen + chosen.index;
11673
+ // end = start + chosen.text.length;
11674
+ // }
11675
+ // }
11676
+ // return {
11677
+ // start: start,
11678
+ // end: end,
11679
+ // note: newPitch
11680
+ // };
11681
+ // }
11682
+
11186
11683
  function replaceGrace(abc, start, end, newGrace, index) {
11187
11684
  var note = abc.substring(start, end);
11188
11685
  // I don't know how to capture more than one note, so I'm separating them. There is a limit of the number of notes in a chord depending on the repeats I have here, but it is unlikely to happen in real music.
@@ -12591,11 +13088,12 @@ module.exports = rendererFactory;
12591
13088
 
12592
13089
  var sequence;
12593
13090
  var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/abc_common.js");
13091
+ var Repeats = __webpack_require__(/*! ./repeats */ "./src/synth/repeats.js");
12594
13092
  (function () {
12595
13093
  "use strict";
12596
13094
 
12597
13095
  var measureLength = 1; // This should be set by the meter, but just in case that is missing, we'll take a guess.
12598
- // The abc is provided to us line by line. It might have repeats in it. We want to re arrange the elements to
13096
+ // The abc is provided to us line by line. It might have repeats in it. We want to rearrange the elements to
12599
13097
  // be an array of voices with all the repeats embedded, and no lines. Then it is trivial to go through the events
12600
13098
  // one at a time and turn it into midi.
12601
13099
 
@@ -12732,8 +13230,7 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
12732
13230
  timing: 0
12733
13231
  };
12734
13232
  var currentVolume;
12735
- var startRepeatPlaceholder = []; // There is a place holder for each voice.
12736
- var skipEndingPlaceholder = []; // This is the place where the first ending starts.
13233
+ var repeats = [];
12737
13234
  var startingDrumSet = false;
12738
13235
  var lines = abctune.lines; //abctune.deline(); TODO-PER: can switch to this, then simplify the loops below.
12739
13236
  for (var i = 0; i < lines.length; i++) {
@@ -12812,6 +13309,7 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
12812
13309
  el_type: "name",
12813
13310
  trackName: voiceName
12814
13311
  });
13312
+ repeats[voiceNumber] = new Repeats(voices[voiceNumber]);
12815
13313
  }
12816
13314
  // Negate any transposition for the percussion staff.
12817
13315
  if (transpose && staff.clef.type === "perc") voices[voiceNumber].push({
@@ -13010,28 +13508,7 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13010
13508
  }); // We need the bar marking to reset the accidentals.
13011
13509
  setDynamics(elem);
13012
13510
  noteEventsInBar = 0;
13013
- // figure out repeats and endings --
13014
- // The important part is where there is a start repeat, and end repeat, or a first ending.
13015
- var endRepeat = elem.type === "bar_right_repeat" || elem.type === "bar_dbl_repeat";
13016
- var startEnding = elem.startEnding === '1';
13017
- var startRepeat = elem.type === "bar_left_repeat" || elem.type === "bar_dbl_repeat" || elem.type === "bar_right_repeat";
13018
- if (endRepeat) {
13019
- var s = startRepeatPlaceholder[voiceNumber];
13020
- if (!s) s = 0; // If there wasn't a left repeat, then we repeat from the beginning.
13021
- var e = skipEndingPlaceholder[voiceNumber];
13022
- if (!e) e = voices[voiceNumber].length; // If there wasn't a first ending marker, then we copy everything.
13023
- // duplicate each of the elements - this has to be a deep copy.
13024
- for (var z = s; z < e; z++) {
13025
- var item = Object.assign({}, voices[voiceNumber][z]);
13026
- if (item.pitches) item.pitches = parseCommon.cloneArray(item.pitches);
13027
- voices[voiceNumber].push(item);
13028
- }
13029
- // reset these in case there is a second repeat later on.
13030
- skipEndingPlaceholder[voiceNumber] = undefined;
13031
- startRepeatPlaceholder[voiceNumber] = undefined;
13032
- }
13033
- if (startEnding) skipEndingPlaceholder[voiceNumber] = voices[voiceNumber].length;
13034
- if (startRepeat) startRepeatPlaceholder[voiceNumber] = voices[voiceNumber].length;
13511
+ repeats[voiceNumber].addBar(elem, voiceNumber);
13035
13512
  rhythmHeadThisBar = false;
13036
13513
  break;
13037
13514
  case 'style':
@@ -13181,6 +13658,10 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13181
13658
  }
13182
13659
  }
13183
13660
  }
13661
+ for (var r = 0; r < repeats.length; r++) {
13662
+ voices[r] = repeats[r].resolveRepeats();
13663
+ }
13664
+
13184
13665
  // If there are tempo changes, make sure they are in all the voices. This must be done post process because all the elements in all the voices need to be created first.
13185
13666
  insertTempoChanges(voices, tempoChanges);
13186
13667
  if (drumIntro) {
@@ -13320,6 +13801,7 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13320
13801
  num: 4,
13321
13802
  den: 4
13322
13803
  };
13804
+ measureLength = 4 / 4;
13323
13805
  break;
13324
13806
  case "cut_time":
13325
13807
  meter = {
@@ -13327,22 +13809,31 @@ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/ab
13327
13809
  num: 2,
13328
13810
  den: 2
13329
13811
  };
13812
+ measureLength = 2 / 2;
13330
13813
  break;
13331
13814
  case "specified":
13332
13815
  // TODO-PER: only taking the first meter, so the complex meters are not handled.
13816
+ var num = 0;
13817
+ if (element.value && element.value.length > 0 && element.value[0].num.indexOf('+') > 0) {
13818
+ var parts = element.value[0].num.split('+');
13819
+ for (var i = 0; i < parts.length; i++) {
13820
+ num += parseInt(parts[i], 10);
13821
+ }
13822
+ } else num = parseInt(element.value[0].num, 10);
13333
13823
  meter = {
13334
13824
  el_type: 'meter',
13335
- num: element.value[0].num,
13825
+ num: num,
13336
13826
  den: element.value[0].den
13337
13827
  };
13828
+ measureLength = num / parseInt(element.value[0].den, 10);
13338
13829
  break;
13339
13830
  default:
13340
13831
  // This should never happen.
13341
13832
  meter = {
13342
13833
  el_type: 'meter'
13343
13834
  };
13835
+ measureLength = 1;
13344
13836
  }
13345
- measureLength = meter.num / meter.den;
13346
13837
  return meter;
13347
13838
  }
13348
13839
  function removeNaturals(accidentals) {
@@ -14438,7 +14929,7 @@ function CreateSynth() {
14438
14929
  if (options.visualObj) {
14439
14930
  self.flattened = options.visualObj.setUpAudio(params);
14440
14931
  var meter = options.visualObj.getMeterFraction();
14441
- if (meter.den) self.meterSize = options.visualObj.getMeterFraction().num / options.visualObj.getMeterFraction().den;
14932
+ if (meter.den) self.meterSize = meter.num / meter.den;
14442
14933
  self.pickupLength = options.visualObj.getPickupLength();
14443
14934
  } else if (options.sequence) self.flattened = options.sequence;else return Promise.reject(new Error("Must pass in either a visualObj or a sequence"));
14444
14935
  self.millisecondsPerMeasure = options.millisecondsPerMeasure ? options.millisecondsPerMeasure : options.visualObj ? options.visualObj.millisecondsPerMeasure(self.flattened.tempo) : 1000;
@@ -15587,6 +16078,221 @@ module.exports = registerAudioContext;
15587
16078
 
15588
16079
  /***/ }),
15589
16080
 
16081
+ /***/ "./src/synth/repeats.js":
16082
+ /*!******************************!*\
16083
+ !*** ./src/synth/repeats.js ***!
16084
+ \******************************/
16085
+ /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
16086
+
16087
+ var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/abc_common.js");
16088
+ function Repeats(voice) {
16089
+ this.sections = [{
16090
+ type: 'startRepeat',
16091
+ index: -1
16092
+ }];
16093
+ this.addBar = function (elem) {
16094
+ // Record the "interesting" parts for analysis at the end.
16095
+ var thisIndex = voice.length - 1;
16096
+ var isStartRepeat = elem.type === "bar_left_repeat" || elem.type === "bar_dbl_repeat";
16097
+ var isEndRepeat = elem.type === "bar_right_repeat" || elem.type === "bar_dbl_repeat";
16098
+ var startEnding = elem.startEnding ? startEndingNumbers(elem.startEnding) : undefined;
16099
+ if (isEndRepeat) this.sections.push({
16100
+ type: "endRepeat",
16101
+ index: thisIndex
16102
+ });
16103
+ if (isStartRepeat) this.sections.push({
16104
+ type: "startRepeat",
16105
+ index: thisIndex
16106
+ });
16107
+ if (startEnding) this.sections.push({
16108
+ type: "startEnding",
16109
+ index: thisIndex,
16110
+ endings: startEnding
16111
+ });
16112
+ };
16113
+ this.resolveRepeats = function () {
16114
+ // this.sections contain all the interesting bars - start and end repeats.
16115
+ var e;
16116
+
16117
+ // There may be one last set of events after the last interesting bar, so capture that now.
16118
+ var lastSection = this.sections[this.sections.length - 1];
16119
+ var lastElement = voice.length - 1;
16120
+ if (lastSection.type === 'startRepeat') lastSection.end = lastElement;else if (lastSection.index + 1 < lastElement) this.sections.push({
16121
+ type: "startRepeat",
16122
+ index: lastSection.index + 1
16123
+ });
16124
+
16125
+ // console.log(voice.map((el,index) => {
16126
+ // return JSON.stringify({i: index, t: el.el_type, p: el.pitches ? el.pitches[0].name: undefined})
16127
+ // }).join("\n"))
16128
+
16129
+ // console.log(this.sections.map(s => JSON.stringify(s)).join("\n"))
16130
+ if (this.sections.length < 2) return voice; // If there are no repeats then don't bother copying anything
16131
+
16132
+ // Go through all the markers and turn that into an array of sets of sections in order.
16133
+ // The output is repeatInstructions. If "endings" is not present, then the common section should just
16134
+ // be copied once. If "endings" is present but is empty, that means it is a plain repeat without
16135
+ // endings so the common section is copied twice. If "endings" contains items, then copy the
16136
+ // common section followed by each ending in turn. If the last item in "endings" is -1, then
16137
+ // the common section should be copied one more time but there isn't a corresponding ending for it.
16138
+ var repeatInstructions = []; // { common: { start: number, end: number }, endings: Array<{start:number, end:number> }
16139
+ var currentRepeat = null;
16140
+ for (var i = 0; i < this.sections.length; i++) {
16141
+ var section = this.sections[i];
16142
+ //var end = i < this.sections.length-1 ? this.sections[i+1].index : lastElement
16143
+ switch (section.type) {
16144
+ case "startRepeat":
16145
+ if (currentRepeat) {
16146
+ if (!currentRepeat.common.end) currentRepeat.common.end = section.index;
16147
+ if (currentRepeat.endings) {
16148
+ for (e = 0; e < currentRepeat.endings.length; e++) {
16149
+ if (currentRepeat.endings[e] && !currentRepeat.endings[e].end && currentRepeat.endings[e].start !== section.index) currentRepeat.endings[e].end = section.index;
16150
+ }
16151
+ }
16152
+ // If the last event was an end repeat, then there is one more repeat of just the common area. (Only when there are ending markers - otherwise it is already taken care of.)
16153
+ if (this.sections[i - 1].type === 'endRepeat' && currentRepeat.endings && currentRepeat.endings.length) currentRepeat.endings[currentRepeat.endings.length] = {
16154
+ start: -1,
16155
+ end: -1
16156
+ };
16157
+ repeatInstructions.push(currentRepeat);
16158
+ }
16159
+
16160
+ // if there is a gap between the last event and this start, then
16161
+ // insert those items.
16162
+ if (currentRepeat) {
16163
+ var lastUsed = currentRepeat.common.end;
16164
+ if (currentRepeat.endings) {
16165
+ for (e = 0; e < currentRepeat.endings.length; e++) {
16166
+ if (currentRepeat.endings[e]) lastUsed = Math.max(lastUsed, currentRepeat.endings[e].end);
16167
+ }
16168
+ }
16169
+ if (lastUsed < section.index - 1) {
16170
+ console.log("gap", voice.slice(lastUsed + 1, section.index));
16171
+ repeatInstructions.push({
16172
+ common: {
16173
+ start: lastUsed + 1,
16174
+ end: section.index
16175
+ }
16176
+ });
16177
+ }
16178
+ }
16179
+ currentRepeat = {
16180
+ common: {
16181
+ start: section.index
16182
+ }
16183
+ };
16184
+ break;
16185
+ case "startEnding":
16186
+ {
16187
+ if (currentRepeat) {
16188
+ if (!currentRepeat.common.end) currentRepeat.common.end = section.index;
16189
+ if (!currentRepeat.endings) currentRepeat.endings = [];
16190
+ for (e = 0; e < section.endings.length; e++) {
16191
+ currentRepeat.endings[section.endings[e]] = {
16192
+ start: section.index + 1
16193
+ };
16194
+ }
16195
+ }
16196
+ break;
16197
+ }
16198
+ case "endRepeat":
16199
+ if (currentRepeat) {
16200
+ if (!currentRepeat.endings) currentRepeat.endings = [];
16201
+ if (currentRepeat.endings.length > 0) {
16202
+ for (e = 0; e < currentRepeat.endings.length; e++) {
16203
+ if (currentRepeat.endings[e] && !currentRepeat.endings[e].end) currentRepeat.endings[e].end = section.index;
16204
+ }
16205
+ }
16206
+ if (!currentRepeat.common.end)
16207
+ // This is a repeat that doesn't have first and second endings
16208
+ currentRepeat.common.end = section.index;
16209
+ }
16210
+ break;
16211
+ }
16212
+ }
16213
+ if (currentRepeat) {
16214
+ if (!currentRepeat.common.end) currentRepeat.common.end = lastElement;
16215
+ if (currentRepeat.endings) {
16216
+ for (e = 0; e < currentRepeat.endings.length; e++) {
16217
+ if (currentRepeat.endings[e] && !currentRepeat.endings[e].end) currentRepeat.endings[e].end = lastElement;
16218
+ }
16219
+ }
16220
+ repeatInstructions.push(currentRepeat);
16221
+ }
16222
+ // for (var x = 0; x < repeatInstructions.length; x++) {
16223
+ // console.log(JSON.stringify(repeatInstructions[x]))
16224
+ // }
16225
+
16226
+ var output = [];
16227
+ var lastEnd = -1;
16228
+ for (var r = 0; r < repeatInstructions.length; r++) {
16229
+ var instructions = repeatInstructions[r];
16230
+ if (!instructions.endings) {
16231
+ duplicateSpan(voice, output, instructions.common.start, instructions.common.end);
16232
+ } else if (instructions.endings.length === 0) {
16233
+ // this is when there is no endings specified - it is just a repeat
16234
+ duplicateSpan(voice, output, instructions.common.start, instructions.common.end);
16235
+ duplicateSpan(voice, output, instructions.common.start, instructions.common.end);
16236
+ } else {
16237
+ for (e = 0; e < instructions.endings.length; e++) {
16238
+ var ending = instructions.endings[e];
16239
+ if (ending) {
16240
+ // this is a sparse array so skip the empty ones
16241
+ duplicateSpan(voice, output, instructions.common.start, instructions.common.end);
16242
+ if (ending.start > 0) {
16243
+ duplicateSpan(voice, output, ending.start, ending.end);
16244
+ }
16245
+ lastEnd = Math.max(lastEnd, ending.end);
16246
+ }
16247
+ }
16248
+ }
16249
+ }
16250
+ return output;
16251
+ };
16252
+ }
16253
+ function duplicateSpan(input, output, start, end) {
16254
+ //console.log("dup", {start, end})
16255
+ for (var i = start; i <= end; i++) {
16256
+ output.push(duplicateItem(input[i]));
16257
+ }
16258
+ }
16259
+ function duplicateItem(src) {
16260
+ var item = Object.assign({}, src);
16261
+ if (item.pitches) item.pitches = parseCommon.cloneArray(item.pitches);
16262
+ return item;
16263
+ }
16264
+ function startEndingNumbers(startEnding) {
16265
+ // The ending can be in four different types: "random-string", "number", "number-number", "number,number"
16266
+ // If we don't get a number out of it then we will just skip the ending - we don't know what to do with it.
16267
+ var nums = [];
16268
+ var ending, endings, i;
16269
+ if (startEnding.indexOf(',') > 0) {
16270
+ endings = startEnding.split(',');
16271
+ for (i = 0; i < endings.length; i++) {
16272
+ ending = parseInt(endings[i], 10);
16273
+ if (ending > 0) {
16274
+ nums.push(ending);
16275
+ }
16276
+ }
16277
+ } else if (startEnding.indexOf('-') > 0) {
16278
+ endings = startEnding.split('-');
16279
+ var se = parseInt(endings[0], 10);
16280
+ var ee = parseInt(endings[1], 10);
16281
+ for (i = se; i <= ee; i++) {
16282
+ nums.push(i);
16283
+ }
16284
+ } else {
16285
+ ending = parseInt(startEnding, 10);
16286
+ if (ending > 0) {
16287
+ nums.push(ending);
16288
+ }
16289
+ }
16290
+ return nums;
16291
+ }
16292
+ module.exports = Repeats;
16293
+
16294
+ /***/ }),
16295
+
15590
16296
  /***/ "./src/synth/sounds-cache.js":
15591
16297
  /*!***********************************!*\
15592
16298
  !*** ./src/synth/sounds-cache.js ***!
@@ -17823,8 +18529,11 @@ AbstractEngraver.prototype.createABCElement = function (isFirstStaff, isSingleLi
17823
18529
  elemset[0] = abselem;
17824
18530
  break;
17825
18531
  case "tempo":
18532
+ // MAE 20 Nov 2025 For %%printtempo after initial header
17826
18533
  var abselem3 = new AbsoluteElement(elem, 0, 0, 'tempo', this.tuneNumber);
17827
- abselem3.addFixedX(new TempoElement(elem, this.tuneNumber, createNoteHead));
18534
+ if (!elem.suppress) {
18535
+ abselem3.addFixedX(new TempoElement(elem, this.tuneNumber, createNoteHead));
18536
+ }
17828
18537
  elemset[0] = abselem3;
17829
18538
  break;
17830
18539
  case "style":
@@ -22055,6 +22764,268 @@ module.exports = drawBrace;
22055
22764
 
22056
22765
  /***/ }),
22057
22766
 
22767
+ /***/ "./src/write/draw/chord-grid.js":
22768
+ /*!**************************************!*\
22769
+ !*** ./src/write/draw/chord-grid.js ***!
22770
+ \**************************************/
22771
+ /***/ (function(module, __unused_webpack_exports, __webpack_require__) {
22772
+
22773
+ var printSymbol = __webpack_require__(/*! ./print-symbol */ "./src/write/draw/print-symbol.js");
22774
+ var printStem = __webpack_require__(/*! ./print-stem */ "./src/write/draw/print-stem.js");
22775
+ function drawChordGrid(renderer, parts, leftMargin, pageWidth, fonts) {
22776
+ var chordFont = fonts.gchordfont;
22777
+ var partFont = fonts.partsfont;
22778
+ var annotationFont = fonts.annotationfont;
22779
+ var endingFont = fonts.repeatfont;
22780
+ var textFont = fonts.textfont;
22781
+ var subtitleFont = fonts.subtitlefont;
22782
+ var ROW_HEIGHT = 50;
22783
+ var ENDING_HEIGHT = 10;
22784
+ var ANNOTATION_HEIGHT = 14;
22785
+ var PART_MARGIN_TOP = 10;
22786
+ var PART_MARGIN_BOTTOM = 20;
22787
+ var TEXT_MARGIN = 16;
22788
+ parts.forEach(function (part) {
22789
+ switch (part.type) {
22790
+ case "text":
22791
+ {
22792
+ text(renderer, part.text, leftMargin, renderer.y, 16, textFont, null, null, false);
22793
+ renderer.moveY(TEXT_MARGIN);
22794
+ }
22795
+ break;
22796
+ case "subtitle":
22797
+ {
22798
+ text(renderer, part.subtitle, leftMargin, renderer.y + PART_MARGIN_TOP, 20, subtitleFont, null, "abcjs-subtitle", false);
22799
+ renderer.moveY(PART_MARGIN_BOTTOM);
22800
+ }
22801
+ break;
22802
+ case "part":
22803
+ if (part.lines.length > 0) {
22804
+ text(renderer, part.name, leftMargin, renderer.y + PART_MARGIN_TOP, 20, subtitleFont, part.name, "abcjs-part", false);
22805
+ renderer.moveY(PART_MARGIN_BOTTOM);
22806
+ var numCols = part.lines[0].length;
22807
+ var colWidth = pageWidth / numCols;
22808
+ part.lines.forEach(function (line, lineNum) {
22809
+ var hasEnding = false;
22810
+ var hasAnnotation = false;
22811
+ line.forEach(function (measure) {
22812
+ if (measure.ending) hasEnding = true;
22813
+ if (measure.annotations && measure.annotations.length > 0) hasAnnotation = true;
22814
+ });
22815
+ var extraTop = hasAnnotation ? ANNOTATION_HEIGHT : hasEnding ? ENDING_HEIGHT : 0;
22816
+ line.forEach(function (measure, barNum) {
22817
+ var RECT_WIDTH = 1;
22818
+ if (!measure.noBorder) {
22819
+ renderer.paper.rect({
22820
+ x: leftMargin + barNum * colWidth,
22821
+ y: renderer.y,
22822
+ width: colWidth,
22823
+ height: extraTop + ROW_HEIGHT
22824
+ });
22825
+ renderer.paper.rect({
22826
+ x: leftMargin + barNum * colWidth + RECT_WIDTH,
22827
+ y: renderer.y + RECT_WIDTH,
22828
+ width: colWidth - RECT_WIDTH * 2,
22829
+ height: extraTop + ROW_HEIGHT - RECT_WIDTH * 2
22830
+ });
22831
+ var repeatLeft = 0;
22832
+ var repeatRight = 0;
22833
+ var top = renderer.y;
22834
+ var left = leftMargin + colWidth * barNum;
22835
+ if (measure.hasStartRepeat) {
22836
+ drawRepeat(renderer, left, top, top + ROW_HEIGHT + extraTop, true, extraTop);
22837
+ repeatLeft = 12;
22838
+ }
22839
+ if (measure.hasEndRepeat) {
22840
+ drawRepeat(renderer, left + colWidth, top, top + ROW_HEIGHT + extraTop, false, extraTop);
22841
+ repeatRight = 12;
22842
+ }
22843
+ var endingWidth = 0;
22844
+ if (measure.ending) {
22845
+ var endingEl = text(renderer, measure.ending, leftMargin + barNum * colWidth + 4, top + 10, 12, endingFont, null, null, false);
22846
+ endingWidth = endingEl.getBBox().width + 4;
22847
+ }
22848
+ drawMeasure(renderer, top, leftMargin + repeatLeft, colWidth, lineNum, barNum, measure.chord, chordFont, repeatLeft + repeatRight, ROW_HEIGHT, extraTop);
22849
+ if (measure.annotations && measure.annotations.length > 0) {
22850
+ drawAnnotations(renderer, top, leftMargin + barNum * colWidth + endingWidth, measure.annotations, annotationFont);
22851
+ }
22852
+ if (extraTop) {
22853
+ renderer.paper.rectBeneath({
22854
+ x: leftMargin + barNum * colWidth,
22855
+ y: renderer.y,
22856
+ width: colWidth,
22857
+ height: extraTop,
22858
+ fill: '#e8e8e8',
22859
+ stroke: 'none'
22860
+ });
22861
+ }
22862
+ }
22863
+ });
22864
+ renderer.moveY(extraTop + ROW_HEIGHT);
22865
+ });
22866
+ renderer.moveY(PART_MARGIN_BOTTOM);
22867
+ }
22868
+ break;
22869
+ }
22870
+ });
22871
+ }
22872
+ function drawPercent(renderer, x, y, offset) {
22873
+ var lineX1 = x - 10;
22874
+ var lineX2 = x + 10;
22875
+ var lineY1 = y + 10;
22876
+ var lineY2 = y - 10;
22877
+ var leftDotX = x - 10;
22878
+ var leftDotY = -renderer.yToPitch(offset) + 2;
22879
+ var rightDotX = x + 6.5;
22880
+ var rightDotY = -renderer.yToPitch(offset) - 2.3;
22881
+ renderer.paper.lineToBack({
22882
+ x1: lineX1,
22883
+ x2: lineX2,
22884
+ y1: lineY1,
22885
+ y2: lineY2,
22886
+ 'stroke-width': '3px',
22887
+ 'stroke-linecap': "round"
22888
+ });
22889
+ printSymbol(renderer, leftDotX, leftDotY, "dots.dot", {
22890
+ scalex: 1,
22891
+ scaley: 1,
22892
+ klass: "",
22893
+ name: "dot"
22894
+ });
22895
+ printSymbol(renderer, rightDotX, rightDotY, "dots.dot", {
22896
+ scalex: 1,
22897
+ scaley: 1,
22898
+ klass: "",
22899
+ name: "dot"
22900
+ });
22901
+ }
22902
+ function drawRepeat(renderer, x, y1, y2, isStart, offset) {
22903
+ var lineX = isStart ? x + 2 : x - 4;
22904
+ var circleX = isStart ? x + 9 : x - 11;
22905
+ renderer.paper.openGroup({
22906
+ klass: 'abcjs-repeat'
22907
+ });
22908
+ printStem(renderer, lineX, 3 + renderer.lineThickness, y1, y2, null, "bar");
22909
+ printSymbol(renderer, circleX, -renderer.yToPitch(offset) - 4, "dots.dot", {
22910
+ scalex: 1,
22911
+ scaley: 1,
22912
+ klass: "",
22913
+ name: "dot"
22914
+ });
22915
+ printSymbol(renderer, circleX, -renderer.yToPitch(offset) - 8, "dots.dot", {
22916
+ scalex: 1,
22917
+ scaley: 1,
22918
+ klass: "",
22919
+ name: "dot"
22920
+ });
22921
+ renderer.paper.closeGroup();
22922
+ }
22923
+ var symbols = {
22924
+ 'segno': "scripts.segno",
22925
+ 'coda': "scripts.coda",
22926
+ "fermata": "scripts.ufermata"
22927
+ };
22928
+ function drawAnnotations(renderer, offset, left, annotations, annotationFont) {
22929
+ left += 3;
22930
+ var el;
22931
+ for (var a = 0; a < annotations.length; a++) {
22932
+ switch (annotations[a]) {
22933
+ case 'segno':
22934
+ case 'coda':
22935
+ case "fermata":
22936
+ {
22937
+ left += 12;
22938
+ el = printSymbol(renderer, left, -3, symbols[annotations[a]], {
22939
+ scalex: 1,
22940
+ scaley: 1,
22941
+ //klass: renderer.controller.classes.generate(klass),
22942
+ name: symbols[annotations[a]]
22943
+ });
22944
+ var box = el.getBBox();
22945
+ left += box.width;
22946
+ }
22947
+ break;
22948
+ default:
22949
+ text(renderer, annotations[a], left, offset + 12, 12, annotationFont, null, null, false);
22950
+ }
22951
+ }
22952
+ }
22953
+ function drawMeasure(renderer, offset, leftMargin, colWidth, lineNum, barNum, chords, chordFont, margin, height, extraTop) {
22954
+ var left = leftMargin + colWidth * barNum;
22955
+ if (!chords[1] && !chords[2] && !chords[3]) drawSingleChord(renderer, left, offset + extraTop, colWidth - margin, height, chords[0], chordFont, extraTop);else if (!chords[1] && !chords[3]) drawTwoChords(renderer, left, offset, colWidth - margin, height, chords[0], chords[2], chordFont, extraTop);else drawFourChords(renderer, left, offset, colWidth - margin, height, chords, chordFont, extraTop);
22956
+ }
22957
+ function renderChord(renderer, x, y, size, chord, font, maxWidth) {
22958
+ var el = text(renderer, chord, x, y, size, font, null, "abcjs-chord", true);
22959
+ var bb = el.getBBox();
22960
+ var fontSize = size;
22961
+ while (bb.width > maxWidth && fontSize >= 14) {
22962
+ fontSize -= 2;
22963
+ el.setAttribute('font-size', fontSize);
22964
+ bb = el.getBBox();
22965
+ }
22966
+ }
22967
+ var MAX_ONE_CHORD = 34;
22968
+ var MAX_TWO_CHORDS = 26;
22969
+ var MAX_FOUR_CHORDS = 20;
22970
+ var TOP_MARGIN = -3;
22971
+ function drawSingleChord(renderer, left, top, width, height, chord, font, extraTop) {
22972
+ if (chord === '%') drawPercent(renderer, left + width / 2, top + height / 2, extraTop + height / 2);else renderChord(renderer, left + width / 2, top + height / 2 + TOP_MARGIN, MAX_ONE_CHORD, chord, font, width);
22973
+ }
22974
+ function drawTwoChords(renderer, left, top, width, height, chord1, chord2, font, extraTop) {
22975
+ renderer.paper.lineToBack({
22976
+ x1: left,
22977
+ x2: left + width,
22978
+ y1: top + height + extraTop,
22979
+ y2: top + 2
22980
+ });
22981
+ renderChord(renderer, left + width / 4, top + height / 4 + 5 + extraTop + TOP_MARGIN, MAX_TWO_CHORDS, chord1, font, width / 2);
22982
+ renderChord(renderer, left + 3 * width / 4, top + 3 * height / 4 + extraTop + TOP_MARGIN, MAX_TWO_CHORDS, chord2, font, width / 2);
22983
+ }
22984
+ function drawFourChords(renderer, left, top, width, height, chords, font, extraTop) {
22985
+ var MARGIN = 3;
22986
+ renderer.paper.lineToBack({
22987
+ x1: left + MARGIN,
22988
+ x2: left + width - MARGIN,
22989
+ y1: top + height / 2 + extraTop,
22990
+ y2: top + height / 2 + extraTop
22991
+ });
22992
+ renderer.paper.lineToBack({
22993
+ x1: left + width / 2,
22994
+ x2: left + width / 2,
22995
+ y1: top + MARGIN + extraTop,
22996
+ y2: top + height - MARGIN + extraTop
22997
+ });
22998
+ if (chords[0]) renderChord(renderer, left + width / 4, top + height / 4 + 2 + extraTop + TOP_MARGIN, MAX_FOUR_CHORDS, shortenChord(chords[0]), font, width / 2);
22999
+ if (chords[1]) renderChord(renderer, left + 3 * width / 4, top + height / 4 + 2 + extraTop + TOP_MARGIN, MAX_FOUR_CHORDS, shortenChord(chords[1]), font, width / 2);
23000
+ if (chords[2]) renderChord(renderer, left + width / 4, top + 3 * height / 4 + extraTop + TOP_MARGIN, MAX_FOUR_CHORDS, shortenChord(chords[2]), font, width / 2);
23001
+ if (chords[3]) renderChord(renderer, left + 3 * width / 4, top + 3 * height / 4 + extraTop + TOP_MARGIN, MAX_FOUR_CHORDS, shortenChord(chords[3]), font, width / 2);
23002
+ }
23003
+ function shortenChord(chord) {
23004
+ if (chord === "No Chord") return "N.C.";
23005
+ return chord;
23006
+ }
23007
+ function text(renderer, str, x, y, size, font, dataName, klass, alignCenter) {
23008
+ var attr = {
23009
+ x: x,
23010
+ y: y,
23011
+ stroke: "none",
23012
+ 'font-size': size,
23013
+ 'font-style': font.style,
23014
+ 'font-family': font.face,
23015
+ 'font-weight': font.weight,
23016
+ 'text-decoration': font.decoration
23017
+ };
23018
+ if (dataName) attr['data-name'] = dataName;
23019
+ if (klass) attr['class'] = klass;
23020
+ attr["text-anchor"] = alignCenter ? "middle" : "start";
23021
+ return renderer.paper.text(str, attr, null, {
23022
+ "alignment-baseline": "middle"
23023
+ });
23024
+ }
23025
+ module.exports = drawChordGrid;
23026
+
23027
+ /***/ }),
23028
+
22058
23029
  /***/ "./src/write/draw/crescendo.js":
22059
23030
  /*!*************************************!*\
22060
23031
  !*** ./src/write/draw/crescendo.js ***!
@@ -22138,7 +23109,8 @@ var setPaperSize = __webpack_require__(/*! ./set-paper-size */ "./src/write/draw
22138
23109
  var nonMusic = __webpack_require__(/*! ./non-music */ "./src/write/draw/non-music.js");
22139
23110
  var spacing = __webpack_require__(/*! ../helpers/spacing */ "./src/write/helpers/spacing.js");
22140
23111
  var Selectables = __webpack_require__(/*! ./selectables */ "./src/write/draw/selectables.js");
22141
- function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, selectTypes, tuneNumber, lineOffset) {
23112
+ var drawChordGrid = __webpack_require__(/*! ./chord-grid */ "./src/write/draw/chord-grid.js");
23113
+ function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, selectTypes, tuneNumber, lineOffset, chordGrid) {
22142
23114
  var selectables = new Selectables(renderer.paper, selectTypes, tuneNumber);
22143
23115
  var groupClasses = {};
22144
23116
  if (classes.shouldAddClasses) groupClasses.klass = "abcjs-meta-top";
@@ -22147,43 +23119,52 @@ function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, se
22147
23119
  nonMusic(renderer, abcTune.topText, selectables);
22148
23120
  renderer.paper.closeGroup();
22149
23121
  renderer.moveY(renderer.spacing.music);
23122
+ var suppressMusic = false;
23123
+ if (chordGrid && abcTune.chordGrid) {
23124
+ drawChordGrid(renderer, abcTune.chordGrid, renderer.padding.left, width, abcTune.formatting);
23125
+ if (chordGrid === 'noMusic') suppressMusic = true;
23126
+ }
22150
23127
  var staffgroups = [];
22151
23128
  var nStaves = 0;
22152
- for (var line = 0; line < abcTune.lines.length; line++) {
22153
- classes.incrLine();
22154
- var abcLine = abcTune.lines[line];
22155
- if (abcLine.staff) {
22156
- // MAE 26 May 2025 - for incipits staff count limiting
22157
- nStaves++;
22158
- if (abcTune.formatting.maxStaves) {
22159
- if (nStaves > abcTune.formatting.maxStaves) {
22160
- break;
23129
+ if (!suppressMusic) {
23130
+ for (var line = 0; line < abcTune.lines.length; line++) {
23131
+ classes.incrLine();
23132
+ var abcLine = abcTune.lines[line];
23133
+ if (abcLine.staff) {
23134
+ // MAE 26 May 2025 - for incipits staff count limiting
23135
+ nStaves++;
23136
+ if (abcTune.formatting.maxStaves) {
23137
+ if (nStaves > abcTune.formatting.maxStaves) {
23138
+ break;
23139
+ }
22161
23140
  }
23141
+ if (classes.shouldAddClasses) groupClasses.klass = "abcjs-staff-wrapper abcjs-l" + classes.lineNumber;
23142
+ renderer.paper.openGroup(groupClasses);
23143
+ if (abcLine.vskip) {
23144
+ renderer.moveY(abcLine.vskip);
23145
+ }
23146
+ if (staffgroups.length >= 1) addStaffPadding(renderer, renderer.spacing.staffSeparation, staffgroups[staffgroups.length - 1], abcLine.staffGroup);
23147
+ var staffgroup = engraveStaffLine(renderer, abcLine.staffGroup, selectables, line);
23148
+ staffgroup.line = lineOffset + line; // If there are non-music lines then the staffgroup array won't line up with the line array, so this keeps track.
23149
+ staffgroups.push(staffgroup);
23150
+ renderer.paper.closeGroup();
23151
+ } else if (abcLine.nonMusic) {
23152
+ if (classes.shouldAddClasses) groupClasses.klass = "abcjs-non-music";
23153
+ renderer.paper.openGroup(groupClasses);
23154
+ nonMusic(renderer, abcLine.nonMusic, selectables);
23155
+ renderer.paper.closeGroup();
22162
23156
  }
22163
- if (classes.shouldAddClasses) groupClasses.klass = "abcjs-staff-wrapper abcjs-l" + classes.lineNumber;
22164
- renderer.paper.openGroup(groupClasses);
22165
- if (abcLine.vskip) {
22166
- renderer.moveY(abcLine.vskip);
22167
- }
22168
- if (staffgroups.length >= 1) addStaffPadding(renderer, renderer.spacing.staffSeparation, staffgroups[staffgroups.length - 1], abcLine.staffGroup);
22169
- var staffgroup = engraveStaffLine(renderer, abcLine.staffGroup, selectables, line);
22170
- staffgroup.line = lineOffset + line; // If there are non-music lines then the staffgroup array won't line up with the line array, so this keeps track.
22171
- staffgroups.push(staffgroup);
22172
- renderer.paper.closeGroup();
22173
- } else if (abcLine.nonMusic) {
22174
- if (classes.shouldAddClasses) groupClasses.klass = "abcjs-non-music";
22175
- renderer.paper.openGroup(groupClasses);
22176
- nonMusic(renderer, abcLine.nonMusic, selectables);
22177
- renderer.paper.closeGroup();
22178
23157
  }
22179
23158
  }
22180
23159
  classes.reset();
22181
- if (abcTune.bottomText && abcTune.bottomText.rows && abcTune.bottomText.rows.length > 0) {
22182
- if (classes.shouldAddClasses) groupClasses.klass = "abcjs-meta-bottom";
22183
- renderer.paper.openGroup(groupClasses);
22184
- renderer.moveY(24); // TODO-PER: Empirically discovered. What variable should this be?
22185
- nonMusic(renderer, abcTune.bottomText, selectables);
22186
- renderer.paper.closeGroup();
23160
+ if (!suppressMusic) {
23161
+ if (abcTune.bottomText && abcTune.bottomText.rows && abcTune.bottomText.rows.length > 0) {
23162
+ if (classes.shouldAddClasses) groupClasses.klass = "abcjs-meta-bottom";
23163
+ renderer.paper.openGroup(groupClasses);
23164
+ renderer.moveY(24); // TODO-PER: Empirically discovered. What variable should this be?
23165
+ nonMusic(renderer, abcTune.bottomText, selectables);
23166
+ renderer.paper.closeGroup();
23167
+ }
22187
23168
  }
22188
23169
  setPaperSize(renderer, maxWidth, scale, responsive);
22189
23170
  return {
@@ -23552,7 +24533,7 @@ function renderText(renderer, params, alreadyInGroup) {
23552
24533
 
23553
24534
  // MAE 9 May 2025 for free text blocks
23554
24535
  var text;
23555
- if (params.name == "free-text") {
24536
+ if (params.name === "free-text") {
23556
24537
  text = params.text.replace(/^[ \t]*\n/gm, ' \n');
23557
24538
  } else {
23558
24539
  text = params.text.replace(/\n\n/g, "\n \n");
@@ -23970,6 +24951,7 @@ var EngraverController = function EngraverController(paper, params) {
23970
24951
  if (params.accentAbove) this.accentAbove = params.accentAbove;
23971
24952
  if (params.germanAlphabet) this.germanAlphabet = params.germanAlphabet;
23972
24953
  if (params.lineThickness) this.lineThickness = params.lineThickness;
24954
+ if (params.chordGrid) this.chordGrid = params.chordGrid;
23973
24955
  this.renderer.controller = this; // TODO-GD needed for highlighting
23974
24956
  this.renderer.foregroundColor = params.foregroundColor ? params.foregroundColor : "currentColor";
23975
24957
  if (params.ariaLabel !== undefined) this.renderer.ariaLabel = params.ariaLabel;
@@ -24177,7 +25159,7 @@ EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOf
24177
25159
  }
24178
25160
 
24179
25161
  // Do all the writing to the SVG
24180
- var ret = draw(this.renderer, this.classes, abcTune, this.width, maxWidth, this.responsive, scale, this.selectTypes, tuneNumber, lineOffset);
25162
+ var ret = draw(this.renderer, this.classes, abcTune, this.width, maxWidth, this.responsive, scale, this.selectTypes, tuneNumber, lineOffset, this.chordGrid);
24181
25163
  this.staffgroups = ret.staffgroups;
24182
25164
  this.selectables = ret.selectables;
24183
25165
  if (this.oneSvgPerLine) {
@@ -25229,7 +26211,22 @@ function createAdditionalBeams(elems, asc, beam, isGrace, dy) {
25229
26211
  var auxBeamEndX = x;
25230
26212
  var auxBeamEndY = bary + sy * (j + 1);
25231
26213
  if (auxBeams[j].single) {
25232
- auxBeamEndX = i === 0 ? x + 5 : x - 5;
26214
+ if (i === 0) {
26215
+ // This is the first note in the group, always draw the beam to the right
26216
+ auxBeamEndX = x + 5;
26217
+ } else if (i === elems.length - 1) {
26218
+ // This is the last note in the group, always draw the beam to the left
26219
+ auxBeamEndX = x - 5;
26220
+ } else {
26221
+ // This is a middle note, check the note durations of the notes to the left and right
26222
+ if (elems[i - 1].duration === elems[i + 1].duration) {
26223
+ // The notes on either side are the same duration, alternate which side the beam goes to
26224
+ auxBeamEndX = i % 2 === 0 ? x + 5 : x - 5;
26225
+ } else {
26226
+ // The notes on either side are different durations, draw the beam to the shorter note
26227
+ auxBeamEndX = elems[i - 1].duration > elems[i + 1].duration ? x + 5 : x - 5;
26228
+ }
26229
+ }
25233
26230
  auxBeamEndY = getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, auxBeamEndX) + sy * (j + 1);
25234
26231
  }
25235
26232
  var b = {
@@ -26421,6 +27418,9 @@ Renderer.prototype.setVerticalSpace = function (formatting) {
26421
27418
  Renderer.prototype.calcY = function (ofs) {
26422
27419
  return this.y - ofs * spacing.STEP;
26423
27420
  };
27421
+ Renderer.prototype.yToPitch = function (ofs) {
27422
+ return ofs / spacing.STEP;
27423
+ };
26424
27424
  Renderer.prototype.moveY = function (em, numLines) {
26425
27425
  if (numLines === undefined) numLines = 1;
26426
27426
  this.y += em * numLines;
@@ -26581,7 +27581,7 @@ Svg.prototype.rectBeneath = function (attr) {
26581
27581
  if (attr['fill-opacity']) el.setAttribute("fill-opacity", attr['fill-opacity']);
26582
27582
  this.svg.insertBefore(el, this.svg.firstChild);
26583
27583
  };
26584
- Svg.prototype.text = function (text, attr, target) {
27584
+ Svg.prototype.text = function (text, attr, target, spanAttr) {
26585
27585
  var el = document.createElementNS(svgNS, 'text');
26586
27586
  el.setAttribute("stroke", "none");
26587
27587
  for (var key in attr) {
@@ -26597,6 +27597,13 @@ Svg.prototype.text = function (text, attr, target) {
26597
27597
  continue;
26598
27598
  }
26599
27599
  var line = document.createElementNS(svgNS, 'tspan');
27600
+ if (spanAttr) {
27601
+ for (var skey in spanAttr) {
27602
+ if (spanAttr.hasOwnProperty(skey)) {
27603
+ line.setAttribute(skey, spanAttr[skey]);
27604
+ }
27605
+ }
27606
+ }
26600
27607
  line.setAttribute("x", attr.x ? attr.x : 0);
26601
27608
  if (i !== 0) line.setAttribute("dy", "1.2em");
26602
27609
  if (lines[i].indexOf("\x03") !== -1) {
@@ -26808,7 +27815,7 @@ module.exports = Svg;
26808
27815
  \********************/
26809
27816
  /***/ (function(module) {
26810
27817
 
26811
- var version = '6.5.2';
27818
+ var version = '6.6.0';
26812
27819
  module.exports = version;
26813
27820
 
26814
27821
  /***/ })