abcjs 6.3.0 → 6.4.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.
- package/README.md +4 -0
- package/RELEASE.md +44 -0
- package/dist/abcjs-basic-min.js +2 -2
- package/dist/abcjs-basic.js +1028 -622
- package/dist/abcjs-basic.js.map +1 -1
- package/dist/abcjs-plugin-min.js +2 -2
- package/index.js +1 -0
- package/package.json +1 -1
- package/src/api/tune-metrics.js +18 -0
- package/src/data/abc_tune.js +13 -2
- package/src/edit/abc_editarea.js +4 -1
- package/src/parse/abc_parse.js +2 -0
- package/src/parse/abc_parse_directive.js +6 -0
- package/src/synth/abc_midi_flattener.js +40 -462
- package/src/synth/abc_midi_sequencer.js +25 -10
- package/src/synth/chord-track.js +562 -0
- package/src/synth/create-note-map.js +2 -1
- package/src/synth/create-synth.js +91 -42
- package/src/test/abc_parser_lint.js +1 -0
- package/src/write/creation/abstract-engraver.js +4 -1
- package/src/write/creation/decoration.js +3 -2
- package/src/write/creation/elements/tie-element.js +23 -0
- package/src/write/draw/draw.js +1 -1
- package/src/write/engraver-controller.js +9 -5
- package/src/write/interactive/create-analysis.js +50 -0
- package/src/write/interactive/find-selectable-element.js +24 -0
- package/src/write/interactive/selection.js +5 -45
- package/src/write/layout/layout-in-grid.js +83 -0
- package/src/write/layout/layout.js +29 -24
- package/src/write/layout/set-upper-and-lower-elements.js +2 -0
- package/src/write/layout/staff-group.js +2 -2
- package/src/write/layout/voice-elements.js +1 -1
- package/src/write/layout/voice.js +1 -1
- package/src/write/renderer.js +3 -0
- package/temp.txt +1 -48
- package/types/index.d.ts +96 -32
- package/version.js +1 -1
- package/abc2xml_239/abc2xml.html +0 -769
- package/abc2xml_239/abc2xml.py +0 -2248
- package/abc2xml_239/abc2xml_changelog.html +0 -124
- package/abc2xml_239/lazy-river.abc +0 -26
- package/abc2xml_239/lazy-river.xml +0 -3698
- package/abc2xml_239/mean-to-me.abc +0 -22
- package/abc2xml_239/mean-to-me.xml +0 -2954
- package/abc2xml_239/pyparsing.py +0 -3672
- package/abc2xml_239/pyparsing.pyc +0 -0
package/dist/abcjs-basic.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 "
|
|
11406
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: '
|
|
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: '
|
|
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:
|
|
13020
|
+
el_type: elem.cmd,
|
|
13415
13021
|
volume: elem.params[0]
|
|
13416
13022
|
});
|
|
13417
13023
|
break;
|
|
13418
|
-
case "
|
|
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:
|
|
13421
|
-
|
|
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)
|
|
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.
|
|
13581
|
+
var newBass = !isBoom && p !== 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:
|
|
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
|
-
|
|
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
|
-
|
|
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,14 @@ 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.getMeterFraction();
|
|
14051
14265
|
var allNotes = {};
|
|
14052
14266
|
var cached = [];
|
|
14053
14267
|
var errorNotes = [];
|
|
@@ -14058,14 +14272,15 @@ function CreateSynth() {
|
|
|
14058
14272
|
if (event.pitch !== undefined) {
|
|
14059
14273
|
var pitchNumber = event.pitch;
|
|
14060
14274
|
var noteName = pitchToNoteName[pitchNumber];
|
|
14275
|
+
var inst = event.instrument !== undefined ? instrumentIndexToName[event.instrument] : currentInstrument;
|
|
14061
14276
|
if (noteName) {
|
|
14062
|
-
if (!allNotes[
|
|
14063
|
-
if (!soundsCache[
|
|
14064
|
-
var label2 =
|
|
14277
|
+
if (!allNotes[inst]) allNotes[inst] = {};
|
|
14278
|
+
if (!soundsCache[inst] || !soundsCache[inst][noteName]) allNotes[inst][noteName] = true;else {
|
|
14279
|
+
var label2 = inst + ":" + noteName;
|
|
14065
14280
|
if (cached.indexOf(label2) < 0) cached.push(label2);
|
|
14066
14281
|
}
|
|
14067
14282
|
} else {
|
|
14068
|
-
var label =
|
|
14283
|
+
var label = inst + ":" + noteName;
|
|
14069
14284
|
console.log("Can't find note: ", pitchNumber, label);
|
|
14070
14285
|
if (errorNotes.indexOf(label) < 0) errorNotes.push(label);
|
|
14071
14286
|
}
|
|
@@ -14206,8 +14421,7 @@ function CreateSynth() {
|
|
|
14206
14421
|
// There might be a previous run that needs to be turned off.
|
|
14207
14422
|
self.stop();
|
|
14208
14423
|
var noteMapTracks = createNoteMap(self.flattened);
|
|
14209
|
-
|
|
14210
|
-
// addSwing(noteMapTracks, self.options.swing, self.beatsPerMeasure)
|
|
14424
|
+
if (self.options.swing) addSwing(noteMapTracks, self.options.swing, self.meterFraction, self.pickupLength);
|
|
14211
14425
|
if (self.sequenceCallback) self.sequenceCallback(noteMapTracks, self.callbackContext);
|
|
14212
14426
|
var panDistances = setPan(noteMapTracks.length, self.pan);
|
|
14213
14427
|
|
|
@@ -14420,41 +14634,65 @@ function CreateSynth() {
|
|
|
14420
14634
|
};
|
|
14421
14635
|
}
|
|
14422
14636
|
};
|
|
14423
|
-
|
|
14424
|
-
|
|
14425
|
-
|
|
14426
|
-
|
|
14427
|
-
|
|
14428
|
-
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
|
|
14434
|
-
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
|
|
14448
|
-
|
|
14449
|
-
|
|
14450
|
-
|
|
14451
|
-
|
|
14452
|
-
|
|
14453
|
-
|
|
14454
|
-
|
|
14455
|
-
|
|
14637
|
+
function addSwing(noteMapTracks, swing, meterFraction, pickupLength) {
|
|
14638
|
+
// we can only swing in X/4 and X/8 meters.
|
|
14639
|
+
if (meterFraction.den != 4 && meterFraction.den != 8) return;
|
|
14640
|
+
swing = parseFloat(swing);
|
|
14641
|
+
|
|
14642
|
+
// 50 (or less) is no swing,
|
|
14643
|
+
if (isNaN(swing) || swing <= 50) return;
|
|
14644
|
+
|
|
14645
|
+
// 66 is triplet swing 2:1, and
|
|
14646
|
+
// 60 is swing with a ratio of 3:2.
|
|
14647
|
+
// 75 is the maximum swing where the first eight is played as a dotted eight and the second as a sixteenth.
|
|
14648
|
+
if (swing > 75) swing = 75;
|
|
14649
|
+
|
|
14650
|
+
// convert the swing percentage to a percentage of increase for the first half of the beat
|
|
14651
|
+
swing = swing / 50 - 1;
|
|
14652
|
+
|
|
14653
|
+
// The volume of the swung notes is increased by this factor
|
|
14654
|
+
// could be also in the settings. Try out values such 0.1, 0.2
|
|
14655
|
+
var volumeIncrease = 0.0;
|
|
14656
|
+
|
|
14657
|
+
// the beatLength in X/8 meters
|
|
14658
|
+
var beatLength = 0.25;
|
|
14659
|
+
|
|
14660
|
+
// in X/8 meters the 16s swing so the beatLength is halved
|
|
14661
|
+
if (meterFraction.den === 8) beatLength = beatLength / 2;
|
|
14662
|
+
|
|
14663
|
+
// duration of a half beat
|
|
14664
|
+
var halfbeatLength = beatLength / 2;
|
|
14665
|
+
|
|
14666
|
+
// the extra duration of the first swung notes and the delay of the second notes
|
|
14667
|
+
var swingDuration = halfbeatLength * swing;
|
|
14668
|
+
for (var t = 0; t < noteMapTracks.length; t++) {
|
|
14669
|
+
var track = noteMapTracks[t];
|
|
14670
|
+
for (var i = 0; i < track.length; i++) {
|
|
14671
|
+
var event = track[i];
|
|
14672
|
+
if (
|
|
14673
|
+
// is halfbeat
|
|
14674
|
+
(event.start - pickupLength) % halfbeatLength == 0 && (event.start - pickupLength) % beatLength != 0 && (
|
|
14675
|
+
// the previous note is on the beat or before OR there is no previous note
|
|
14676
|
+
i == 0 || track[i - 1].start <= track[i].start - halfbeatLength) && (
|
|
14677
|
+
// the next note is on the beat or after OR there is no next note
|
|
14678
|
+
i == track.length - 1 || track[i + 1].start >= track[i].start + halfbeatLength)) {
|
|
14679
|
+
var oldEventStart = event.start;
|
|
14680
|
+
event.start += swingDuration;
|
|
14681
|
+
|
|
14682
|
+
// Increase volume of swung notes
|
|
14683
|
+
event.volume *= 1 + volumeIncrease;
|
|
14684
|
+
|
|
14685
|
+
// if there is a previous note ending at the start of this note, extend its end
|
|
14686
|
+
// and decrease its volume
|
|
14687
|
+
if (i > 0 && track[i - 1].end == oldEventStart) {
|
|
14688
|
+
track[i - 1].end = event.start;
|
|
14689
|
+
track[i - 1].volume *= 1 - volumeIncrease;
|
|
14690
|
+
}
|
|
14691
|
+
}
|
|
14692
|
+
}
|
|
14693
|
+
}
|
|
14694
|
+
}
|
|
14456
14695
|
}
|
|
14457
|
-
|
|
14458
14696
|
module.exports = CreateSynth;
|
|
14459
14697
|
|
|
14460
14698
|
/***/ }),
|
|
@@ -17747,7 +17985,10 @@ AbstractEngraver.prototype.createNote = function (elem, nostem, isSingleLineStaf
|
|
|
17747
17985
|
roomtaken += this.addGraceNotes(elem, voice, abselem, notehead, this.stemHeight * this.voiceScale, this.isBagpipes, roomtaken);
|
|
17748
17986
|
}
|
|
17749
17987
|
if (elem.decoration) {
|
|
17750
|
-
this.
|
|
17988
|
+
// TODO-PER: nostem is true if this is beamed. In that case we don't know where to place the decoration yet so just make a guess. This should be refactored to not place decorations until after the beams are determined.
|
|
17989
|
+
// This should probably be combined with moveDecorations()
|
|
17990
|
+
var bottom = nostem ? Math.min(-3, abselem.bottom - 6) : abselem.bottom;
|
|
17991
|
+
this.decoration.createDecoration(voice, elem.decoration, abselem.top, notehead ? notehead.w : 0, abselem, roomtaken, dir, bottom, elem.positioning, this.hasVocals, this.accentAbove);
|
|
17751
17992
|
}
|
|
17752
17993
|
if (elem.barNumber) {
|
|
17753
17994
|
abselem.addFixed(new RelativeElement(elem.barNumber, -10, 0, 0, {
|
|
@@ -18742,7 +18983,8 @@ var stackedDecoration = function stackedDecoration(decoration, width, abselem, y
|
|
|
18742
18983
|
y = placement === 'above' ? y + height / 2 : y - height / 2; // Center the element vertically.
|
|
18743
18984
|
abselem.addFixedX(new RelativeElement(symbol, deltaX, glyphs.getSymbolWidth(symbol), y, {
|
|
18744
18985
|
klass: 'ornament',
|
|
18745
|
-
thickness: glyphs.symbolHeightInPitches(symbol)
|
|
18986
|
+
thickness: glyphs.symbolHeightInPitches(symbol),
|
|
18987
|
+
position: placement
|
|
18746
18988
|
}));
|
|
18747
18989
|
incrementPlacement(placement, height);
|
|
18748
18990
|
}
|
|
@@ -18939,7 +19181,8 @@ Decoration.prototype.createDecoration = function (voice, decoration, pitch, widt
|
|
|
18939
19181
|
// yPos is an object containing 'above' and 'below'. That is the placement of the next symbol on either side.
|
|
18940
19182
|
|
|
18941
19183
|
yPos.above = Math.max(yPos.above, this.minTop);
|
|
18942
|
-
|
|
19184
|
+
yPos.below = Math.min(yPos.below, minPitch);
|
|
19185
|
+
var hasOne = stackedDecoration(decoration, width, abselem, yPos, positioning.ornamentPosition, this.minTop, minPitch, accentAbove);
|
|
18943
19186
|
//if (hasOne) {
|
|
18944
19187
|
// abselem.top = Math.max(yPos.above + 3, abselem.top); // TODO-PER: Not sure why we need this fudge factor.
|
|
18945
19188
|
//}
|
|
@@ -20208,6 +20451,23 @@ TieElem.prototype.calcSlurY = function () {
|
|
|
20208
20451
|
this.endY = midPoint;
|
|
20209
20452
|
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
20453
|
} else this.endY = this.above && beamInterferes ? this.anchor2.highestVert : this.anchor2.pitch;
|
|
20454
|
+
if (this.anchor1.scalex === 1) {
|
|
20455
|
+
// 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.
|
|
20456
|
+
var hasBeam1 = !!this.anchor1.parent.beam;
|
|
20457
|
+
var hasBeam2 = !!this.anchor2.parent.beam;
|
|
20458
|
+
if (hasBeam1) {
|
|
20459
|
+
var isLastInBeam = this.anchor1.parent === this.anchor1.parent.beam.elems[this.anchor1.parent.beam.elems.length - 1];
|
|
20460
|
+
if (!isLastInBeam) {
|
|
20461
|
+
if (this.above) this.startY = this.anchor1.parent.fixed.t;else this.startY = this.anchor1.parent.fixed.b;
|
|
20462
|
+
}
|
|
20463
|
+
}
|
|
20464
|
+
if (hasBeam2) {
|
|
20465
|
+
var isFirstInBeam = this.anchor2.parent === this.anchor2.parent.beam.elems[0];
|
|
20466
|
+
if (!isFirstInBeam) {
|
|
20467
|
+
if (this.above) this.endY = this.anchor2.parent.fixed.t;else this.endY = this.anchor2.parent.fixed.b;
|
|
20468
|
+
}
|
|
20469
|
+
}
|
|
20470
|
+
}
|
|
20211
20471
|
} else if (this.anchor1) {
|
|
20212
20472
|
this.startY = this.endY = this.anchor1.pitch;
|
|
20213
20473
|
} else if (this.anchor2) {
|
|
@@ -21507,7 +21767,7 @@ function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, se
|
|
|
21507
21767
|
classes.incrLine();
|
|
21508
21768
|
var abcLine = abcTune.lines[line];
|
|
21509
21769
|
if (abcLine.staff) {
|
|
21510
|
-
if (classes.shouldAddClasses) groupClasses.klass = "abcjs-staff l" + classes.lineNumber;
|
|
21770
|
+
if (classes.shouldAddClasses) groupClasses.klass = "abcjs-staff-wrapper abcjs-l" + classes.lineNumber;
|
|
21511
21771
|
renderer.paper.openGroup(groupClasses);
|
|
21512
21772
|
if (abcLine.vskip) {
|
|
21513
21773
|
renderer.moveY(abcLine.vskip);
|
|
@@ -23257,6 +23517,7 @@ var GetFontAndAttr = __webpack_require__(/*! ./helpers/get-font-and-attr */ "./s
|
|
|
23257
23517
|
var GetTextSize = __webpack_require__(/*! ./helpers/get-text-size */ "./src/write/helpers/get-text-size.js");
|
|
23258
23518
|
var draw = __webpack_require__(/*! ./draw/draw */ "./src/write/draw/draw.js");
|
|
23259
23519
|
var tablatures = __webpack_require__(/*! ../api/abc_tablatures */ "./src/api/abc_tablatures.js");
|
|
23520
|
+
var findSelectableElement = __webpack_require__(/*! ./interactive/find-selectable-element */ "./src/write/interactive/find-selectable-element.js");
|
|
23260
23521
|
|
|
23261
23522
|
/**
|
|
23262
23523
|
* @class
|
|
@@ -23272,6 +23533,7 @@ var tablatures = __webpack_require__(/*! ../api/abc_tablatures */ "./src/api/abc
|
|
|
23272
23533
|
*/
|
|
23273
23534
|
var EngraverController = function EngraverController(paper, params) {
|
|
23274
23535
|
params = params || {};
|
|
23536
|
+
this.findSelectableElement = findSelectableElement;
|
|
23275
23537
|
this.oneSvgPerLine = params.oneSvgPerLine;
|
|
23276
23538
|
this.selectionColor = params.selectionColor;
|
|
23277
23539
|
this.dragColor = params.dragColor ? params.dragColor : params.selectionColor;
|
|
@@ -23280,6 +23542,7 @@ var EngraverController = function EngraverController(paper, params) {
|
|
|
23280
23542
|
this.responsive = params.responsive;
|
|
23281
23543
|
this.space = 3 * spacing.SPACE;
|
|
23282
23544
|
this.initialClef = params.initialClef;
|
|
23545
|
+
this.timeBasedLayout = params.timeBasedLayout;
|
|
23283
23546
|
this.expandToWidest = !!params.expandToWidest;
|
|
23284
23547
|
this.scale = params.scale ? parseFloat(params.scale) : 0;
|
|
23285
23548
|
this.classes = new Classes({
|
|
@@ -23359,7 +23622,7 @@ EngraverController.prototype.getMeasureWidths = function (abcTune) {
|
|
|
23359
23622
|
this.constructTuneElements(abcTune);
|
|
23360
23623
|
// layout() sets the x-coordinate of the abcTune element here:
|
|
23361
23624
|
// abcTune.lines[0].staffGroup.voices[0].children[0].x
|
|
23362
|
-
layout(this.renderer, abcTune, 0, this.space);
|
|
23625
|
+
layout(this.renderer, abcTune, 0, this.space, this.timeBasedLayout);
|
|
23363
23626
|
var ret = [];
|
|
23364
23627
|
var section;
|
|
23365
23628
|
var needNewSection = true;
|
|
@@ -23414,6 +23677,7 @@ EngraverController.prototype.setupTune = function (abcTune, tuneNumber) {
|
|
|
23414
23677
|
percmap: abcTune.formatting.percmap,
|
|
23415
23678
|
initialClef: this.initialClef,
|
|
23416
23679
|
jazzchords: this.jazzchords,
|
|
23680
|
+
timeBasedLayout: this.timeBasedLayout,
|
|
23417
23681
|
accentAbove: this.accentAbove,
|
|
23418
23682
|
germanAlphabet: this.germanAlphabet
|
|
23419
23683
|
});
|
|
@@ -23472,7 +23736,7 @@ EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOf
|
|
|
23472
23736
|
//Set the top text now that we know the width
|
|
23473
23737
|
|
|
23474
23738
|
// Do all the positioning, both horizontally and vertically
|
|
23475
|
-
var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest);
|
|
23739
|
+
var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest, this.timeBasedLayout);
|
|
23476
23740
|
|
|
23477
23741
|
//Set the top text now that we know the width
|
|
23478
23742
|
if (this.expandToWidest && maxWidth > this.width + 1) {
|
|
@@ -23516,14 +23780,14 @@ EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOf
|
|
|
23516
23780
|
this.selectables = ret.selectables;
|
|
23517
23781
|
if (this.oneSvgPerLine) {
|
|
23518
23782
|
var div = this.renderer.paper.svg.parentNode;
|
|
23519
|
-
this.svgs = splitSvgIntoLines(this.renderer, div, abcTune.metaText.title, this.responsive);
|
|
23783
|
+
this.svgs = splitSvgIntoLines(this.renderer, div, abcTune.metaText.title, this.responsive, scale);
|
|
23520
23784
|
} else {
|
|
23521
23785
|
this.svgs = [this.renderer.paper.svg];
|
|
23522
23786
|
}
|
|
23523
23787
|
setupSelection(this, this.svgs);
|
|
23524
23788
|
this.jazzchords = origJazzChords;
|
|
23525
23789
|
};
|
|
23526
|
-
function splitSvgIntoLines(renderer, output, title, responsive) {
|
|
23790
|
+
function splitSvgIntoLines(renderer, output, title, responsive, scale) {
|
|
23527
23791
|
// Each line is a top level <g> in the svg. To split it into separate
|
|
23528
23792
|
// svgs iterate through each of those and put them in a new svg. Since
|
|
23529
23793
|
// they are placed absolutely, the viewBox needs to be manipulated to
|
|
@@ -23546,7 +23810,7 @@ function splitSvgIntoLines(renderer, output, title, responsive) {
|
|
|
23546
23810
|
var height = box.height + gapBetweenLines;
|
|
23547
23811
|
var wrapper = document.createElement("div");
|
|
23548
23812
|
var divStyles = "overflow: hidden;";
|
|
23549
|
-
if (responsive !== 'resize') divStyles += "height:" + height + "px;";
|
|
23813
|
+
if (responsive !== 'resize') divStyles += "height:" + height * scale + "px;";
|
|
23550
23814
|
wrapper.setAttribute("style", divStyles);
|
|
23551
23815
|
var svg = duplicateSvg(source);
|
|
23552
23816
|
var fullTitle = "Sheet Music for \"" + title + "\" section " + (i + 1);
|
|
@@ -23873,6 +24137,98 @@ module.exports = spacing;
|
|
|
23873
24137
|
|
|
23874
24138
|
/***/ }),
|
|
23875
24139
|
|
|
24140
|
+
/***/ "./src/write/interactive/create-analysis.js":
|
|
24141
|
+
/*!**************************************************!*\
|
|
24142
|
+
!*** ./src/write/interactive/create-analysis.js ***!
|
|
24143
|
+
\**************************************************/
|
|
24144
|
+
/***/ (function(module) {
|
|
24145
|
+
|
|
24146
|
+
function findNumber(klass, match, target, name) {
|
|
24147
|
+
if (klass.indexOf(match) === 0) {
|
|
24148
|
+
var value = klass.replace(match, '');
|
|
24149
|
+
var num = parseInt(value, 10);
|
|
24150
|
+
if ('' + num === value) target[name] = num;
|
|
24151
|
+
}
|
|
24152
|
+
}
|
|
24153
|
+
function createAnalysis(target, ev) {
|
|
24154
|
+
var classes = [];
|
|
24155
|
+
if (target.absEl.elemset) {
|
|
24156
|
+
var classObj = {};
|
|
24157
|
+
for (var j = 0; j < target.absEl.elemset.length; j++) {
|
|
24158
|
+
var es = target.absEl.elemset[j];
|
|
24159
|
+
if (es) {
|
|
24160
|
+
var klass = es.getAttribute("class").split(' ');
|
|
24161
|
+
for (var k = 0; k < klass.length; k++) {
|
|
24162
|
+
classObj[klass[k]] = true;
|
|
24163
|
+
}
|
|
24164
|
+
}
|
|
24165
|
+
}
|
|
24166
|
+
for (var kk = 0; kk < Object.keys(classObj).length; kk++) {
|
|
24167
|
+
classes.push(Object.keys(classObj)[kk]);
|
|
24168
|
+
}
|
|
24169
|
+
}
|
|
24170
|
+
var analysis = {};
|
|
24171
|
+
for (var ii = 0; ii < classes.length; ii++) {
|
|
24172
|
+
findNumber(classes[ii], "abcjs-v", analysis, "voice");
|
|
24173
|
+
findNumber(classes[ii], "abcjs-l", analysis, "line");
|
|
24174
|
+
findNumber(classes[ii], "abcjs-m", analysis, "measure");
|
|
24175
|
+
}
|
|
24176
|
+
if (target.staffPos) analysis.staffPos = target.staffPos;
|
|
24177
|
+
var closest = ev.target;
|
|
24178
|
+
while (closest && closest.dataset && !closest.dataset.name && closest.tagName.toLowerCase() !== 'svg') {
|
|
24179
|
+
closest = closest.parentNode;
|
|
24180
|
+
}
|
|
24181
|
+
var parent = ev.target;
|
|
24182
|
+
while (parent && parent.dataset && !parent.dataset.index && parent.tagName.toLowerCase() !== 'svg') {
|
|
24183
|
+
parent = parent.parentNode;
|
|
24184
|
+
}
|
|
24185
|
+
if (parent && parent.dataset) {
|
|
24186
|
+
analysis.name = parent.dataset.name;
|
|
24187
|
+
analysis.clickedName = closest.dataset.name;
|
|
24188
|
+
analysis.parentClasses = parent.classList;
|
|
24189
|
+
}
|
|
24190
|
+
if (closest && closest.classList) analysis.clickedClasses = closest.classList;
|
|
24191
|
+
analysis.selectableElement = target.svgEl;
|
|
24192
|
+
return {
|
|
24193
|
+
classes: classes,
|
|
24194
|
+
analysis: analysis
|
|
24195
|
+
};
|
|
24196
|
+
}
|
|
24197
|
+
module.exports = createAnalysis;
|
|
24198
|
+
|
|
24199
|
+
/***/ }),
|
|
24200
|
+
|
|
24201
|
+
/***/ "./src/write/interactive/find-selectable-element.js":
|
|
24202
|
+
/*!**********************************************************!*\
|
|
24203
|
+
!*** ./src/write/interactive/find-selectable-element.js ***!
|
|
24204
|
+
\**********************************************************/
|
|
24205
|
+
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
|
|
24206
|
+
|
|
24207
|
+
var createAnalysis = __webpack_require__(/*! ./create-analysis */ "./src/write/interactive/create-analysis.js");
|
|
24208
|
+
function findSelectableElement(event) {
|
|
24209
|
+
var selectable = event;
|
|
24210
|
+
while (selectable && selectable.attributes && selectable.tagName.toLowerCase() !== 'svg' && !selectable.attributes.selectable) {
|
|
24211
|
+
selectable = selectable.parentNode;
|
|
24212
|
+
}
|
|
24213
|
+
if (selectable && selectable.attributes && selectable.attributes.selectable) {
|
|
24214
|
+
var index = selectable.attributes['data-index'].nodeValue;
|
|
24215
|
+
if (index) {
|
|
24216
|
+
index = parseInt(index, 10);
|
|
24217
|
+
if (index >= 0 && index < this.selectables.length) {
|
|
24218
|
+
var element = this.selectables[index];
|
|
24219
|
+
var ret = createAnalysis(element, event);
|
|
24220
|
+
ret.index = index;
|
|
24221
|
+
ret.element = element;
|
|
24222
|
+
return ret;
|
|
24223
|
+
}
|
|
24224
|
+
}
|
|
24225
|
+
}
|
|
24226
|
+
return null;
|
|
24227
|
+
}
|
|
24228
|
+
module.exports = findSelectableElement;
|
|
24229
|
+
|
|
24230
|
+
/***/ }),
|
|
24231
|
+
|
|
23876
24232
|
/***/ "./src/write/interactive/highlight.js":
|
|
23877
24233
|
/*!********************************************!*\
|
|
23878
24234
|
!*** ./src/write/interactive/highlight.js ***!
|
|
@@ -23896,6 +24252,7 @@ module.exports = highlight;
|
|
|
23896
24252
|
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
|
|
23897
24253
|
|
|
23898
24254
|
var spacing = __webpack_require__(/*! ../helpers/spacing */ "./src/write/helpers/spacing.js");
|
|
24255
|
+
var createAnalysis = __webpack_require__(/*! ./create-analysis */ "./src/write/interactive/create-analysis.js");
|
|
23899
24256
|
function setupSelection(engraver, svgs) {
|
|
23900
24257
|
engraver.rangeHighlight = rangeHighlight;
|
|
23901
24258
|
if (engraver.dragging) {
|
|
@@ -24201,44 +24558,9 @@ function setSelection(dragIndex) {
|
|
|
24201
24558
|
}
|
|
24202
24559
|
}
|
|
24203
24560
|
function notifySelect(target, dragStep, dragMax, dragIndex, ev) {
|
|
24204
|
-
var
|
|
24205
|
-
|
|
24206
|
-
|
|
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;
|
|
24561
|
+
var ret = createAnalysis(target, ev);
|
|
24562
|
+
var classes = ret.classes;
|
|
24563
|
+
var analysis = ret.analysis;
|
|
24242
24564
|
for (var i = 0; i < this.listeners.length; i++) {
|
|
24243
24565
|
this.listeners[i](target.absEl.abcelem, target.absEl.tuneNumber, classes.join(' '), analysis, {
|
|
24244
24566
|
step: dragStep,
|
|
@@ -24248,13 +24570,6 @@ function notifySelect(target, dragStep, dragMax, dragIndex, ev) {
|
|
|
24248
24570
|
}, ev);
|
|
24249
24571
|
}
|
|
24250
24572
|
}
|
|
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
24573
|
function clearSelection() {
|
|
24259
24574
|
for (var i = 0; i < this.selected.length; i++) {
|
|
24260
24575
|
this.selected[i].unhighlight(undefined, this.renderer.foregroundColor);
|
|
@@ -24609,6 +24924,94 @@ module.exports = getLeftEdgeOfStaff;
|
|
|
24609
24924
|
|
|
24610
24925
|
/***/ }),
|
|
24611
24926
|
|
|
24927
|
+
/***/ "./src/write/layout/layout-in-grid.js":
|
|
24928
|
+
/*!********************************************!*\
|
|
24929
|
+
!*** ./src/write/layout/layout-in-grid.js ***!
|
|
24930
|
+
\********************************************/
|
|
24931
|
+
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
|
|
24932
|
+
|
|
24933
|
+
var getLeftEdgeOfStaff = __webpack_require__(/*! ./get-left-edge-of-staff */ "./src/write/layout/get-left-edge-of-staff.js");
|
|
24934
|
+
function layoutInGrid(renderer, staffGroup, timeBasedLayout) {
|
|
24935
|
+
var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
|
|
24936
|
+
var ret = getTotalDuration(staffGroup, timeBasedLayout.minPadding);
|
|
24937
|
+
var totalDuration = ret.totalDuration;
|
|
24938
|
+
var minSpacing = ret.minSpacing;
|
|
24939
|
+
var totalWidth = minSpacing * totalDuration;
|
|
24940
|
+
if (timeBasedLayout.minWidth) totalWidth = Math.max(totalWidth, timeBasedLayout.minWidth);
|
|
24941
|
+
var leftAlignPadding = timeBasedLayout.minPadding ? timeBasedLayout.minPadding / 2 : 2; // If the padding isn't specified still give it some
|
|
24942
|
+
|
|
24943
|
+
staffGroup.startx = leftEdge;
|
|
24944
|
+
staffGroup.w = totalWidth + leftEdge;
|
|
24945
|
+
for (var i = 0; i < staffGroup.voices.length; i++) {
|
|
24946
|
+
var voice = staffGroup.voices[i];
|
|
24947
|
+
voice.startx = leftEdge;
|
|
24948
|
+
voice.w = totalWidth + leftEdge;
|
|
24949
|
+
var x = leftEdge;
|
|
24950
|
+
var afterFixedLeft = false;
|
|
24951
|
+
var durationUnit = 0;
|
|
24952
|
+
for (var j = 0; j < voice.children.length; j++) {
|
|
24953
|
+
var child = voice.children[j];
|
|
24954
|
+
if (!afterFixedLeft) {
|
|
24955
|
+
if (child.duration !== 0) {
|
|
24956
|
+
// We got to the first music element on the line
|
|
24957
|
+
afterFixedLeft = true;
|
|
24958
|
+
durationUnit = (totalWidth + leftEdge - x) / totalDuration;
|
|
24959
|
+
staffGroup.gridStart = x;
|
|
24960
|
+
} else {
|
|
24961
|
+
// We are still doing the preliminary stuff - clef, time sig, etc.
|
|
24962
|
+
child.x = x;
|
|
24963
|
+
x += child.w + child.minspacing;
|
|
24964
|
+
}
|
|
24965
|
+
}
|
|
24966
|
+
if (afterFixedLeft) {
|
|
24967
|
+
if (timeBasedLayout.align === 'center') child.x = x + child.duration * durationUnit / 2 - child.w / 2;else {
|
|
24968
|
+
// left align with padding - but no padding for barlines, they should be right aligned.
|
|
24969
|
+
// TODO-PER: it looks better to move bar lines one pixel to right. Not sure why.
|
|
24970
|
+
if (child.duration === 0) {
|
|
24971
|
+
child.x = x + 1 - child.w;
|
|
24972
|
+
} else {
|
|
24973
|
+
// 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.
|
|
24974
|
+
child.x = x + leftAlignPadding - child.extraw;
|
|
24975
|
+
}
|
|
24976
|
+
}
|
|
24977
|
+
x += child.duration * durationUnit;
|
|
24978
|
+
}
|
|
24979
|
+
for (var k = 0; k < child.children.length; k++) {
|
|
24980
|
+
var grandchild = child.children[k];
|
|
24981
|
+
// some elements don't have a dx - Tempo, for instance
|
|
24982
|
+
var dx = grandchild.dx ? grandchild.dx : 0;
|
|
24983
|
+
grandchild.x = child.x + dx;
|
|
24984
|
+
}
|
|
24985
|
+
}
|
|
24986
|
+
staffGroup.gridEnd = x;
|
|
24987
|
+
}
|
|
24988
|
+
return totalWidth;
|
|
24989
|
+
}
|
|
24990
|
+
function getTotalDuration(staffGroup, timeBasedLayout) {
|
|
24991
|
+
var maxSpacing = 0;
|
|
24992
|
+
var maxCount = 0;
|
|
24993
|
+
for (var i = 0; i < staffGroup.voices.length; i++) {
|
|
24994
|
+
var count = 0;
|
|
24995
|
+
var voice = staffGroup.voices[i];
|
|
24996
|
+
for (var j = 0; j < voice.children.length; j++) {
|
|
24997
|
+
var element = voice.children[j];
|
|
24998
|
+
count += element.duration;
|
|
24999
|
+
if (element.duration) {
|
|
25000
|
+
var width = (element.w + timeBasedLayout) / element.duration;
|
|
25001
|
+
maxSpacing = Math.max(maxSpacing, width);
|
|
25002
|
+
}
|
|
25003
|
+
}
|
|
25004
|
+
maxCount = Math.max(maxCount, count);
|
|
25005
|
+
}
|
|
25006
|
+
return {
|
|
25007
|
+
totalDuration: maxCount,
|
|
25008
|
+
minSpacing: maxSpacing
|
|
25009
|
+
};
|
|
25010
|
+
}
|
|
25011
|
+
module.exports = layoutInGrid;
|
|
25012
|
+
|
|
25013
|
+
/***/ }),
|
|
25014
|
+
|
|
24612
25015
|
/***/ "./src/write/layout/layout.js":
|
|
24613
25016
|
/*!************************************!*\
|
|
24614
25017
|
!*** ./src/write/layout/layout.js ***!
|
|
@@ -24619,7 +25022,12 @@ var layoutVoice = __webpack_require__(/*! ./voice */ "./src/write/layout/voice.j
|
|
|
24619
25022
|
var setUpperAndLowerElements = __webpack_require__(/*! ./set-upper-and-lower-elements */ "./src/write/layout/set-upper-and-lower-elements.js");
|
|
24620
25023
|
var layoutStaffGroup = __webpack_require__(/*! ./staff-group */ "./src/write/layout/staff-group.js");
|
|
24621
25024
|
var getLeftEdgeOfStaff = __webpack_require__(/*! ./get-left-edge-of-staff */ "./src/write/layout/get-left-edge-of-staff.js");
|
|
24622
|
-
var
|
|
25025
|
+
var layoutInGrid = __webpack_require__(/*! ./layout-in-grid */ "./src/write/layout/layout-in-grid.js");
|
|
25026
|
+
|
|
25027
|
+
// This sets the "x" attribute on all the children in abctune.lines
|
|
25028
|
+
// It also sets the "w" and "startx" attributes on "voices"
|
|
25029
|
+
// It also sets the "w" and "startx" attributes on "voices.children"
|
|
25030
|
+
var layout = function layout(renderer, abctune, width, space, expandToWidest, timeBasedLayout) {
|
|
24623
25031
|
var i;
|
|
24624
25032
|
var abcLine;
|
|
24625
25033
|
// Adjust the x-coordinates to their absolute positions
|
|
@@ -24628,7 +25036,8 @@ var layout = function layout(renderer, abctune, width, space, expandToWidest) {
|
|
|
24628
25036
|
abcLine = abctune.lines[i];
|
|
24629
25037
|
if (abcLine.staff) {
|
|
24630
25038
|
// console.log("=== line", i)
|
|
24631
|
-
var thisWidth
|
|
25039
|
+
var thisWidth;
|
|
25040
|
+
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
25041
|
// console.log(thisWidth, maxWidth)
|
|
24633
25042
|
if (Math.round(thisWidth) > Math.round(maxWidth)) {
|
|
24634
25043
|
// to take care of floating point weirdness
|
|
@@ -24663,40 +25072,34 @@ var layout = function layout(renderer, abctune, width, space, expandToWidest) {
|
|
|
24663
25072
|
var setXSpacing = function setXSpacing(renderer, width, space, staffGroup, formatting, isLastLine, debug) {
|
|
24664
25073
|
var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
|
|
24665
25074
|
var newspace = space;
|
|
25075
|
+
//dumpGroup("before", staffGroup)
|
|
24666
25076
|
for (var it = 0; it < 8; it++) {
|
|
24667
25077
|
// 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
25078
|
// console.log("iteration", it)
|
|
24669
|
-
|
|
24670
|
-
var ret = layoutStaffGroup(newspace, renderer, debug, staffGroup, leftEdge);
|
|
24671
|
-
// dumpGroup("after",staffGroup)
|
|
25079
|
+
var ret = layoutStaffGroup(newspace, renderer.minPadding, debug, staffGroup, leftEdge);
|
|
24672
25080
|
newspace = calcHorizontalSpacing(isLastLine, formatting.stretchlast, width + renderer.padding.left, staffGroup.w, newspace, ret.spacingUnits, ret.minSpace, renderer.padding.left + renderer.padding.right);
|
|
24673
25081
|
if (debug) console.log("setXSpace", it, staffGroup.w, newspace, staffGroup.minspace);
|
|
24674
25082
|
if (newspace === null) break;
|
|
24675
25083
|
}
|
|
25084
|
+
//dumpGroup("after",staffGroup)
|
|
24676
25085
|
centerWholeRests(staffGroup.voices);
|
|
24677
25086
|
return staffGroup.w - leftEdge;
|
|
24678
25087
|
};
|
|
24679
|
-
|
|
24680
|
-
//
|
|
24681
|
-
|
|
24682
|
-
|
|
24683
|
-
|
|
24684
|
-
|
|
24685
|
-
|
|
24686
|
-
|
|
24687
|
-
|
|
24688
|
-
|
|
24689
|
-
|
|
24690
|
-
|
|
24691
|
-
|
|
24692
|
-
|
|
24693
|
-
|
|
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
|
-
|
|
25088
|
+
function replacer(key, value) {
|
|
25089
|
+
// Filtering out properties
|
|
25090
|
+
if (key === 'parent') {
|
|
25091
|
+
return 'parent';
|
|
25092
|
+
}
|
|
25093
|
+
if (key === 'beam') {
|
|
25094
|
+
return 'beam';
|
|
25095
|
+
}
|
|
25096
|
+
return value;
|
|
25097
|
+
}
|
|
25098
|
+
function dumpGroup(label, staffGroup) {
|
|
25099
|
+
console.log("=================== " + label + " =========================");
|
|
25100
|
+
console.log(staffGroup);
|
|
25101
|
+
console.log(JSON.stringify(staffGroup, replacer, "\t"));
|
|
25102
|
+
}
|
|
24700
25103
|
function calcHorizontalSpacing(isLastLine, stretchLast, targetWidth, lineWidth, spacing, spacingUnits, minSpace, padding) {
|
|
24701
25104
|
if (isLastLine) {
|
|
24702
25105
|
if (stretchLast === undefined) {
|
|
@@ -24827,6 +25230,7 @@ var setUpperAndLowerElements = function setUpperAndLowerElements(renderer, staff
|
|
|
24827
25230
|
var addedSpace = minSpacingInPitches - forcedSpacingBetween;
|
|
24828
25231
|
if (addedSpace > 0) staff.top += addedSpace;
|
|
24829
25232
|
}
|
|
25233
|
+
staff.top += renderer.spacing.staffTopMargin / spacing.STEP;
|
|
24830
25234
|
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
25235
|
|
|
24832
25236
|
// Now we need a little margin on the top, so we'll just throw that in.
|
|
@@ -24985,7 +25389,7 @@ function checkLastBarX(voices) {
|
|
|
24985
25389
|
}
|
|
24986
25390
|
}
|
|
24987
25391
|
}
|
|
24988
|
-
var layoutStaffGroup = function layoutStaffGroup(spacing,
|
|
25392
|
+
var layoutStaffGroup = function layoutStaffGroup(spacing, minPadding, debug, staffGroup, leftEdge) {
|
|
24989
25393
|
var epsilon = 0.0000001; // Fudging for inexactness of floating point math.
|
|
24990
25394
|
var spacingunits = 0; // number of times we will have ended up using the spacing distance (as opposed to fixed width distances)
|
|
24991
25395
|
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 +25445,7 @@ var layoutStaffGroup = function layoutStaffGroup(spacing, renderer, debug, staff
|
|
|
25041
25445
|
if (v.voicenumber === 0) lastTopVoice = i;
|
|
25042
25446
|
var topVoice = lastTopVoice !== undefined && currentvoices[lastTopVoice].voicenumber !== v.voicenumber ? currentvoices[lastTopVoice] : undefined;
|
|
25043
25447
|
if (!isSameStaff(v, topVoice)) topVoice = undefined;
|
|
25044
|
-
var voicechildx = layoutVoiceElements.layoutOneItem(x, spacing, v,
|
|
25448
|
+
var voicechildx = layoutVoiceElements.layoutOneItem(x, spacing, v, minPadding, topVoice);
|
|
25045
25449
|
var dx = voicechildx - x;
|
|
25046
25450
|
if (dx > 0) {
|
|
25047
25451
|
x = voicechildx; //update x
|
|
@@ -25267,7 +25671,7 @@ VoiceElement.shiftRight = function (dx, voice) {
|
|
|
25267
25671
|
|
|
25268
25672
|
// call when spacingduration has been updated
|
|
25269
25673
|
VoiceElement.updateNextX = function (x, spacing, voice) {
|
|
25270
|
-
voice.nextx = x + spacing *
|
|
25674
|
+
voice.nextx = x + spacing * this.getSpacingUnits(voice);
|
|
25271
25675
|
};
|
|
25272
25676
|
VoiceElement.updateIndices = function (voice) {
|
|
25273
25677
|
if (!this.layoutEnded(voice)) {
|
|
@@ -25332,7 +25736,7 @@ function moveDecorations(beam) {
|
|
|
25332
25736
|
var top = yAtNote(child, beam);
|
|
25333
25737
|
for (var i = 0; i < child.children.length; i++) {
|
|
25334
25738
|
var el = child.children[i];
|
|
25335
|
-
if (el.klass === 'ornament') {
|
|
25739
|
+
if (el.klass === 'ornament' && el.position !== 'below') {
|
|
25336
25740
|
if (el.bottom - padding < top) {
|
|
25337
25741
|
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
25742
|
el.bottom += distance;
|
|
@@ -25536,6 +25940,7 @@ Renderer.prototype.initVerticalSpace = function () {
|
|
|
25536
25940
|
// Set the slur height factor.
|
|
25537
25941
|
staffSeparation: 61.33,
|
|
25538
25942
|
// Do not put a staff system closer than <unit> from the previous system.
|
|
25943
|
+
staffTopMargin: 0,
|
|
25539
25944
|
stemHeight: 26.67 + 10,
|
|
25540
25945
|
// Set the stem height.
|
|
25541
25946
|
subtitle: 3.78,
|
|
@@ -25587,6 +25992,7 @@ Renderer.prototype.setVerticalSpace = function (formatting) {
|
|
|
25587
25992
|
if (formatting.musicspace !== undefined) this.spacing.music = formatting.musicspace * 4 / 3;
|
|
25588
25993
|
if (formatting.titlespace !== undefined) this.spacing.title = formatting.titlespace * 4 / 3;
|
|
25589
25994
|
if (formatting.sysstaffsep !== undefined) this.spacing.systemStaffSeparation = formatting.sysstaffsep * 4 / 3;
|
|
25995
|
+
if (formatting.stafftopmargin !== undefined) this.spacing.staffTopMargin = formatting.stafftopmargin * 4 / 3;
|
|
25590
25996
|
if (formatting.subtitlespace !== undefined) this.spacing.subtitle = formatting.subtitlespace * 4 / 3;
|
|
25591
25997
|
if (formatting.topspace !== undefined) this.spacing.top = formatting.topspace * 4 / 3;
|
|
25592
25998
|
if (formatting.vocalspace !== undefined) this.spacing.vocal = formatting.vocalspace * 4 / 3;
|
|
@@ -25970,7 +26376,7 @@ module.exports = Svg;
|
|
|
25970
26376
|
\********************/
|
|
25971
26377
|
/***/ (function(module) {
|
|
25972
26378
|
|
|
25973
|
-
var version = '6.
|
|
26379
|
+
var version = '6.4.0';
|
|
25974
26380
|
module.exports = version;
|
|
25975
26381
|
|
|
25976
26382
|
/***/ })
|