Magenta.js の MusicRNN の basic_rnn/melody_rnn/chord_pitches_improv を試す（環境は p5.js Web Editor上）

以下の記事に書いた 「Magenta」の JavaScript 版である Magenta.js のお試しの話の続きです。

●機械学習・AI で音を作れる「Magenta」の JavaScript 版（Magenta.js）を p5.js Web Editor上で動かしてみる - Qiita
　https://qiita.com/youtoy/items/32eedd5c5c9280fe3f0f

今回は、上記の記事でも試した「MusicRNN」の話です。
ちなみに MusicRNN　は、「利用者が特定のメロディを入力すると、その続きを作ってくれる」という機械学習モデルになっているようです。

上記の記事の中では、公式の手順を試す中で MusicRNN の「basic_rnn」を使う部分がありました。
[それ以外に、「melody_rnn」や「chord_pitches_improv」といったものもあるようだった](https://github.com/magenta/magenta-js/blob/master/music/checkpoints/README.md#table)ので、今回はそれらを試すプログラムを作っていきます。
![MusicRNNで試すもの.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/b11cdb52-2bbd-4133-31a9-4010204a58ac.jpeg)

なお、上記の画像の中で「drum_kit_rnn」というのがありますが、これは別の機会に試していければと思います。

## 【追記】 音が鳴る様子の動画を追加しました
どんな音が作られるのか、この記事で書いていたプログラムを実行しているところを、キャプチャしたものをツイートしました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">前に以下の記事を書いていた件、どんな音がなるのか分からない状態だったので、音が作られる様子を動画にしました。<br><br>●Magenta.js の MusicRNN の basic_rnn/melody_rnn/chord_pitches_improv を試す（環境は p5.js Web Editor上） - Qiita<br>　<a href="https://t.co/MbvbL7WrOG">https://t.co/MbvbL7WrOG</a><a href="https://twitter.com/hashtag/p5js?src=hash&amp;ref_src=twsrc%5Etfw">#p5js</a> <a href="https://t.co/sQmFMUWAyd">pic.twitter.com/sQmFMUWAyd</a></p>&mdash; you (@youtoy) <a href="https://twitter.com/youtoy/status/1469953032398176258?ref_src=twsrc%5Etfw">December 12, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 音を作る
### p5.js Web Editor の下準備
[前回の記事](https://qiita.com/youtoy/items/32eedd5c5c9280fe3f0f)の手順と同じように、まずは p5.js Web Editor の index.html でライブラリを読み込みます（前回と同じ、以下を読み込み）。

```html
<script src="https://cdn.jsdelivr.net/npm/@magenta/music@^1.0.0"></script>
```

次に、sketch.js を作っていきます。

### JavaScript のプログラム
今回、入力として与えるデータを 2つ増やしてみました（「チューリップ」と「かえるの歌」を用意してみました）。
また `preload()` の部分で「basic_rnn/melody_rnn/chord_pitches_improv」の 3つをそれぞれ読み込んでいます。

本来は、もう少しきれいに書けそうな内容になってますが、とりあえず版ということで...

```javascript
const TWINKLE_TWINKLE = {
  notes: [
    { pitch: 60, startTime: 0.0, endTime: 0.5 },
    { pitch: 60, startTime: 0.5, endTime: 1.0 },
    { pitch: 67, startTime: 1.0, endTime: 1.5 },
    { pitch: 67, startTime: 1.5, endTime: 2.0 },
    { pitch: 69, startTime: 2.0, endTime: 2.5 },
    { pitch: 69, startTime: 2.5, endTime: 3.0 },
    { pitch: 67, startTime: 3.0, endTime: 4.0 },
    { pitch: 65, startTime: 4.0, endTime: 4.5 },
    { pitch: 65, startTime: 4.5, endTime: 5.0 },
    { pitch: 64, startTime: 5.0, endTime: 5.5 },
    { pitch: 64, startTime: 5.5, endTime: 6.0 },
    { pitch: 62, startTime: 6.0, endTime: 6.5 },
    { pitch: 62, startTime: 6.5, endTime: 7.0 },
    { pitch: 60, startTime: 7.0, endTime: 8.0 },
  ],
  totalTime: 8,
};

const TULIP = {
  notes: [
    { pitch: 60, startTime: 0.0, endTime: 0.5 },
    { pitch: 62, startTime: 0.5, endTime: 1.0 },
    { pitch: 64, startTime: 1.0, endTime: 2.0 },
    { pitch: 60, startTime: 2.0, endTime: 2.5 },
    { pitch: 62, startTime: 2.5, endTime: 3.0 },
    { pitch: 64, startTime: 3.0, endTime: 4.0 },
    { pitch: 67, startTime: 4.0, endTime: 4.5 },
    { pitch: 64, startTime: 4.5, endTime: 5.0 },
    { pitch: 62, startTime: 5.0, endTime: 5.5 },
    { pitch: 60, startTime: 5.5, endTime: 6.0 },
    { pitch: 62, startTime: 6.0, endTime: 6.5 },
    { pitch: 64, startTime: 6.5, endTime: 7.0 },
    { pitch: 62, startTime: 7.0, endTime: 7.5 },
  ],
  totalTime: 7.5,
};

const FROG = {
  notes: [
    { pitch: 60, startTime: 0.0, endTime: 0.5 },
    { pitch: 62, startTime: 0.5, endTime: 1.0 },
    { pitch: 64, startTime: 1.0, endTime: 1.5 },
    { pitch: 65, startTime: 1.5, endTime: 2.0 },
    { pitch: 64, startTime: 2.0, endTime: 2.5 },
    { pitch: 62, startTime: 2.5, endTime: 3.0 },
    { pitch: 60, startTime: 3.0, endTime: 4.0 },
  ],
  totalTime: 4,
};

let player;

function preload() {
  music_rnn_melody_rnn = new mm.MusicRNN(
    "https://storage.googleapis.com/magentadata/js/checkpoints/music_rnn/melody_rnn"
  );
  music_rnn_melody_rnn.initialize();

  music_rnn_chord_pitches_improv = new mm.MusicRNN(
    "https://storage.googleapis.com/magentadata/js/checkpoints/music_rnn/chord_pitches_improv"
  );
  music_rnn_chord_pitches_improv.initialize();

  music_rnn_basic_rnn = new mm.MusicRNN(
    "https://storage.googleapis.com/magentadata/js/checkpoints/music_rnn/basic_rnn"
  );
  music_rnn_basic_rnn.initialize();
}

function setup() {
  createCanvas(400, 400);
  background(220);

  player = new mm.Player();
}

function draw() {}

function keyPressed() {
  if (!player.isPlaying()) {
    let rnn_steps, rnn_temperature;

    switch (key) {
      case "q":
        player.start(TWINKLE_TWINKLE);
        break;
      case "w":
        makeMusicBasic(TWINKLE_TWINKLE, 4, 16 * 2, 1.5);
        break;
      case "e":
        makeMusicBasic(TWINKLE_TWINKLE, 4, 16 * 8, 1.5);
        break;
      case "r":
        makeMusicMelody(TWINKLE_TWINKLE, 4, 16 * 2, 1.5);
        break;
      case "t":
        makeMusicChord(TWINKLE_TWINKLE, 4, 16 * 2, 1.5, ["G", "Em", "C", "D"]);
        break;

      case "a":
        player.start(TULIP);
        break;
      case "s":
        makeMusicBasic(TULIP, 4, 16 * 2, 1.5);
        break;

      case "z":
        player.start(FROG);
        break;
      case "x":
        makeMusicBasic(FROG, 4, 16 * 2, 1.5);
        break;
    }
  } else if (player.isPlaying()) {
    switch (key) {
      case " ":
        console.log("Stop");
        player.stop();
        break;
    }
  }
}

function makeMusicBasic(SONG, steps_per_quarter, rnn_steps, rnn_temperature) {
  const qns = mm.sequences.quantizeNoteSequence(SONG, steps_per_quarter);
  music_rnn_basic_rnn
    .continueSequence(qns, rnn_steps, rnn_temperature)
    .then((sample) => player.start(sample));
}

function makeMusicMelody(SONG, steps_per_quarter, rnn_steps, rnn_temperature) {
  const qns = mm.sequences.quantizeNoteSequence(SONG, steps_per_quarter);
  music_rnn_melody_rnn
    .continueSequence(qns, rnn_steps, rnn_temperature)
    .then((sample) => player.start(sample));
}

function makeMusicChord(
  SONG,
  steps_per_quarter,
  rnn_steps,
  rnn_temperature,
  chord_progression
) {
  const qns = mm.sequences.quantizeNoteSequence(SONG, steps_per_quarter);
  music_rnn_chord_pitches_improv
    .continueSequence(qns, rnn_steps, rnn_temperature, chord_progression)
    .then((sample) => {
      console.log(sample);
      player.start(sample);
    });
}
```

上記を実行して、あとは特定のキーを押下していけば、以下の内容を試せます。

- 入力をそのまま実行
   - きらきら星、チューリップ、かえるの歌 の3つ
- 音を生成1： basic_rnn
   - 3つの入力のそれぞれを、32ステップで指定（他のパラメータは前回の記事と同様）
   - きらきら星について、128ステップで指定（他のパラメータは前回の記事と同様）
- 音を生成2・3： melody_rnn/chord_pitches_improv
   - きらきら星について、32ステップで指定（他のパラメータは前回の記事と同様）

上記を試してみると、それぞれで異なる音が鳴るのが確認できました。

### 補足： chord_pitches_improv を使った場合
chord_pitches_improv を使った場合だけ、 `continueSequence()` でのパラメータ指定が他と異なります。

[公式情報の continueSequence の部分](https://magenta.github.io/magenta-js/music/classes/_music_rnn_model_.musicrnn.html#continuesequence)で、以下の画像中に出てきている「chordProgression」の指定をしています。
![continueSequenceのchordProgression.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/6eb492ec-6d57-e4b2-31dd-36fcab7adc59.jpeg)

これは、basic_rnn や　melody_rnn を使った場合と同じ処理内容を、chord_pitches_improv を使った場合にも用いてみたところ、エラーメッセージで chordProgression に関するメッセージが出たためです。
今回のプログラム中では、公式情報の例に記載されている「["G", "Em", "C", "D"]」を指定してみました。

#### 余談
上記の補足の話の中に出てくる「tonalパッケージ」というのも気になりました。

●tonaljs/tonal: A functional music theory library for Javascript
　https://github.com/tonaljs/tonal

[ドキュメント](https://github.com/tonaljs/tonal#documentation)をざっくり見てみたところ、音系の処理を扱うのに便利そうでした。
![tonaljsのドキュメント.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/ee7ac3d7-4cca-66ab-24a7-fc9c893a95c8.jpeg)