kshをjson化した変な話

#まえがき
こちらの記事は、[CCC Advent Calendar 2018](https://adventar.org/calendars/3082)の21日目の記事です。

#前振り
大半の人はタイトルを読んだとき、「ん？」って思ったかもしれません。
一部の人にとっては、タイトルを読んだとき「は？」って思ったかもしれません。

つまりは、誰からしても変なことしているなと、思ったことでしょう。
まさにその通りで、この記事は誰得極まりないものですｗ
だけど自分にとっては需要があったということなだけですｗ

それは置いといて。

##ksh？

kshとは何か。
一言で言えば「音楽ゲームにおける譜面データ」ということです。

「 [K-Shoot Mania](https://www.kshootmania.com/)  (以降KSM)」 という某K○NAMIさんから出ている音楽ゲーム「SD○X」を模したPCゲームがあるんです。
自分はS○VXが好きだったこともあって、遊ぶことが多々ありました。
KSMには譜面作成機能というのがありまして、作成した譜面を保存するときのファイル形式が「.ksh」なのです。

##なんでkshをjson化したの？

細かい経緯は端折るんですが、
あるときですね、自分は音楽ゲームの譜面データがjson形式で必要になってしまいました。

jsonには譜面を提示するタイミング(ms)とどのボタンに割り当てるか。

この二つの情報が必要になってしまいました。
提示するタイミングがmsで必要なので、いちいち計算するの面倒くさい・・・。


さてどうするか。

「json形式で必要な音楽ゲームがパネル4枚。」
「KSMの白鍵の数は4つ。」
「kshで作ったものをjson化できるなら、GUIで楽に譜面生成できそう」

そんな変なことに気が付いた私は、「kshをjson化しよう」そんな変なことを思いついてしまいました。


#本題

さて本題。
音楽ゲームにおける譜面の要素で必要になってくるのが
BPMとどのタイミングでどこに譜面を配置するか。

##BPM2ms
BPMというのは「**B**eat **P**er **M**inute」の略です。
日本語に訳すと、「1分間に何回4音符を鳴らすか」ということです。
これはもう計算式が決まっています。
一応、しっかり導出してみましょう。

BPM160を例にとりましょう。
これは1分間に160回4分音符が鳴ることを意味します。(はやっ)
つまり、
$ \dfrac{60(s)}{160(回)}=0.375(s)=375(ms)\quad -①$

ということです。
つまり、BPM160は`375ms`間隔でというのは`4分音符`が鳴ることを示しています。

それっぽく言うならば、
BPM160で、4分音符間隔でなっている音が時刻 $t[ms]$ で$n$番目に鳴ったとする(これを $S_n$ とする)。
$S_{n+1}$ で鳴る音は、$S_n$ の`375[ms]`後ということです。
(要らん説明でしたかね)

もし、八分音符ならばこの値の半分になり、二分音符の場合は倍になるのは自明だと思います。
例えば、BPM100の8分音符とBPM200の4分音符は同じこと言っていることが分かれば、すぐ気づくと思います。

では、先ほどの式①を少し変えましょう。
$\text{音符の長さ}[ms] = 60\times 1000[ms]\times A_{p}\div \text{BPM}$
ただし、$A_p$ は $p$ 分音符の時の定数をさします。
<details><summary>補足</summary><div>$A_1=4$
$A_2=2$
$A_4=1$
$A_8=0.5$
$A_{12}=0.333..$
$A_{16}=0.25$
$A_n= \dfrac{4}{n}$</div></details>

これで、音符の長さが分かります。

例えば、BPM200の12分音符の長さはどれくらいでしょうか？
<details><summary>こたえ</summary><div>$\text{音符の長さ}[ms] = 60\times 1000[ms]\times 1/3\div 200$
$\text{音符の長さ} = 100[ms] = 0.1[s]$</div>
</details>

ではこれをコードに起こします。
今回は自分がよく使っているprocessingを使用します。
正直なところ、書くまでもないんだけど、一応書いておきます。

```java:bpm2ms.pde
float bpm2ms (float bpm, int n) {
  float ms = 60.0 / bpm * 4.0 / n * 1000;
  return ms;
}
```

BPM170の8分音符を計算してみましょう。

```java:bpm2ms_result.pde
 176.4706
```

はい。出ましたね。
ネット上には自動で計算してくれるWebサイトがあるので確認してみます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/256240/d48a8bd2-4fff-6d6a-c3dd-91498993ac92.png)
↑のサイトは[ディレイタイム計算機](https://www.studiorag.com/blog/blog/2015/06/17/162?disp=more)で確認できます。
四捨五入すれば、一致することが確認できました。問題なさそうです。

では次です。

##kshの構造に迫る
では早速kshの構造について迫っていきましょう。
一例をぶん投げます。長いので、たたんでおきます。
<details><summary>example.ksh</summary><div>

```text:example.ksh
title=example
artist=dj example
effect=
jacket=.jpg
illustrator=
difficulty=extended
level=1
t=150
m=example.mp3
mvol=75
o=0
bg=desert
layer=arrow
po=0
plength=15000
pfiltergain=50
filtertype=peak
chokkakuautovol=0
chokkakuvol=50
ver=160
--
beat=4/4
0000|00|--
--
0001|00|--
--
0001|00|--
1000|00|--
--
0001|00|--
0000|00|--
0000|00|--
1000|00|--
--
0001|00|--
0000|00|--
0000|00|--
0000|00|--
0010|00|--
0100|00|--
1000|00|--
0000|00|--
--
0001|00|--
0010|00|--
0100|00|--
1000|00|--
0001|00|--
0000|00|--
0000|00|--
0000|00|--
1000|00|--
0000|00|--
0000|00|--
0000|00|--
0001|00|--
0000|00|--
0000|00|--
0000|00|--
--
0001|00|--
0010|00|--
0100|00|--
0010|00|--
0100|00|--
1000|00|--
0001|00|--
0000|00|--
0000|00|--
0000|00|--
0100|00|--
0000|00|--
--
0001|00|--
0000|00|--
1000|00|--
0100|00|--
0010|00|--
0000|00|--
0100|00|--
0000|00|--
--

```

</div></details>

kshを詳しく見ていきましょう。
最初の18行くらいまではスルーして、(楽曲情報など今回には関係ないため)その先をよく見てましょう。

`--`だけの行は小節の区切りをさしていて、`--`～`--`間が1小節であることを意味します。
少しずつ掘り起こしていきましょう。

※ここでは`0000|00|--`の`|`や`--`は無視します。

beat=4/4のため、以降4分の4拍子として説明していきます。
4分の4拍子とは、1小節が4分が4個で構成されていることを意味しています。
(これは音楽の授業で習ったよね？)

```text:cite1小節.txt
--
0001|00|--
--
```

これは一番右の白い鍵盤が小節の冒頭(1拍目)に割り当てられていることが書かれています。
それ以外に情報はないので、記述はそれのみとなっています。

```text:cite2.txt
--
0001|00|--
0000|00|--
0000|00|--
1000|00|--
--
```

これは、1小節に4つの行があります。つまり、4分で1拍目と4拍目にそれぞれ譜面が割り当てられていることが分かります。
しかし、2拍目、3拍目には譜面は割り当てられてないので0のみの記述になっています。


```text:cite3.txt
--
0001|00|--
0010|00|--
0100|00|--
1000|00|--
0001|00|--
0000|00|--
0000|00|--
0000|00|--
1000|00|--
0000|00|--
0000|00|--
0000|00|--
0001|00|--
0000|00|--
0000|00|--
0000|00|--
--
```
これは16行で小節が構成されています。つまり、16分で表現されていることを意味します。4分の4拍子は16分の16拍子の相当するので、1～5個目までは16分でできていますが、5個目以降は4分で3拍分なので間に16分の3拍分は空欄になっています。

つまり、小節の表現の構成は`表現できる最小の単位(n分)まで記述する`ということになっていそうです。

kshの大まかな構造理解はこれで良いとしましょう。

##json化する
言葉で説明するのは正直なところ難しいのですが、頑張って説明していきます
kshの構造はこんな感じでした。

```text:kshspl.txt
曲や譜面などの基本情報(～20行目)
BPMは8行目の"t="以降
offsetは11行目の"o="以降、単位は(ms)
--
1小節目
--
2小節目
--
3小節目
--
：
：
--
n小節目
--
```

offsetは音源と譜面の軽微な調整に使います。これは譜面生成タイミングに影響するので、抽出する必要があります。

パースのポイントは`0000|00|--`のある行がいくつあるかで、表現されている刻みが分かるということ。譜面情報はその中に含まれているということ。

つまり、`--`間に、幾つの0000|00|--の行があるかを調べ、1000(左から1番目)や0010(左から3番目)のようにどこに割り当てられているかを取得できればいいですね。

それをコードに起こします。

かなり頭の悪い実装の仕方をしてますので、簡易版で。

```java:getNotes.pde
String ksh2json(String str[]){
   //どこからどこまでがn小節目かを取得する。
   //ここから
   int getSectionTop = -1;//n小節目の先頭の行を参照する
   int getSectionBottom = -1;//n小節目の最後の行を参照する
   int top = 0;//さいごに参照した行
   int bottom = str.length;//kshファイル自体の行の大きさ
   int bpm = ~;//BPM保存用
   int offset = ~;//ある行にoffsetがあるので取得する
   int nowMs += offset;//現在の何msかを計算する。
   //ここまでは全部 global変数
   while (/*topがbottomに到達するまで*/){
      getRange(top, bottom, str);
      jsn += picNotes(str);//jsonを更新する
   }
   return jsn;
}
void getRange(int above, int under, String str[]){
   //範囲を取得して、picNotesに役立てる
    for (int i=above; i<under; i++) {
       if (getSectionTop == -1) {
         if (str[i].equals("--")) {
           getSectionTop = i;
         }
       } else if (getSectionBottom == -1) {
         if (str[i].equals("--")) {
           getSectionBottom = i;
         }
    }
}
String picNotes(String str[]){
   int barRange;//何分かどうか
   boolean noNote[] = new boolean [barRange];//ノートがあるかどうかの判定
   int tiles[] = new int[barRange];//譜面データ
   for (int i=getSectionTop+1; i<getSectionBottom; i++){
      //ノーツ情報の有無を取得->noNoteに保存
   }
   for (int i=0; i<tiles.length; i++){
      //ノート情報を記録する
   }
   ms = bpm2ms(~);//分(ぶ)数ごとのmsを計算し、json化するときにタイミングを合わせられるようにする。
   for (int i=0; i<tiles.length; i++){
      //ノート情報をjson化する
      //msを逐一計算
      //ノート情報がなければすっ飛ばして、jsonを作る
   }
   //次の小節を取得するために更新。
   top = getSectionBottom+1;
   getSectionTop = getSectionBottom;
   getSectionBottom = -1;
   return json;
} 
```

<details><summary>B4U.ksh</summary><div>

```text:B4U.ksh
title=B4U
artist=
effect=
jacket=.jpg
illustrator=
difficulty=extended
level=1
t=170
m=b4u cut.mp3
mvol=75
o=40
bg=desert
layer=arrow
po=0
plength=15000
pfiltergain=50
filtertype=peak
chokkakuautovol=0
chokkakuvol=50
ver=160
--
beat=4/4
0000|00|--
--
0000|00|--
0100|00|--
--
0000|00|--
0000|00|--
0010|00|--
1000|00|--
--
0001|00|--
0100|00|--
0010|00|--
0000|00|--
--
0000|00|--
0000|00|--
1000|00|--
0100|00|--
--
0010|00|--
0001|00|--
1000|00|--
0000|00|--
--
0000|00|--
0000|00|--
0010|00|--
0100|00|--
--
0010|00|--
0100|00|--
0010|00|--
0000|00|--
--
0000|00|--
1000|00|--
--
0000|00|--
0001|00|--
0010|00|--
0000|00|--
--
0100|00|--
0000|00|--
0001|00|--
0010|00|--
--
0100|00|--
0000|00|--
1000|00|--
0000|00|--
0100|00|--
0000|00|--
0000|00|--
0010|00|--
--
0000|00|--
0100|00|--
0010|00|--
0000|00|--
--
0100|00|--
0000|00|--
0000|00|--
0010|00|--
0000|00|--
0000|00|--
0100|00|--
0000|00|--
--
0000|00|--
0000|00|--
0001|00|--
0100|00|--
--
0010|00|--
1000|00|--
0001|00|--
0000|00|--
--
1000|00|--
0010|00|--
--
0100|00|--
0010|00|--
0001|00|--
0000|00|--
--
0000|00|--
0100|00|--
--
0000|00|--
0010|00|--
--
0000|00|--
0100|00|--
--
0000|00|--
1000|00|--
--
0000|00|--
0001|00|--
--
0000|00|--
0010|00|--
--
0100|00|--
0010|00|--
--
0000|00|--
1000|00|--
0001|00|--
0000|00|--
--
0000|00|--
0010|00|--
--
0000|00|--
--
0000|00|--
1000|00|--
--
0000|00|--
--
0000|00|--
0001|00|--
--
0000|00|--
--
0000|00|--
0100|00|--
--
0010|00|--
0000|00|--
0000|00|--
0000|00|--
0100|00|--
0000|00|--
0000|00|--
0010|00|--
--
0000|00|--
0000|00|--
0001|00|--
0000|00|--
0010|00|--
0000|00|--
0000|00|--
0100|00|--
--
0000|00|--
1000|00|--
0100|00|--
0000|00|--
--

```

</div></details>
これをjson化すると、
<details><summary>B4U.json</summary><div>

```json:B4U.json
[
    {
        "start": "2711",
        "tileNum": "1"
    },
    {
        "start": "5885",
        "tileNum": "2"
    },
    {
        "start": "6842",
        "tileNum": "0"
    },
    {
        "start": "7799",
        "tileNum": "3"
    },
    {
        "start": "8756",
        "tileNum": "1"
    },
    {
        "start": "9713",
        "tileNum": "2"
    },
    {
        "start": "13541",
        "tileNum": "0"
    },
    {
        "start": "14498",
        "tileNum": "1"
    },
    {
        "start": "15455",
        "tileNum": "2"
    },
    {
        "start": "16412",
        "tileNum": "3"
    },
    {
        "start": "17369",
        "tileNum": "0"
    },
    {
        "start": "21197",
        "tileNum": "2"
    },
    {
        "start": "22154",
        "tileNum": "1"
    },
    {
        "start": "23111",
        "tileNum": "2"
    },
    {
        "start": "24068",
        "tileNum": "1"
    },
    {
        "start": "25025",
        "tileNum": "2"
    },
    {
        "start": "28199",
        "tileNum": "0"
    },
    {
        "start": "30416",
        "tileNum": "3"
    },
    {
        "start": "31373",
        "tileNum": "2"
    },
    {
        "start": "33287",
        "tileNum": "1"
    },
    {
        "start": "35201",
        "tileNum": "3"
    },
    {
        "start": "36158",
        "tileNum": "2"
    },
    {
        "start": "37115",
        "tileNum": "1"
    },
    {
        "start": "37819",
        "tileNum": "0"
    },
    {
        "start": "38523",
        "tileNum": "1"
    },
    {
        "start": "39579",
        "tileNum": "2"
    },
    {
        "start": "40888",
        "tileNum": "1"
    },
    {
        "start": "41845",
        "tileNum": "2"
    },
    {
        "start": "43759",
        "tileNum": "1"
    },
    {
        "start": "44815",
        "tileNum": "2"
    },
    {
        "start": "45871",
        "tileNum": "1"
    },
    {
        "start": "48489",
        "tileNum": "3"
    },
    {
        "start": "49446",
        "tileNum": "1"
    },
    {
        "start": "50403",
        "tileNum": "2"
    },
    {
        "start": "51360",
        "tileNum": "0"
    },
    {
        "start": "52317",
        "tileNum": "3"
    },
    {
        "start": "54231",
        "tileNum": "0"
    },
    {
        "start": "55491",
        "tileNum": "2"
    },
    {
        "start": "56751",
        "tileNum": "1"
    },
    {
        "start": "57708",
        "tileNum": "2"
    },
    {
        "start": "58665",
        "tileNum": "3"
    },
    {
        "start": "61839",
        "tileNum": "1"
    },
    {
        "start": "64359",
        "tileNum": "2"
    },
    {
        "start": "66879",
        "tileNum": "1"
    },
    {
        "start": "69399",
        "tileNum": "0"
    },
    {
        "start": "71919",
        "tileNum": "3"
    },
    {
        "start": "74439",
        "tileNum": "2"
    },
    {
        "start": "75699",
        "tileNum": "1"
    },
    {
        "start": "76959",
        "tileNum": "2"
    },
    {
        "start": "79176",
        "tileNum": "0"
    },
    {
        "start": "80133",
        "tileNum": "3"
    },
    {
        "start": "83307",
        "tileNum": "2"
    },
    {
        "start": "87238",
        "tileNum": "0"
    },
    {
        "start": "91169",
        "tileNum": "3"
    },
    {
        "start": "95100",
        "tileNum": "1"
    },
    {
        "start": "96360",
        "tileNum": "2"
    },
    {
        "start": "97768",
        "tileNum": "1"
    },
    {
        "start": "98824",
        "tileNum": "2"
    },
    {
        "start": "99880",
        "tileNum": "3"
    },
    {
        "start": "100584",
        "tileNum": "2"
    },
    {
        "start": "101640",
        "tileNum": "1"
    },
    {
        "start": "102949",
        "tileNum": "0"
    },
    {
        "start": "103906",
        "tileNum": "1"
    }
]

```

</div></details>

こんな感じになります。
ソフランや変拍子に対応してないなど、色々と不足部分があるのですが、ひとまず、骨格となる部分は完成です。

# さいごに
はい。
誰得な話でしたが、今回の記事は終わりです。
唐突な終わり方ですし、解説とかも皆無。
これ大丈夫なん？って思うかもしれませんが、こんな感じでいいでしょ？（だってロクなことしてないし）
というわけで、大切な時間を無駄にしていただきありがとうございました。