abcjs-rails 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,487 @@
1
+ // abc_write.js: Prints an abc file parsed by abc_parse.js
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
+
18
+ /*global window, ABCJS, Math */
19
+
20
+ if (!window.ABCJS)
21
+ window.ABCJS = {};
22
+
23
+ if (!window.ABCJS.write)
24
+ window.ABCJS.write = {};
25
+
26
+ ABCJS.write.spacing = function() {};
27
+ ABCJS.write.spacing.FONTEM = 360;
28
+ ABCJS.write.spacing.FONTSIZE = 30;
29
+ ABCJS.write.spacing.STEP = ABCJS.write.spacing.FONTSIZE*93/720;
30
+ ABCJS.write.spacing.SPACE = 10;
31
+ ABCJS.write.spacing.TOPNOTE = 20;
32
+ ABCJS.write.spacing.STAVEHEIGHT = 100;
33
+
34
+
35
+ //--------------------------------------------------------------------PRINTER
36
+
37
+ ABCJS.write.Printer = function(paper, params) {
38
+ params = params || {};
39
+ this.y = 0;
40
+ this.paper = paper;
41
+ this.space = 3*ABCJS.write.spacing.SPACE;
42
+ this.glyphs = new ABCJS.write.Glyphs();
43
+ this.listeners = [];
44
+ this.selected = [];
45
+ this.ingroup = false;
46
+ this.scale = params.scale || 1;
47
+ this.staffwidth = params.staffwidth || 740;
48
+ this.paddingtop = params.paddingtop || 15;
49
+ this.paddingbottom = params.paddingbottom || 30;
50
+ this.paddingright = params.paddingright || 50;
51
+ this.paddingleft = params.paddingleft || 15;
52
+ this.editable = params.editable || false;
53
+ };
54
+
55
+ // notify all listeners that a graphical element has been selected
56
+ ABCJS.write.Printer.prototype.notifySelect = function (abselem) {
57
+ this.clearSelection();
58
+ this.selected = [abselem];
59
+ abselem.highlight();
60
+ for (var i=0; i<this.listeners.length;i++) {
61
+ this.listeners[i].highlight(abselem.abcelem);
62
+ }
63
+ };
64
+
65
+ ABCJS.write.Printer.prototype.notifyChange = function (abselem) {
66
+ for (var i=0; i<this.listeners.length;i++) {
67
+ this.listeners[i].modelChanged();
68
+ }
69
+ };
70
+
71
+ ABCJS.write.Printer.prototype.clearSelection = function () {
72
+ for (var i=0;i<this.selected.length;i++) {
73
+ this.selected[i].unhighlight();
74
+ }
75
+ this.selected = [];
76
+ };
77
+
78
+ ABCJS.write.Printer.prototype.addSelectListener = function (listener) {
79
+ this.listeners[this.listeners.length] = listener;
80
+ };
81
+
82
+ ABCJS.write.Printer.prototype.rangeHighlight = function(start,end)
83
+ {
84
+ this.clearSelection();
85
+ for (var line=0;line<this.staffgroups.length; line++) {
86
+ var voices = this.staffgroups[line].voices;
87
+ for (var voice=0;voice<voices.length;voice++) {
88
+ var elems = voices[voice].children;
89
+ for (var elem=0; elem<elems.length; elem++) {
90
+ // Since the user can highlight more than an element, or part of an element, a hit is if any of the endpoints
91
+ // is inside the other range.
92
+ var elStart = elems[elem].abcelem.startChar;
93
+ var elEnd = elems[elem].abcelem.endChar;
94
+ if ((end>elStart && start<elEnd) || ((end===start) && end===elEnd)) {
95
+ // if (elems[elem].abcelem.startChar>=start && elems[elem].abcelem.endChar<=end) {
96
+ this.selected[this.selected.length]=elems[elem];
97
+ elems[elem].highlight();
98
+ }
99
+ }
100
+ }
101
+ }
102
+ };
103
+
104
+ ABCJS.write.Printer.prototype.beginGroup = function () {
105
+ this.path = [];
106
+ this.lastM = [0,0];
107
+ this.ingroup = true;
108
+ };
109
+
110
+ ABCJS.write.Printer.prototype.addPath = function (path) {
111
+ path = path || [];
112
+ if (path.length===0) return;
113
+ path[0][0]="m";
114
+ path[0][1]-=this.lastM[0];
115
+ path[0][2]-=this.lastM[1];
116
+ this.lastM[0]+=path[0][1];
117
+ this.lastM[1]+=path[0][2];
118
+ this.path.push(path[0]);
119
+ for (var i=1,ii=path.length;i<ii;i++) {
120
+ if (path[i][0]==="m") {
121
+ this.lastM[0]+=path[i][1];
122
+ this.lastM[1]+=path[i][2];
123
+ }
124
+ this.path.push(path[i]);
125
+ }
126
+ };
127
+
128
+ ABCJS.write.Printer.prototype.endGroup = function () {
129
+ this.ingroup = false;
130
+ if (this.path.length===0) return null;
131
+ var ret = this.paper.path().attr({path:this.path, stroke:"none", fill:"#000000"});
132
+ if (this.scale!==1) {
133
+ ret.scale(this.scale, this.scale, 0, 0);
134
+ }
135
+ return ret;
136
+ };
137
+
138
+ ABCJS.write.Printer.prototype.printStaveLine = function (x1,x2, pitch) {
139
+ var isIE=/*@cc_on!@*/false;//IE detector
140
+ var dy = 0.35;
141
+ var fill = "#000000";
142
+ if (isIE) {
143
+ dy = 1;
144
+ fill = "#666666";
145
+ }
146
+ var y = this.calcY(pitch);
147
+ var pathString = ABCJS.write.sprintf("M %f %f L %f %f L %f %f L %f %f z", x1, y-dy, x2, y-dy,
148
+ x2, y+dy, x1, y+dy);
149
+ var ret = this.paper.path().attr({path:pathString, stroke:"none", fill:fill}).toBack();
150
+ if (this.scale!==1) {
151
+ ret.scale(this.scale, this.scale, 0, 0);
152
+ }
153
+ return ret;
154
+ };
155
+
156
+ ABCJS.write.Printer.prototype.printStem = function (x, dx, y1, y2) {
157
+ if (dx<0) { // correct path "handedness" for intersection with other elements
158
+ var tmp = y2;
159
+ y2 = y1;
160
+ y1 = tmp;
161
+ }
162
+ var isIE=/*@cc_on!@*/false;//IE detector
163
+ var fill = "#000000";
164
+ if (isIE && dx<1) {
165
+ dx = 1;
166
+ fill = "#666666";
167
+ }
168
+ if (~~x === x) x+=0.05; // raphael does weird rounding (for VML)
169
+ var pathArray = [["M",x,y1],["L", x, y2],["L", x+dx, y2],["L",x+dx,y1],["z"]];
170
+ if (!isIE && this.ingroup) {
171
+ this.addPath(pathArray);
172
+ } else {
173
+ var ret = this.paper.path().attr({path:pathArray, stroke:"none", fill:fill}).toBack();
174
+ if (this.scale!==1) {
175
+ ret.scale(this.scale, this.scale, 0, 0);
176
+ }
177
+ return ret;
178
+ }
179
+ };
180
+
181
+ ABCJS.write.Printer.prototype.printText = function (x, offset, text, anchor) {
182
+ anchor = anchor || "start";
183
+ var ret = this.paper.text(x, this.calcY(offset), text).attr({"text-anchor":anchor, "font-size":12});
184
+ if (this.scale!==1) {
185
+ ret.scale(this.scale, this.scale, 0, 0);
186
+ }
187
+ return ret;
188
+ };
189
+
190
+ // assumes this.y is set appropriately
191
+ // if symbol is a multichar string without a . (as in scripts.staccato) 1 symbol per char is assumed
192
+ // not scaled if not in printgroup
193
+ ABCJS.write.Printer.prototype.printSymbol = function(x, offset, symbol, scalex, scaley) {
194
+ var el;
195
+ if (!symbol) return null;
196
+ if (symbol.length>0 && symbol.indexOf(".")<0) {
197
+ var elemset = this.paper.set();
198
+ var dx =0;
199
+ for (var i=0; i<symbol.length; i++) {
200
+ var ycorr = this.glyphs.getYCorr(symbol.charAt(i));
201
+ el = this.glyphs.printSymbol(x+dx, this.calcY(offset+ycorr), symbol.charAt(i), this.paper);
202
+ if (el) {
203
+ elemset.push(el);
204
+ dx+=this.glyphs.getSymbolWidth(symbol.charAt(i));
205
+ } else {
206
+ this.debugMsg(x,"no symbol:" +symbol);
207
+ }
208
+ }
209
+ if (this.scale!==1) {
210
+ elemset.scale(this.scale, this.scale, 0, 0);
211
+ }
212
+ return elemset;
213
+ } else {
214
+ var ycorr = this.glyphs.getYCorr(symbol);
215
+ if (this.ingroup) {
216
+ this.addPath(this.glyphs.getPathForSymbol(x, this.calcY(offset+ycorr), symbol, scalex, scaley));
217
+ } else {
218
+ el = this.glyphs.printSymbol(x, this.calcY(offset+ycorr), symbol, this.paper);
219
+ if (el) {
220
+ if (this.scale!==1) {
221
+ el.scale(this.scale, this.scale, 0, 0);
222
+ }
223
+ return el;
224
+ } else
225
+ this.debugMsg(x,"no symbol:" +symbol);
226
+ }
227
+ return null;
228
+ }
229
+ };
230
+
231
+ ABCJS.write.Printer.prototype.printPath = function (attrs) {
232
+ var ret = this.paper.path().attr(attrs);
233
+ if (this.scale!==1) ret.scale(this.scale, this.scale, 0, 0);
234
+ return ret;
235
+ };
236
+
237
+ ABCJS.write.Printer.prototype.drawArc = function(x1, x2, pitch1, pitch2, above) {
238
+
239
+
240
+ x1 = x1 + 6;
241
+ x2 = x2 + 4;
242
+ pitch1 = pitch1 + ((above)?1.5:-1.5);
243
+ pitch2 = pitch2 + ((above)?1.5:-1.5);
244
+ var y1 = this.calcY(pitch1);
245
+ var y2 = this.calcY(pitch2);
246
+
247
+ //unit direction vector
248
+ var dx = x2-x1;
249
+ var dy = y2-y1;
250
+ var norm= Math.sqrt(dx*dx+dy*dy);
251
+ var ux = dx/norm;
252
+ var uy = dy/norm;
253
+
254
+ var flatten = norm/3.5;
255
+ var curve = ((above)?-1:1)*Math.min(25, Math.max(4, flatten));
256
+
257
+ var controlx1 = x1+flatten*ux-curve*uy;
258
+ var controly1 = y1+flatten*uy+curve*ux;
259
+ var controlx2 = x2-flatten*ux-curve*uy;
260
+ var controly2 = y2-flatten*uy+curve*ux;
261
+ var thickness = 2;
262
+ var pathString = ABCJS.write.sprintf("M %f %f C %f %f %f %f %f %f C %f %f %f %f %f %f z", x1, y1,
263
+ controlx1, controly1, controlx2, controly2, x2, y2,
264
+ controlx2-thickness*uy, controly2+thickness*ux, controlx1-thickness*uy, controly1+thickness*ux, x1, y1);
265
+ var ret = this.paper.path().attr({path:pathString, stroke:"none", fill:"#000000"});
266
+ if (this.scale!==1) {
267
+ ret.scale(this.scale, this.scale, 0, 0);
268
+ }
269
+ return ret;
270
+ };
271
+
272
+ ABCJS.write.Printer.prototype.debugMsg = function(x, msg) {
273
+ return this.paper.text(x, this.y, msg).scale(this.scale, this.scale, 0, 0);
274
+ };
275
+
276
+ ABCJS.write.Printer.prototype.debugMsgLow = function(x, msg) {
277
+ return this.paper.text(x, this.calcY(this.layouter.minY-7), msg).attr({"font-family":"serif", "font-size":12, "text-anchor":"begin"}).scale(this.scale, this.scale, 0, 0);
278
+ };
279
+
280
+ ABCJS.write.Printer.prototype.printLyrics = function(x, msg) {
281
+ var el = this.paper.text(x, this.calcY(this.layouter.minY-7), msg).attr({"font-family":"Times New Roman", "font-weight":'bold', "font-size":14, "text-anchor":"begin"}).scale(this.scale, this.scale, 0, 0);
282
+ el[0].setAttribute("class", "abc-lyric");
283
+ return el;
284
+ };
285
+
286
+ ABCJS.write.Printer.prototype.calcY = function(ofs) {
287
+ return this.y+((ABCJS.write.spacing.TOPNOTE-ofs)*ABCJS.write.spacing.STEP);
288
+ };
289
+
290
+ ABCJS.write.Printer.prototype.printStave = function (startx, endx, numLines) { // PER: print out requested number of lines
291
+ // If there is one line, it is the B line. Otherwise, the bottom line is the E line.
292
+ if (numLines === 1) {
293
+ this.printStaveLine(startx,endx,6);
294
+ return;
295
+ }
296
+ for (var i = 0; i < numLines; i++) {
297
+ this.printStaveLine(startx,endx,(i+1)*2);
298
+ }
299
+ // this.printStaveLine(startx,endx,2);
300
+ // this.printStaveLine(startx,endx,4);
301
+ // this.printStaveLine(startx,endx,6);
302
+ // this.printStaveLine(startx,endx,8);
303
+ // this.printStaveLine(startx,endx,10);
304
+ };
305
+
306
+ ABCJS.write.Printer.prototype.printABC = function(abctunes) {
307
+ if (abctunes[0]===undefined) {
308
+ abctunes = [abctunes];
309
+ }
310
+ this.y=0;
311
+
312
+ for (var i = 0; i < abctunes.length; i++) {
313
+ this.printTune(abctunes[i]);
314
+ }
315
+
316
+ };
317
+
318
+ ABCJS.write.Printer.prototype.printTempo = function (tempo, paper, layouter, y, printer, x) {
319
+ if (tempo.preString) {
320
+ var text = paper.text(x, y + 20, tempo.preString).attr({"text-anchor":"start"});
321
+ x += (text.getBBox().width + 10);
322
+ }
323
+ if (tempo.duration) {
324
+ var temposcale = 0.75;
325
+ var tempopitch = 14.5;
326
+ var duration = tempo.duration[0]; // TODO when multiple durations
327
+ var abselem = new ABCJS.write.AbsoluteElement(tempo, duration, 1);
328
+ var durlog = Math.floor(Math.log(duration) / Math.log(2));
329
+ var dot = 0;
330
+ for (var tot = Math.pow(2, durlog), inc = tot / 2; tot < duration; dot++, tot += inc, inc /= 2);
331
+ var c = layouter.chartable.note[-durlog];
332
+ var flag = layouter.chartable.uflags[-durlog];
333
+ var temponote = layouter.printNoteHead(abselem,
334
+ c,
335
+ {verticalPos:tempopitch},
336
+ "up",
337
+ 0,
338
+ 0,
339
+ flag,
340
+ dot,
341
+ 0,
342
+ temposcale
343
+ );
344
+ abselem.addHead(temponote);
345
+ if (duration < 1) {
346
+ var p1 = tempopitch + 1 / 3 * temposcale;
347
+ var p2 = tempopitch + 7 * temposcale;
348
+ var dx = temponote.dx + temponote.w;
349
+ var width = -0.6;
350
+ abselem.addExtra(new ABCJS.write.RelativeElement(null, dx, 0, p1, {"type":"stem", "pitch2":p2, linewidth:width}));
351
+ }
352
+ abselem.x = x;
353
+ abselem.draw(printer, null);
354
+ x += (abselem.w + 5);
355
+ text = paper.text(x, y + 20, "= " + tempo.bpm).attr({"text-anchor":"start"});
356
+ x += text.getBBox().width + 10;
357
+ }
358
+ if (tempo.postString) {
359
+ paper.text(x, y + 20, tempo.postString).attr({"text-anchor":"start"});
360
+ }
361
+ y += 15;
362
+ return y;
363
+ };
364
+
365
+ ABCJS.write.Printer.prototype.printTune = function (abctune) {
366
+ this.layouter = new ABCJS.write.Layout(this.glyphs, abctune.formatting.bagpipes);
367
+ this.layouter.printer = this; // TODO-PER: this is a hack to get access, but it tightens the coupling.
368
+ if (abctune.media === 'print') {
369
+ // TODO create the page the size of
370
+ // tune.formatting.pageheight by tune.formatting.pagewidth
371
+ // create margins the size of
372
+ // TODO-PER: setting the defaults to 3/4" for now. What is the real value?
373
+ var m = abctune.formatting.topmargin === undefined ? 54 : abctune.formatting.topmargin;
374
+ this.y+=m;
375
+ // TODO tune.formatting.botmargin
376
+ // m = abctune.formatting.leftmargin === undefined ? 54 : abctune.formatting.leftmargin;
377
+ // this.paddingleft = m;
378
+ // m = abctune.formatting.rightmargin === undefined ? 54 : abctune.formatting.rightmargin;
379
+ // this.paddingright = m;
380
+ }
381
+ else
382
+ this.y+=this.paddingtop;
383
+ // FIXED BELOW, NEEDS CHECKING if (abctune.formatting.stretchlast) { this.paper.text(200, this.y, "Format: stretchlast"); this.y += 20; }
384
+ if (abctune.formatting.staffwidth) {
385
+ this.width=abctune.formatting.staffwidth;
386
+ } else {
387
+ this.width=this.staffwidth;
388
+ }
389
+ this.width+=this.paddingleft;
390
+ if (abctune.formatting.scale) { this.scale=abctune.formatting.scale; }
391
+ this.paper.text(this.width/2, this.y, abctune.metaText.title).attr({"font-size":20, "font-family":"serif"});
392
+ this.y+=20;
393
+ if (abctune.lines[0] && abctune.lines[0].subtitle) {
394
+ this.printSubtitleLine(abctune.lines[0]);
395
+ this.y+=20;
396
+ }
397
+ if (abctune.metaText.rhythm) {
398
+ this.paper.text(this.paddingleft, this.y, abctune.metaText.rhythm).attr({"text-anchor":"start","font-style":"italic","font-family":"serif", "font-size":12});
399
+ !(abctune.metaText.author || abctune.metaText.origin || abctune.metaText.composer) && (this.y+=15);
400
+ }
401
+ if (abctune.metaText.author) {this.paper.text(this.width, this.y, abctune.metaText.author).attr({"text-anchor":"end","font-style":"italic","font-family":"serif", "font-size":12}); this.y+=15;}
402
+ if (abctune.metaText.origin) {this.paper.text(this.width, this.y, "(" + abctune.metaText.origin + ")").attr({"text-anchor":"end","font-style":"italic","font-family":"serif", "font-size":12});this.y+=15;}
403
+ if (abctune.metaText.composer) {this.paper.text(this.width, this.y, abctune.metaText.composer).attr({"text-anchor":"end","font-style":"italic","font-family":"serif", "font-size":12});this.y+=15;}
404
+ if (abctune.metaText.tempo && !abctune.metaText.tempo.suppress) {
405
+ this.y = this.printTempo(abctune.metaText.tempo, this.paper, this.layouter, this.y, this, 50);
406
+ }
407
+ this.staffgroups = [];
408
+ var maxwidth = this.width;
409
+ for(var line=0; line<abctune.lines.length; line++) {
410
+ var abcline = abctune.lines[line];
411
+ if (abcline.staff) {
412
+ var staffgroup = this.layouter.printABCLine(abcline.staff);
413
+ var newspace = this.space;
414
+ for (var it=0;it<3;it++) {
415
+ staffgroup.layout(newspace,this);
416
+ if (line && line===abctune.lines.length-1 && staffgroup.w/this.width<0.66 && !abctune.formatting.stretchlast) break; // don't stretch last line too much unless it is 1st
417
+ var relspace = staffgroup.spacingunits*newspace;
418
+ var constspace = staffgroup.w-relspace;
419
+ if (staffgroup.spacingunits>0) {
420
+ newspace = (this.width-constspace)/staffgroup.spacingunits;
421
+ if (newspace*staffgroup.minspace>50) {
422
+ newspace = 50/staffgroup.minspace;
423
+ }
424
+ }
425
+ }
426
+ staffgroup.draw(this,this.y);
427
+ if (staffgroup.w>maxwidth) maxwidth = staffgroup.w;
428
+ this.staffgroups[this.staffgroups.length] = staffgroup;
429
+ this.y = staffgroup.y+staffgroup.height;
430
+ this.y+=ABCJS.write.spacing.STAVEHEIGHT*0.2;
431
+ } else if (abcline.subtitle && line!==0) {
432
+ this.printSubtitleLine(abcline);
433
+ this.y+=20; //hardcoded
434
+ } else if (abcline.text) {
435
+ if (typeof abcline.text === 'string')
436
+ this.paper.text(100, this.y, "TEXT: " + abcline.text);
437
+ else {
438
+ var str = "";
439
+ for (var i = 0; i < abcline.text.length; i++) {
440
+ str += " FONT " + abcline.text[i].text;
441
+ }
442
+ this.paper.text(100, this.y, "TEXT: " + str);
443
+ }
444
+ this.y+=20; //hardcoded
445
+ }
446
+ }
447
+ var extraText = ""; // TODO-PER: This is just an easy way to display this info for now.
448
+ if (abctune.metaText.partOrder) extraText += "Part Order: " + abctune.metaText.partOrder + "\n";
449
+ if (abctune.metaText.notes) extraText += "Notes:\n" + abctune.metaText.notes + "\n";
450
+ if (abctune.metaText.book) extraText += "Book: " + abctune.metaText.book + "\n";
451
+ if (abctune.metaText.source) extraText += "Source: " + abctune.metaText.source + "\n";
452
+ if (abctune.metaText.transcription) extraText += "Transcription: " + abctune.metaText.transcription + "\n";
453
+ if (abctune.metaText.discography) extraText += "Discography: " + abctune.metaText.discography + "\n";
454
+ if (abctune.metaText.history) extraText += "History: " + abctune.metaText.history + "\n";
455
+ if (abctune.metaText.unalignedWords) {
456
+ extraText += "Words:\n";
457
+ for (var j = 0; j < abctune.metaText.unalignedWords.length; j++) {
458
+ if (typeof abctune.metaText.unalignedWords[j] === 'string')
459
+ extraText += abctune.metaText.unalignedWords[j] + "\n";
460
+ else {
461
+ for (var k = 0; k < abctune.metaText.unalignedWords[j].length; k++) {
462
+ extraText += " FONT " + abctune.metaText.unalignedWords[j][k].text;
463
+ }
464
+ extraText += "\n";
465
+ }
466
+ }
467
+ }
468
+ var text2 = this.paper.text(this.paddingleft, this.y+25, extraText).attr({"text-anchor":"start", "font-family":"serif", "font-size":13});
469
+ var height = text2.getBBox().height;
470
+ text2.translate(0,height/2);
471
+ this.y+=25+height;
472
+ var sizetoset = {w: maxwidth*this.scale+this.paddingright,h: this.y*this.scale+this.paddingbottom};
473
+ this.paper.setSize(sizetoset.w,sizetoset.h);
474
+ // Correct for IE problem in calculating height
475
+ var isIE=/*@cc_on!@*/false;//IE detector
476
+ if (isIE) {
477
+ this.paper.canvas.parentNode.style.width=sizetoset.w+"px";
478
+ this.paper.canvas.parentNode.style.height=""+sizetoset.h+"px";
479
+ } else
480
+ this.paper.canvas.parentNode.setAttribute("style","width:"+sizetoset.w+"px");
481
+ };
482
+
483
+ ABCJS.write.Printer.prototype.printSubtitleLine = function(abcline) {
484
+ this.paper.text(this.width/2, this.y, abcline.subtitle).attr({"font-size":16}).scale(this.scale, this.scale, 0,0);
485
+ };
486
+
487
+