satie
Version:
A sheet music renderer for the web
871 lines (870 loc) • 35.7 kB
JavaScript
/**
* This file is part of Satie music engraver <https://github.com/jnetterf/satie>.
* Copyright (C) Joshua Netterfield <joshua.ca> 2015 - present.
*
* Satie is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Satie is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Satie. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
var musicxml_interfaces_1 = require("musicxml-interfaces");
var builders_1 = require("musicxml-interfaces/builders");
var lodash_1 = require("lodash");
var invariant = require("invariant");
var document_1 = require("./document");
var private_chordUtil_1 = require("./private_chordUtil");
var private_metre_checkBeaming_1 = require("./private_metre_checkBeaming");
var private_metre_modifyRest_1 = require("./private_metre_modifyRest");
var private_util_1 = require("./private_util");
function _prependPatch() {
var prefix = [];
for (var _i = 0; _i < arguments.length; _i++) {
prefix[_i] = arguments[_i];
}
return function __prependPatch(patch) {
patch.p = prefix.concat(patch.p);
return patch;
};
}
function genUUID() {
var MAX_SAFE_INTEGER = 9007199254740991;
return Math.floor(Math.random() * MAX_SAFE_INTEGER);
}
function moreImportant(type, model, doc) {
switch (type) {
case document_1.Type.Print:
return !doc.modelHasType(model, document_1.Type.VisualCursor);
case document_1.Type.Grouping:
return !doc.modelHasType(model, document_1.Type.Print, document_1.Type.VisualCursor);
case document_1.Type.FiguredBass:
return !doc.modelHasType(model, document_1.Type.Print, document_1.Type.Grouping, document_1.Type.VisualCursor);
case document_1.Type.Attributes:
return !doc.modelHasType(model, document_1.Type.Print, document_1.Type.Grouping, document_1.Type.FiguredBass, document_1.Type.VisualCursor);
case document_1.Type.Sound:
return !doc.modelHasType(model, document_1.Type.Print, document_1.Type.Grouping, document_1.Type.FiguredBass, document_1.Type.Attributes, document_1.Type.VisualCursor);
case document_1.Type.Direction:
return !doc.modelHasType(model, document_1.Type.Print, document_1.Type.Grouping, document_1.Type.FiguredBass, document_1.Type.Attributes, document_1.Type.Sound, document_1.Type.VisualCursor);
case document_1.Type.Harmony:
return false;
case document_1.Type.Proxy:
return false;
case document_1.Type.Spacer:
return false;
case document_1.Type.Chord:
case document_1.Type.VisualCursor:
return true;
}
}
var StaffBuilder = (function () {
function StaffBuilder(segment, document, idx) {
this._patches = [];
this._segment = segment;
this._document = document;
this._idx = idx;
}
Object.defineProperty(StaffBuilder.prototype, "patches", {
get: function () {
return this._patches.slice();
},
enumerable: true,
configurable: true
});
StaffBuilder.prototype.at = function (idx) {
this._idx = idx;
return this;
};
StaffBuilder.prototype.next = function () {
++this._idx;
return this;
};
StaffBuilder.prototype.atDiv = function (div, type) {
var currDiv = 0;
for (var i = 0; i < this._segment.length; ++i) {
if (div < currDiv + this._segment[i].divCount ||
div === currDiv + this._segment[i].divCount &&
moreImportant(type, this._segment[i], this._document)) {
var start = currDiv;
var end = currDiv + this._segment[i].divCount;
if (div === start && moreImportant(type, this._segment[i], this._document)) {
return this.at(i);
}
else if (div === end) {
return this.at(i + 1);
}
else {
var s1 = div - start;
var s2 = end - div;
return this
.at(i)
.setDivCount(s1)
.next()
.insertSpacer(s2)
.at(i + 1);
}
}
currDiv += this._segment[i].divCount;
}
var diff = div - currDiv;
if (diff) {
// Note: we should enforce this to not be possible, since the staff segment should
// always be full.
return this
.at(this._segment.length)
.insertSpacer(diff)
.next();
}
return this.at(this._segment.length);
};
StaffBuilder.prototype.setDivCount = function (divCount) {
this._patches = this._patches.concat({
oi: divCount,
od: this._segment[this._idx].divCount,
p: [this._idx, "divCount"],
});
return this;
};
StaffBuilder.prototype.barline = function (builder) {
var model = this._segment[this._idx];
invariant(model, "no such model");
invariant(this._document.modelHasType(model, document_1.Type.Barline), "model is not barline");
this._patches = this._patches.concat(builders_1.patchBarline(model, builder).map(_prependPatch(this._idx)));
return this;
};
StaffBuilder.prototype.insertBarline = function (builder) {
var li = builders_1.buildBarline(builder);
var p = [this._idx];
this._patches = this._patches.concat({ li: li, p: p });
return this;
};
StaffBuilder.prototype.attributes = function (builder) {
var model = this._segment[this._idx];
invariant(model, "no such model");
invariant(this._document.modelHasType(model, document_1.Type.Attributes), "model is not attributes");
this._patches = this._patches.concat(builders_1.patchAttributes(model, builder).map(_prependPatch(this._idx)));
return this;
};
StaffBuilder.prototype.insertAttributes = function (builder) {
var li = builders_1.buildAttributes(builder);
var p = [this._idx];
this._patches = this._patches.concat({ li: li, p: p });
return this;
};
StaffBuilder.prototype.direction = function (builder) {
var model = this._segment[this._idx];
invariant(model, "no such model");
invariant(this._document.modelHasType(model, document_1.Type.Direction), "model is not direction");
this._patches = this._patches.concat(builders_1.patchDirection(model, builder).map(_prependPatch(this._idx)));
return this;
};
StaffBuilder.prototype.insertDirection = function (builder) {
if (typeof builder === "function") {
var li_1 = builders_1.buildDirection(builder);
var p_1 = [this._idx];
this._patches = this._patches.concat({ li: li_1, p: p_1 });
return this;
}
var p = [this._idx];
var li = private_util_1.cloneObject(builder);
li._class = "Direction";
this._patches = this._patches.concat({ li: li, p: p });
return this;
};
StaffBuilder.prototype.print = function (builder) {
var model = this._segment[this._idx];
invariant(model, "no such model");
invariant(this._document.modelHasType(model, document_1.Type.Print), "model is not Print");
this._patches = this._patches.concat(builders_1.patchPrint(model, builder).map(_prependPatch(this._idx)));
return this;
};
StaffBuilder.prototype.insertPrint = function (builder) {
var li = builders_1.buildPrint(builder);
var p = [this._idx];
this._patches = this._patches.concat({ li: li, p: p });
return this;
};
StaffBuilder.prototype.insertSpacer = function (divs) {
this._patches = this._patches.concat({
li: {
_class: "Spacer",
divCount: divs,
},
p: [this._idx],
});
return this;
};
StaffBuilder.prototype.remove = function () {
this._patches = this._patches.concat({
p: [this._idx],
ld: this._segment[this._idx]
});
return this;
};
return StaffBuilder;
}());
exports.StaffBuilder = StaffBuilder;
var VoiceBuilder = (function () {
function VoiceBuilder(segment, document, idx) {
this._patches = [];
this._segment = segment;
this._document = document;
this._idx = idx;
}
Object.defineProperty(VoiceBuilder.prototype, "patches", {
get: function () {
return this._patches.slice();
},
enumerable: true,
configurable: true
});
VoiceBuilder.prototype.at = function (idx) {
this._idx = idx;
return this;
};
VoiceBuilder.prototype.next = function () {
++this._idx;
return this;
};
VoiceBuilder.prototype.addVisualCursor = function () {
this._patches = this._patches.concat({
li: {
_class: "VisualCursor",
},
p: [this._idx],
});
return this;
};
VoiceBuilder.prototype.note = function (noteIDX, builder) {
var model = this._segment[this._idx];
invariant(model, "no such model");
invariant(this._document.modelHasType(model, document_1.Type.Chord), "model is not a chord");
var note = model[noteIDX];
invariant(note, "invalid note");
this._patches = this._patches.concat(builders_1.patchNote(note, builder).map(_prependPatch(this._idx, "notes", noteIDX)));
return this;
};
VoiceBuilder.prototype.insertChord = function (builders) {
invariant(!isNaN(this._idx), "%s must be a number", this._idx);
var li = builders.map(function (builder) { return builders_1.buildNote(builder); });
li._class = "Chord";
invariant(li[0].noteType.duration, "Invalid note type");
var p = [this._idx];
this._patches = this._patches.concat({ li: li, p: p });
return this;
};
VoiceBuilder.prototype.insertNote = function (position, builder) {
var model = this._segment[this._idx];
invariant(model, "no such model");
invariant(this._document.modelHasType(model, document_1.Type.Chord), "model is not a chord");
var li = builders_1.buildNote(builder);
var chord = model;
invariant(chord[position - 1] || chord[position + 1] || !chord.length, "Invalid position for note");
invariant(li.noteType.duration, "Invalid note type");
var p = [this._idx, "notes", position];
this._patches = this._patches.concat({ p: p, li: li });
return this;
};
VoiceBuilder.prototype.remove = function () {
this._patches = this._patches.concat({
p: [this._idx],
ld: this._segment[this._idx]
});
return this;
};
return VoiceBuilder;
}());
exports.VoiceBuilder = VoiceBuilder;
var PartBuilder = (function () {
function PartBuilder(part, document) {
this._patches = [];
this._part = part;
this._document = document;
}
Object.defineProperty(PartBuilder.prototype, "patches", {
get: function () {
return this._patches.slice();
},
enumerable: true,
configurable: true
});
PartBuilder.prototype.voice = function (voiceID, builder, idx) {
var voice = this._part ? this._part.voices[voiceID] : null;
invariant(!this._part || Boolean(voice), "invalid voice");
this._patches = this._patches.concat(builder(new VoiceBuilder(voice, this._document, idx))
.patches
.map(_prependPatch("voices", voiceID)));
return this;
};
PartBuilder.prototype.staff = function (staffID, builder, idx) {
var staff = this._part ? this._part.staves[staffID] : null;
invariant(!this._part || Boolean(staff), "invalid staff");
this._patches = this._patches.concat(builder(new StaffBuilder(staff, this._document, idx))
.patches
.map(_prependPatch("staves", staffID)));
return this;
};
return PartBuilder;
}());
exports.PartBuilder = PartBuilder;
var MeasureBuilder = (function () {
function MeasureBuilder(measure, document) {
this._patches = [];
this._measure = measure;
this._document = document;
}
Object.defineProperty(MeasureBuilder.prototype, "patches", {
get: function () {
return this._patches.slice();
},
enumerable: true,
configurable: true
});
MeasureBuilder.prototype.part = function (partID, builder) {
var part = this._measure ? this._measure.parts[partID] : null;
invariant(!this._measure || Boolean(part), "invalid part id");
this._patches = this._patches.concat(builder(new PartBuilder(part, this._document))
.patches
.map(_prependPatch("parts", partID)));
return this;
};
return MeasureBuilder;
}());
exports.MeasureBuilder = MeasureBuilder;
var DocumentBuilder = (function () {
function DocumentBuilder(doc) {
this._patches = [];
this._doc = doc;
}
Object.defineProperty(DocumentBuilder.prototype, "patches", {
get: function () {
return this._patches.slice();
},
enumerable: true,
configurable: true
});
DocumentBuilder.prototype.measure = function (measureUUID, builder) {
var measure = lodash_1.find(this._doc.measures, function (it) { return it.uuid === measureUUID; });
invariant(Boolean(measure), "invalid measure uuid " + measureUUID);
this._patches = this._patches.concat(builder(new MeasureBuilder(measure, this._doc))
.patches
.map(_prependPatch(measureUUID)));
return this;
};
DocumentBuilder.prototype.insertMeasure = function (measureIndex, builder, uuid) {
if (uuid === void 0) { uuid = genUUID(); }
this._patches = this._patches.concat({
li: {
uuid: uuid,
},
p: ["measures", measureIndex],
});
this._patches = this._patches.concat(builder(new MeasureBuilder(null, this._doc))
.patches
.map(_prependPatch(uuid)));
return this;
};
DocumentBuilder.prototype.removeMeasure = function (measureIndex) {
this._patches = this._patches.concat({
ld: JSON.parse(JSON.stringify(this._doc.measures[measureIndex])),
p: ["measures", measureIndex],
});
return this;
};
return DocumentBuilder;
}());
exports.DocumentBuilder = DocumentBuilder;
var ModelMetreMutationSpec = (function () {
function ModelMetreMutationSpec(spec, originalModel) {
lodash_1.extend(this, spec);
this._originalModel = originalModel;
}
ModelMetreMutationSpec.prototype.toSpec = function () {
var _this = this;
if (!this._originalModel) {
throw new Error("Only valid for mutations!");
}
var originalModel = private_util_1.cloneObject(this._originalModel);
if (originalModel._class === "Chord" || originalModel.length) {
var chordModel = originalModel;
lodash_1.forEach(chordModel, function (c) {
c.noteType.duration = _this.newCount;
if (_this.rest) {
c.rest = c.rest || {};
delete c.pitch;
}
else {
delete c.rest;
}
if (_this.newTimeModification) {
c.timeModification = _this.newTimeModification;
}
else {
delete c.timeModification;
}
if (!isNaN(_this.newDots)) {
c.dots = lodash_1.times(_this.newDots, function () { return ({}); });
}
else {
delete c.dots;
}
});
return chordModel;
}
else {
return originalModel;
}
};
return ModelMetreMutationSpec;
}());
exports.ModelMetreMutationSpec = ModelMetreMutationSpec;
function getMutationInfo(document, patches) {
var segments = {};
var attributes = {};
var elementInfos = {};
var elementInfoByChord = {};
patches.forEach(function (patch) {
if (patch.p[0] === "measures") {
// XXX: implement!
return;
}
var measureUUID = parseInt(patch.p[0], 10);
var measure = lodash_1.find(document.measures, function (doc) { return doc.uuid === measureUUID; });
if (!measure) {
// TODO: validate blank measures
return;
}
if (patch.p[1] !== "parts") {
return;
}
var part = measure.parts[patch.p[2]];
invariant(part, "part " + patch.p[2] + " should exist in measure " + measureUUID);
if (patch.p[3] === "staves") {
return;
}
invariant(patch.p[3] === "voices", "only voices are supported here");
var voice = part.voices[patch.p[4]];
invariant(voice, "expected to find voice " + patch.p[4] + " in part " + patch.p[2] + " in measure " + measureUUID);
var segID = patch.p.slice(0, 5).join("++");
if (!segments[segID]) {
segments[segID] = voice;
var currDiv_1 = 0;
attributes[segID] = document.search(part.staves[1], 0, document_1.Type.Attributes)[0]._snapshot;
var time_1 = attributes[segID].time; // TODO: TS changes
var divisions_1 = attributes[segID].divisions;
elementInfos[segID] = voice.reduce(function (elementInfo, model, idx) {
if (!document.modelHasType(model, document_1.Type.Chord)) {
return elementInfo.concat(new ModelMetreMutationSpec({
idx: idx,
oldIdx: idx,
start: currDiv_1,
previousDivisions: 0,
newDivisions: 0,
newCount: 0,
newDots: 0,
newTimeModification: null,
time: time_1,
rest: true,
beam: null,
touched: false,
}, model));
}
var divs = private_chordUtil_1.divisions(model, { time: time_1, divisions: divisions_1 });
var info = new ModelMetreMutationSpec({
idx: idx,
oldIdx: idx,
start: currDiv_1,
previousDivisions: divs,
newDivisions: divs,
newCount: private_chordUtil_1.count(model),
newDots: private_chordUtil_1.dots(model),
newTimeModification: private_chordUtil_1.timeModification(model),
time: time_1,
rest: !!private_chordUtil_1.rest(model),
beam: private_chordUtil_1.beams(model),
touched: false,
}, model);
elementInfoByChord[model.key] = info;
currDiv_1 += divs;
return elementInfo.concat(info);
}, []);
}
var divisions = attributes[segID].divisions;
if (patch.p.length === 6) {
if (patch.li) {
var isChord = patch.li._class === "Chord";
var b = isChord ? private_chordUtil_1.beams(patch.li) : null;
var c = isChord ? private_chordUtil_1.count(patch.li) : 0;
var d = isChord ? private_chordUtil_1.dots(patch.li) : 0;
var tm = isChord ? private_chordUtil_1.timeModification(patch.li) : null;
var isRest = isChord && !!private_chordUtil_1.rest(patch.li);
var divs = isChord ?
private_chordUtil_1.divisions(patch.li, { time: attributes[segID].time, divisions: divisions }) :
0;
var start = void 0;
var spliceIdx = parseInt(patch.p[5], 10);
invariant(lodash_1.isInteger(spliceIdx) && !isNaN(spliceIdx), "Expected an integer");
if (spliceIdx === 0) {
start = 0;
}
else {
start = elementInfos[segID][spliceIdx - 1].newDivisions + elementInfos[segID][spliceIdx - 1].start;
}
var newInfo = new ModelMetreMutationSpec({
idx: spliceIdx,
oldIdx: undefined,
newCount: c,
newDivisions: divs,
newDots: d,
previousDivisions: 0,
newTimeModification: tm,
start: start,
time: attributes[segID].time,
rest: isRest,
beam: b,
touched: true,
});
for (var i = spliceIdx; i < elementInfos[segID].length; ++i) {
elementInfos[segID][i].start += divs;
elementInfos[segID][i].idx += 1;
}
elementInfos[segID].splice(spliceIdx, 0, newInfo);
}
if (patch.ld) {
var divs = patch.ld._class === "Chord" ?
private_chordUtil_1.divisions(patch.ld, { time: attributes[segID].time, divisions: divisions }) :
0;
var spliceIdx = parseInt(patch.p[5], 10);
elementInfos[segID].splice(spliceIdx, 1);
for (var i = spliceIdx; i < elementInfos[segID].length; ++i) {
elementInfos[segID][i].start -= divs;
elementInfos[segID][i].idx -= 1;
}
}
return;
}
var el = voice[patch.p[5]];
invariant(el, "expected to find element $" + patch.p[5] + " in part " + patch.p[2] + " in voice " + patch.p[4] + " in measure " + measureUUID);
if (!document.modelHasType(el, document_1.Type.Chord) || patch.p[6] !== "notes" || patch.p[7] !== 0) {
return;
}
var info = elementInfoByChord[el.key];
if (patch.p.length === 9 && patch.p[8] === "pitch") {
info.touched = true;
if (patch.oi !== undefined) {
info.rest = !patch.oi;
}
else if (patch.od !== undefined) {
info.rest = true;
}
}
if (patch.p.length === 9 && patch.p[8] === "rest") {
info.touched = true;
if (patch.oi !== undefined) {
info.rest = !!patch.oi;
}
else if (patch.od !== undefined) {
info.rest = false;
}
}
if (patch.p[8] === "noteType" && patch.p[9] === "duration") {
if (patch.oi) {
info.newCount = patch.oi;
}
else {
invariant(false, "noteType is required...");
}
}
if (patch.p.length === 9 && patch.p[8] === "dots") {
if (patch.oi) {
info.newDots = patch.oi.length;
}
else if (patch.od) {
info.newDots = 0;
}
}
info.newDivisions = private_chordUtil_1.divisions({
count: info.newCount,
dots: info.newDots,
timeModification: info.newTimeModification
}, {
time: info.time,
divisions: divisions,
});
if (info.newDivisions !== info.previousDivisions) {
info.touched = true;
}
});
return {
segments: segments,
attributes: attributes,
elementInfos: elementInfos,
elementInfoByChord: elementInfoByChord
};
}
function fixMetre(document, patches) {
patches = patches.slice();
var segments;
var attributes;
var elementInfos;
var mi = getMutationInfo(document, patches);
segments = mi.segments;
attributes = mi.attributes;
elementInfos = mi.elementInfos;
lodash_1.forEach(elementInfos, function (voiceInfo, key) {
var anyChanged = voiceInfo.some(function (n) { return n.touched; });
if (!anyChanged) {
return;
}
var restSpecs = private_metre_modifyRest_1.simplifyRests(voiceInfo, document, attributes[key]);
patches = patches.concat(restSpecs.map(function (spec, idx) { return (lodash_1.extend({}, spec, {
p: key.split("++").concat(spec.p),
})); }));
});
return patches;
}
function fixBarlines(doc, patches) {
// XXX: FIXME
// const measureCount = doc.measures.length;
// const previouslyLastMeasure = doc.measures[measureCount - 1];
// forEach(previouslyLastMeasure.parts, (part, partName) => {
// const segment = part.staves[1];
// const barlineIdx = findLastIndex(segment, el => doc.modelHasType(el, Type.Barline));
// patches = patches.slice();
// patches.forEach(patch => {
// if (patch.p[0] === "measures" &&
// patch.p.length === 2 &&
// patch.p[1] === previouslyLastMeasure.idx + 1) {
// const removeDoubleBarline = createPatch(false, doc,
// previouslyLastMeasure.uuid, partName,
// part => part.staff(1, staff => staff
// .barline(barline => barline
// .barStyle(barStyle => barStyle
// .data(BarStyleType.Regular)
// )
// ),
// barlineIdx
// )
// );
// patches = patches.concat(removeDoubleBarline);
// }
// });
// });
return patches;
}
function fixCursor(doc, patches) {
var _a = getMutationInfo(doc, patches), segments = _a.segments, attributes = _a.attributes, elementInfos = _a.elementInfos;
var newCursor = patches.filter(function (patch) { return patch.li && patch.li._class === "VisualCursor"; });
if (!newCursor.length) {
return patches;
}
invariant(newCursor.length === 1, "Limit 1 cursor operation per patch");
patches = patches.slice();
lodash_1.forEach(doc.measures, function (measure) {
lodash_1.forEach(measure.parts, function (part, partName) {
lodash_1.forEach(part.voices, function (voice, voiceIDX) {
if (!voice) {
return;
}
var segID = [measure.uuid, "parts", partName, "voices", voiceIDX].join("++");
var segInfo = elementInfos[segID];
if (segInfo) {
var offset_1 = 0;
lodash_1.forEach(segInfo, function (element) {
if (!isNaN(element.idx) && !isNaN(element.oldIdx) &&
doc.modelHasType(voice[element.oldIdx], document_1.Type.VisualCursor)) {
patches.push({
p: [measure.uuid, "parts", partName, "voices", voiceIDX, element.idx + offset_1],
ld: JSON.parse(JSON.stringify(voice[element.oldIdx])),
});
offset_1 -= 1;
}
});
}
else {
var offset_2 = 0;
lodash_1.forEach(voice, function (el, idx) {
if (doc.modelHasType(el, document_1.Type.VisualCursor)) {
patches.push({
p: [measure.uuid, "parts", partName, "voices", voiceIDX, idx + offset_2],
ld: JSON.parse(JSON.stringify(el)),
});
offset_2 -= 1;
}
});
}
});
});
});
return patches;
}
var COUNT_TO_BEAMS = (_a = {},
_a[musicxml_interfaces_1.Count.Eighth] = 1,
_a[musicxml_interfaces_1.Count._16th] = 2,
_a[musicxml_interfaces_1.Count._32nd] = 3,
_a[musicxml_interfaces_1.Count._64th] = 4,
_a[musicxml_interfaces_1.Count._128th] = 5,
_a[musicxml_interfaces_1.Count._256th] = 6,
_a[musicxml_interfaces_1.Count._512th] = 7,
_a);
function addBeams(document, patches) {
patches = patches.slice();
var _a = getMutationInfo(document, patches), segments = _a.segments, elementInfos = _a.elementInfos;
lodash_1.forEach(elementInfos, function (voiceInfo, key) {
var segment = segments[key];
var time = voiceInfo[0].time;
var stdBP = private_metre_checkBeaming_1.getBeamingPattern(time); // TODO: TS changes in bar
// const cleanBP = getBeamingPattern(time, "clean");
// const altBP = getBeamingPattern(time, "alt");
var prevInfo;
var beamGroup = [];
var beamBeams = [];
var inCandidate = [];
var beamingPattern = stdBP;
function applyCandidate() {
// Remove all rests at the end and beginning.
var start = lodash_1.findIndex(beamGroup, function (i) { return !voiceInfo[i].rest; });
var end = lodash_1.findLastIndex(beamGroup, function (i) { return !voiceInfo[i].rest; });
beamBeams = beamBeams.slice(start, end + 1);
beamGroup = beamGroup.slice(start, end + 1);
if (beamGroup.length < 2) {
return;
}
// Mark elements in the candidate
beamGroup.forEach(function (b) { return inCandidate[b] = true; });
if (!lodash_1.some(beamGroup, function (i) { return voiceInfo[i].touched; })) {
// We did not modify this beam group, so don't change it here.
return;
}
patches = patches.concat(beamGroup.map(function (i, j) {
var type = null;
var beams = lodash_1.times(beamBeams[j], function (beamNumber) {
if (i === beamGroup[0] || beamBeams[j - 1] < beamNumber + 1) {
if (i === lodash_1.last(beamGroup) || beamBeams[j + 1] < beamNumber + 1) {
// HACK HACK HACK -- it's more complex than this
type = (j === 0 ? musicxml_interfaces_1.BeamType.ForwardHook : musicxml_interfaces_1.BeamType.BackwardHook);
}
else {
type = musicxml_interfaces_1.BeamType.Begin;
}
}
else if (i === lodash_1.last(beamGroup) || beamBeams[j + 1] < beamNumber + 1) {
type = musicxml_interfaces_1.BeamType.End;
}
else {
type = musicxml_interfaces_1.BeamType.Continue;
}
return builders_1.buildBeam(function (beam) { return beam
.number(beamNumber + 1)
.type(type); });
});
var miniP = {
p: key.split("++").concat([i, "notes", 0, "beams"]),
oi: beams
};
if (voiceInfo[i].beam) {
miniP.od = voiceInfo[i].beam;
}
return miniP;
}));
}
var bpIDX = -1;
var divisionsInCurrentBucket = 0;
function advanceBP(divs) {
divisionsInCurrentBucket -= divs;
if (divisionsInCurrentBucket <= 0) {
applyCandidate();
prevInfo = null;
beamGroup = [];
beamBeams = [];
++bpIDX;
if (!beamingPattern[bpIDX]) {
// End of bar / overflowed bar
divisionsInCurrentBucket = Infinity;
return;
}
var next = private_chordUtil_1.divisions(beamingPattern[bpIDX], { time: time, divisions: segment.divisions });
divisionsInCurrentBucket += next;
}
}
advanceBP(0);
voiceInfo.forEach(function (elInfo, originalIdx) {
if (!elInfo.newDivisions) {
// Skip this non-note.
return;
}
var divs = private_chordUtil_1.divisions({
count: elInfo.newCount,
dots: elInfo.newDots,
}, {
time: time,
divisions: segment.divisions,
}, true);
var isCandidate = private_chordUtil_1.countToIsBeamable[elInfo.newCount] &&
(!elInfo.rest || elInfo.beam) &&
divs <= divisionsInCurrentBucket;
if (isCandidate) {
prevInfo = elInfo;
beamGroup.push(originalIdx);
beamBeams.push(COUNT_TO_BEAMS[elInfo.newCount]);
}
else {
applyCandidate();
prevInfo = null;
beamGroup = [];
beamBeams = [];
}
advanceBP(divs);
});
applyCandidate();
// Now remove invalid beams!
voiceInfo.forEach(function (elInfo, i) {
if (elInfo.beam && !inCandidate[i]) {
patches = patches.concat({
p: key.split("++").concat([i, "notes", 0, "beams"]),
od: elInfo.beam
});
}
});
});
return patches;
}
function cleanupPatches(document, patches) {
patches = fixMetre(document, patches);
patches = addBeams(document, patches);
patches = fixBarlines(document, patches);
patches = fixCursor(document, patches);
return patches;
}
function createPatch(isPreview, document, builderOrMeasure, part, partBuilder) {
var patches;
if (builderOrMeasure instanceof Array) {
patches = cleanupPatches(document, builderOrMeasure);
}
else if (typeof builderOrMeasure === "function") {
invariant(part === undefined && partBuilder === undefined, "createPatch: invalid usage");
var builder = builderOrMeasure;
patches = builder(new DocumentBuilder(document)).patches;
if (!isPreview) {
patches = cleanupPatches(document, patches);
}
}
else {
var measure_1 = builderOrMeasure;
var builder_1 = partBuilder;
patches = createPatch(isPreview, document, function (document) { return document
.measure(measure_1, function (measure) { return measure
.part(part, builder_1); }); });
}
return patches;
}
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = createPatch;
var _a;