File size: 4,674 Bytes
af6912c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
var layoutBeam = require('./beam');
var getBarYAt = require('./get-bar-y-at');
var layoutTriplet = require('./triplet');

var layoutVoice = function (voice) {
	for (var i = 0; i < voice.beams.length; i++) {
		if (voice.beams[i].type === 'BeamElem') {
			layoutBeam(voice.beams[i]);
			moveDecorations(voice.beams[i]);
			// The above will change the top and bottom of the abselem children, so see if we need to expand our range.
			for (var j = 0; j < voice.beams[i].elems.length; j++) {
				voice.adjustRange(voice.beams[i].elems[j]);
			}
		}
	}
	voice.staff.specialY.chordLines = setLaneForChord(voice.children);

	// Now we can layout the triplets
	for (i = 0; i < voice.otherchildren.length; i++) {
		var child = voice.otherchildren[i];
		if (child.type === 'TripletElem') {
			layoutTriplet(child);
			voice.adjustRange(child);
		}
	}
	voice.staff.top = Math.max(voice.staff.top, voice.top);
	voice.staff.bottom = Math.min(voice.staff.bottom, voice.bottom);
};

function moveDecorations(beam) {
	var padding = 1.5; // This is the vertical padding between elements, in pitches.
	for (var ch = 0; ch < beam.elems.length; ch++) {
		var child = beam.elems[ch];
		if (child.top) {
			// We now know where the ornaments should have been placed, so move them if they would overlap.
			var top = yAtNote(child, beam);
			for (var i = 0; i < child.children.length; i++) {
				var el = child.children[i];
				if (el.klass === 'ornament' && el.position !== 'below') {
					if (el.bottom - padding < top) {
						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.
						el.bottom += distance;
						el.top += distance;
						el.pitch += distance;
						top = child.top = el.top;
					}
				}
			}
		}
	}
}

function placeInLane(rightMost, relElem) {
	// These items are centered so figure the coordinates accordingly.
	// The font reports some extra space so the margin is built in.
	var xCoords = relElem.getChordDim();
	if (xCoords) {
		for (var i = 0; i < rightMost.length; i++) {
			var fits = rightMost[i] < xCoords.left;
			if (fits) {
				if (i > 0)
					relElem.putChordInLane(i);
				rightMost[i] = xCoords.right;
				return;
			}
		}
		// If we didn't return early, then we need a new row
		rightMost.push(xCoords.right);
		relElem.putChordInLane(rightMost.length - 1);
	}
}

function setLaneForChord(absElems) {
	// Criteria:
	// 1) lane numbers start from the bottom so that as many items as possible are in lane 0, closest to the music.
	// 2) a chord can have more than one line (for instance "C\nD") each line is a lane.
	// 3) if two adjoining items would touch then push the second one to the next lane.
	// 4) use as many lanes as is necessary to get everything to not touch.
	// 5) leave a margin between items, so use another lane if the chords would have less than a character's width.
	// 6) if the chord only has one character, allow it to be closer than if the chord has more than one character.
	var rightMostAbove = [0];
	var rightMostBelow = [0];
	var i;
	var j;
	var relElem;
	for (i = 0; i < absElems.length; i++) {
		for (j = 0; j < absElems[i].children.length; j++) {
			relElem = absElems[i].children[j];
			if (relElem.chordHeightAbove) {
				placeInLane(rightMostAbove, relElem);
			}
		}
		for (j = absElems[i].children.length - 1; j >= 0; j--) {
			relElem = absElems[i].children[j];
			if (relElem.chordHeightBelow) {
				placeInLane(rightMostBelow, relElem);
			}
		}
	}
	// If we used a second line, then we need to go back and set the first lines.
	// Also we need to flip the indexes of the names so that we can count from the top line.
	if (rightMostAbove.length > 1 || rightMostBelow.length > 1)
		setLane(absElems, rightMostAbove.length, rightMostBelow.length);
	return { above: rightMostAbove.length, below: rightMostBelow.length };
}

function numAnnotationsBelow(absElem) {
	var count = 0;
	for (var j = 0; j < absElem.children.length; j++) {
		var relElem = absElem.children[j];
		if (relElem.chordHeightBelow)
			count++;
	}
	return count;
}

function setLane(absElems, numLanesAbove, numLanesBelow) {
	for (var i = 0; i < absElems.length; i++) {
		var below = numAnnotationsBelow(absElems[i]);
		for (var j = 0; j < absElems[i].children.length; j++) {
			var relElem = absElems[i].children[j];
			if (relElem.chordHeightAbove) {
				relElem.invertLane(numLanesAbove);
				// } else if (relElem.chordHeightBelow) {
				// 	relElem.invertLane(below);
			}
		}
	}
}

function yAtNote(element, beam) {
	beam = beam.beams[0];
	return getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, element.x);
}


module.exports = layoutVoice;