abcjs-rails 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,959 @@
1
+ // abc_layout.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
+ ABCJS.write.getDuration = function(elem) {
26
+ var d = 0;
27
+ if (elem.duration) {
28
+ d = elem.duration;
29
+ }
30
+ return d;
31
+ };
32
+
33
+ ABCJS.write.getDurlog = function(duration) {
34
+ return Math.floor(Math.log(duration)/Math.log(2));
35
+ };
36
+
37
+ ABCJS.write.Layout = function(glyphs, bagpipes) {
38
+ this.glyphs = glyphs;
39
+ this.isBagpipes = bagpipes;
40
+ 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"},
41
+ 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"},
42
+ uflags:{3:"flags.u8th", 4:"flags.u16th", 5:"flags.u32nd", 6:"flags.u64th"},
43
+ dflags:{3:"flags.d8th", 4:"flags.d16th", 5:"flags.d32nd", 6:"flags.d64th"}};
44
+ this.slurs = {};
45
+ this.ties = [];
46
+ this.slursbyvoice = {};
47
+ this.tiesbyvoice = {};
48
+ this.endingsbyvoice = {};
49
+ this.s = 0; // current staff number
50
+ this.v = 0; // current voice number on current staff
51
+ this.stafflines = 5;
52
+ };
53
+
54
+ ABCJS.write.Layout.prototype.getCurrentVoiceId = function() {
55
+ return "s"+this.s+"v"+this.v;
56
+ };
57
+
58
+ ABCJS.write.Layout.prototype.pushCrossLineElems = function() {
59
+ this.slursbyvoice[this.getCurrentVoiceId()] = this.slurs;
60
+ this.tiesbyvoice[this.getCurrentVoiceId()] = this.ties;
61
+ this.endingsbyvoice[this.getCurrentVoiceId()] = this.partstartelem;
62
+ };
63
+
64
+ ABCJS.write.Layout.prototype.popCrossLineElems = function() {
65
+ this.slurs = this.slursbyvoice[this.getCurrentVoiceId()] || {};
66
+ this.ties = this.tiesbyvoice[this.getCurrentVoiceId()] || [];
67
+ this.partstartelem = this.endingsbyvoice[this.getCurrentVoiceId()];
68
+ };
69
+
70
+ ABCJS.write.Layout.prototype.getElem = function() {
71
+ if (this.abcline.length <= this.pos)
72
+ return null;
73
+ return this.abcline[this.pos];
74
+ };
75
+
76
+ ABCJS.write.Layout.prototype.getNextElem = function() {
77
+ if (this.abcline.length <= this.pos+1)
78
+ return null;
79
+ return this.abcline[this.pos+1];
80
+ };
81
+
82
+ ABCJS.write.Layout.prototype.printABCLine = function(staffs) {
83
+ this.minY = 2; // PER: This is the lowest that any note reaches. It will be used to set the dynamics row.
84
+ this.staffgroup = new ABCJS.write.StaffGroupElement();
85
+ for (this.s = 0; this.s < staffs.length; this.s++) {
86
+ this.printABCStaff(staffs[this.s]);
87
+ }
88
+ return this.staffgroup;
89
+ };
90
+
91
+ ABCJS.write.Layout.prototype.printABCStaff = function(abcstaff) {
92
+
93
+ var header = "";
94
+ if (abcstaff.bracket) header += "bracket "+abcstaff.bracket+" ";
95
+ if (abcstaff.brace) header += "brace "+abcstaff.brace+" ";
96
+
97
+
98
+ for (this.v = 0; this.v < abcstaff.voices.length; this.v++) {
99
+ this.voice = new ABCJS.write.VoiceElement(this.v,abcstaff.voices.length);
100
+ if (this.v===0) {
101
+ this.voice.barfrom = (abcstaff.connectBarLines==="start" || abcstaff.connectBarLines==="continue");
102
+ this.voice.barto = (abcstaff.connectBarLines==="continue" || abcstaff.connectBarLines==="end");
103
+ } else {
104
+ this.voice.duplicate = true; // barlines and other duplicate info need not be printed
105
+ }
106
+ if (abcstaff.title && abcstaff.title[this.v]) this.voice.header=abcstaff.title[this.v];
107
+ // TODO make invisible if voice is duplicate
108
+ this.voice.addChild(this.printClef(abcstaff.clef));
109
+ this.voice.addChild(this.printKeySignature(abcstaff.key));
110
+ if (abcstaff.meter) this.voice.addChild(this.printTimeSignature(abcstaff.meter));
111
+ this.printABCVoice(abcstaff.voices[this.v]);
112
+ this.staffgroup.addVoice(this.voice,this.s,this.stafflines);
113
+ }
114
+
115
+ };
116
+
117
+ ABCJS.write.Layout.prototype.printABCVoice = function(abcline) {
118
+ this.popCrossLineElems();
119
+ this.stemdir = (this.isBagpipes)?"down":null;
120
+ this.abcline = abcline;
121
+ if (this.partstartelem) {
122
+ this.partstartelem = new ABCJS.write.EndingElem("", null, null);
123
+ this.voice.addOther(this.partstartelem);
124
+ }
125
+ for (var slur in this.slurs) {
126
+ if (this.slurs.hasOwnProperty(slur)) {
127
+ this.slurs[slur]= new ABCJS.write.TieElem(null, null, this.slurs[slur].above, this.slurs[slur].force);
128
+ this.voice.addOther(this.slurs[slur]);
129
+ }
130
+ }
131
+ for (var i=0; i<this.ties.length; i++) {
132
+ this.ties[i]=new ABCJS.write.TieElem(null, null, this.ties[i].above, this.ties[i].force);
133
+ this.voice.addOther(this.ties[i]);
134
+ }
135
+
136
+ for (this.pos=0; this.pos<this.abcline.length; this.pos++) {
137
+ var abselems = this.printABCElement();
138
+ for (i=0; i<abselems.length; i++) {
139
+ this.voice.addChild(abselems[i]);
140
+ }
141
+ }
142
+ this.pushCrossLineElems();
143
+ };
144
+
145
+
146
+ // return an array of ABCJS.write.AbsoluteElement
147
+ ABCJS.write.Layout.prototype.printABCElement = function() {
148
+ var elemset = [];
149
+ var elem = this.getElem();
150
+ switch (elem.el_type) {
151
+ case "note":
152
+ elemset = this.printBeam();
153
+ break;
154
+ case "bar":
155
+ elemset[0] = this.printBarLine(elem);
156
+ if (this.voice.duplicate) elemset[0].invisible = true;
157
+ break;
158
+ case "meter":
159
+ elemset[0] = this.printTimeSignature(elem);
160
+ if (this.voice.duplicate) elemset[0].invisible = true;
161
+ break;
162
+ case "clef":
163
+ elemset[0] = this.printClef(elem);
164
+ if (this.voice.duplicate) elemset[0].invisible = true;
165
+ break;
166
+ case "key":
167
+ elemset[0] = this.printKeySignature(elem);
168
+ if (this.voice.duplicate) elemset[0].invisible = true;
169
+ break;
170
+ case "stem":
171
+ this.stemdir=elem.direction;
172
+ break;
173
+ case "part":
174
+ var abselem = new ABCJS.write.AbsoluteElement(elem,0,0);
175
+ abselem.addChild(new ABCJS.write.RelativeElement(elem.title, 0, 0, 18, {type:"text", attributes:{"font-weight":"bold", "font-size":"16px", "font-family":"serif"}}));
176
+ elemset[0] = abselem;
177
+ break;
178
+ // case "tempo":
179
+ // this.printer.y = this.printer.printTempo(elem, this.printer.paper, this.printer.layouter, this.printer.y, this.printer, this.printer.x);
180
+ // break;
181
+ default:
182
+ var abselem2 = new ABCJS.write.AbsoluteElement(elem,0,0);
183
+ abselem2.addChild(new ABCJS.write.RelativeElement("element type "+elem.el_type, 0, 0, 0, {type:"debug"}));
184
+ elemset[0] = abselem2;
185
+ }
186
+
187
+ return elemset;
188
+ };
189
+
190
+ ABCJS.write.Layout.prototype.printBeam = function() {
191
+ var abselemset = [];
192
+
193
+ if (this.getElem().startBeam && !this.getElem().endBeam) {
194
+ var beamelem = new ABCJS.write.BeamElem(this.stemdir);
195
+ // PER: need two passes: the first one decides if the stems are up or down.
196
+ // TODO-PER: This could be more efficient.
197
+ var oldPos = this.pos;
198
+ var abselem;
199
+ while (this.getElem()) {
200
+ abselem = this.printNote(this.getElem(),true,true);
201
+ beamelem.add(abselem);
202
+ if (this.getElem().endBeam)
203
+ break;
204
+ this.pos++;
205
+ }
206
+ var dir = beamelem.calcDir();
207
+ this.pos = oldPos;
208
+
209
+ beamelem = new ABCJS.write.BeamElem(dir ? "up" : "down");
210
+ var oldDir = this.stemdir;
211
+ this.stemdir = dir ? "up" : "down";
212
+ while (this.getElem()) {
213
+ abselem = this.printNote(this.getElem(),true);
214
+ abselemset.push(abselem);
215
+ beamelem.add(abselem);
216
+ if (this.getElem().endBeam) {
217
+ break;
218
+ }
219
+ this.pos++;
220
+ }
221
+ this.stemdir = oldDir;
222
+ this.voice.addOther(beamelem);
223
+ } else {
224
+ abselemset[0] = this.printNote(this.getElem());
225
+ }
226
+ return abselemset;
227
+ };
228
+
229
+ ABCJS.write.sortPitch = function(elem) {
230
+ var sorted;
231
+ do {
232
+ sorted = true;
233
+ for (var p = 0; p<elem.pitches.length-1; p++) {
234
+ if (elem.pitches[p].pitch>elem.pitches[p+1].pitch) {
235
+ sorted = false;
236
+ var tmp = elem.pitches[p];
237
+ elem.pitches[p] = elem.pitches[p+1];
238
+ elem.pitches[p+1] = tmp;
239
+ }
240
+ }
241
+ } while (!sorted);
242
+ };
243
+
244
+ ABCJS.write.Layout.prototype.printNote = function(elem, nostem, dontDraw) { //stem presence: true for drawing stemless notehead
245
+ var notehead = null;
246
+ var grace= null;
247
+ this.roomtaken = 0; // room needed to the left of the note
248
+ this.roomtakenright = 0; // room needed to the right of the note
249
+ var dotshiftx = 0; // room taken by chords with displaced noteheads which cause dots to shift
250
+ var c="";
251
+ var flag = null;
252
+ var additionalLedgers = []; // PER: handle the case of [bc'], where the b doesn't have a ledger line
253
+
254
+ var p, i, pp;
255
+ var width, p1, p2, dx;
256
+
257
+ var duration = ABCJS.write.getDuration(elem);
258
+ var durlog = Math.floor(Math.log(duration)/Math.log(2));
259
+ var dot=0;
260
+
261
+ for (var tot = Math.pow(2,durlog), inc=tot/2; tot<duration; dot++,tot+=inc,inc/=2);
262
+
263
+ var abselem = new ABCJS.write.AbsoluteElement(elem, duration, 1);
264
+
265
+
266
+ if (elem.rest) {
267
+ var restpitch = 7;
268
+ if (this.stemdir==="down") restpitch = 3;
269
+ if (this.stemdir==="up") restpitch = 11;
270
+ switch(elem.rest.type) {
271
+ case "rest":
272
+ c = this.chartable.rest[-durlog];
273
+ elem.averagepitch=restpitch;
274
+ elem.minpitch=restpitch;
275
+ elem.maxpitch=restpitch;
276
+ break;
277
+ case "invisible":
278
+ case "spacer":
279
+ c="";
280
+ }
281
+ if (!dontDraw)
282
+ notehead = this.printNoteHead(abselem, c, {verticalPos:restpitch}, null, 0, -this.roomtaken, null, dot, 0, 1);
283
+ if (notehead) abselem.addHead(notehead);
284
+ this.roomtaken+=this.accidentalshiftx;
285
+ this.roomtakenright = Math.max(this.roomtakenright,this.dotshiftx);
286
+
287
+ } else {
288
+ ABCJS.write.sortPitch(elem);
289
+
290
+ // determine averagepitch, minpitch, maxpitch and stem direction
291
+ var sum=0;
292
+ for (p=0, pp=elem.pitches.length; p<pp; p++) {
293
+ sum += elem.pitches[p].verticalPos;
294
+ }
295
+ elem.averagepitch = sum/elem.pitches.length;
296
+ elem.minpitch = elem.pitches[0].verticalPos;
297
+ this.minY = Math.min(elem.minpitch, this.minY);
298
+ elem.maxpitch = elem.pitches[elem.pitches.length-1].verticalPos;
299
+ var dir = (elem.averagepitch>=6) ? "down": "up";
300
+ if (this.stemdir) dir=this.stemdir;
301
+
302
+ // determine elements of chords which should be shifted
303
+ for (p=(dir==="down")?elem.pitches.length-2:1; (dir==="down")?p>=0:p<elem.pitches.length; p=(dir==="down")?p-1:p+1) {
304
+ var prev = elem.pitches[(dir==="down")?p+1:p-1];
305
+ var curr = elem.pitches[p];
306
+ var delta = (dir==="down")?prev.pitch-curr.pitch:curr.pitch-prev.pitch;
307
+ if (delta<=1 && !prev.printer_shift) {
308
+ curr.printer_shift=(delta)?"different":"same";
309
+ if (curr.pitch > 11 || curr.pitch < 1) { // PER: add extra ledger line
310
+ additionalLedgers.push(curr.pitch - (curr.pitch%2));
311
+ }
312
+ if (dir==="down") {
313
+ this.roomtaken = this.glyphs.getSymbolWidth(this.chartable.note[-durlog])+2;
314
+ } else {
315
+ dotshiftx = this.glyphs.getSymbolWidth(this.chartable.note[-durlog])+2;
316
+ }
317
+ }
318
+ }
319
+
320
+ // The accidentalSlot will hold a list of all the accidentals on this chord. Each element is a vertical place,
321
+ // and contains a pitch, which is the last pitch that contains an accidental in that slot. The slots are numbered
322
+ // from closest to the note to farther left. We only need to know the last accidental we placed because
323
+ // we know that the pitches are sorted by now.
324
+ this.accidentalSlot = [];
325
+
326
+ for (p=0; p<elem.pitches.length; p++) {
327
+
328
+ if (!nostem) {
329
+ if ((dir==="down" && p!==0) || (dir==="up" && p!==pp-1)) { // not the stemmed elem of the chord
330
+ flag = null;
331
+ } else {
332
+ flag = this.chartable[(dir==="down")?"dflags":"uflags"][-durlog];
333
+ }
334
+ c = this.chartable.note[-durlog];
335
+ } else {
336
+ c="noteheads.quarter";
337
+ }
338
+
339
+ // 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.
340
+ // If the note is the only one in the chord, then any slur it has counts as if it were on the whole chord.
341
+ elem.pitches[p].highestVert = elem.pitches[p].verticalPos;
342
+ var isTopWhenStemIsDown = (this.stemdir==="up" || dir==="up") && p===0;
343
+ var isBottomWhenStemIsUp = (this.stemdir==="down" || dir==="down") && p===pp-1;
344
+ if (!dontDraw && (isTopWhenStemIsDown || isBottomWhenStemIsUp)) { // place to put slurs if not already on pitches
345
+
346
+ if (elem.startSlur || pp === 1) {
347
+ elem.pitches[p].highestVert = elem.pitches[pp-1].verticalPos;
348
+ if (this.stemdir==="up" || dir==="up")
349
+ elem.pitches[p].highestVert += 6; // If the stem is up, then compensate for the length of the stem
350
+ }
351
+ if (elem.startSlur) {
352
+ if (!elem.pitches[p].startSlur) elem.pitches[p].startSlur = []; //TODO possibly redundant, provided array is not optional
353
+ for (i=0; i<elem.startSlur.length; i++) {
354
+ elem.pitches[p].startSlur.push(elem.startSlur[i]);
355
+ }
356
+ }
357
+
358
+ if (!dontDraw && elem.endSlur) {
359
+ elem.pitches[p].highestVert = elem.pitches[pp-1].verticalPos;
360
+ if (this.stemdir==="up" || dir==="up")
361
+ elem.pitches[p].highestVert += 6; // If the stem is up, then compensate for the length of the stem
362
+ if (!elem.pitches[p].endSlur) elem.pitches[p].endSlur = []; //TODO possibly redundant, provided array is not optional
363
+ for (i=0; i<elem.endSlur.length; i++) {
364
+ elem.pitches[p].endSlur.push(elem.endSlur[i]);
365
+ }
366
+ }
367
+ }
368
+
369
+ if (!dontDraw)
370
+ notehead = this.printNoteHead(abselem, c, elem.pitches[p], dir, 0, -this.roomtaken, flag, dot, dotshiftx, 1);
371
+ if (notehead) abselem.addHead(notehead);
372
+ this.roomtaken += this.accidentalshiftx;
373
+ this.roomtakenright = Math.max(this.roomtakenright,this.dotshiftx);
374
+ }
375
+
376
+ // draw stem from the furthest note to a pitch above/below the stemmed note
377
+ if (!nostem && durlog<=-1) {
378
+ p1 = (dir==="down") ? elem.minpitch-7 : elem.minpitch+1/3;
379
+ // PER added stemdir test to make the line meet the note.
380
+ if (p1>6 && !this.stemdir) p1=6;
381
+ p2 = (dir==="down") ? elem.maxpitch-1/3 : elem.maxpitch+7;
382
+ // PER added stemdir test to make the line meet the note.
383
+ if (p2<6 && !this.stemdir) p2=6;
384
+ dx = (dir==="down" || abselem.heads.length === 0)?0:abselem.heads[0].w;
385
+ width = (dir==="down")?1:-1;
386
+ abselem.addExtra(new ABCJS.write.RelativeElement(null, dx, 0, p1, {"type": "stem", "pitch2":p2, linewidth: width}));
387
+ this.minY = Math.min(p1, this.minY);
388
+ this.minY = Math.min(p2, this.minY);
389
+ }
390
+
391
+ }
392
+
393
+ if (elem.lyric !== undefined) {
394
+ var lyricStr = "";
395
+ window.ABCJS.parse.each(elem.lyric, function(ly) {
396
+ lyricStr += ly.syllable + ly.divider + "\n";
397
+ });
398
+ abselem.addRight(new ABCJS.write.RelativeElement(lyricStr, 0, lyricStr.length*5, 0, {type:"debugLow"}));
399
+ }
400
+
401
+ if (!dontDraw && elem.gracenotes !== undefined) {
402
+ var gracescale = 3/5;
403
+ var gracebeam = null;
404
+ if (elem.gracenotes.length>1) {
405
+ gracebeam = new ABCJS.write.BeamElem("grace",this.isBagpipes);
406
+ }
407
+
408
+ var graceoffsets = [];
409
+ for (i=elem.gracenotes.length-1; i>=0; i--) { // figure out where to place each gracenote
410
+ this.roomtaken+=10;
411
+ graceoffsets[i] = this.roomtaken;
412
+ if (elem.gracenotes[i].accidental) {
413
+ this.roomtaken+=7;
414
+ }
415
+ }
416
+
417
+ for (i=0; i<elem.gracenotes.length; i++) {
418
+ var gracepitch = elem.gracenotes[i].verticalPos;
419
+
420
+ flag = (gracebeam) ? null : this.chartable.uflags[(this.isBagpipes)?5:3];
421
+ grace = this.printNoteHead(abselem, "noteheads.quarter", elem.gracenotes[i], "up", -graceoffsets[i], -graceoffsets[i], flag, 0, 0, gracescale);
422
+ abselem.addExtra(grace);
423
+ // PER: added acciaccatura slash
424
+ if (elem.gracenotes[i].acciaccatura) {
425
+ var pos = elem.gracenotes[i].verticalPos+7*gracescale; // the same formula that determines the flag position.
426
+ var dAcciaccatura = gracebeam ? 5 : 6; // just an offset to make it line up correctly.
427
+ abselem.addRight(new ABCJS.write.RelativeElement("flags.ugrace", -graceoffsets[i]+dAcciaccatura, 0, pos, {scalex:gracescale, scaley: gracescale}));
428
+ }
429
+ if (gracebeam) { // give the beam the necessary info
430
+ var pseudoabselem = {heads:[grace],
431
+ abcelem:{averagepitch: gracepitch, minpitch: gracepitch, maxpitch: gracepitch},
432
+ duration:(this.isBagpipes)?1/32:1/16};
433
+ gracebeam.add(pseudoabselem);
434
+ } else { // draw the stem
435
+ p1 = gracepitch+1/3*gracescale;
436
+ p2 = gracepitch+7*gracescale;
437
+ dx = grace.dx + grace.w;
438
+ width = -0.6;
439
+ abselem.addExtra(new ABCJS.write.RelativeElement(null, dx, 0, p1, {"type": "stem", "pitch2":p2, linewidth: width}));
440
+ }
441
+
442
+ 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));
443
+ }
444
+
445
+ if (gracebeam) {
446
+ this.voice.addOther(gracebeam);
447
+ }
448
+ }
449
+
450
+ if (!dontDraw && elem.decoration) {
451
+ var addMark = this.printDecoration(elem.decoration, elem.maxpitch, (notehead)?notehead.w:0, abselem, this.roomtaken, dir, elem.minpitch);
452
+ if (addMark) {
453
+ abselem.klass = "mark";
454
+ }
455
+ }
456
+
457
+ if (elem.barNumber) {
458
+ abselem.addChild(new ABCJS.write.RelativeElement(elem.barNumber, -10, 0, 0, {type:"debug"}));
459
+ }
460
+
461
+ // ledger lines
462
+ for (i=elem.maxpitch; i>11; i--) {
463
+ if (i%2===0 && !elem.rest) {
464
+ abselem.addChild(new ABCJS.write.RelativeElement(null, -2, this.glyphs.getSymbolWidth(c)+4, i, {type:"ledger"}));
465
+ }
466
+ }
467
+
468
+ for (i=elem.minpitch; i<1; i++) {
469
+ if (i%2===0 && !elem.rest) {
470
+ abselem.addChild(new ABCJS.write.RelativeElement(null, -2, this.glyphs.getSymbolWidth(c)+4, i, {type:"ledger"}));
471
+ }
472
+ }
473
+
474
+ for (i = 0; i < additionalLedgers.length; i++) { // PER: draw additional ledgers
475
+ var ofs = this.glyphs.getSymbolWidth(c);
476
+ if (dir === 'down') ofs = -ofs;
477
+ abselem.addChild(new ABCJS.write.RelativeElement(null, ofs-2, this.glyphs.getSymbolWidth(c)+4, additionalLedgers[i], {type:"ledger"}));
478
+ }
479
+
480
+ if (elem.chord !== undefined) { //16 -> high E.
481
+ for (i = 0; i < elem.chord.length; i++) {
482
+ var x = 0;
483
+ var y = 16;
484
+ switch (elem.chord[i].position) {
485
+ case "left":
486
+ this.roomtaken+=7;
487
+ x = -this.roomtaken; // TODO-PER: This is just a guess from trial and error
488
+ y = elem.averagepitch;
489
+ abselem.addExtra(new ABCJS.write.RelativeElement(elem.chord[i].name, x, this.glyphs.getSymbolWidth(elem.chord[i].name[0])+4, y, {type:"text"}));
490
+ break;
491
+ case "right":
492
+ this.roomtakenright+=4;
493
+ x = this.roomtakenright;// TODO-PER: This is just a guess from trial and error
494
+ y = elem.averagepitch;
495
+ abselem.addRight(new ABCJS.write.RelativeElement(elem.chord[i].name, x, this.glyphs.getSymbolWidth(elem.chord[i].name[0])+4, y, {type:"text"}));
496
+ break;
497
+ case "below":
498
+ y = -3;
499
+ abselem.addChild(new ABCJS.write.RelativeElement(elem.chord[i].name, x, 0, y, {type:"text"}));
500
+ break;
501
+ default:
502
+ abselem.addChild(new ABCJS.write.RelativeElement(elem.chord[i].name, x, 0, y, {type:"text"}));
503
+ }
504
+ }
505
+ }
506
+
507
+
508
+ if (elem.startTriplet) {
509
+ this.triplet = new ABCJS.write.TripletElem(elem.startTriplet, notehead, null, true); // above is opposite from case of slurs
510
+ if (!dontDraw)
511
+ this.voice.addOther(this.triplet);
512
+ }
513
+
514
+ if (elem.endTriplet && this.triplet) {
515
+ this.triplet.anchor2 = notehead;
516
+ this.triplet = null;
517
+ }
518
+
519
+ return abselem;
520
+ };
521
+
522
+
523
+
524
+
525
+ ABCJS.write.Layout.prototype.printNoteHead = function(abselem, c, pitchelem, dir, headx, extrax, flag, dot, dotshiftx, scale) {
526
+
527
+ // TODO scale the dot as well
528
+ var pitch = pitchelem.verticalPos;
529
+ var notehead;
530
+ var i;
531
+ this.accidentalshiftx = 0;
532
+ this.dotshiftx = 0;
533
+ if (c === undefined)
534
+ abselem.addChild(new ABCJS.write.RelativeElement("pitch is undefined", 0, 0, 0, {type:"debug"}));
535
+ else if (c==="") {
536
+ notehead = new ABCJS.write.RelativeElement(null, 0, 0, pitch);
537
+ } else {
538
+ var shiftheadx = headx;
539
+ if (pitchelem.printer_shift) {
540
+ var adjust = (pitchelem.printer_shift==="same")?1:0;
541
+ shiftheadx = (dir==="down")?-this.glyphs.getSymbolWidth(c)*scale+adjust:this.glyphs.getSymbolWidth(c)*scale-adjust;
542
+ }
543
+ notehead = new ABCJS.write.RelativeElement(c, shiftheadx, this.glyphs.getSymbolWidth(c)*scale, pitch, {scalex:scale, scaley: scale, extreme: ((dir==="down")?"below":"above")});
544
+ if (flag) {
545
+ var pos = pitch+((dir==="down")?-7:7)*scale;
546
+ if (scale===1 && (dir==="down")?(pos>6):(pos<6)) pos=6;
547
+ var xdelta = (dir==="down")?headx:headx+notehead.w-0.6;
548
+ abselem.addRight(new ABCJS.write.RelativeElement(flag, xdelta, this.glyphs.getSymbolWidth(flag)*scale, pos, {scalex:scale, scaley: scale}));
549
+ }
550
+ this.dotshiftx = notehead.w+dotshiftx-2+5*dot;
551
+ for (;dot>0;dot--) {
552
+ var dotadjusty = (1-Math.abs(pitch)%2); //PER: take abs value of the pitch. And the shift still happens on ledger lines.
553
+ abselem.addRight(new ABCJS.write.RelativeElement("dots.dot", notehead.w+dotshiftx-2+5*dot, this.glyphs.getSymbolWidth("dots.dot"), pitch+dotadjusty));
554
+ }
555
+ }
556
+ if (notehead)
557
+ notehead.highestVert = pitchelem.highestVert;
558
+
559
+ if (pitchelem.accidental) {
560
+ var symb;
561
+ switch (pitchelem.accidental) {
562
+ case "quartersharp":
563
+ symb = "accidentals.halfsharp";
564
+ break;
565
+ case "dblsharp":
566
+ symb = "accidentals.dblsharp";
567
+ break;
568
+ case "sharp":
569
+ symb = "accidentals.sharp";
570
+ break;
571
+ case "quarterflat":
572
+ symb = "accidentals.halfflat";
573
+ break;
574
+ case "flat":
575
+ symb = "accidentals.flat";
576
+ break;
577
+ case "dblflat":
578
+ symb = "accidentals.dblflat";
579
+ break;
580
+ case "natural":
581
+ symb = "accidentals.nat";
582
+ }
583
+ // if a note is at least a sixth away, it can share a slot with another accidental
584
+ var accSlotFound = false;
585
+ var accPlace = extrax;
586
+ for (var j = 0; j < this.accidentalSlot.length; j++) {
587
+ if (pitch - this.accidentalSlot[j][0] >= 6) {
588
+ this.accidentalSlot[j][0] = pitch;
589
+ accPlace = this.accidentalSlot[j][1];
590
+ accSlotFound = true;
591
+ break;
592
+ }
593
+ }
594
+ if (accSlotFound === false) {
595
+ accPlace -= (this.glyphs.getSymbolWidth(symb)*scale+2);
596
+ this.accidentalSlot.push([pitch,accPlace]);
597
+ this.accidentalshiftx = (this.glyphs.getSymbolWidth(symb)*scale+2);
598
+ }
599
+ abselem.addExtra(new ABCJS.write.RelativeElement(symb, accPlace, this.glyphs.getSymbolWidth(symb), pitch, {scalex:scale, scaley: scale}));
600
+ }
601
+
602
+ if (pitchelem.endTie) {
603
+ if (this.ties[0]) {
604
+ this.ties[0].anchor2=notehead;
605
+ this.ties = this.ties.slice(1,this.ties.length);
606
+ }
607
+ }
608
+
609
+ if (pitchelem.startTie) {
610
+ //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"));
611
+ var tie = new ABCJS.write.TieElem(notehead, null, (this.stemdir==="down" || dir==="down") && this.stemdir!=="up",(this.stemdir==="down" || this.stemdir==="up"));
612
+ this.ties[this.ties.length]=tie;
613
+ this.voice.addOther(tie);
614
+ }
615
+
616
+ if (pitchelem.endSlur) {
617
+ for (i=0; i<pitchelem.endSlur.length; i++) {
618
+ var slurid = pitchelem.endSlur[i];
619
+ var slur;
620
+ if (this.slurs[slurid]) {
621
+ slur = this.slurs[slurid].anchor2=notehead;
622
+ delete this.slurs[slurid];
623
+ } else {
624
+ slur = new ABCJS.write.TieElem(null, notehead, dir==="down",(this.stemdir==="up" || dir==="down") && this.stemdir!=="down", this.stemdir);
625
+ this.voice.addOther(slur);
626
+ }
627
+ if (this.startlimitelem) {
628
+ slur.startlimitelem = this.startlimitelem;
629
+ }
630
+ }
631
+ }
632
+
633
+ if (pitchelem.startSlur) {
634
+ for (i=0; i<pitchelem.startSlur.length; i++) {
635
+ var slurid = pitchelem.startSlur[i].label;
636
+ //PER: bug fix: var slur = new ABCJS.write.TieElem(notehead, null, (this.stemdir=="up" || dir=="down") && this.stemdir!="down", this.stemdir);
637
+ var slur = new ABCJS.write.TieElem(notehead, null, (this.stemdir==="down" || dir==="down") && this.stemdir!=="up", false);
638
+ this.slurs[slurid]=slur;
639
+ this.voice.addOther(slur);
640
+ }
641
+ }
642
+
643
+ return notehead;
644
+
645
+ };
646
+
647
+ ABCJS.write.Layout.prototype.printDecoration = function(decoration, pitch, width, abselem, roomtaken, dir, minPitch) {
648
+ var dec;
649
+ var compoundDec; // PER: for decorations with two symbols
650
+ var diminuendo;
651
+ var crescendo;
652
+ var unknowndecs = [];
653
+ var yslot = (pitch>9) ? pitch+3 : 12;
654
+ var ypos;
655
+ //var dir = (this.stemdir==="down" || pitch>=6) && this.stemdir!=="up";
656
+ var below = false; // PER: whether decoration goes above or below.
657
+ var yslotB = this.minY - 4; // (pitch<1) ? pitch-9 : -6;
658
+ var i;
659
+ roomtaken = roomtaken || 0;
660
+ if (pitch===5) yslot=14; // avoid upstem of the A
661
+ var addMark = false; // PER: to allow the user to add a class whereever
662
+
663
+ for (i=0;i<decoration.length; i++) { // treat staccato first (may need to shift other markers) //TODO, same with tenuto?
664
+ if (decoration[i]==="staccato") {
665
+ ypos = (dir==="down") ? pitch+2:minPitch-2;
666
+ // don't place on a stave line. The stave lines are 2,4,6,8,10
667
+ switch (ypos) {
668
+ case 2:
669
+ case 4:
670
+ case 6:
671
+ case 8:
672
+ case 10:
673
+ if (dir === "up") ypos--;
674
+ else ypos++;
675
+ break;
676
+ }
677
+ if (pitch>9) yslot++; // take up some room of those that are above
678
+ var deltax = width/2;
679
+ if (this.glyphs.getSymbolAlign("scripts.staccato")!=="center") {
680
+ deltax -= (this.glyphs.getSymbolWidth(dec)/2);
681
+ }
682
+ abselem.addChild(new ABCJS.write.RelativeElement("scripts.staccato", deltax, this.glyphs.getSymbolWidth("scripts.staccato"), ypos));
683
+ }
684
+ if (decoration[i]==="slide" && abselem.heads[0]) {
685
+ ypos = abselem.heads[0].pitch;
686
+ var blank1 = new ABCJS.write.RelativeElement("", -roomtaken-15, 0, ypos-1);
687
+ var blank2 = new ABCJS.write.RelativeElement("", -roomtaken-5, 0, ypos+1);
688
+ abselem.addChild(blank1);
689
+ abselem.addChild(blank2);
690
+ this.voice.addOther(new ABCJS.write.TieElem(blank1, blank2, false));
691
+ }
692
+ }
693
+
694
+ for (i=0;i<decoration.length; i++) {
695
+ below = false;
696
+ switch(decoration[i]) {
697
+ case "trill":dec="scripts.trill";break;
698
+ case "roll": dec="scripts.roll"; break; //TODO put abc2ps roll in here
699
+ case "irishroll": dec="scripts.roll"; break;
700
+ case "marcato": dec="scripts.umarcato"; break;
701
+ case "marcato2": dec="scriopts.dmarcato"; break;//other marcato
702
+ case "turn": dec="scripts.turn"; break;
703
+ case "uppermordent": dec="scripts.prall"; break;
704
+ case "mordent":
705
+ case "lowermordent": dec="scripts.mordent"; break;
706
+ case "staccato":
707
+ case "slide": continue;
708
+ case "downbow": dec="scripts.downbow";break;
709
+ case "upbow": dec="scripts.upbow";break;
710
+ case "fermata": dec="scripts.ufermata"; break;
711
+ case "invertedfermata": below = true; dec="scripts.dfermata"; break;
712
+ case "breath": dec=","; break;
713
+ case "accent": dec="scripts.sforzato"; break;
714
+ case "tenuto": dec="scripts.tenuto"; break;
715
+ case "coda": dec="scripts.coda"; break;
716
+ case "segno": dec="scripts.segno"; break;
717
+ case "/": compoundDec=["flags.ugrace", 1]; continue; // PER: added new decorations
718
+ case "//": compoundDec=["flags.ugrace", 2]; continue;
719
+ case "///": compoundDec=["flags.ugrace", 3]; continue;
720
+ case "////": compoundDec=["flags.ugrace", 4]; continue;
721
+ case "p":
722
+ case "mp":
723
+ case "pp":
724
+ case "ppp":
725
+ case "pppp":
726
+ case "f":
727
+ case "ff":
728
+ case "fff":
729
+ case "ffff":
730
+ case "sfz":
731
+ case "mf":
732
+ var ddelem = new ABCJS.write.DynamicDecoration(abselem, decoration[i]);
733
+ this.voice.addOther(ddelem);
734
+ continue;
735
+ case "mark": addMark = true; continue;
736
+ case "diminuendo(":
737
+ ABCJS.write.Layout.prototype.startDiminuendoX = abselem;
738
+ diminuendo = undefined;
739
+ continue;
740
+ case "diminuendo)":
741
+ diminuendo = { start: ABCJS.write.Layout.prototype.startDiminuendoX, stop: abselem};
742
+ ABCJS.write.Layout.prototype.startDiminuendoX = undefined;
743
+ continue;
744
+ case "crescendo(":
745
+ ABCJS.write.Layout.prototype.startCrescendoX = abselem;
746
+ crescendo = undefined;
747
+ continue;
748
+ case "crescendo)":
749
+ crescendo = { start: ABCJS.write.Layout.prototype.startCrescendoX, stop: abselem};
750
+ ABCJS.write.Layout.prototype.startCrescendoX = undefined;
751
+ continue;
752
+ default:
753
+ unknowndecs[unknowndecs.length]=decoration[i];
754
+ continue;
755
+ }
756
+ if (below) {
757
+ ypos = yslotB;
758
+ yslotB -= 4;
759
+ } else {
760
+ ypos=yslot;
761
+ yslot+=3;
762
+ }
763
+ var deltax = width/2;
764
+ if (this.glyphs.getSymbolAlign(dec)!=="center") {
765
+ deltax -= (this.glyphs.getSymbolWidth(dec)/2);
766
+ }
767
+ abselem.addChild(new ABCJS.write.RelativeElement(dec, deltax, this.glyphs.getSymbolWidth(dec), ypos));
768
+ }
769
+ if (compoundDec) { // PER: added new decorations
770
+ ypos = (dir) ? pitch+1:pitch+9;
771
+ deltax = width/2;
772
+ deltax += (dir) ? -5 : 3;
773
+ for (var xx = 0; xx < compoundDec[1]; xx++) {
774
+ ypos -= 1;
775
+ abselem.addChild(new ABCJS.write.RelativeElement(compoundDec[0], deltax, this.glyphs.getSymbolWidth(compoundDec[0]), ypos));
776
+ }
777
+ }
778
+ if (diminuendo) {
779
+ var delem = new ABCJS.write.CrescendoElem(diminuendo.start, diminuendo.stop, ">");
780
+ this.voice.addOther(delem);
781
+ }
782
+ if (crescendo) {
783
+ var celem = new ABCJS.write.CrescendoElem(crescendo.start, crescendo.stop, "<");
784
+ this.voice.addOther(celem);
785
+ }
786
+ if (unknowndecs.length>0)
787
+ abselem.addChild(new ABCJS.write.RelativeElement(unknowndecs.join(','), 0, 0, 0, {type:"debug"}));
788
+ return addMark;
789
+ };
790
+
791
+ ABCJS.write.Layout.prototype.printBarLine = function (elem) {
792
+ // bar_thin, bar_thin_thick, bar_thin_thin, bar_thick_thin, bar_right_repeat, bar_left_repeat, bar_double_repeat
793
+
794
+ var abselem = new ABCJS.write.AbsoluteElement(elem, 0, 10);
795
+ var anchor = null; // place to attach part lines
796
+ var dx = 0;
797
+
798
+
799
+
800
+ var firstdots = (elem.type==="bar_right_repeat" || elem.type==="bar_dbl_repeat");
801
+ var firstthin = (elem.type!=="bar_left_repeat" && elem.type!=="bar_thick_thin" && elem.type!=="bar_invisible");
802
+ var thick = (elem.type==="bar_right_repeat" || elem.type==="bar_dbl_repeat" || elem.type==="bar_left_repeat" ||
803
+ elem.type==="bar_thin_thick" || elem.type==="bar_thick_thin");
804
+ var secondthin = (elem.type==="bar_left_repeat" || elem.type==="bar_thick_thin" || elem.type==="bar_thin_thin" || elem.type==="bar_dbl_repeat");
805
+ var seconddots = (elem.type==="bar_left_repeat" || elem.type==="bar_dbl_repeat");
806
+
807
+ // limit positionning of slurs
808
+ if (firstdots || seconddots) {
809
+ for (var slur in this.slurs) {
810
+ if (this.slurs.hasOwnProperty(slur)) {
811
+ this.slurs[slur].endlimitelem = abselem;
812
+ }
813
+ }
814
+ this.startlimitelem = abselem;
815
+ }
816
+
817
+ if (firstdots) {
818
+ abselem.addRight(new ABCJS.write.RelativeElement("dots.dot", dx, 1, 7));
819
+ abselem.addRight(new ABCJS.write.RelativeElement("dots.dot", dx, 1, 5));
820
+ dx+=6; //2 hardcoded, twice;
821
+ }
822
+
823
+ if (firstthin) {
824
+ anchor = new ABCJS.write.RelativeElement(null, dx, 1, 2, {"type": "bar", "pitch2":10, linewidth:0.6});
825
+ abselem.addRight(anchor);
826
+ }
827
+
828
+ if (elem.type==="bar_invisible") {
829
+ anchor = new ABCJS.write.RelativeElement(null, dx, 1, 2, {"type": "none", "pitch2":10, linewidth:0.6});
830
+ abselem.addRight(anchor);
831
+ }
832
+
833
+ if (elem.decoration) {
834
+ this.printDecoration(elem.decoration, 12, (thick)?3:1, abselem, 0, "down", 2);
835
+ }
836
+
837
+ if (thick) {
838
+ dx+=4; //3 hardcoded;
839
+ anchor = new ABCJS.write.RelativeElement(null, dx, 4, 2, {"type": "bar", "pitch2":10, linewidth:4});
840
+ abselem.addRight(anchor);
841
+ dx+=5;
842
+ }
843
+
844
+ // if (this.partstartelem && (thick || (firstthin && secondthin))) { // means end of nth part
845
+ // this.partstartelem.anchor2=anchor;
846
+ // this.partstartelem = null;
847
+ // }
848
+
849
+ if (this.partstartelem && elem.endEnding) {
850
+ this.partstartelem.anchor2=anchor;
851
+ this.partstartelem = null;
852
+ }
853
+
854
+ if (secondthin) {
855
+ dx+=3; //3 hardcoded;
856
+ anchor = new ABCJS.write.RelativeElement(null, dx, 1, 2, {"type": "bar", "pitch2":10, linewidth:0.6});
857
+ abselem.addRight(anchor); // 3 is hardcoded
858
+ }
859
+
860
+ if (seconddots) {
861
+ dx+=3; //3 hardcoded;
862
+ abselem.addRight(new ABCJS.write.RelativeElement("dots.dot", dx, 1, 7));
863
+ abselem.addRight(new ABCJS.write.RelativeElement("dots.dot", dx, 1, 5));
864
+ } // 2 is hardcoded
865
+
866
+ if (elem.startEnding) {
867
+ this.partstartelem = new ABCJS.write.EndingElem(elem.startEnding, anchor, null);
868
+ this.voice.addOther(this.partstartelem);
869
+ }
870
+
871
+ return abselem;
872
+
873
+ };
874
+
875
+ ABCJS.write.Layout.prototype.printClef = function(elem) {
876
+ var clef = "clefs.G";
877
+ var octave = 0;
878
+ var pitch = 4;
879
+ var abselem = new ABCJS.write.AbsoluteElement(elem,0,10);
880
+ switch (elem.type) {
881
+ case "treble": break;
882
+ case "tenor": clef="clefs.C"; pitch=8; break;
883
+ case "alto": clef="clefs.C"; pitch=6; break;
884
+ case "bass": clef="clefs.F"; pitch=8; break;
885
+ case 'treble+8': octave = 1; break;
886
+ case 'tenor+8':clef="clefs.C"; pitch=8; break;
887
+ case 'bass+8': clef="clefs.F"; pitch=8; break;
888
+ case 'alto+8': clef="clefs.C"; pitch=6; break;
889
+ case 'treble-8': octave = -1; break;
890
+ case 'tenor-8':clef="clefs.C"; pitch=8; break;
891
+ case 'bass-8': clef="clefs.F"; pitch=8; break;
892
+ case 'alto-8': clef="clefs.C"; pitch=6; break;
893
+ case 'none': clef=""; break;
894
+ case 'perc': clef="clefs.perc"; pitch=6; break;
895
+ default: abselem.addChild(new ABCJS.write.RelativeElement("clef="+elem.type, 0, 0, 0, {type:"debug"}));
896
+ }
897
+ if (elem.verticalPos) {
898
+ pitch = elem.verticalPos;
899
+ }
900
+
901
+ var dx =10;
902
+ if (clef!=="") {
903
+ abselem.addRight(new ABCJS.write.RelativeElement(clef, dx, this.glyphs.getSymbolWidth(clef), pitch));
904
+ }
905
+ if (octave!==0) {
906
+ var scale= 2/3;
907
+ var adjustspacing = (this.glyphs.getSymbolWidth(clef)-this.glyphs.getSymbolWidth("8")*scale)/2;
908
+ abselem.addRight(new ABCJS.write.RelativeElement("8", dx+adjustspacing, this.glyphs.getSymbolWidth("8")*scale, (octave>0)?16:-2, {scalex:scale, scaley:scale}));
909
+ }
910
+
911
+ if (elem.stafflines===0) {
912
+ this.stafflines = 0;
913
+ } else {
914
+ this.stafflines =elem.stafflines;
915
+ }
916
+
917
+ return abselem;
918
+ };
919
+
920
+
921
+ ABCJS.write.Layout.prototype.printKeySignature = function(elem) {
922
+ var abselem = new ABCJS.write.AbsoluteElement(elem,0,10);
923
+ var dx = 0;
924
+ if (elem.accidentals) {
925
+ window.ABCJS.parse.each(elem.accidentals, function(acc) {
926
+ var symbol = (acc.acc === "sharp") ? "accidentals.sharp" : (acc.acc === "natural") ? "accidentals.nat" : "accidentals.flat";
927
+ //var notes = { 'A': 5, 'B': 6, 'C': 0, 'D': 1, 'E': 2, 'F': 3, 'G':4, 'a': 12, 'b': 13, 'c': 7, 'd': 8, 'e': 9, 'f': 10, 'g':11 };
928
+ abselem.addRight(new ABCJS.write.RelativeElement(symbol, dx, this.glyphs.getSymbolWidth(symbol), acc.verticalPos));
929
+ dx += this.glyphs.getSymbolWidth(symbol)+2;
930
+ }, this);
931
+ }
932
+ this.startlimitelem = abselem; // limit ties here
933
+ return abselem;
934
+ };
935
+
936
+ ABCJS.write.Layout.prototype.printTimeSignature= function(elem) {
937
+
938
+ var abselem = new ABCJS.write.AbsoluteElement(elem,0,20);
939
+ if (elem.type === "specified") {
940
+ //TODO make the alignment for time signatures centered
941
+ for (var i = 0; i < elem.value.length; i++) {
942
+ if (i !== 0)
943
+ abselem.addRight(new ABCJS.write.RelativeElement('+', i*20-9, this.glyphs.getSymbolWidth("+"), 7));
944
+ if (elem.value[i].den) {
945
+ abselem.addRight(new ABCJS.write.RelativeElement(elem.value[i].num, i*20, this.glyphs.getSymbolWidth(elem.value[i].num.charAt(0))*elem.value[i].num.length, 9));
946
+ abselem.addRight(new ABCJS.write.RelativeElement(elem.value[i].den, i*20, this.glyphs.getSymbolWidth(elem.value[i].den.charAt(0))*elem.value[i].den.length, 5));
947
+ } else {
948
+ abselem.addRight(new ABCJS.write.RelativeElement(elem.value[i].num, i*20, this.glyphs.getSymbolWidth(elem.value[i].num.charAt(0))*elem.value[i].num.length, 7));
949
+ }
950
+ }
951
+ } else if (elem.type === "common_time") {
952
+ abselem.addRight(new ABCJS.write.RelativeElement("timesig.common", 0, this.glyphs.getSymbolWidth("timesig.common"), 7));
953
+
954
+ } else if (elem.type === "cut_time") {
955
+ abselem.addRight(new ABCJS.write.RelativeElement("timesig.cut", 0, this.glyphs.getSymbolWidth("timesig.cut"), 7));
956
+ }
957
+ this.startlimitelem = abselem; // limit ties here
958
+ return abselem;
959
+ };