1// abc2svg - ABC to SVG translator 2// @source: https://chiselapp.com/user/moinejf/repository/abc2svg 3// Copyright (C) 2014-2020 Jean-Francois Moine - LGPL3+ 4//grid.js-module to insert a chord grid before or after a tune 5abc2svg.grid={pl:'<path class="stroke" stroke-width="1" d="M',block_gen:function(of,s){if(s.subtype!="grid"){of(s) 6return} 7var abc=this,img,cls,cfmt=abc.cfmt(),grid=cfmt.grid 8function build_grid(chords,bars,font,wmx){var i,k,l,nr,bar,w,hr,x0,x,y,yl,lc='',cells=[],nc=grid.n 9function set_chords(){var i,ch,pch='-' 10for(i=0;i<chords.length;i++){ch=chords[i] 11if(!ch[0]) 12ch[0]=pch 13if(ch.length==0) 14continue 15if(ch.length==1){pch=ch[0] 16continue} 17if(ch.length==2){ch[2]=ch[1];ch[1]=null;pch=ch[2] 18continue} 19if(ch.length==3){pch=ch[2] 20continue} 21if(!ch[2]) 22ch[2]=ch[1]||ch[0];pch=ch[3]}} 23function build_cell(cell,x,y,yl,hr){if(cell.length>1){abc.out_svg(abc2svg.grid.pl) 24abc.out_sxsy(x-wmx/2,' ',yl) 25abc.out_svg('l'+ 26wmx.toFixed(1)+' -'+hr.toFixed(1)+'"/>\n') 27if(cell[1]){abc.out_svg(abc2svg.grid.pl) 28abc.out_sxsy(x-wmx/2,' ',yl+hr) 29abc.out_svg('l'+ 30(wmx/2).toFixed(1)+' '+(hr/2).toFixed(1)+'"/>\n') 31abc.set_font('gs') 32abc.xy_str(x-wmx/3,y,cell[0]) 33abc.xy_str(x,y+hr/3,cell[1])}else{abc.set_font('gs') 34abc.xy_str(x-wmx*.2,y+hr/4,cell[0])} 35if(cell.length>=3){if(cell[3]){abc.out_svg(abc2svg.grid.pl) 36abc.out_sxsy(x,' ',yl+hr/2) 37abc.out_svg('l'+ 38(wmx/2).toFixed(1)+' '+(hr/2).toFixed(1)+'"/>\n') 39abc.set_font('gs') 40abc.xy_str(x,y-hr/3,cell[2]) 41abc.xy_str(x+wmx/3,y,cell[3])}else{abc.set_font('gs') 42abc.xy_str(x+wmx*.2,y-hr/4,cell[2])}}}else{abc.set_font('grid') 43abc.xy_str(x,y,cell[0])}} 44set_chords() 45if(!grid.ls){cells=chords}else{bar=bars;bars=[] 46for(i=0;i<grid.ls.length;i++){l=grid.ls[i] 47if(l.indexOf('-')<0) 48l=[l,l] 49else 50l=l.split('-') 51for(k=l[0]-1;k<l[1];k++){if(!chords[k]) 52break 53cells.push(chords[k]);bars.push(bar[k])}}} 54if(nc<0) 55nc=-nc 56if(nc<3) 57nc=cells.length%6==0?6:8 58if(nc>cells.length) 59nc=cells.length;hr=font.size*2 60if(wmx<hr*1.5) 61wmx=hr*1.5 62x0=img.width-img.lm-img.rm 63w=wmx*nc 64if(w>x0){nc/=2;w/=2} 65yl=-1 66y=-1+font.size*.6 67nr=0 68x0=(x0/cfmt.scale-w)/2 69for(i=0;i<cells.length;i++){if(i==0||(grid.repbrk&&(bars[i].slice(-1)==':'||bars[i][0]==':'))||k>=nc){y-=hr 70yl-=hr 71x=x0+wmx/2 72k=0 73nr++} 74k++ 75build_cell(cells[i],x,y,yl,hr) 76x+=wmx} 77abc.out_svg('<path class="stroke" stroke-width="1" d="\n') 78y=-1 79for(i=0;i<=nr;i++){abc.out_svg('M') 80abc.out_sxsy(x0,' ',y) 81abc.out_svg('h'+w.toFixed(1)+'\n') 82y-=hr} 83x=x0 84for(i=0;i<=nc;i++){abc.out_svg('M') 85abc.out_sxsy(x,' ',-1) 86abc.out_svg('v'+(hr*nr).toFixed(1)+'\n') 87x+=wmx} 88abc.out_svg('"/>\n') 89y=-1+font.size*.7 90x=x0 91for(i=0;i<bars.length;i++){bar=bars[i] 92if(bar[0]==':'){abc.out_svg('<text class="'+cls+'" x="') 93abc.out_sxsy(x-5,'" y="',y) 94abc.out_svg('" style="font-weight:bold;font-size:'+ 95(font.size*1.5).toFixed(1)+'px">:</text>\n')} 96if(i==0||(grid.repbrk&&(bars[i].slice(-1)==':'||bars[i][0]==':'))||k>=nc){y-=hr;x=x0 97k=0} 98k++ 99if(bar.slice(-1)==':'){abc.out_svg('<text class="'+cls+'" x="') 100abc.out_sxsy(x+5,'" y="',y) 101abc.out_svg('" style="font-weight:bold;font-size:'+ 102(font.size*1.5).toFixed(1)+'px">:</text>\n')} 103x+=wmx} 104abc.vskip(hr*nr+6)} 105var p_voice,n,font,f2 106img=abc.get_img() 107if(!cfmt.gridfont) 108abc.param_set_font("gridfont","serif 16") 109font=abc.get_font('grid') 110if(font.class) 111font.class+=' mid' 112else 113font.class='mid' 114cls=abc.font_class(font) 115abc.param_set_font("gsfont",font.name+' '+(font.size*.7).toFixed(1)) 116f2=cfmt.gsfont 117if(font.weight) 118f2.weight=font.weight 119if(font.style) 120f2.style=font.style 121f2.class=font.class 122abc.add_style("\n.mid {text-anchor:middle}") 123abc.blk_flush() 124build_grid(s.chords,s.bars,font,s.wmx) 125abc.blk_flush()},output_music:function(of){var C=abc2svg.C,abc=this,tsfirst=abc.get_tsfirst(),voice_tb=abc.get_voice_tb(),grid=abc.cfmt().grid 126function get_beat(s){var beat=C.BLEN/4 127if(!s.a_meter[0]||s.a_meter[0].top[0]=='C'||!s.a_meter[0].bot) 128return beat 129beat=C.BLEN/s.a_meter[0].bot[0]|0 130if(s.a_meter[0].bot[0]==8&&s.a_meter[0].top[0]%3==0) 131beat=C.BLEN/8*3 132return beat} 133function build_chords(sb){var s,i,w,bt,rep,bars=[],chords=[],chord=[],beat=get_beat(voice_tb[0].meter),wm=voice_tb[0].meter.wmeasure,cur_beat=0,beat_i=0,wmx=0 134bars.push('|') 135for(s=tsfirst;s;s=s.ts_next){while(s.time>cur_beat){if(beat_i<3) 136beat_i++ 137cur_beat+=beat} 138switch(s.type){case C.NOTE:case C.REST:if(!s.a_gch) 139break 140for(i=0;i<s.a_gch.length;i++){if(s.a_gch[i].type=='g'){if(!chord[beat_i]){chord[beat_i]=s.a_gch[i].text 141w=abc.strwh(chord[beat_i])[0] 142if(w>wmx) 143wmx=w} 144break}} 145break 146case C.BAR:bt=grid.norep?'|':s.bar_type 147if(s.time<wm){if(chord.length){chords.push(chord) 148bars.push(bt)}else{bars[0]=bt}}else{if(!s.bar_num) 149break 150chords.push(chord) 151bars.push(bt)} 152chord=[] 153cur_beat=s.time 154beat_i=0 155if(bt.indexOf(':')>=0) 156rep=true 157while(s.ts_next&&s.ts_next.type==C.BAR) 158s=s.ts_next 159break 160case C.METER:beat=get_beat(s) 161wm=s.wmeasure 162break}} 163if(chord.length){bars.push('') 164chords.push(chord)} 165if(!chords.length) 166return 167wmx+=abc.strwh(rep?' ':' ')[0] 168sb.chords=chords 169sb.bars=bars 170sb.wmx=wmx} 171if(grid){var C=abc2svg.C,tsfirst=this.get_tsfirst(),voice_tb=this.get_voice_tb(),p_v=voice_tb[this.get_top_v()],s={type:C.BLOCK,subtype:'grid',dur:0,time:0,p_v:p_v,v:p_v.v,st:p_v.st} 172build_chords(s) 173if(!s.chords){}else if(grid.nomusic){this.set_tsfirst(s)}else if(grid.n<0){for(var s2=tsfirst;s2.ts_next;s2=s2.ts_next);s.time=s2.time 174s.prev=p_v.last_sym.prev 175s.prev.next=s 176s.next=p_v.last_sym 177p_v.last_sym.prev=s 178s.ts_prev=s2.ts_prev 179s.ts_prev.ts_next=s 180s.ts_next=s2 181s2.ts_prev=s 182if(s2.seqst){s.seqst=true 183s2.seqst=false}}else{s.next=p_v.sym 184s.ts_next=tsfirst 185tsfirst.ts_prev=s 186this.set_tsfirst(s) 187p_v.sym.prev=s 188p_v.sym=s}} 189of()},set_fmt:function(of,cmd,parm){if(cmd=="grid"){if(!parm) 190parm="1";parm=parm.split(/\s+/) 191var grid={n:Number(parm.shift())} 192if(isNaN(grid.n)){if(parm.length){this.syntax(1,this.errs.bad_val,"%%grid") 193return} 194grid.n=1} 195while(parm.length){var item=parm.shift() 196if(item=="norepeat") 197grid.norep=true 198else if(item=="nomusic") 199grid.nomusic=true 200else if(item=="repbrk") 201grid.repbrk=true 202else if(item.slice(0,8)=="include=") 203grid.ls=item.slice(8).split(',')} 204this.cfmt().grid=grid 205return} 206of(cmd,parm)},set_hooks:function(abc){abc.block_gen=abc2svg.grid.block_gen.bind(abc,abc.block_gen) 207abc.output_music=abc2svg.grid.output_music.bind(abc,abc.output_music);abc.set_format=abc2svg.grid.set_fmt.bind(abc,abc.set_format)}} 208abc2svg.modules.hooks.push(abc2svg.grid.set_hooks);abc2svg.modules.grid.loaded=true 209