/** * SinriScoreDrawer * ----------------------- * Load from score text to display numbered musical notation on a canvas element. var SSD=new SinriScoreDrawer('myCanvas'); var textarea=SSD.E('ta'); var the_score_data=SSD.parseScoreString(textarea.value); SSD.loadScoreData(the_score_data,{width:30,height:50}); * For debug, you can get the notes in the format of JSON Array with this var the_score_data=SSD.parseScoreString(textarea.value); */ if (!('remove' in Element.prototype)) { Element.prototype.remove = function() { if (this.parentNode) { this.parentNode.removeChild(this); } }; } function SinriScoreDrawer(canvas_id){ // Helper this.helper={ E:function(id){ return document.getElementById(id); }, INC:function(obj,delta){ if(!obj){ obj=0; } return obj+delta; }, NUM_SCALE:function(number_in_old_scale,old_scalue,new_scale){ return number_in_old_scale/old_scalue*new_scale; }, LAST_OBJ_OF_ARRAY:function(array){ if(!array){ return false; } if(array.length===0){ return null; } return array[array.length-1]; }, PARSE_TO_DATA_URL:function(score_text){ let tmp_canvas_id='SinriScoreDrawer_TMP_CANVAS_OF_'+(new Date().getTime()); let tmp_canvas=document.createElement('canvas'); tmp_canvas.setAttribute('id',tmp_canvas_id); document.body.insertAdjacentElement('beforeend',tmp_canvas); let SSD=new SinriScoreDrawer(tmp_canvas_id); let the_score_data=SSD.parseScoreString(score_text); SSD.loadScoreData(the_score_data,{width:30,height:50}); let result = SSD.toDataUrl(); tmp_canvas.remove();//see above Polyfill code return result; }, DOWNLOAD_URL:function(url){ let a = document.createElement('a'); let filename = 'SinriScoreDrawer_Untitled.png'; a.href = url; a.download = filename; a.click(); window.URL.revokeObjectURL(url); }, MERGE_SEIGE:function(array,seige_item){ let after=[]; let inside=null;// null ('' 'X' )null for(let i=0;i0){ // // really diffs // real_ss=(this.canvas.width-entire_offset.x*2-3*ss)/(real_cell_in_this_row-1); // real_kk=parseInt(Math.floor(real_ss*0.6),10); // } for(let x=0;x0 && x>0 && has_numbered_lyric){ real_ss=(this.canvas.width-entire_offset.x*2-3*ss)/(real_cell_in_this_row-1); real_kk=parseInt(Math.floor(real_ss*0.6),10); } this.printOneScoreCell({ s:s,//cell's total height k:k,//char area height ss:real_ss,//cell's total width kk:real_kk,//char area width min_ss:ss, min_kk:kk, cell_offset_x:(entire_offset.x+real_ss*x+ss), cell_offset_y:(s*y+s), score_size:score_size, t:parseInt(Math.floor((s-k)/2.0),10), tt:parseInt(Math.floor((real_ss-real_kk)/2.0),10) }, score_line[x], false // true //debug cell ); } } for(let keep_index=0;keep_indexw){ w=score_data[i].length; } } w+=2; return {h:h,w:w}; } this.getCertainPointOfCell=function(cell_attr,type){ let p_x=0.5,p_y=0.5; if(type==='center_of_cell'){ p_x=0.5; p_y=0.5; } if(type==='score_dot'){ p_x=0.8; p_y=0.5; } if(type==='SFN'){ p_x=0.05; p_y=0.25; } return [ cell_attr.cell_offset_x+cell_attr.ss*p_x, cell_attr.cell_offset_y+cell_attr.t+cell_attr.k*p_y ]; } this.debugDrawCellBorder=function(cell_attr){ this.setStrokeStyle("lightblue"); this.drawPolygon([ [cell_attr.cell_offset_x,cell_attr.cell_offset_y], [cell_attr.cell_offset_x+cell_attr.ss,cell_attr.cell_offset_y], [cell_attr.cell_offset_x+cell_attr.ss,cell_attr.cell_offset_y+cell_attr.s], [cell_attr.cell_offset_x,cell_attr.cell_offset_y+cell_attr.s] ]); this.setStrokeStyle("lightgray"); this.drawPolygon([ [cell_attr.cell_offset_x+cell_attr.tt,cell_attr.cell_offset_y+cell_attr.t], [cell_attr.cell_offset_x+cell_attr.tt+cell_attr.kk,cell_attr.cell_offset_y+cell_attr.t], [cell_attr.cell_offset_x+cell_attr.tt+cell_attr.kk,cell_attr.cell_offset_y+cell_attr.t+cell_attr.k], [cell_attr.cell_offset_x+cell_attr.tt,cell_attr.cell_offset_y+cell_attr.t+cell_attr.k] ]); } this.printOneScoreCell=function(cell_attr,score,show_cell_border){ if(show_cell_border){ this.debugDrawCellBorder(cell_attr); } this.setStrokeStyle("black"); this.setFillStyle("black"); if(typeof score === 'string'){ this.printOneScoreCellWithPureString(cell_attr,score[0]); }else{ this.printOneScoreCellWithObject(cell_attr,score); } } this.printOneScoreCellWithPureString=function(cell_attr,score){ this.writeText( score, this.getCertainPointOfCell(cell_attr,'center_of_cell'), { font:''+(Math.min(cell_attr.k,cell_attr.kk))+'px sans-serif', textAlign:'center', textBaseline:'middle' } ); } this.printOneScoreCellWithObject=function(cell_attr,score){ //note this.printOneScoreCellWithObjectForText(cell_attr,score); // SFND this.printOneScoreCellWithObjectForSFN(cell_attr,score); //upper part let upper_y=this.printOneScoreCellWithObjectForUpper(cell_attr,score); //under part let underline_y=this.printOneScoreCellWithObjectForUnder(cell_attr,score); //keep this.printOneScoreCellWithObjectForKeep(cell_attr,score,upper_y); } this.printOneScoreCellWithObjectForText=function(cell_attr,score){ let note_text=''; if(score.note){ note_text=score.note[0]; } if(score.special_note){ let mp={ 'REPEAT_START_DOUBLE':"‖:", 'REPEAT_END_DOUBLE':":‖", 'REPEAT_START_SINGLE':"|:", 'REPEAT_END_SINGLE':":|", 'LONGER_LINE':"ー", 'FIN':"‖", 'PHARSE_FIN':"|", 'DOT':'・' } if(score.special_note==='AS_IS' && score.note){ note_text=score.note; }else if(mp[score.special_note]){ note_text=mp[score.special_note]; } } let text_point=this.getCertainPointOfCell(cell_attr,'center_of_cell'); let font_setting='sans-serif'; if(score.title){ //text_point as cancvs center text_point=[this.canvas.width/2,cell_attr.cell_offset_y+cell_attr.t+cell_attr.k*0.5]; //ctx.measureText("foo").width 要不要考虑后面自动调整字体大小,现在还是算了 font_setting='serif';//for ios, Palatino } if(score.indentation){ //line head 1,2,3... or All Sing font_setting='serif';//for ios, Palatino } this.writeText( note_text, text_point, { font:''+(Math.min(cell_attr.k,cell_attr.min_kk))+'px '+font_setting, textAlign:'center',//(score.title?'left':'center'), textBaseline:'middle' } ); } this.printOneScoreCellWithObjectForSFN=function(cell_attr,score){ let sfn_char=''; if(score.sharp){ sfn_char='♯'; }else if(score.flat){ sfn_char='♭'; }else if(score.natual){ sfn_char='♮'; } if(sfn_char!==''){ this.writeText( sfn_char, this.getCertainPointOfCell(cell_attr,'SFN'), { font:''+(0.8*Math.min(cell_attr.k,cell_attr.min_kk))+'px sans-serif', textAlign:'center', textBaseline:'middle' } ); } if(score.dot){ this.drawDot( this.getCertainPointOfCell(cell_attr,'score_dot'), this.helper.NUM_SCALE(2,50,cell_attr.s) // 2 ); } } this.printOneScoreCellWithObjectForUpper=function(cell_attr,score){ let upper_y=cell_attr.cell_offset_y+cell_attr.t; //upper points let upperpoints=score.upperpoints; if(upperpoints && upperpoints>0){ upper_y=upper_y-this.helper.NUM_SCALE(1,50,cell_attr.s); for(let i=0;i0 && i0 && i] * 1. Each line should be seperated by one or more RETURNs: [\r\n]+ * 2. Each note should be seperated by one or more SPACEs: [ ]+ * 3. Line begin with ~, full line use AS_IS mode * 4. Line begin with >, line is lyrics,use * 5. A 1/4 note is a char of [0-7], with < for lower and > for higher * 6.1 For longer note, use NOTE*X: \*[1-9][0-9]* to describe X-times of length * 6.2 For longer note, use [\-]+ to simply memo it, such as 1- or 1--- * 7.1 For shorter note, use NOTE/X: \/[1-9][0-9]* to describe one Xth of length * 7.2 For shorter note, use [\_]+ to simple memo it, such as 1_ or 1__ * 8. Attached dot, use one dot \. after NOTE * 9. For pharse, use one \| * 10. For inner repeat, use \|\: and \:\| * 11. For final repeat, use \||\: and \:\|\| * 12. For fin, use \|\| * 13. Sharp and flat */ this.parseScoreString=function(score_text){ let score_data=[]; let lines=score_text.split(/[\r\n]+/); let the_notes_list=[]; let has_numbered_lyric=false; for(let line_index=0;line_index'){ type='LYRIC'; notes=lines[line_index].slice(2).split(''); notes=this.helper.MERGE_SEIGE(notes,'`'); } else if(first_note_char==='#'){ type='NUMBERED_LYRIC'; notes=lines[line_index].slice(2).split(''); has_numbered_lyric=true; notes=this.helper.MERGE_SEIGE(notes,'`'); } else if(first_note_char==='@'){ type='ALL_LYRIC'; notes=lines[line_index].slice(2).split(''); has_numbered_lyric=true; notes=this.helper.MERGE_SEIGE(notes,'`'); } // old // let line_data=this.parseScoreLineString(notes,type); // score_data.push(line_data); //new the_notes_list.push({notes:notes,type:type}); } let number=0; let prev_score_line_cells=0; for(let i=0;i)*))[~]?(([\._]+)|(\-+)|(\*[1-9][0-9]*)|(\/[1-9][0-9]*))?[\)]?(:[A-Z]+)?$/; if(!regex.test(note_text)){ return [{ special_note:'AS_IS', note:note_text }]; } let note={ _has_long_line:0, _times_divided:0, _times_multiply:0 }; let flag=0;//beginning let parts=note_text.split(':'); if(parts[1] && this.NoteEffectWordDictory[parts[1]]){ note.effect_word=this.NoteEffectWordDictory[parts[1]]; } note_text=parts[0]; for(let i=0;i='0' && c<='9'){ if(flag<=2){ note.note=c; flag=3;//has note }else if(flag===7){ note._times_multiply=note._times_multiply*10+1*c; }else if(flag===8){ note._times_divided=note._times_divided*10+1*c; } }else if(c==='<' && flag===3){ note.underpoints=this.helper.INC(note.underpoints,1); }else if(c==='>' && flag===3){ note.upperpoints=this.helper.INC(note.upperpoints,1); }else if(c==='.' && (flag===3 || flag===4 || flag===5)){ note.dot=true; flag=4;//has dot }else if(c==='_' && (flag===3 || flag===4 || flag===5)){ note.underlines=this.helper.INC(note.underlines,1); flag=5;//has underlines }else if(c==='-' && (flag===3 || flag===6)){ note._has_long_line+=1; flag=6;//has long line }else if(c==='*' && (flag===3 || flag===7)){ flag=7; }else if(c==='\/' && (flag===3 || flag===8)){ flag=8; }else if(c===')' && flag>=3){ note.keep_end=true; flag=9; }else if(c==='~' && flag===3){ note.fermata=true; } return flag; } this.parseNoteStringForNotationAddition=function(note){ if(note._times_multiply>0){ note._has_long_line=note._times_multiply-1; } if(note._times_divided>0){ if(note._times_divided===3){ note.triplets=true; } else if(note._times_divided%2===0){ note.underlines=note._times_divided/2; } else{ note.underlines=Math.floor(note._times_divided/2); note.dot=true; } } let has_long_line=note._has_long_line; delete note._has_long_line; delete note._times_divided; delete note._times_multiply; let notes=[note]; if(note.dot){ notes.push({ special_note:'DOT' }); note.dot=false; } for(let j=0;j" } // initialize if(!canvas_id){ return this; } this.canvas=this.helper.E(canvas_id); }