abcjs-rails 1.11 → 2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/abcjs/api/abc_animation.js +41 -1
- data/app/assets/javascripts/abcjs/api/abc_tunebook.js +5 -0
- data/app/assets/javascripts/abcjs/data/abc_tune.js +4 -3
- data/app/assets/javascripts/abcjs/edit/abc_editor.js +10 -0
- data/app/assets/javascripts/abcjs/parse/abc_parse.js +120 -19
- data/app/assets/javascripts/abcjs/parse/abc_parse_directive.js +456 -115
- data/app/assets/javascripts/abcjs/raphael.js +2 -2
- data/app/assets/javascripts/abcjs/write/abc_absolute_element.js +111 -4
- data/app/assets/javascripts/abcjs/write/abc_abstract_engraver.js +899 -0
- data/app/assets/javascripts/abcjs/write/abc_beam_element.js +263 -37
- data/app/assets/javascripts/abcjs/write/abc_create_clef.js +76 -0
- data/app/assets/javascripts/abcjs/write/abc_create_key_signature.js +41 -0
- data/app/assets/javascripts/abcjs/write/abc_create_time_signature.js +51 -0
- data/app/assets/javascripts/abcjs/write/{abc_cresendo_element.js → abc_crescendo_element.js} +32 -1
- data/app/assets/javascripts/abcjs/write/abc_decoration.js +321 -0
- data/app/assets/javascripts/abcjs/write/abc_dynamic_decoration.js +22 -1
- data/app/assets/javascripts/abcjs/write/abc_ending_element.js +31 -1
- data/app/assets/javascripts/abcjs/write/abc_engraver_controller.js +359 -0
- data/app/assets/javascripts/abcjs/write/abc_glyphs.js +119 -9
- data/app/assets/javascripts/abcjs/write/abc_layout.js +2 -2
- data/app/assets/javascripts/abcjs/write/abc_relative_element.js +106 -8
- data/app/assets/javascripts/abcjs/write/abc_renderer.js +754 -0
- data/app/assets/javascripts/abcjs/write/abc_staff_group_element.js +249 -9
- data/app/assets/javascripts/abcjs/write/abc_tempo_element.js +104 -0
- data/app/assets/javascripts/abcjs/write/abc_tie_element.js +69 -22
- data/app/assets/javascripts/abcjs/write/abc_triplet_element.js +77 -10
- data/app/assets/javascripts/abcjs/write/abc_voice_element.js +100 -8
- data/app/assets/javascripts/abcjs/write/abc_write.js +64 -68
- data/lib/abcjs-rails/version.rb +1 -1
- metadata +12 -4
@@ -1539,10 +1539,10 @@
|
|
1539
1539
|
var paths = function (ps) {
|
1540
1540
|
var p = paths.ps = paths.ps || {};
|
1541
1541
|
if (p[ps]) {
|
1542
|
-
p[ps].sleep =
|
1542
|
+
p[ps].sleep = 1;
|
1543
1543
|
} else {
|
1544
1544
|
p[ps] = {
|
1545
|
-
sleep:
|
1545
|
+
sleep: 1
|
1546
1546
|
};
|
1547
1547
|
}
|
1548
1548
|
setTimeout(function () {
|
@@ -26,6 +26,7 @@ if (!window.ABCJS.write)
|
|
26
26
|
// minspacing - spacing which must be taken on top of the width defined by the duration
|
27
27
|
// type is a meta-type for the element. It is not necessary for drawing, but it is useful to make semantic sense of the element. For instance, it can be used in the element's class name.
|
28
28
|
ABCJS.write.AbsoluteElement = function(abcelem, duration, minspacing, type) {
|
29
|
+
//console.log("Absolute:",abcelem, type);
|
29
30
|
this.abcelem = abcelem;
|
30
31
|
this.duration = duration;
|
31
32
|
this.minspacing = minspacing || 0;
|
@@ -38,9 +39,44 @@ ABCJS.write.AbsoluteElement = function(abcelem, duration, minspacing, type) {
|
|
38
39
|
this.w = 0;
|
39
40
|
this.right = [];
|
40
41
|
this.invisible = false;
|
41
|
-
this.bottom =
|
42
|
-
this.top =
|
42
|
+
this.bottom = undefined;
|
43
|
+
this.top = undefined;
|
43
44
|
this.type = type;
|
45
|
+
// these are the heights of all of the vertical elements that can't be placed until the end of the line.
|
46
|
+
// the vertical order of elements that are above is: tempo, part, volume/dynamic, ending/chord, lyric
|
47
|
+
// the vertical order of elements that are below is: lyric, chord, volume/dynamic
|
48
|
+
this.specialY = {
|
49
|
+
tempoHeightAbove: 0,
|
50
|
+
partHeightAbove: 0,
|
51
|
+
volumeHeightAbove: 0,
|
52
|
+
dynamicHeightAbove: 0,
|
53
|
+
endingHeightAbove: 0,
|
54
|
+
chordHeightAbove: 0,
|
55
|
+
lyricHeightAbove: 0,
|
56
|
+
|
57
|
+
lyricHeightBelow: 0,
|
58
|
+
chordHeightBelow: 0,
|
59
|
+
volumeHeightBelow: 0,
|
60
|
+
dynamicHeightBelow: 0
|
61
|
+
};
|
62
|
+
};
|
63
|
+
|
64
|
+
// For each of the relative elements that can't be placed in advance (because their vertical placement depends on everything
|
65
|
+
// else on the line), this iterates through them and sets their pitch. By the time this is called, specialYResolved contains a
|
66
|
+
// hash with the vertical placement (in pitch units) for each type.
|
67
|
+
// TODO-PER: I think this needs to be separated by "above" and "below". How do we know that for dynamics at the point where they are being defined, though? We need a pass through all the relative elements to set "above" and "below".
|
68
|
+
ABCJS.write.AbsoluteElement.prototype.setUpperAndLowerElements = function(specialYResolved) {
|
69
|
+
// specialYResolved contains the actual pitch for each of the classes of elements.
|
70
|
+
for (var i = 0; i < this.children.length; i++) {
|
71
|
+
var child = this.children[i];
|
72
|
+
for (var key in this.specialY) { // for each class of element that needs to be placed vertically
|
73
|
+
if (this.specialY.hasOwnProperty(key)) {
|
74
|
+
if (child[key]) { // If this relative element has defined a height for this class of element
|
75
|
+
child.pitch = specialYResolved[key];
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
44
80
|
};
|
45
81
|
|
46
82
|
ABCJS.write.AbsoluteElement.prototype.getMinWidth = function () { // absolute space taken to the right of the note
|
@@ -69,38 +105,98 @@ ABCJS.write.AbsoluteElement.prototype.addRight = function (right) {
|
|
69
105
|
this.addChild(right);
|
70
106
|
};
|
71
107
|
|
108
|
+
ABCJS.write.AbsoluteElement.prototype.setLimit = function(member, child) {
|
109
|
+
if (!child[member]) return;
|
110
|
+
if (!this.specialY[member])
|
111
|
+
this.specialY[member] = child[member];
|
112
|
+
else
|
113
|
+
this.specialY[member] = Math.max(this.specialY[member], child[member]);
|
114
|
+
};
|
115
|
+
|
72
116
|
ABCJS.write.AbsoluteElement.prototype.addChild = function (child) {
|
117
|
+
//console.log("Relative:",child);
|
73
118
|
child.parent = this;
|
74
119
|
this.children[this.children.length] = child;
|
75
120
|
this.pushTop(child.top);
|
76
121
|
this.pushBottom(child.bottom);
|
122
|
+
this.setLimit('tempoHeightAbove', child);
|
123
|
+
this.setLimit('partHeightAbove', child);
|
124
|
+
this.setLimit('volumeHeightAbove', child);
|
125
|
+
this.setLimit('dynamicHeightAbove', child);
|
126
|
+
this.setLimit('endingHeightAbove', child);
|
127
|
+
this.setLimit('chordHeightAbove', child);
|
128
|
+
this.setLimit('lyricHeightAbove', child);
|
129
|
+
this.setLimit('lyricHeightBelow', child);
|
130
|
+
this.setLimit('chordHeightBelow', child);
|
131
|
+
this.setLimit('volumeHeightBelow', child);
|
132
|
+
this.setLimit('dynamicHeightBelow', child);
|
77
133
|
};
|
78
134
|
|
79
135
|
ABCJS.write.AbsoluteElement.prototype.pushTop = function (top) {
|
80
|
-
|
136
|
+
if (top !== undefined) {
|
137
|
+
if (this.top === undefined)
|
138
|
+
this.top = top;
|
139
|
+
else
|
140
|
+
this.top = Math.max(top, this.top);
|
141
|
+
}
|
81
142
|
};
|
82
143
|
|
83
144
|
ABCJS.write.AbsoluteElement.prototype.pushBottom = function (bottom) {
|
84
|
-
|
145
|
+
if (bottom !== undefined) {
|
146
|
+
if (this.bottom === undefined)
|
147
|
+
this.bottom = bottom;
|
148
|
+
else
|
149
|
+
this.bottom = Math.min(bottom, this.bottom);
|
150
|
+
}
|
151
|
+
};
|
152
|
+
|
153
|
+
<<<<<<< HEAD
|
154
|
+
ABCJS.write.AbsoluteElement.prototype.setX = function (x) {
|
155
|
+
this.x = x;
|
156
|
+
for (var i=0; i<this.children.length; i++)
|
157
|
+
this.children[i].setX(x);
|
85
158
|
};
|
86
159
|
|
160
|
+
=======
|
161
|
+
>>>>>>> origin/master
|
87
162
|
ABCJS.write.AbsoluteElement.prototype.draw = function (renderer, bartop) {
|
88
163
|
this.elemset = renderer.paper.set();
|
89
164
|
if (this.invisible) return;
|
90
165
|
renderer.beginGroup();
|
91
166
|
for (var i=0; i<this.children.length; i++) {
|
167
|
+
<<<<<<< HEAD
|
168
|
+
if (ABCJS.write.debugPlacement) {
|
169
|
+
if (this.children[i].klass === 'ornament')
|
170
|
+
renderer.printShadedBox(this.x, renderer.calcY(this.children[i].top), this.w, renderer.calcY(this.children[i].bottom)-renderer.calcY(this.children[i].top), "rgba(0,0,200,0.3)");
|
171
|
+
}
|
172
|
+
this.elemset.push(this.children[i].draw(renderer,bartop));
|
173
|
+
=======
|
92
174
|
this.elemset.push(this.children[i].draw(renderer,this.x, bartop));
|
175
|
+
>>>>>>> origin/master
|
93
176
|
}
|
94
177
|
this.elemset.push(renderer.endGroup(this.type));
|
95
178
|
if (this.klass)
|
96
179
|
this.setClass("mark", "", "#00ff00");
|
180
|
+
var color = ABCJS.write.debugPlacement ? "rgba(0,0,0,0.3)" : "rgba(0,0,0,0)"; // Create transparent box that encompasses the element, and not so transparent to debug it.
|
181
|
+
var target = renderer.printShadedBox(this.x, renderer.calcY(this.top), this.w, renderer.calcY(this.bottom)-renderer.calcY(this.top), color);
|
97
182
|
var self = this;
|
183
|
+
<<<<<<< HEAD
|
184
|
+
var controller = renderer.controller;
|
185
|
+
// this.elemset.mouseup(function () {
|
186
|
+
target.mouseup(function () {
|
187
|
+
controller.notifySelect(self);
|
188
|
+
});
|
189
|
+
this.abcelem.abselem = this;
|
190
|
+
|
191
|
+
var spacing = ABCJS.write.spacing.STEP;
|
192
|
+
=======
|
98
193
|
this.elemset.mouseup(function () {
|
99
194
|
renderer.notifySelect(self);
|
100
195
|
});
|
101
196
|
this.abcelem.abselem = this;
|
102
197
|
|
103
198
|
var spacing = ABCJS.write.spacing.STEP*renderer.scale;
|
199
|
+
>>>>>>> origin/master
|
104
200
|
|
105
201
|
var start = function () {
|
106
202
|
// storing original relative coordinates
|
@@ -114,12 +210,23 @@ ABCJS.write.AbsoluteElement.prototype.draw = function (renderer, bartop) {
|
|
114
210
|
this.translate(0,this.dy);
|
115
211
|
},
|
116
212
|
up = function () {
|
213
|
+
<<<<<<< HEAD
|
214
|
+
if (self.abcelem.pitches) {
|
215
|
+
var delta = -Math.round(this.dy / spacing);
|
216
|
+
self.abcelem.pitches[0].pitch += delta;
|
217
|
+
self.abcelem.pitches[0].verticalPos += delta;
|
218
|
+
controller.notifyChange();
|
219
|
+
}
|
220
|
+
};
|
221
|
+
if (this.abcelem.el_type==="note" && controller.editable)
|
222
|
+
=======
|
117
223
|
var delta = -Math.round(this.dy/spacing);
|
118
224
|
self.abcelem.pitches[0].pitch += delta;
|
119
225
|
self.abcelem.pitches[0].verticalPos += delta;
|
120
226
|
renderer.notifyChange();
|
121
227
|
};
|
122
228
|
if (this.abcelem.el_type==="note" && renderer.editable)
|
229
|
+
>>>>>>> origin/master
|
123
230
|
this.elemset.drag(move, start, up);
|
124
231
|
};
|
125
232
|
|
@@ -0,0 +1,899 @@
|
|
1
|
+
// abc_abstract_engraver.js: Creates a data structure suitable for printing a line of abc
|
2
|
+
// Copyright (C) 2010 Gregory Dyke (gregdyke at gmail dot com)
|
3
|
+
//
|
4
|
+
// This program is free software: you can redistribute it and/or modify
|
5
|
+
// it under the terms of the GNU General Public License as published by
|
6
|
+
// the Free Software Foundation, either version 3 of the License, or
|
7
|
+
// (at your option) any later version.
|
8
|
+
//
|
9
|
+
// This program is distributed in the hope that it will be useful,
|
10
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
// GNU General Public License for more details.
|
13
|
+
//
|
14
|
+
// You should have received a copy of the GNU General Public License
|
15
|
+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
/*global window, ABCJS */
|
18
|
+
|
19
|
+
if (!window.ABCJS)
|
20
|
+
window.ABCJS = {};
|
21
|
+
|
22
|
+
if (!window.ABCJS.write)
|
23
|
+
window.ABCJS.write = {};
|
24
|
+
|
25
|
+
(function() {
|
26
|
+
"use strict";
|
27
|
+
|
28
|
+
ABCJS.write.getDuration = function(elem) {
|
29
|
+
var d = 0;
|
30
|
+
if (elem.duration) {
|
31
|
+
d = elem.duration;
|
32
|
+
}
|
33
|
+
return d;
|
34
|
+
};
|
35
|
+
|
36
|
+
ABCJS.write.getDurlog = function(duration) {
|
37
|
+
// TODO-PER: This is a hack to prevent a Chrome lockup. Duration should have been defined already,
|
38
|
+
// but there's definitely a case where it isn't. [Probably something to do with triplets.]
|
39
|
+
if (duration === undefined) {
|
40
|
+
return 0;
|
41
|
+
}
|
42
|
+
// console.log("getDurlog: " + duration);
|
43
|
+
return Math.floor(Math.log(duration)/Math.log(2));
|
44
|
+
};
|
45
|
+
|
46
|
+
ABCJS.write.AbstractEngraver = function(bagpipes, renderer) {
|
47
|
+
this.decoration = new ABCJS.write.Decoration();
|
48
|
+
this.renderer = renderer;
|
49
|
+
this.isBagpipes = bagpipes;
|
50
|
+
this.chartable = {rest:{0:"rests.whole", 1:"rests.half", 2:"rests.quarter", 3:"rests.8th", 4: "rests.16th",5: "rests.32nd", 6: "rests.64th", 7: "rests.128th"},
|
51
|
+
note:{"-1": "noteheads.dbl", 0:"noteheads.whole", 1:"noteheads.half", 2:"noteheads.quarter", 3:"noteheads.quarter", 4:"noteheads.quarter", 5:"noteheads.quarter", 6:"noteheads.quarter", 7:"noteheads.quarter", 'nostem':"noteheads.quarter"},
|
52
|
+
rhythm:{"-1": "noteheads.slash.whole", 0:"noteheads.slash.whole", 1:"noteheads.slash.whole", 2:"noteheads.slash.quarter", 3:"noteheads.slash.quarter", 4:"noteheads.slash.quarter", 5:"noteheads.slash.quarter", 6:"noteheads.slash.quarter", 7:"noteheads.slash.quarter", nostem: "noteheads.slash.nostem"},
|
53
|
+
x:{"-1": "noteheads.indeterminate", 0:"noteheads.indeterminate", 1:"noteheads.indeterminate", 2:"noteheads.indeterminate", 3:"noteheads.indeterminate", 4:"noteheads.indeterminate", 5:"noteheads.indeterminate", 6:"noteheads.indeterminate", 7:"noteheads.indeterminate", nostem: "noteheads.indeterminate"},
|
54
|
+
harmonic:{"-1": "noteheads.harmonic.quarter", 0:"noteheads.harmonic.quarter", 1:"noteheads.harmonic.quarter", 2:"noteheads.harmonic.quarter", 3:"noteheads.harmonic.quarter", 4:"noteheads.harmonic.quarter", 5:"noteheads.harmonic.quarter", 6:"noteheads.harmonic.quarter", 7:"noteheads.harmonic.quarter", nostem: "noteheads.harmonic.quarter"},
|
55
|
+
uflags:{3:"flags.u8th", 4:"flags.u16th", 5:"flags.u32nd", 6:"flags.u64th"},
|
56
|
+
dflags:{3:"flags.d8th", 4:"flags.d16th", 5:"flags.d32nd", 6:"flags.d64th"}};
|
57
|
+
this.reset();
|
58
|
+
};
|
59
|
+
|
60
|
+
ABCJS.write.AbstractEngraver.prototype.reset = function() {
|
61
|
+
this.slurs = {};
|
62
|
+
this.ties = [];
|
63
|
+
this.slursbyvoice = {};
|
64
|
+
this.tiesbyvoice = {};
|
65
|
+
this.endingsbyvoice = {};
|
66
|
+
this.s = 0; // current staff number
|
67
|
+
this.v = 0; // current voice number on current staff
|
68
|
+
this.tripletmultiplier = 1;
|
69
|
+
|
70
|
+
this.abcline = undefined;
|
71
|
+
this.accidentalSlot = undefined;
|
72
|
+
this.accidentalshiftx = undefined;
|
73
|
+
this.dotshiftx = undefined;
|
74
|
+
this.hasVocals = false;
|
75
|
+
this.minY = undefined;
|
76
|
+
this.partstartelem = undefined;
|
77
|
+
this.pos = undefined;
|
78
|
+
this.roomtaken = undefined;
|
79
|
+
this.roomtakenright = undefined;
|
80
|
+
this.staffgroup = undefined;
|
81
|
+
this.startlimitelem = undefined;
|
82
|
+
this.stemdir = undefined;
|
83
|
+
this.voice = undefined;
|
84
|
+
};
|
85
|
+
|
86
|
+
ABCJS.write.AbstractEngraver.prototype.setStemHeight = function(heightInPixels) {
|
87
|
+
this.stemHeight = heightInPixels / ABCJS.write.spacing.STEP;
|
88
|
+
};
|
89
|
+
|
90
|
+
ABCJS.write.AbstractEngraver.prototype.getCurrentVoiceId = function() {
|
91
|
+
return "s"+this.s+"v"+this.v;
|
92
|
+
};
|
93
|
+
|
94
|
+
ABCJS.write.AbstractEngraver.prototype.pushCrossLineElems = function() {
|
95
|
+
this.slursbyvoice[this.getCurrentVoiceId()] = this.slurs;
|
96
|
+
this.tiesbyvoice[this.getCurrentVoiceId()] = this.ties;
|
97
|
+
this.endingsbyvoice[this.getCurrentVoiceId()] = this.partstartelem;
|
98
|
+
};
|
99
|
+
|
100
|
+
ABCJS.write.AbstractEngraver.prototype.popCrossLineElems = function() {
|
101
|
+
this.slurs = this.slursbyvoice[this.getCurrentVoiceId()] || {};
|
102
|
+
this.ties = this.tiesbyvoice[this.getCurrentVoiceId()] || [];
|
103
|
+
this.partstartelem = this.endingsbyvoice[this.getCurrentVoiceId()];
|
104
|
+
};
|
105
|
+
|
106
|
+
ABCJS.write.AbstractEngraver.prototype.getElem = function() {
|
107
|
+
if (this.abcline.length <= this.pos)
|
108
|
+
return null;
|
109
|
+
return this.abcline[this.pos];
|
110
|
+
};
|
111
|
+
|
112
|
+
ABCJS.write.AbstractEngraver.prototype.getNextElem = function() {
|
113
|
+
if (this.abcline.length <= this.pos+1)
|
114
|
+
return null;
|
115
|
+
return this.abcline[this.pos+1];
|
116
|
+
};
|
117
|
+
|
118
|
+
ABCJS.write.AbstractEngraver.prototype.containsLyrics = function(staves) {
|
119
|
+
for (var i = 0; i < staves.length; i++) {
|
120
|
+
for (var j = 0; j < staves[i].voices.length; j++) {
|
121
|
+
for (var k = 0; k < staves[i].voices[j].length; k++) {
|
122
|
+
var el = staves[i].voices[j][k];
|
123
|
+
if (el.lyric) {
|
124
|
+
// We just want to see if there are vocals below the music to know where to put the dynamics.
|
125
|
+
if (!el.positioning || el.positioning.vocalPosition === 'below')
|
126
|
+
this.hasVocals = true;
|
127
|
+
return;
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
}
|
132
|
+
};
|
133
|
+
|
134
|
+
ABCJS.write.AbstractEngraver.prototype.createABCLine = function(staffs, tempo) {
|
135
|
+
this.minY = 2; // PER: This is the lowest that any note reaches. It will be used to set the dynamics row.
|
136
|
+
// See if there are any lyrics on this line.
|
137
|
+
this.containsLyrics(staffs);
|
138
|
+
this.staffgroup = new ABCJS.write.StaffGroupElement();
|
139
|
+
this.tempoSet = false;
|
140
|
+
for (this.s = 0; this.s < staffs.length; this.s++) {
|
141
|
+
this.createABCStaff(staffs[this.s], tempo);
|
142
|
+
}
|
143
|
+
|
144
|
+
return this.staffgroup;
|
145
|
+
};
|
146
|
+
|
147
|
+
ABCJS.write.AbstractEngraver.prototype.createABCStaff = function(abcstaff, tempo) {
|
148
|
+
// If the tempo is passed in, then the first element should get the tempo attached to it.
|
149
|
+
for (this.v = 0; this.v < abcstaff.voices.length; this.v++) {
|
150
|
+
this.voice = new ABCJS.write.VoiceElement(this.v,abcstaff.voices.length);
|
151
|
+
if (this.v===0) {
|
152
|
+
this.voice.barfrom = (abcstaff.connectBarLines==="start" || abcstaff.connectBarLines==="continue");
|
153
|
+
this.voice.barto = (abcstaff.connectBarLines==="continue" || abcstaff.connectBarLines==="end");
|
154
|
+
} else {
|
155
|
+
this.voice.duplicate = true; // bar lines and other duplicate info need not be created
|
156
|
+
}
|
157
|
+
if (abcstaff.title && abcstaff.title[this.v]) this.voice.header=abcstaff.title[this.v];
|
158
|
+
var clef = ABCJS.write.createClef(abcstaff.clef);
|
159
|
+
if (clef)
|
160
|
+
this.voice.addChild(clef);
|
161
|
+
var keySig = ABCJS.write.createKeySignature(abcstaff.key);
|
162
|
+
if (keySig) {
|
163
|
+
this.voice.addChild(keySig);
|
164
|
+
this.startlimitelem = keySig; // limit ties here
|
165
|
+
}
|
166
|
+
if (abcstaff.meter) {
|
167
|
+
var ts = ABCJS.write.createTimeSignature(abcstaff.meter);
|
168
|
+
this.voice.addChild(ts);
|
169
|
+
this.startlimitelem = ts; // limit ties here
|
170
|
+
}
|
171
|
+
if (this.voice.duplicate)
|
172
|
+
this.voice.children = []; // we shouldn't reprint the above if we're reusing the same staff. We just created them to get the right spacing.
|
173
|
+
var staffLines = abcstaff.clef.stafflines || abcstaff.clef.stafflines === 0 ? abcstaff.clef.stafflines : 5;
|
174
|
+
this.staffgroup.addVoice(this.voice,this.s,staffLines);
|
175
|
+
this.createABCVoice(abcstaff.voices[this.v],tempo);
|
176
|
+
this.staffgroup.setStaffLimits(this.voice);
|
177
|
+
}
|
178
|
+
};
|
179
|
+
|
180
|
+
ABCJS.write.AbstractEngraver.prototype.createABCVoice = function(abcline, tempo) {
|
181
|
+
this.popCrossLineElems();
|
182
|
+
this.stemdir = (this.isBagpipes)?"down":null;
|
183
|
+
this.abcline = abcline;
|
184
|
+
if (this.partstartelem) {
|
185
|
+
this.partstartelem = new ABCJS.write.EndingElem("", null, null);
|
186
|
+
this.voice.addOther(this.partstartelem);
|
187
|
+
}
|
188
|
+
for (var slur in this.slurs) {
|
189
|
+
if (this.slurs.hasOwnProperty(slur)) {
|
190
|
+
this.slurs[slur]= new ABCJS.write.TieElem(null, null, this.slurs[slur].above, this.slurs[slur].force, false);
|
191
|
+
this.voice.addOther(this.slurs[slur]);
|
192
|
+
}
|
193
|
+
}
|
194
|
+
for (var i=0; i<this.ties.length; i++) {
|
195
|
+
this.ties[i]=new ABCJS.write.TieElem(null, null, this.ties[i].above, this.ties[i].force, true);
|
196
|
+
this.voice.addOther(this.ties[i]);
|
197
|
+
}
|
198
|
+
|
199
|
+
for (this.pos=0; this.pos<this.abcline.length; this.pos++) {
|
200
|
+
var abselems = this.createABCElement();
|
201
|
+
if (abselems) {
|
202
|
+
for (i=0; i<abselems.length; i++) {
|
203
|
+
if (!this.tempoSet && tempo && !tempo.suppress) {
|
204
|
+
this.tempoSet = true;
|
205
|
+
abselems[i].addChild(new ABCJS.write.TempoElement(tempo));
|
206
|
+
}
|
207
|
+
this.voice.addChild(abselems[i]);
|
208
|
+
}
|
209
|
+
}
|
210
|
+
}
|
211
|
+
this.pushCrossLineElems();
|
212
|
+
};
|
213
|
+
|
214
|
+
|
215
|
+
// return an array of ABCJS.write.AbsoluteElement
|
216
|
+
ABCJS.write.AbstractEngraver.prototype.createABCElement = function() {
|
217
|
+
var elemset = [];
|
218
|
+
var elem = this.getElem();
|
219
|
+
switch (elem.el_type) {
|
220
|
+
case "note":
|
221
|
+
elemset = this.createBeam();
|
222
|
+
break;
|
223
|
+
case "bar":
|
224
|
+
elemset[0] = this.createBarLine(elem);
|
225
|
+
if (this.voice.duplicate) elemset[0].invisible = true;
|
226
|
+
break;
|
227
|
+
case "meter":
|
228
|
+
elemset[0] = ABCJS.write.createTimeSignature(elem);
|
229
|
+
this.startlimitelem = elemset[0]; // limit ties here
|
230
|
+
if (this.voice.duplicate) elemset[0].invisible = true;
|
231
|
+
break;
|
232
|
+
case "clef":
|
233
|
+
elemset[0] = ABCJS.write.createClef(elem);
|
234
|
+
if (!elemset[0]) return null;
|
235
|
+
if (this.voice.duplicate) elemset[0].invisible = true;
|
236
|
+
break;
|
237
|
+
case "key":
|
238
|
+
var absKey = ABCJS.write.createKeySignature(elem);
|
239
|
+
if (absKey) {
|
240
|
+
elemset[0] = absKey;
|
241
|
+
this.startlimitelem = elemset[0]; // limit ties here
|
242
|
+
}
|
243
|
+
if (this.voice.duplicate) elemset[0].invisible = true;
|
244
|
+
break;
|
245
|
+
case "stem":
|
246
|
+
this.stemdir=elem.direction;
|
247
|
+
break;
|
248
|
+
case "part":
|
249
|
+
var abselem = new ABCJS.write.AbsoluteElement(elem,0,0, 'part');
|
250
|
+
abselem.addChild(new ABCJS.write.RelativeElement(elem.title, 0, 0, undefined, {type:"part"}));
|
251
|
+
elemset[0] = abselem;
|
252
|
+
break;
|
253
|
+
case "tempo":
|
254
|
+
var abselem3 = new ABCJS.write.AbsoluteElement(elem,0,0, 'tempo');
|
255
|
+
abselem3.addChild(new ABCJS.write.TempoElement(elem));
|
256
|
+
elemset[0] = abselem3;
|
257
|
+
break;
|
258
|
+
case "style":
|
259
|
+
if (elem.head === "normal")
|
260
|
+
delete this.style;
|
261
|
+
else
|
262
|
+
this.style = elem.head;
|
263
|
+
break;
|
264
|
+
default:
|
265
|
+
var abselem2 = new ABCJS.write.AbsoluteElement(elem,0,0, 'unsupported');
|
266
|
+
abselem2.addChild(new ABCJS.write.RelativeElement("element type "+elem.el_type, 0, 0, undefined, {type:"debug"}));
|
267
|
+
elemset[0] = abselem2;
|
268
|
+
}
|
269
|
+
|
270
|
+
return elemset;
|
271
|
+
};
|
272
|
+
|
273
|
+
ABCJS.write.AbstractEngraver.prototype.calcBeamDir = function() {
|
274
|
+
if (this.stemdir) // If the user or voice is forcing the stem direction, we already know the answer.
|
275
|
+
return this.stemdir;
|
276
|
+
var beamelem = new ABCJS.write.BeamElem(this.stemHeight, this.stemdir);
|
277
|
+
// PER: need two passes: the first one decides if the stems are up or down.
|
278
|
+
var oldPos = this.pos;
|
279
|
+
var abselem;
|
280
|
+
while (this.getElem()) {
|
281
|
+
abselem = this.createNote(this.getElem(), true, true);
|
282
|
+
beamelem.add(abselem);
|
283
|
+
if (this.getElem().endBeam)
|
284
|
+
break;
|
285
|
+
this.pos++;
|
286
|
+
}
|
287
|
+
var dir = beamelem.calcDir();
|
288
|
+
this.pos = oldPos;
|
289
|
+
return dir ? "up" : "down";
|
290
|
+
};
|
291
|
+
|
292
|
+
ABCJS.write.AbstractEngraver.prototype.createBeam = function() {
|
293
|
+
var abselemset = [];
|
294
|
+
|
295
|
+
if (this.getElem().startBeam && !this.getElem().endBeam) {
|
296
|
+
var dir = this.calcBeamDir();
|
297
|
+
var beamelem = new ABCJS.write.BeamElem(this.stemHeight, dir);
|
298
|
+
var oldDir = this.stemdir;
|
299
|
+
this.stemdir = dir;
|
300
|
+
while (this.getElem()) {
|
301
|
+
var abselem = this.createNote(this.getElem(),true);
|
302
|
+
abselemset.push(abselem);
|
303
|
+
beamelem.add(abselem);
|
304
|
+
if (this.getElem().endBeam) {
|
305
|
+
break;
|
306
|
+
}
|
307
|
+
this.pos++;
|
308
|
+
}
|
309
|
+
this.stemdir = oldDir;
|
310
|
+
this.voice.addOther(beamelem);
|
311
|
+
} else {
|
312
|
+
abselemset[0] = this.createNote(this.getElem());
|
313
|
+
}
|
314
|
+
return abselemset;
|
315
|
+
};
|
316
|
+
|
317
|
+
ABCJS.write.sortPitch = function(elem) {
|
318
|
+
var sorted;
|
319
|
+
do {
|
320
|
+
sorted = true;
|
321
|
+
for (var p = 0; p<elem.pitches.length-1; p++) {
|
322
|
+
if (elem.pitches[p].pitch>elem.pitches[p+1].pitch) {
|
323
|
+
sorted = false;
|
324
|
+
var tmp = elem.pitches[p];
|
325
|
+
elem.pitches[p] = elem.pitches[p+1];
|
326
|
+
elem.pitches[p+1] = tmp;
|
327
|
+
}
|
328
|
+
}
|
329
|
+
} while (!sorted);
|
330
|
+
};
|
331
|
+
|
332
|
+
ABCJS.write.AbstractEngraver.prototype.createNote = function(elem, nostem, dontDraw) { //stem presence: true for drawing stemless notehead
|
333
|
+
var notehead = null;
|
334
|
+
var grace= null;
|
335
|
+
this.roomtaken = 0; // room needed to the left of the note
|
336
|
+
this.roomtakenright = 0; // room needed to the right of the note
|
337
|
+
var dotshiftx = 0; // room taken by chords with displaced noteheads which cause dots to shift
|
338
|
+
var c="";
|
339
|
+
var flag = null;
|
340
|
+
var additionalLedgers = []; // PER: handle the case of [bc'], where the b doesn't have a ledger line
|
341
|
+
|
342
|
+
var p, i, pp;
|
343
|
+
var width, p1, p2, dx;
|
344
|
+
|
345
|
+
var duration = ABCJS.write.getDuration(elem);
|
346
|
+
var zeroDuration = false;
|
347
|
+
if (duration === 0) { zeroDuration = true; duration = 0.25; nostem = true; } //PER: zero duration will draw a quarter note head.
|
348
|
+
var durlog = Math.floor(Math.log(duration)/Math.log(2)); //TODO use getDurlog
|
349
|
+
var dot=0;
|
350
|
+
|
351
|
+
for (var tot = Math.pow(2,durlog), inc=tot/2; tot<duration; dot++,tot+=inc,inc/=2);
|
352
|
+
|
353
|
+
|
354
|
+
if (elem.startTriplet) {
|
355
|
+
if (elem.startTriplet === 2)
|
356
|
+
this.tripletmultiplier = 3/2;
|
357
|
+
else
|
358
|
+
this.tripletmultiplier=(elem.startTriplet-1)/elem.startTriplet;
|
359
|
+
}
|
360
|
+
|
361
|
+
|
362
|
+
var abselem = new ABCJS.write.AbsoluteElement(elem, duration * this.tripletmultiplier, 1, 'note');
|
363
|
+
|
364
|
+
|
365
|
+
if (elem.rest) {
|
366
|
+
var restpitch = 7;
|
367
|
+
if (this.stemdir==="down") restpitch = 3;
|
368
|
+
if (this.stemdir==="up") restpitch = 11;
|
369
|
+
// There is special placement for the percussion staff. If there is one staff line, then move the rest position.
|
370
|
+
var numLines = this.staffgroup.staffs[this.staffgroup.staffs.length-1].lines;
|
371
|
+
if (numLines === 1) {
|
372
|
+
// The half and whole rests are attached to different lines normally, so we need to tweak their position to get them to both be attached to the same one.
|
373
|
+
if (duration < 0.5)
|
374
|
+
restpitch = 7;
|
375
|
+
else if (duration < 1)
|
376
|
+
restpitch = 6.8; // half rest
|
377
|
+
else
|
378
|
+
restpitch = 4.8; // whole rest
|
379
|
+
}
|
380
|
+
switch(elem.rest.type) {
|
381
|
+
case "whole":
|
382
|
+
c = this.chartable.rest[0];
|
383
|
+
elem.averagepitch=restpitch;
|
384
|
+
elem.minpitch=restpitch;
|
385
|
+
elem.maxpitch=restpitch;
|
386
|
+
dot = 0;
|
387
|
+
break;
|
388
|
+
case "rest":
|
389
|
+
c = this.chartable.rest[-durlog];
|
390
|
+
elem.averagepitch=restpitch;
|
391
|
+
elem.minpitch=restpitch;
|
392
|
+
elem.maxpitch=restpitch;
|
393
|
+
break;
|
394
|
+
case "invisible":
|
395
|
+
case "spacer":
|
396
|
+
c="";
|
397
|
+
elem.averagepitch=restpitch;
|
398
|
+
elem.minpitch=restpitch;
|
399
|
+
elem.maxpitch=restpitch;
|
400
|
+
}
|
401
|
+
if (!dontDraw)
|
402
|
+
notehead = this.createNoteHead(abselem, c, {verticalPos:restpitch}, null, 0, -this.roomtaken, null, dot, 0, 1);
|
403
|
+
if (notehead) abselem.addHead(notehead);
|
404
|
+
this.roomtaken+=this.accidentalshiftx;
|
405
|
+
this.roomtakenright = Math.max(this.roomtakenright,this.dotshiftx);
|
406
|
+
|
407
|
+
} else {
|
408
|
+
ABCJS.write.sortPitch(elem);
|
409
|
+
|
410
|
+
// determine averagepitch, minpitch, maxpitch and stem direction
|
411
|
+
var sum=0;
|
412
|
+
for (p=0, pp=elem.pitches.length; p<pp; p++) {
|
413
|
+
sum += elem.pitches[p].verticalPos;
|
414
|
+
}
|
415
|
+
elem.averagepitch = sum/elem.pitches.length;
|
416
|
+
elem.minpitch = elem.pitches[0].verticalPos;
|
417
|
+
this.minY = Math.min(elem.minpitch, this.minY);
|
418
|
+
elem.maxpitch = elem.pitches[elem.pitches.length-1].verticalPos;
|
419
|
+
var dir = (elem.averagepitch>=6) ? "down": "up";
|
420
|
+
if (this.stemdir) dir=this.stemdir;
|
421
|
+
|
422
|
+
var style = elem.style ? elem.style : this.style; // get the style of note head.
|
423
|
+
if (!style || style === "normal") style = "note";
|
424
|
+
var noteSymbol;
|
425
|
+
if (zeroDuration)
|
426
|
+
noteSymbol = this.chartable[style].nostem;
|
427
|
+
else
|
428
|
+
noteSymbol = this.chartable[style][-durlog];
|
429
|
+
if (!noteSymbol)
|
430
|
+
console.log("noteSymbol:", style, durlog, zeroDuration);
|
431
|
+
|
432
|
+
// determine elements of chords which should be shifted
|
433
|
+
for (p=(dir==="down")?elem.pitches.length-2:1; (dir==="down")?p>=0:p<elem.pitches.length; p=(dir==="down")?p-1:p+1) {
|
434
|
+
var prev = elem.pitches[(dir==="down")?p+1:p-1];
|
435
|
+
var curr = elem.pitches[p];
|
436
|
+
var delta = (dir==="down")?prev.pitch-curr.pitch:curr.pitch-prev.pitch;
|
437
|
+
if (delta<=1 && !prev.printer_shift) {
|
438
|
+
curr.printer_shift=(delta)?"different":"same";
|
439
|
+
if (curr.verticalPos > 11 || curr.verticalPos < 1) { // PER: add extra ledger line
|
440
|
+
additionalLedgers.push(curr.verticalPos - (curr.verticalPos%2));
|
441
|
+
}
|
442
|
+
if (dir==="down") {
|
443
|
+
this.roomtaken = ABCJS.write.glyphs.getSymbolWidth(noteSymbol)+2;
|
444
|
+
} else {
|
445
|
+
dotshiftx = ABCJS.write.glyphs.getSymbolWidth(noteSymbol)+2;
|
446
|
+
}
|
447
|
+
}
|
448
|
+
}
|
449
|
+
|
450
|
+
// The accidentalSlot will hold a list of all the accidentals on this chord. Each element is a vertical place,
|
451
|
+
// and contains a pitch, which is the last pitch that contains an accidental in that slot. The slots are numbered
|
452
|
+
// from closest to the note to farther left. We only need to know the last accidental we placed because
|
453
|
+
// we know that the pitches are sorted by now.
|
454
|
+
this.accidentalSlot = [];
|
455
|
+
|
456
|
+
for (p=0; p<elem.pitches.length; p++) {
|
457
|
+
|
458
|
+
if (!nostem) {
|
459
|
+
if ((dir==="down" && p!==0) || (dir==="up" && p!==pp-1)) { // not the stemmed elem of the chord
|
460
|
+
flag = null;
|
461
|
+
} else {
|
462
|
+
flag = this.chartable[(dir==="down")?"dflags":"uflags"][-durlog];
|
463
|
+
}
|
464
|
+
}
|
465
|
+
c = noteSymbol;
|
466
|
+
// The highest position for the sake of placing slurs is itself if the slur is internal. It is the highest position possible if the slur is for the whole chord.
|
467
|
+
// If the note is the only one in the chord, then any slur it has counts as if it were on the whole chord.
|
468
|
+
elem.pitches[p].highestVert = elem.pitches[p].verticalPos;
|
469
|
+
var isTopWhenStemIsDown = (this.stemdir==="up" || dir==="up") && p===0;
|
470
|
+
var isBottomWhenStemIsUp = (this.stemdir==="down" || dir==="down") && p===pp-1;
|
471
|
+
if (!dontDraw && (isTopWhenStemIsDown || isBottomWhenStemIsUp)) { // place to put slurs if not already on pitches
|
472
|
+
|
473
|
+
if (elem.startSlur || pp === 1) {
|
474
|
+
elem.pitches[p].highestVert = elem.pitches[pp-1].verticalPos;
|
475
|
+
if (this.stemdir==="up" || dir==="up")
|
476
|
+
elem.pitches[p].highestVert += 6; // If the stem is up, then compensate for the length of the stem
|
477
|
+
}
|
478
|
+
if (elem.startSlur) {
|
479
|
+
if (!elem.pitches[p].startSlur) elem.pitches[p].startSlur = []; //TODO possibly redundant, provided array is not optional
|
480
|
+
for (i=0; i<elem.startSlur.length; i++) {
|
481
|
+
elem.pitches[p].startSlur.push(elem.startSlur[i]);
|
482
|
+
}
|
483
|
+
}
|
484
|
+
|
485
|
+
if (!dontDraw && elem.endSlur) {
|
486
|
+
elem.pitches[p].highestVert = elem.pitches[pp-1].verticalPos;
|
487
|
+
if (this.stemdir==="up" || dir==="up")
|
488
|
+
elem.pitches[p].highestVert += 6; // If the stem is up, then compensate for the length of the stem
|
489
|
+
if (!elem.pitches[p].endSlur) elem.pitches[p].endSlur = []; //TODO possibly redundant, provided array is not optional
|
490
|
+
for (i=0; i<elem.endSlur.length; i++) {
|
491
|
+
elem.pitches[p].endSlur.push(elem.endSlur[i]);
|
492
|
+
}
|
493
|
+
}
|
494
|
+
}
|
495
|
+
|
496
|
+
var hasStem = !nostem && durlog<=-1;
|
497
|
+
if (!dontDraw)
|
498
|
+
notehead = this.createNoteHead(abselem, c, elem.pitches[p], hasStem ? dir : null, 0, -this.roomtaken, flag, dot, dotshiftx, 1);
|
499
|
+
if (notehead) abselem.addHead(notehead);
|
500
|
+
this.roomtaken += this.accidentalshiftx;
|
501
|
+
this.roomtakenright = Math.max(this.roomtakenright,this.dotshiftx);
|
502
|
+
}
|
503
|
+
|
504
|
+
// draw stem from the furthest note to a pitch above/below the stemmed note
|
505
|
+
if (hasStem) {
|
506
|
+
p1 = (dir==="down") ? elem.minpitch-7 : elem.minpitch+1/3;
|
507
|
+
// PER added stemdir test to make the line meet the note.
|
508
|
+
if (p1>6 && !this.stemdir) p1=6;
|
509
|
+
p2 = (dir==="down") ? elem.maxpitch-1/3 : elem.maxpitch+7;
|
510
|
+
// PER added stemdir test to make the line meet the note.
|
511
|
+
if (p2<6 && !this.stemdir) p2=6;
|
512
|
+
dx = (dir==="down" || abselem.heads.length === 0)?0:abselem.heads[0].w;
|
513
|
+
width = (dir==="down")?1:-1;
|
514
|
+
// TODO-PER-HACK: One type of note head has a different placement of the stem. This should be more generically calculated:
|
515
|
+
if (notehead.c === 'noteheads.slash.quarter') {
|
516
|
+
if (dir === 'down')
|
517
|
+
p2 -= 1;
|
518
|
+
else
|
519
|
+
p1 += 1;
|
520
|
+
}
|
521
|
+
abselem.addExtra(new ABCJS.write.RelativeElement(null, dx, 0, p1, {"type": "stem", "pitch2":p2, linewidth: width}));
|
522
|
+
this.minY = Math.min(p1, this.minY);
|
523
|
+
this.minY = Math.min(p2, this.minY);
|
524
|
+
}
|
525
|
+
|
526
|
+
}
|
527
|
+
|
528
|
+
if (elem.lyric !== undefined) {
|
529
|
+
var lyricStr = "";
|
530
|
+
window.ABCJS.parse.each(elem.lyric, function(ly) {
|
531
|
+
lyricStr += ly.syllable + ly.divider + "\n";
|
532
|
+
});
|
533
|
+
// TODO-PER: get the width of the lyric and use that for "0, lyricStr.length*5" below.
|
534
|
+
var position = elem.positioning ? elem.positioning.vocalPosition : 'below';
|
535
|
+
abselem.addRight(new ABCJS.write.RelativeElement(lyricStr, 0, lyricStr.length*5, undefined, {type:"lyric", position: position }));
|
536
|
+
}
|
537
|
+
|
538
|
+
if (!dontDraw && elem.gracenotes !== undefined) {
|
539
|
+
var gracescale = 3/5;
|
540
|
+
var graceScaleStem = 3.5/5; // TODO-PER: empirically found constant.
|
541
|
+
var gracebeam = null;
|
542
|
+
if (elem.gracenotes.length>1) {
|
543
|
+
gracebeam = new ABCJS.write.BeamElem(this.stemHeight*graceScaleStem, "grace",this.isBagpipes);
|
544
|
+
gracebeam.mainNote = abselem; // this gives us a reference back to the note this is attached to so that the stems can be attached somewhere.
|
545
|
+
}
|
546
|
+
|
547
|
+
var graceoffsets = [];
|
548
|
+
for (i=elem.gracenotes.length-1; i>=0; i--) { // figure out where to place each gracenote
|
549
|
+
this.roomtaken+=10;
|
550
|
+
graceoffsets[i] = this.roomtaken;
|
551
|
+
if (elem.gracenotes[i].accidental) {
|
552
|
+
this.roomtaken+=7;
|
553
|
+
}
|
554
|
+
}
|
555
|
+
|
556
|
+
for (i=0; i<elem.gracenotes.length; i++) {
|
557
|
+
var gracepitch = elem.gracenotes[i].verticalPos;
|
558
|
+
|
559
|
+
flag = (gracebeam) ? null : this.chartable.uflags[(this.isBagpipes)?5:3];
|
560
|
+
grace = this.createNoteHead(abselem, "noteheads.quarter", elem.gracenotes[i], "up", -graceoffsets[i], -graceoffsets[i], flag, 0, 0, gracescale);
|
561
|
+
abselem.addExtra(grace);
|
562
|
+
// PER: added acciaccatura slash
|
563
|
+
if (elem.gracenotes[i].acciaccatura) {
|
564
|
+
var pos = elem.gracenotes[i].verticalPos+7*gracescale; // the same formula that determines the flag position.
|
565
|
+
var dAcciaccatura = gracebeam ? 5 : 6; // just an offset to make it line up correctly.
|
566
|
+
abselem.addRight(new ABCJS.write.RelativeElement("flags.ugrace", -graceoffsets[i]+dAcciaccatura, 0, pos, {scalex:gracescale, scaley: gracescale}));
|
567
|
+
}
|
568
|
+
if (gracebeam) { // give the beam the necessary info
|
569
|
+
var graceDuration = elem.gracenotes[i].duration / 2;
|
570
|
+
if (this.isBagpipes) graceDuration /= 2;
|
571
|
+
var pseudoabselem = {heads:[grace],
|
572
|
+
abcelem:{averagepitch: gracepitch, minpitch: gracepitch, maxpitch: gracepitch, duration: graceDuration }};
|
573
|
+
gracebeam.add(pseudoabselem);
|
574
|
+
} else { // draw the stem
|
575
|
+
p1 = gracepitch+1/3*gracescale;
|
576
|
+
p2 = gracepitch+7*gracescale;
|
577
|
+
dx = grace.dx + grace.w;
|
578
|
+
width = -0.6;
|
579
|
+
abselem.addExtra(new ABCJS.write.RelativeElement(null, dx, 0, p1, {"type": "stem", "pitch2":p2, linewidth: width}));
|
580
|
+
}
|
581
|
+
|
582
|
+
if (i===0 && !this.isBagpipes && !(elem.rest && (elem.rest.type==="spacer"||elem.rest.type==="invisible"))) this.voice.addOther(new ABCJS.write.TieElem(grace, notehead, false, true, false));
|
583
|
+
}
|
584
|
+
|
585
|
+
if (gracebeam) {
|
586
|
+
this.voice.addOther(gracebeam);
|
587
|
+
}
|
588
|
+
}
|
589
|
+
|
590
|
+
if (!dontDraw && elem.decoration) {
|
591
|
+
this.decoration.createDecoration(this.voice, elem.decoration, abselem.top, (notehead)?notehead.w:0, abselem, this.roomtaken, dir, abselem.bottom, elem.positioning, this.hasVocals);
|
592
|
+
}
|
593
|
+
|
594
|
+
if (elem.barNumber) {
|
595
|
+
abselem.addChild(new ABCJS.write.RelativeElement(elem.barNumber, -10, 0, 0, {type:"barNumber"}));
|
596
|
+
}
|
597
|
+
|
598
|
+
// ledger lines
|
599
|
+
for (i=elem.maxpitch; i>11; i--) {
|
600
|
+
if (i%2===0 && !elem.rest) {
|
601
|
+
abselem.addChild(new ABCJS.write.RelativeElement(null, -2, ABCJS.write.glyphs.getSymbolWidth(c)+4, i, {type:"ledger"}));
|
602
|
+
}
|
603
|
+
}
|
604
|
+
|
605
|
+
for (i=elem.minpitch; i<1; i++) {
|
606
|
+
if (i%2===0 && !elem.rest) {
|
607
|
+
abselem.addChild(new ABCJS.write.RelativeElement(null, -2, ABCJS.write.glyphs.getSymbolWidth(c)+4, i, {type:"ledger"}));
|
608
|
+
}
|
609
|
+
}
|
610
|
+
|
611
|
+
for (i = 0; i < additionalLedgers.length; i++) { // PER: draw additional ledgers
|
612
|
+
var ofs = ABCJS.write.glyphs.getSymbolWidth(c);
|
613
|
+
if (dir === 'down') ofs = -ofs;
|
614
|
+
abselem.addChild(new ABCJS.write.RelativeElement(null, ofs-2, ABCJS.write.glyphs.getSymbolWidth(c)+4, additionalLedgers[i], {type:"ledger"}));
|
615
|
+
}
|
616
|
+
|
617
|
+
if (elem.chord !== undefined) {
|
618
|
+
for (i = 0; i < elem.chord.length; i++) {
|
619
|
+
var x = 0;
|
620
|
+
var y;
|
621
|
+
switch (elem.chord[i].position) {
|
622
|
+
case "left":
|
623
|
+
this.roomtaken+=7;
|
624
|
+
x = -this.roomtaken; // TODO-PER: This is just a guess from trial and error
|
625
|
+
y = elem.averagepitch;
|
626
|
+
// TODO-PER: get the width of the lyric and use that for "0, lyricStr.length*5" below.
|
627
|
+
abselem.addExtra(new ABCJS.write.RelativeElement(elem.chord[i].name, x, ABCJS.write.glyphs.getSymbolWidth(elem.chord[i].name[0])+4, y, {type:"text"}));
|
628
|
+
break;
|
629
|
+
case "right":
|
630
|
+
this.roomtakenright+=4;
|
631
|
+
x = this.roomtakenright;// TODO-PER: This is just a guess from trial and error
|
632
|
+
y = elem.averagepitch;
|
633
|
+
// TODO-PER: get the width of the lyric and use that for "0, lyricStr.length*5" below.
|
634
|
+
abselem.addRight(new ABCJS.write.RelativeElement(elem.chord[i].name, x, ABCJS.write.glyphs.getSymbolWidth(elem.chord[i].name[0])+4, y, {type:"text"}));
|
635
|
+
break;
|
636
|
+
case "below":
|
637
|
+
// setting the y-coordinate to undefined for now: it will be overwritten later one, after we figure out what the highest element on the line is.
|
638
|
+
var eachLine = elem.chord[i].name.split("\n");
|
639
|
+
for (var ii = 0; ii < eachLine.length; ii++) {
|
640
|
+
abselem.addChild(new ABCJS.write.RelativeElement(eachLine[ii], x, 0, undefined, {type:"text", position: "below"}));
|
641
|
+
}
|
642
|
+
break;
|
643
|
+
case "above":
|
644
|
+
// setting the y-coordinate to undefined for now: it will be overwritten later one, after we figure out what the highest element on the line is.
|
645
|
+
abselem.addChild(new ABCJS.write.RelativeElement(elem.chord[i].name, x, 0, undefined, {type: "text"}));
|
646
|
+
break;
|
647
|
+
default:
|
648
|
+
if (elem.chord[i].rel_position) {
|
649
|
+
var relPositionY = elem.chord[i].rel_position.y + 3*ABCJS.write.spacing.STEP; // TODO-PER: this is a fudge factor to make it line up with abcm2ps
|
650
|
+
abselem.addChild(new ABCJS.write.RelativeElement(elem.chord[i].name, x + elem.chord[i].rel_position.x, 0, elem.minpitch + relPositionY / ABCJS.write.spacing.STEP, {type: "text"}));
|
651
|
+
} else {
|
652
|
+
// setting the y-coordinate to undefined for now: it will be overwritten later one, after we figure out what the highest element on the line is.
|
653
|
+
var pos2 = 'above';
|
654
|
+
if (elem.positioning && elem.positioning.chordPosition)
|
655
|
+
pos2 = elem.positioning.chordPosition;
|
656
|
+
|
657
|
+
abselem.addChild(new ABCJS.write.RelativeElement(elem.chord[i].name, x, 0, undefined, {type: "chord", position: pos2 }));
|
658
|
+
}
|
659
|
+
}
|
660
|
+
}
|
661
|
+
}
|
662
|
+
|
663
|
+
|
664
|
+
if (elem.startTriplet) {
|
665
|
+
this.triplet = new ABCJS.write.TripletElem(elem.startTriplet, notehead, null, true); // above is opposite from case of slurs
|
666
|
+
if (!dontDraw)
|
667
|
+
this.voice.addOther(this.triplet);
|
668
|
+
}
|
669
|
+
|
670
|
+
if (elem.endTriplet && this.triplet) {
|
671
|
+
this.triplet.setCloseAnchor(notehead);
|
672
|
+
this.triplet = null;
|
673
|
+
this.tripletmultiplier = 1;
|
674
|
+
}
|
675
|
+
|
676
|
+
return abselem;
|
677
|
+
};
|
678
|
+
|
679
|
+
|
680
|
+
|
681
|
+
|
682
|
+
ABCJS.write.AbstractEngraver.prototype.createNoteHead = function(abselem, c, pitchelem, dir, headx, extrax, flag, dot, dotshiftx, scale) {
|
683
|
+
|
684
|
+
// TODO scale the dot as well
|
685
|
+
var pitch = pitchelem.verticalPos;
|
686
|
+
var notehead;
|
687
|
+
var i;
|
688
|
+
this.accidentalshiftx = 0;
|
689
|
+
this.dotshiftx = 0;
|
690
|
+
if (c === undefined)
|
691
|
+
abselem.addChild(new ABCJS.write.RelativeElement("pitch is undefined", 0, 0, 0, {type:"debug"}));
|
692
|
+
else if (c==="") {
|
693
|
+
notehead = new ABCJS.write.RelativeElement(null, 0, 0, pitch);
|
694
|
+
} else {
|
695
|
+
var shiftheadx = headx;
|
696
|
+
if (pitchelem.printer_shift) {
|
697
|
+
var adjust = (pitchelem.printer_shift==="same")?1:0;
|
698
|
+
shiftheadx = (dir==="down")?-ABCJS.write.glyphs.getSymbolWidth(c)*scale+adjust:ABCJS.write.glyphs.getSymbolWidth(c)*scale-adjust;
|
699
|
+
}
|
700
|
+
var opts = {scalex:scale, scaley: scale, thickness: ABCJS.write.glyphs.symbolHeightInPitches(c)*scale };
|
701
|
+
//if (dir)
|
702
|
+
// opts.stemHeight = ((dir==="down")?-this.stemHeight:this.stemHeight);
|
703
|
+
notehead = new ABCJS.write.RelativeElement(c, shiftheadx, ABCJS.write.glyphs.getSymbolWidth(c)*scale, pitch, opts);
|
704
|
+
if (flag) {
|
705
|
+
var pos = pitch+((dir==="down")?-7:7)*scale;
|
706
|
+
if (scale===1 && (dir==="down")?(pos>6):(pos<6)) pos=6;
|
707
|
+
var xdelta = (dir==="down")?headx:headx+notehead.w-0.6;
|
708
|
+
abselem.addRight(new ABCJS.write.RelativeElement(flag, xdelta, ABCJS.write.glyphs.getSymbolWidth(flag)*scale, pos, {scalex:scale, scaley: scale}));
|
709
|
+
}
|
710
|
+
this.dotshiftx = notehead.w+dotshiftx-2+5*dot;
|
711
|
+
for (;dot>0;dot--) {
|
712
|
+
var dotadjusty = (1-Math.abs(pitch)%2); //PER: take abs value of the pitch. And the shift still happens on ledger lines.
|
713
|
+
abselem.addRight(new ABCJS.write.RelativeElement("dots.dot", notehead.w+dotshiftx-2+5*dot, ABCJS.write.glyphs.getSymbolWidth("dots.dot"), pitch+dotadjusty));
|
714
|
+
}
|
715
|
+
}
|
716
|
+
if (notehead)
|
717
|
+
notehead.highestVert = pitchelem.highestVert;
|
718
|
+
|
719
|
+
if (pitchelem.accidental) {
|
720
|
+
var symb;
|
721
|
+
switch (pitchelem.accidental) {
|
722
|
+
case "quartersharp":
|
723
|
+
symb = "accidentals.halfsharp";
|
724
|
+
break;
|
725
|
+
case "dblsharp":
|
726
|
+
symb = "accidentals.dblsharp";
|
727
|
+
break;
|
728
|
+
case "sharp":
|
729
|
+
symb = "accidentals.sharp";
|
730
|
+
break;
|
731
|
+
case "quarterflat":
|
732
|
+
symb = "accidentals.halfflat";
|
733
|
+
break;
|
734
|
+
case "flat":
|
735
|
+
symb = "accidentals.flat";
|
736
|
+
break;
|
737
|
+
case "dblflat":
|
738
|
+
symb = "accidentals.dblflat";
|
739
|
+
break;
|
740
|
+
case "natural":
|
741
|
+
symb = "accidentals.nat";
|
742
|
+
}
|
743
|
+
// if a note is at least a sixth away, it can share a slot with another accidental
|
744
|
+
var accSlotFound = false;
|
745
|
+
var accPlace = extrax;
|
746
|
+
for (var j = 0; j < this.accidentalSlot.length; j++) {
|
747
|
+
if (pitch - this.accidentalSlot[j][0] >= 6) {
|
748
|
+
this.accidentalSlot[j][0] = pitch;
|
749
|
+
accPlace = this.accidentalSlot[j][1];
|
750
|
+
accSlotFound = true;
|
751
|
+
break;
|
752
|
+
}
|
753
|
+
}
|
754
|
+
if (accSlotFound === false) {
|
755
|
+
accPlace -= (ABCJS.write.glyphs.getSymbolWidth(symb)*scale+2);
|
756
|
+
this.accidentalSlot.push([pitch,accPlace]);
|
757
|
+
this.accidentalshiftx = (ABCJS.write.glyphs.getSymbolWidth(symb)*scale+2);
|
758
|
+
}
|
759
|
+
abselem.addExtra(new ABCJS.write.RelativeElement(symb, accPlace, ABCJS.write.glyphs.getSymbolWidth(symb), pitch, {scalex:scale, scaley: scale}));
|
760
|
+
}
|
761
|
+
|
762
|
+
if (pitchelem.endTie) {
|
763
|
+
if (this.ties[0]) {
|
764
|
+
this.ties[0].setEndAnchor(notehead);
|
765
|
+
this.ties = this.ties.slice(1,this.ties.length);
|
766
|
+
}
|
767
|
+
}
|
768
|
+
|
769
|
+
if (pitchelem.startTie) {
|
770
|
+
//PER: bug fix: var tie = new ABCJS.write.TieElem(notehead, null, (this.stemdir=="up" || dir=="down") && this.stemdir!="down",(this.stemdir=="down" || this.stemdir=="up"));
|
771
|
+
var tie = new ABCJS.write.TieElem(notehead, null, (this.stemdir==="down" || dir==="down") && this.stemdir!=="up",(this.stemdir==="down" || this.stemdir==="up"), true);
|
772
|
+
this.ties[this.ties.length]=tie;
|
773
|
+
this.voice.addOther(tie);
|
774
|
+
// HACK-PER: For the animation, we need to know if a note is tied to the next one, so here's a flag.
|
775
|
+
// Unfortunately, only some of the notes in the current event might be tied, but this will consider it
|
776
|
+
// tied if any one of them is. That will work for most cases.
|
777
|
+
abselem.startTie = true;
|
778
|
+
}
|
779
|
+
|
780
|
+
if (pitchelem.endSlur) {
|
781
|
+
for (i=0; i<pitchelem.endSlur.length; i++) {
|
782
|
+
var slurid = pitchelem.endSlur[i];
|
783
|
+
var slur;
|
784
|
+
if (this.slurs[slurid]) {
|
785
|
+
slur = this.slurs[slurid];
|
786
|
+
slur.setEndAnchor(notehead);
|
787
|
+
delete this.slurs[slurid];
|
788
|
+
} else {
|
789
|
+
slur = new ABCJS.write.TieElem(null, notehead, dir==="down",(this.stemdir==="up" || dir==="down") && this.stemdir!=="down", false);
|
790
|
+
this.voice.addOther(slur);
|
791
|
+
}
|
792
|
+
if (this.startlimitelem) {
|
793
|
+
slur.setStartX(this.startlimitelem);
|
794
|
+
}
|
795
|
+
}
|
796
|
+
}
|
797
|
+
|
798
|
+
if (pitchelem.startSlur) {
|
799
|
+
for (i=0; i<pitchelem.startSlur.length; i++) {
|
800
|
+
var slurid = pitchelem.startSlur[i].label;
|
801
|
+
//PER: bug fix: var slur = new ABCJS.write.TieElem(notehead, null, (this.stemdir=="up" || dir=="down") && this.stemdir!="down", this.stemdir);
|
802
|
+
var slur = new ABCJS.write.TieElem(notehead, null, (this.stemdir==="down" || dir==="down") && this.stemdir!=="up", false, false);
|
803
|
+
this.slurs[slurid]=slur;
|
804
|
+
this.voice.addOther(slur);
|
805
|
+
}
|
806
|
+
}
|
807
|
+
|
808
|
+
return notehead;
|
809
|
+
|
810
|
+
};
|
811
|
+
|
812
|
+
ABCJS.write.AbstractEngraver.prototype.createBarLine = function (elem) {
|
813
|
+
// bar_thin, bar_thin_thick, bar_thin_thin, bar_thick_thin, bar_right_repeat, bar_left_repeat, bar_double_repeat
|
814
|
+
|
815
|
+
var abselem = new ABCJS.write.AbsoluteElement(elem, 0, 10, 'bar');
|
816
|
+
var anchor = null; // place to attach part lines
|
817
|
+
var dx = 0;
|
818
|
+
|
819
|
+
|
820
|
+
|
821
|
+
var firstdots = (elem.type==="bar_right_repeat" || elem.type==="bar_dbl_repeat");
|
822
|
+
var firstthin = (elem.type!=="bar_left_repeat" && elem.type!=="bar_thick_thin" && elem.type!=="bar_invisible");
|
823
|
+
var thick = (elem.type==="bar_right_repeat" || elem.type==="bar_dbl_repeat" || elem.type==="bar_left_repeat" ||
|
824
|
+
elem.type==="bar_thin_thick" || elem.type==="bar_thick_thin");
|
825
|
+
var secondthin = (elem.type==="bar_left_repeat" || elem.type==="bar_thick_thin" || elem.type==="bar_thin_thin" || elem.type==="bar_dbl_repeat");
|
826
|
+
var seconddots = (elem.type==="bar_left_repeat" || elem.type==="bar_dbl_repeat");
|
827
|
+
|
828
|
+
// limit positioning of slurs
|
829
|
+
if (firstdots || seconddots) {
|
830
|
+
for (var slur in this.slurs) {
|
831
|
+
if (this.slurs.hasOwnProperty(slur)) {
|
832
|
+
this.slurs[slur].setEndX(abselem);
|
833
|
+
}
|
834
|
+
}
|
835
|
+
this.startlimitelem = abselem;
|
836
|
+
}
|
837
|
+
|
838
|
+
if (firstdots) {
|
839
|
+
abselem.addRight(new ABCJS.write.RelativeElement("dots.dot", dx, 1, 7));
|
840
|
+
abselem.addRight(new ABCJS.write.RelativeElement("dots.dot", dx, 1, 5));
|
841
|
+
dx+=6; //2 hardcoded, twice;
|
842
|
+
}
|
843
|
+
|
844
|
+
if (firstthin) {
|
845
|
+
anchor = new ABCJS.write.RelativeElement(null, dx, 1, 2, {"type": "bar", "pitch2":10, linewidth:0.6});
|
846
|
+
abselem.addRight(anchor);
|
847
|
+
}
|
848
|
+
|
849
|
+
if (elem.type==="bar_invisible") {
|
850
|
+
anchor = new ABCJS.write.RelativeElement(null, dx, 1, 2, {"type": "none", "pitch2":10, linewidth:0.6});
|
851
|
+
abselem.addRight(anchor);
|
852
|
+
}
|
853
|
+
|
854
|
+
if (elem.decoration) {
|
855
|
+
this.decoration.createDecoration(this.voice, elem.decoration, 12, (thick)?3:1, abselem, 0, "down", 2, elem.positioning, this.hasVocals);
|
856
|
+
}
|
857
|
+
|
858
|
+
if (thick) {
|
859
|
+
dx+=4; //3 hardcoded;
|
860
|
+
anchor = new ABCJS.write.RelativeElement(null, dx, 4, 2, {"type": "bar", "pitch2":10, linewidth:4});
|
861
|
+
abselem.addRight(anchor);
|
862
|
+
dx+=5;
|
863
|
+
}
|
864
|
+
|
865
|
+
// if (this.partstartelem && (thick || (firstthin && secondthin))) { // means end of nth part
|
866
|
+
// this.partstartelem.anchor2=anchor;
|
867
|
+
// this.partstartelem = null;
|
868
|
+
// }
|
869
|
+
|
870
|
+
if (this.partstartelem && elem.endEnding) {
|
871
|
+
this.partstartelem.anchor2=anchor;
|
872
|
+
this.partstartelem = null;
|
873
|
+
}
|
874
|
+
|
875
|
+
if (secondthin) {
|
876
|
+
dx+=3; //3 hardcoded;
|
877
|
+
anchor = new ABCJS.write.RelativeElement(null, dx, 1, 2, {"type": "bar", "pitch2":10, linewidth:0.6});
|
878
|
+
abselem.addRight(anchor); // 3 is hardcoded
|
879
|
+
}
|
880
|
+
|
881
|
+
if (seconddots) {
|
882
|
+
dx+=3; //3 hardcoded;
|
883
|
+
abselem.addRight(new ABCJS.write.RelativeElement("dots.dot", dx, 1, 7));
|
884
|
+
abselem.addRight(new ABCJS.write.RelativeElement("dots.dot", dx, 1, 5));
|
885
|
+
} // 2 is hardcoded
|
886
|
+
|
887
|
+
if (elem.startEnding) {
|
888
|
+
var textWidth = this.renderer.getTextSize(elem.startEnding, "repeatfont", '').width;
|
889
|
+
abselem.minspacing += textWidth + 10; // Give plenty of room for the ending number.
|
890
|
+
this.partstartelem = new ABCJS.write.EndingElem(elem.startEnding, anchor, null);
|
891
|
+
this.voice.addOther(this.partstartelem);
|
892
|
+
}
|
893
|
+
|
894
|
+
return abselem;
|
895
|
+
|
896
|
+
};
|
897
|
+
|
898
|
+
|
899
|
+
})();
|