abcjs-rails 1.11 → 2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/abcjs/api/abc_animation.js +41 -1
  3. data/app/assets/javascripts/abcjs/api/abc_tunebook.js +5 -0
  4. data/app/assets/javascripts/abcjs/data/abc_tune.js +4 -3
  5. data/app/assets/javascripts/abcjs/edit/abc_editor.js +10 -0
  6. data/app/assets/javascripts/abcjs/parse/abc_parse.js +120 -19
  7. data/app/assets/javascripts/abcjs/parse/abc_parse_directive.js +456 -115
  8. data/app/assets/javascripts/abcjs/raphael.js +2 -2
  9. data/app/assets/javascripts/abcjs/write/abc_absolute_element.js +111 -4
  10. data/app/assets/javascripts/abcjs/write/abc_abstract_engraver.js +899 -0
  11. data/app/assets/javascripts/abcjs/write/abc_beam_element.js +263 -37
  12. data/app/assets/javascripts/abcjs/write/abc_create_clef.js +76 -0
  13. data/app/assets/javascripts/abcjs/write/abc_create_key_signature.js +41 -0
  14. data/app/assets/javascripts/abcjs/write/abc_create_time_signature.js +51 -0
  15. data/app/assets/javascripts/abcjs/write/{abc_cresendo_element.js → abc_crescendo_element.js} +32 -1
  16. data/app/assets/javascripts/abcjs/write/abc_decoration.js +321 -0
  17. data/app/assets/javascripts/abcjs/write/abc_dynamic_decoration.js +22 -1
  18. data/app/assets/javascripts/abcjs/write/abc_ending_element.js +31 -1
  19. data/app/assets/javascripts/abcjs/write/abc_engraver_controller.js +359 -0
  20. data/app/assets/javascripts/abcjs/write/abc_glyphs.js +119 -9
  21. data/app/assets/javascripts/abcjs/write/abc_layout.js +2 -2
  22. data/app/assets/javascripts/abcjs/write/abc_relative_element.js +106 -8
  23. data/app/assets/javascripts/abcjs/write/abc_renderer.js +754 -0
  24. data/app/assets/javascripts/abcjs/write/abc_staff_group_element.js +249 -9
  25. data/app/assets/javascripts/abcjs/write/abc_tempo_element.js +104 -0
  26. data/app/assets/javascripts/abcjs/write/abc_tie_element.js +69 -22
  27. data/app/assets/javascripts/abcjs/write/abc_triplet_element.js +77 -10
  28. data/app/assets/javascripts/abcjs/write/abc_voice_element.js +100 -8
  29. data/app/assets/javascripts/abcjs/write/abc_write.js +64 -68
  30. data/lib/abcjs-rails/version.rb +1 -1
  31. 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 = 100;
1542
+ p[ps].sleep = 1;
1543
1543
  } else {
1544
1544
  p[ps] = {
1545
- sleep: 100
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 = 7;
42
- this.top = 7;
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
- this.top = Math.max(top, this.top);
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
- this.bottom = Math.min(bottom, this.bottom);
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
+ })();