曲別のHTMLファイルを取得して、内部のJavaScript部を解析するところまで
bms2jsh.js の譜面種別判定部
先頭の定数定義部
b64="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; s=location.search,LNDEF=384,cob=["s","w","b","w","b","w","b","w"];
b64
は Base64 encode/decode用の独自文字列。
s
は URL のクエリ文字列で、?1H800
のように(?
を含む)文字列が格納される。
obr=[[0,1,2,3,4,5,6,7],[0,1,2,3,4,5,6,7]];kc=[[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0]]; dpalls=[[0,1,2,3,4,5,6,7],[0,7,6,5,4,3,2,1]];imgdir="../";diftype="";twstr="";memo=""; ln=[],sp=[],dp=[],tc=[],c1=[],c2=[],cn=[],sides=["",2,2],csd=["","left","right"];cnc=[0,0,0]; dw=[134,121],dr=[38,4],df=[134,119],ms=["",""," class=m1"," class=m2"];tm=new Date();hsa=8;
各種の配列などの初期化。
genre=title=artist=bpm=opt=lnse=lnhs="",key=ky=back=7,hs=gap=ty=k=1;cncnt=bsscnt=legacy=prt=pty=0;
soflan=level=notes=measure=a=l=m=g=db=p1o=hps=flp=off=lnln=lnst=lned=alls=hids=sran=kuro=sftkey=os=hcn=ttl=0;
空文字(""
)または0
や1
などで変数初期化。
char1=s.charAt(1); char2=s.charAt(2); level=s.charAt(3)? s.charAt(3):0; char2lower=char2.toLowerCase(); if(char2==char2lower){prt=1;imgdir="../prt/";} char2=char2.toUpperCase();
クエリ文字列の 2 文字目をchar1
に設定し、3 文字目をchar2
に設定。
if(char2=="X"){a=1;kuro=1;} if(char2=="A")a=1; if(char2=="L")l=1; if(char2=="N"){l=1;hps=1;} if(char2=="H")hps=1; if(char2=="B"){l=1;g=1;} if(char2=="G" || char2=="R"){l=1;hps=1;g=1;} if(char2=="P"){l=1;hps=1;pty=1;g=1;}
char2
は、"P"
がBEGINNER、"N"
がNORMAL、"H"
がHYPER、"A"
がANOTHER、"X"
がLEGGENDARIAに相当。
譜面種別と変数の対応表
- | l | hps | a | kuro | pty | g |
P (BEGINNER) | 1 | 1 | 0 | 0 | 1 | 1 |
N (NORMAL) | 1 | 1 | 0 | 0 | 0 | 0 |
H (HYPER) | 0 | 1 | 0 | 0 | 0 | 0 |
A (ANOTHER) | 0 | 0 | 1 | 0 | 0 | 0 |
X (LEGGENDARIA) | 0 | 0 | 1 | 1 | 0 | 0 |
if(char1=="1")sides[1]=1; if(char1=="D"){k=0;key=14;} if(char1=="L"){k=0;key=14;os=1;sides[1]=1;} if(char1=="R"){k=0;key=14;os=2;} if(char1=="F"){flp=1;k=0;key=14;} if(char1=="M"){m=1;k=0;key=14;} if(char1=="B"){db=1;key=14;}
char1
は、"1"
がSINGLE(1P)、"2"
がSINGLE(2P)、"D"
がDOUBLEに相当。
モードと変数の対応表
- | sides[1] | k | key |
SINGLE(1P) | 1 | 1 | 7 |
SINGLE(2P) | 2 | 1 | 7 |
DOUBLE | 2 | 0 | 14 |
曲別のHTML直書き部
最序盤
genre="EPIC TRANCE";title="ABSOLUTE"; artist="dj TAKA";bpm="60~144";measure=69;tc[0]=["14400"]; tc[67]=["13364","12096"];tc[68]=["11200"," 8932"," 8564"," 6096"];tc[69]=[" 8000"];
tc
配列は BPM を定義しており、先頭から 3 文字が BPM の数字、4 文字以降が小節内の BPM 変更位置(単位は 4 分音符 1 つの長さを 32 とする)を表す。BPM が 100 未満のときは、1 文字目を半角スペースとする。
genre="SPIRITUAL";title="桜"; artist="Reven-G";bpm="13~320";measure=104;ln[61]=768; tc[1]=["3000"];tc[49]=["1500"];tc[51]=["13564","12796"];tc[102]=["3000","27064"]; tc[52]=["1170"," 8932"," 6064"," 4496"," 13112"];tc[53]=["1500"];tc[61]=["3200"]; tc[103]=["2500","22064","17096"];tc[104]=["1490","12932"," 8964"," 6096"];
別の例では、「桜」のtc[52][4]
が" 13112"
と 6 文字になっており、112 はいわゆる「4 拍目の裏」(= 8 拍子の 8 拍目)にあたる。
if(kuro){hcn=1; if(k){ if(a){notes=1412; // SP-LEGGENDARIA の定義部 } }else{ if(a){notes=1488; // DP-LEGGENDARIA の定義部 } } }else if(g&&hps){notes=309; // SP-BEGINNER の定義部 }else if(k){notes=935; // SP-HYPER の定義部 if(a){notes=1218; // SP-ANOTHER の定義部 } if(l){notes=682; // SP-NORMAL の定義部 if(g){notes=514; // ???????? } } }else{notes=934; // DP-HYPER の定義部 if(a){notes=1338; // DP-ANOTHER の定義部 } if(l){notes=728; // DP-NORMAL の定義部 } }
構造が複雑だが、直前で定義した譜面パターンを小節単位で流用したい意図があるようだ。
譜面パターンの記述
DP-NORMAL 譜面から一部抜粋
sp=[,"#O4YTC","#oBADC_","#OEFAI","#O90+u","#O4dDD", /*(中略) */ ,"#OgysG",]; dp=[,"#OEFAI_","#O10+u",sp[12],"#oRRTK","#OEAQI", /* (中略) */, "80"];}
sp
配列は、SPまたはDP-1P側の譜面パターン、dp
配列は、DP-2P側の譜面パターンを定義している。
配列の要素 1 つにつき、1 小節の譜面パターンを定義している。
配列の要素が"#"
で始まる文字列か、そうではない文字列かによって、解釈のアルゴリズムが異なる。
DP-LEGGENDARIA 譜面から一部抜粋
c2[2]=c2[4]=c1[6]=c1[13]=c1[15]=c1[17]=c1[19]=[[7,0,126]];c1[41]=[[7,64,62]]; c1[45]=[[7,64,14],[6,80,14],[5,96,14],[4,112,14]];c2[46]=[[1,0,94],[3,96]]; c2[47]=[[1,0],[2,32],[3,64],[4,96]];c2[48]=[[3,0,94],[4,96]];c2[49]=[[2,0]];
c1
配列は、SP または DP-1P 側のチャージノーツ譜面パターン、c2
配列は、DP-2P 側のチャージノーツ譜面パターンを定義している。
bms2jsh.js の譜面解析部
通常ノーツ "#" 始まり
if(sdd.charAt(0)=="#"){ sft++; v2c=0; while(sft<sdd.length){ v2o=""; v2v=(v2c? 1:3)*ln[n]/6; switch(sdd.charAt(sft)){ case "C":v2s= 0;v2p=192;v2t=0;if(!v2c)v2o=sdd.charAt(++sft);sft++;break; case "c":v2s=96;v2p=192;v2t=0;if(!v2c)v2o=sdd.charAt(++sft);sft++;break; //(中略) case "B":v2s= 0;v2p=192;v2t=1;v2b=Math.ceil(v2v/v2p)+1;v2o=sdd.substring(sft+1,sft+v2b);sft+=v2b;break; case "b":v2s=96;v2p=192;v2t=1;v2b=Math.ceil(v2v/v2p)+1;v2o=sdd.substring(sft+1,sft+v2b);sft+=v2b;break; //(中略) case "S":v2s= 0;v2p= 64;v2t=1;v2b=Math.ceil(v2v/v2p)+1;v2o=sdd.substring(sft+1,sft+v2b);sft+=v2b;break; case "s":v2s=32;v2p= 64;v2t=1;v2b=Math.ceil(v2v/v2p)+1;v2o=sdd.substring(sft+1,sft+v2b);sft+=v2b;break; //(中略) case "1":case "2":case "3":case "4":case "5":case "6":case "7": v2o=sdd.substring(sft,sft+3);v2t=2;sft+=3;break; case "9":v2o="1"+sdd.substring(sft+2,sft+4); case "8":for(i2=0;i2<6;i2++){ if(b64.indexOf(sdd.charAt(sft+1))&(1<<i2))v2o+=(i2+2)+sdd.substring(sft+2,sft+4); }v2t=2;sft+=4;break; case "-":v2c=1;sft++;break; case "_":v2o=(sft==sdd.length-1)? "AA":sdd.substring(sft+1);v2c=v2t=2;break; default:w("<font color=red>error<\/red>");return; } if(sdd.charAt(sft-1)=="-")continue;
変数
sdd |
文字列 | sp 配列やdp 配列の要素文字列が設定されている(例えば"#O4YTC" ) |
sft |
整数 | 初期値0 。sdd の何文字目を読むかを表す |
v2c |
整数 | 初期値0 。sdd を読み進めて"-" が登場したら1 、"_" が登場したら2 の値に更新される |
v2o |
文字列 | sdd の部分文字列を格納 |
v2v |
整数 | なんらかの長さ。ln[n] がデフォルト値384 のとき、192 または64 の値をとる |
v2s |
整数 | 0 ,12 ,24 ,48 ,96 または16 ,32 の値をとる |
v2p |
整数 | 12 ,24 ,48 ,96 ,192 または16 ,32 ,64 の値をとる |
v2t |
整数 | 0 ,1 ,2 の値をとる |
v2b |
整数 | Math.ceil(v2v /v2p ) + 1 の計算結果が入り、sdd から何文字取得するかを意味する |
v2k |
文字列 | v2o を 1 文字ずつ解析した結果を格納 |
v2t
が0
のとき
// case "C":v2s= 0;v2p=192;v2t=0;if(!v2c)v2o=sdd.charAt(++sft);sft++;break; // ln[n] はたいてい 384 if(v2t==0){ for(i2=v2s;i2<ln[n];i2+=v2p)v2k+=(v2c ? "1":v2o); }
この例の場合、i2
は0
と192
を取り、v2k
にはv2o
の文字列が 2 文字重ねられている。
v2t
が1
のとき
// b64="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; if(v2t==1){ for(i2=0;i2<v2o.length;i2++){ if(v2c==0){ v2x=b64.indexOf(v2o.charAt(i2)); v2k+=Math.floor(v2x/8)+""+v2x%8; }else if(v2c==1){ v2x=b64.indexOf(v2o.charAt(i2)); for(i3=5;i3>=0;i3--)v2k+=(v2x>>i3)&1 ? 1:0; } } }
v2o
に対してBase64 decodeのような処理を行う。v2x
には0
~63
のいずれかが代入される。
v2k
が0
のとき、その整数を「2 文字の 8 進数表記」としてv2k
に格納し、
v2k
が1
のとき、その整数を「6 文字の 2 進数表記」としてv2k
に格納する。
v2t
が2
以外の時(= 0
または1
のとき)
v2k
をもとにノーツ描画する。
if(v2t!=2){ for(v2i=0,i2=v2s;i2<ln[n];v2i++,i2+=v2p){ if(hids && v2c)continue; if((ob2=v2k.charAt(v2i))!=0){ if(alls&&key==14){ // (ALL SCRATCH パターンの処理)(省略) }else if(v2c&&!alls){ ob2=0; if(conum==0)co[0]=((Math.floor(i2/6/mnbase)%mnloop)==mn)? "r":"s"; }else if(sran) { // (S-RANDOM パターンの処理)(省略) } if(h==1 && stat_on==0)p1o++; objtab[i2] |= (1<<ob2); jpos=obr[h-1][ob2]; if(jpos==8)continue; if(stat_on==0){kc[h-1][jpos]++;npos.push((n+gap)*100000+Math.floor(i2*hs/3)+nmergin[n+gap]);} // obj! stat_insert(h-1, ob2, stat_pos + i2, 0); objstr=imgdir+co[jpos]+dstr; ttl++; o+="<img src="+objstr+".gif"+msc+" name=ttl"+ttl+ " onMousedown=dragOn('ttl"+ttl+"') style='top:"+ (nbar*hs-Math.floor(i2*hs/3)+coy)+"px;left:"+ (ob2 ? jpos*(14-d)-sh*(37-d*8)+60-d*15 :(sh-1)*(98-d*7))+"px'>"; } } }
img 要素の style 属性に含まれる
"left:" + (ob2 ? jpos*(14-d)-sh*(37-d*8)+60-d*15 : (sh-1)*(98-d*7))+"px"
部分について検討する。
ob2
は、v2k
の先頭から順に 1 文字代入される。このときob2
が0
ならば読み飛ばす。
また、v2c
が1
または2
のとき、ob2
は0
として扱う。
次に、jpos
はobr[h-1][ob2]
の値が設定される。
obr=[[0,1,2,3,4,5,6,7],[0,1,2,3,4,5,6,7]];
h
は、1P 側を描画するときに1
、2P 側を描画するときに2
が設定されている。
obr
は、RANDOM配置のときに、どのレーンがどのレーンに入れ替わるかを保存する二次元配列である。正規譜面のときは上述のデフォルト値から変更されないため、jpos
にはob2
と同じ値が代入される。
よって、jpos
すなわちob2
が分かれば、鍵盤レーンかターンテーブルレーンかが判明する。
同様に、
"top:" + (nbar*hs-Math.floor(i2*hs/3)+coy)+"px"
部分について検討する。
coy=-5; // (中略) c4tmp=parseInt(s.charAt(4),16); d=(c4tmp&1)? 1:0; hs=((c4tmp&6)+4)/4; // (中略) function bars_(n,mn){ // (中略) if(!ln[n])ln[n]=LNDEF; nbar=Math.ceil(ln[n]/3); if(nbar<4)nbar=4; // (中略) for(v2i=0,i2=v2s;i2<ln[n];v2i++,i2+=v2p){
s.charAt(4)
は標準0
なので、c4tmp
は0
で、hs
は1
になる。
ln[n]
は標準384
なので、nbar
は128
になる。
i2
は for 構文において、初期値v2s
、増分v2p
となる。
case "C":v2s= 0;v2p=192;v2t=0;if(!v2c)v2o=sdd.charAt(++sft);sft++;break; // (中略) case "s":v2s=32;v2p= 64;v2t=1;v2b=Math.ceil(v2v/v2p)+1;v2o=sdd.substring(sft+1,sft+v2b);sft+=v2b;break;||<
case "C" のとき、v2s=0; v2p=192;
とあり、i2
は0
,192
を取り(=4分音符のオモテ)、
case "s" のとき、v2s=32; v2p=64;
とあり、i2
は32
,96
,160
,224
,288
,352
を取る(=『6 分音符』のウラ)。
ln[n]
の標準である384
は、「4 分の 4 拍子」における「4 拍」ぶんを表しており、96
で「4 分音符 1 つぶん」に相当する長さである。
1 小節の長さln[n]
におけるi2
が、小節の先頭からの位置にあたる。
v2t が 2 のとき
// b64="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; for(i2=0;i2<v2o.length;i2+=2){ if(v2c==0){ob2=v2o.charAt(i2);i2++;}else ob2=0; if(hids && ob2==0)continue; v2h=b64.indexOf(v2o.charAt(i2))*64+b64.indexOf(v2o.charAt(i2+1))*1; if(alls&&key==14){ // (ALL SCRATCH パターンの処理)(省略) }else if(mn!=void(0)&&!alls){ if(conum==0)co[0]=((Math.floor(v2h/6/mnbase)%mnloop)==mn)? "r":"s"; }else if(sran) { // (S-RANDOM パターンの処理)(省略) } if(h==1 && stat_on==0)p1o++; objtab[v2h] |= (1<<ob2); jpos=obr[h-1][ob2]; if(jpos==8)continue; // (省略) ttl++; o+="<img src="+objstr+".gif"+msc+" name=ttl"+ttl+ " onMousedown=dragOn('ttl"+ttl+"') style='top:"+ (nbar*hs-Math.floor(v2h*hs/3)+coy)+"px;left:"+ (ob2 ? jpos*(14-d)-sh*(37-d*8)+60-d*15 :(sh-1)*(98-d*7))+"px'>";
v2c
が0
のとき、v2o
を 3 文字ずつ読み込む。そのうち前 1 文字をob2
に代入し、後ろ 2 文字はb64
を利用して 64 進数の 2 文字とみなしてv2h
に代入する。
v2c
が0
以外のとき、ob2
に0
を代入する。v2o
から 2 文字ずつ読み込み、b64
を利用して 64 進数の 2 文字とみなしてv2h
に代入する。
正規譜面のとき、jpos
にはob2
と同じ値が代入される。
s.charAt(4)
は標準0
なので、c4tmp
は0
で、hs
は1
になる。
ln[n]
は標準384
なので、nbar
は128
になる。
1 小節の長さln[n]
におけるv2h
が、小節の先頭からの位置にあたる。
通常ノーツ "#" 以外で始まり
sft=div=0; // (中略) if(sdd.charAt(0)=="x"){ len=parseInt(sdd.substring(1,4),16); sft=4; }else len=sdd.length;
sdd
が "x" で始まるときは、sdd
の 2 ~ 4 文字目にあたる 3 文字を 16 進数とみなして、len
に設定し、sft
を4
とする。
sdd
が "x" で始まらないときは、len
はsdd
の長さとする。この場合、sft
は初期値の0
である。
sd=[[],sp,dp]; // (中略) for(mm=0; mm<=m; mm++){ sdd=sd[h+mm][n]; // (中略) for(;sft<sdd.length;sft+=2,div+=2){ while(sdd.charAt(sft)=="@"){ div+=parseInt(sd[h+mm][n].substring(sft+1,sft+3),16)*2; sft+=3; } if(mn!=void(0)&&conum==0)co[0]=((Math.floor(nbar*div/(len*2*mnbase))%mnloop)==mn)?"r":"s"; y=parseInt(sdd.substring(sft,sft+2),16); for(j=0;j<=ky;j++){ if(y>>j==0)break; if(hids && j==0)continue; if(y>>j&1){ if(h==1 && stat_on==0)p1o++; jpos = obr[h-1][j]; if(jpos==8)continue; if(alls&&key==14){ // (ALL-SCRATCH パターン)(省略) }else if (sran) { // (S-RANDOM パターン)(省略) } // (統計情報操作)(省略) ttl++; o+="<img src="+objstr+".gif"+msc+" name=ttl"+ttl+ " onMousedown=dragOn('ttl"+ttl+"') style='top:"+ (nbar*hs-Math.floor(nbar*div*hs/len)+coy)+"px;left:"+ (jpos? jpos*(14-d)-sh*(37-d*8)+60-d*15:(sh-1)*(98-d*7))+"px'>"; } } }
sdd
文字列を先頭からsft
文字読み飛ばし、そこから 2 文字ずつ繰り返し読み込んで処理していく for 構文である。
sdd.charAt(sft)
が"@"
であるとき、"@"
の次の 2 文字を 16 進数として数値変換した値を 2 倍してdiv
に加える。"@"
が複数回登場するうちはdiv
を調整する。ここで、div+=parseInt(sd[h+mm][n].substring(sft+1,sft+3),16)*2;
におけるsd[h+mm][n]
は、sdd
と同じ文字列である。
次にsdd
から 2 文字読み取り、16 進数として数値変換した値をy
に設定する。
このy
を、0 ビット~ 7 ビット右シフトした最下位ビットが1
であるかどうかを判定し、1
である場合に右シフトしたビット数(=j
)に音符ノーツ(1
~7
)またはターンテーブルノーツ(0
)があることを意味する。
正規譜面の場合は、jpos
にj
と同じ値が設定される。
s.charAt(4)
は標準0
なので、c4tmp
は0
で、hs
は1
になる。
ln[n]
は標準384
なので、nbar
は128
になる。
1 小節の長さln[n]
におけるdiv
が、小節の先頭からの位置にあたる。
チャージノーツの書式
「MIRACLE MEETS」DP-NORMAL より抜粋
c2[25]=[/* [4,0,62,2], */[4,112,16,1]]; c2[26]=[[4,0,128,0]]; c2[27]=[[4,0,62,2]]; c2[28]=[[0,0,128,1]]; c2[29]=[[0,0,96,2]]; c1[30]=[[0,0,128,1]]; c1[31]=[[0,0,64,2]];
https://textage.cc/score/17/miraclem.html?DN604~24-31 より
配列には最大 4 要素を持つ。[0]
はレーン番号(1
~7
: 鍵盤、0
: ターンテーブル)を表す。[1]
はその小節における描画開始位置(小節の先頭を0
、単位は 4 分音符 1 つを32
とする)を表す。[2]
はその小節における描画の長さを表す(省略時は30
)。[3]
はこのチャージノーツがこの小節内に収まるか、複数小節にまたがるかを表現する値を表し、0
は「前の小節から継続、次の小節に継続」、1
は「この小節から開始、次の小節に継続」、2
は「前の小節から継続、この小節で終端」、3
は「この小節で開始して終端する」、7
は MSS(Multi Spin Scratch)のいずれかを取る。[3]
の省略時は3
が採用される。
c2[52]=c1[60]=[[57,0,62],[46,64],[35,96]];c2[53]=c1[61]=[[24,0,62],[13,96]];
[0]
が 10 以上の場合は、2 つ以上のレーンを 1 つの定義にまとめている。57
は 5 鍵と 7 鍵の同時押しチャージノーツ。
sc32
配列について
「(This Is Not) The Angels」SP-ANOTHER より抜粋
sc32=[],sc32base=[],sc32loop=[]; // (中略) sc32[19]=sc32[55]=4;
19 小節目にあるのは「32 分音符」間隔のターンテーブルノーツのようだ。
// (中略) function bars_(n,mn){ // (中略) if(hs>1)mn=void(0); else if(sc32[n]>=0)mn=sc32[n]; mnbase=sc32base[n]; if(sc32base[n]==undefined)mnbase=8; mnloop=sc32loop[n]; if(sc32loop[n]==undefined)mnloop=mnbase; ttlstart=ttl; ttlcn=0;
hs
は1
なので else if 節に進み、n
が19
または55
のときには、mn
にsc32[19]
すなわち4
が設定される。
sc32base[n]
は未定義なので、mnbase
にデフォルト値8
が設定され、同様にsc32loop[n]
も未定義なので、mnloop
にも8
が設定される。
}else if(v2c&&!alls){ ob2=0; if(conum==0)co[0]=((Math.floor(i2/6/mnbase)%mnloop)==mn)? "r":"s"; // (中略) objstr=imgdir+co[jpos]+dstr; ttl++; o+="<img src="+objstr+".gif"+msc+" name=ttl"+ttl+
mnbase
, mnloop
, mn
は、co[0]
に"r"
または"s"
のどちらを設定するかを決めるためのもので、これはobjstr
文字列を構成して、img 要素に組み込まれる gif ファイル名に使われるだけだ。