JavaScriptでMIDIからUTAUのUSTファイルへ雑に変換する

語弊を承知でいうと、ボーカロイドみたいなツールの**UTAU**。
`.midi`ファイルからUTAUで使う`.ust`ファイルへ歌詞付きで雑に変換しダウンロードする方法のおぼえがきです。

## 前提

- create-react-appベースのReactアプリ
- ES6準拠のJavaScript

## tone.jsでMIDIのインポート

だいぶつよいMIDIライブラリの[tone.js](https://tonejs.github.io/)を利用します。

### インストール
```sh
yarn add @tonejs/midi
```

### インポート
``` js
import { Midi } from '@tonejs/midi';
```

### ボタンクリックでMIDIを取り込めるようにする

ここは読み飛ばしていただいても。

```jsx
import React, { useState } from 'react';
import { Midi } from '@tonejs/midi';

const MidiPage = (props) => {
  const [midi, setMidi] = useState(false);
  // FileReaderでMIDIファイルをArrayBufferで読み込む
  const handleChangeFile = (e) => {
    const file = e.target.files[0];
    const reader = new FileReader();
    reader.readAsArrayBuffer(file);
    reader.onload = (e) => {
      const midiData = e.target.result;
      if (midiData) {
         // tone.jsで変換
         const midi = new Midi(midiData);
         // stateに入れておく
         setMidi(midi); 
      }
    };
    reader.onerror = (e) => {
      // ロード失敗
    };
  };
  return (
    <div>
      <input
        type="file"
        accept="audio/midi, audio/x-midi"
        onChange={handleChangeFile}
       />
    </div>
  );
```

## USTファイルに変換する

### tone.jsでインポートしたMIDIをUSTに変換できるようにする

tone.jsのMIDIからUSTに雑に変換します。
歌詞にも一応対応してみます。

```js
const parseMidiToUST = (midi, lyric, trackIndex, ustOptions = {
  // USTファイルのオプション
  projectName: '(no title)',
  flags: '',
  outFile: '',
  voiceDir: '${DEFAULT}/',
  mode2: true,
}) => {
  const json = midi.toJSON();
  const header = json.header;
  // MIDIヘッダから拍子を取得
  const beats = Array.isArray(header.timeSignatures) && header.timeSignatures.length > 0 ? header.timeSignatures[0].timeSignature : [4,4];
  // MIDIヘッダからテンポを取得
  // テンポ情報がないMIDIファイルの場合、UTAU-Synth側で50000になり操作不能になるので、ない場合は120
  const tempo = header.tempos.length ? midi.header.tempos.slice(-1)[0].bpm : '120';
  // 使用するトラックを指定
  const track = json.tracks[trackIndex];
  return `
    [#VERSION]
    UST Version2.0
    Charset=UTF-8
    [#SETTING]
    TimeSignatures=(${beats[0]}/${beats[1]}/0),
    Tempo=${tempo}
    ProjectName=${ustOptions.projectName}
    OutFile=${ustOptions.outFile}
    VoiceDir=${ustOptions.voiceDir}
    Flags=${ustOptions.flags}
    Mode2=${ustOptions.mode2 ? 'True' : 'False'}
    ${track.notes.map((note, index, origin) => {
      return `
        [#${('0000' + index).slice(-4)}]
        Delta=${index === 0 ? note.ticks : note.ticks - origin[index - 1].ticks}
        Duration=${note.durationTicks}
        Length=${origin[index + 1] ? origin[index + 1].ticks - note.ticks : note.durationTicks}
        Lyric=${lyric[index] ? lyric[index] : ''}
        NoteNum=${note.midi}
        Velocity=${Math.floor(note.velocity) * 10000 / 100}
        StartPoint=0.00
        Intensity=100
        Modulation=0
        PBS=-40.0,0.0
        PBW=80.0
        Envelope=5.0,1.0,0.0,100.0,
        100.0,100.0,100.0,7.0,80.0,1.0,100.0,0.0,1.0,100.0,1.0,100.0
        VBR=0,0,0,0,0,0,0,0,0,0`;
    }).join('')}
  [#TRACKEND]`.replace(/^\s+/gm, ''); // Stringリテラルのインデントを削除
}
```

### USTファイルをダウンロードできるようにする

ここは読み飛ばしていただいても。

```jsx
import React, { useState } from 'react';

const parseMidiToUST = ...

const MidiPage = (props) => {
   const [midi, setMidi] = useState(false);
   // 適当な歌詞
   const [lyric, setLyric] = useState(['か','し','で','す','よ']);
   const handleDownloadUST = () => {
    const text = parseMidiToUST(midi, lyric, 0);
    const blob = new Blob([text], {
      type: 'text/plain;charset=utf-8',
    });
    const a = document.createElement('a');
    a.download = 'score.ust';
    a.href = URL.createObjectURL(blob);
    a.click();
  };
  return (
    <div>
      <input
        type="file"
        accept="audio/midi, audio/x-midi"
        onChange={handleChangeFile}
       />
      <button type="button" onClick={handleDownloadUST}>ダウンロード</button>
    </div>
  );
```

間違いなどありましたらご指摘ください、なにかの役に立てば幸いです。
(似たような要領でMusicXMLも一応いけるのですが、雑すぎるので別にします)