Inside Music のビルド・楽曲追加方法

## はじめに

Inside Music という、Web Audio API と A-Frame を使った音楽アプリがあります。

- 動くもの: https://experiments.withgoogle.com/webvr/inside-music/view/ 
- 日本語紹介記事: https://www.moguravr.com/inside-music-vr/
- ソースコード: https://github.com/googlecreativelab/inside-music

公式ドキュメントのビルド方法がいまいちよく分からず、四苦八苦しつつビルドできて曲も追加できたのでここに書いておきます。プルリク、、、は英語で書く気力がありません。

## 前提

- Ubuntu 16.04 LTS
- メモリ: 2GB
- CPU: 1 core
- ストレージ: OS 含めて 10GB もあれば十分

## 主要パッケージのバージョン

今回私がビルドしたソフトウェア環境は以下です。

- Node.js: 8.15.0
- FFmpeg: 2.8.15
- Python: 2.7.12
- Inside Music commit ID: 914b155658cb7928383975ba423600f5203ef1cb

## ビルド方法

公式ドキュメントには記載されてませんが Node.js v8.15.0 で動きます (言い換えると最新の Node.js では動作しません)。流れとしては Node.js のバージョンマネージャ n をインストールして Node.js v8.15.0 を導入します。次に Github から inside-music をダウンロードし、実行に必要なライブラリをインストールします。そして webpack で Javascript をまとめれば実行可能となります。http://127.0.0.1:8080 や http://192.168.1.10:8080 (実行環境の DHCP サーバーにより、この IP アドレスは様々です) にアクセスし、ページが読み込まれれば OK です。この時点では音楽が含まれていないので楽曲を再生することはできません。

次のステップでは、再生に必要な mp3 ファイルを生成し (espeak という Text-to-Speech コマンドと ffmpeg を組み合わせていくつかのファイルを作成する)、また楽曲ファイルをダウンロードし、mp3 用楽曲処理スクリプトを作成して30秒毎のファイルに区切ります。また、Config ファイルも修正します。そして webpack することで楽曲が含まれた inside-music を楽しむことができます。

~~~
# 必要なパッケージのインストール
$ sudo apt update
$ sudo apt upgrade
$ sudo apt install nodejs npm git ffmpeg libavcodec-extra python python-pip espeak zip
$ pip install pydub
$ sudo npm cache clean
$ sudo npm install n -g
$ sudo n stable
$ sudo ln -sf /usr/local/bin/node /usr/bin/node
$ sudo n 8.15.0
$ node -v
v8.15.0
$ sudo npm install http-server -g

# ひとまず inside-music を起動 (音楽ファイルが含まれていないので、音楽の再生は不可)
$ mkdir repo
$ cd repo
$ git clone https://github.com/googlecreativelab/inside-music
$ cd inside-music
$ npm install
$ npx webpack -p
$ http-server -p 8080
Starting up http-server, serving ./
Available on:
  http://127.0.0.1:8080
  http://192.168.1.10:8080
Hit CTRL-C to stop the server

# 必要なmp3ファイルを生成し、楽曲をダウンロードして加工
# まずは espeak で mp3 ファイルを生成 (intro.mp3, loading.mp3, MBIRA-0.mp3 という名前のファイルが必要)
$ mkdir audio/vo
$ espeak "Welcome to inside music demo." --stdout | ffmpeg -i - -ar 44100 -ac 2 -ab 192k -f mp3 audio/vo/intro.mp3
$ espeak "loading... please wait for a while." --stdout | ffmpeg -i - -ar 44100 -ac 2 -ab 192k -f mp3 audio/vo/loading.mp3
$ mkdir audio/perfume_genius
$ espeak "Dummy sound." --stdout | ffmpeg -i - -ar 44100 -ac 2 -ab 192k -f mp3 audio/perfume_genius/MBIRA-0.mp3

# 次に楽曲をダウロードして加工 (例として example.com の etracks.zip にしているが、適当にステムデータを入れればよい)
$ cd audio/stems
$ wget http://example.com/etracks.zip
$ unzip -j etracks.zip -d etracks
# split.py は wav ファイル専用のツールなので mp3 に対しても動作するように改変。また、split.py は Mac 用のツールである xld を使ってるのでここを ffmpeg に置き換える。(また、ffmpeg のコマンドの例も載っているが、誤りがあり、ogg となるべきところが mp3 となっている。また、 -codec:a vorbis ではなく -code:a libvorbis とするとエラーなく動作する)
$ cp split.py split_from_mp3.py
$ vi split_from_mp3.py
$ cat split_from_mp3.py
from pydub import AudioSegment
import os
import tempfile
from os.path import basename
import random
import string
import sys

def tmp_mp3():
    return (''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8))) + ".mp3"

# split the track up in 30 second segments
def split_track(folder, track_name, out_path):
    segment = 0
    print folder, track_name
    # normalize the wave file
    tmp_audio = tmp_mp3()
    os.system("ffmpeg -i %s/%s.mp3 -sample_fmt s16 -ac 1 -sample_rate 44100 %s" % (folder, track_name, tmp_audio))
    track = AudioSegment.from_mp3(tmp_audio)
    while segment * 30 < track.duration_seconds:
        seg_audio = track[segment*30000:(segment+1)*30005]
        tmp = tmp_mp3()
        seg_audio.fade_out(5).export(tmp, format="mp3")
        # convert the sample rate and channel numbers
        os.system("ffmpeg -y -i %s -codec:a libmp3lame -q:a 4 %s/%s-%d.mp3 " % (tmp, out_path, track_name, segment))
        os.system("ffmpeg -y -i %s -codec:a libvorbis -aq 50 %s/%s-%d.ogg " % (tmp, out_path, track_name, segment))
        os.remove(tmp)
        segment+=1
        print track_name, segment
    os.remove(tmp_audio)

# go through and run the split track on each track
def split_song(folder):
    out_path = "../%s" % (folder)
    print folder
    if not os.path.exists(out_path):
        os.makedirs(out_path)
    # get all of the tracks in the folder
    for file in os.listdir(folder):
        if file.endswith(".mp3"):
            split_track(folder, os.path.splitext(file)[0], out_path)

if __name__ == "__main__":
    split_song(sys.argv[1])

$ python split_from_mp3.py etracks/
# ↑ inside-music/audio/etracks というディレクトリが作られてその下に30秒毎に分割された mp3 が保存される
# (option) トラックネームを json 形式で取得
$ cd bb
$ rm *.meta
$ python -c 'import os, json; print json.dumps(os.listdir("."))' | sed -e "s/.mp3//g"
["vocal", "bass", "gutar", "keyboard", "drum"]
# ↓ では、上記で生成した楽曲ファイルを含めるために src/Config.js を修正
$ cd ~/repo/inside-music
$ cp src/Config.js src/Config.js.orig
$ vi src/Config.js
$ cat src/Config.js
import Tone from 'Tone/core/Tone'
import Detector from 'three/examples/js/Detector'

export const unitsPerSecond = 7
export const circleHeight = 0
export const radius = 1.1
export const sceneColor = '#faa'
export const tubeColor = '#ffffff'
export const title = 'Inside Music'
export const trackRadius = 2
export const useVoiceOver = true
export const supported = true; //Detector.webgl && Tone.supported
export const trackConfig = [
        {
                artist : 'Some artists',
                track : 'etracks',
                folder : 'etracks',
                segments : 6,
                duration : 194,
                trackNames : ["Vocal", "Bass", "Gutar", "Keyboard", "Drum"],
                names : ["vocal", "bass", "gutar", "keyboard", "drum"],
                soundRings: {
                        startColor: '#f7002d',
                        endColor: '#00edaa',
                        shape: 'triangle',
                        size: 8
                },
                floor: {
                        color: '#253934' //#263330'
                }
        },
]
export function getTrackData(artist){
        const index = trackConfig.findIndex(t => t.artist === artist)
        return trackConfig[index]
}
$ npx webpack -p
$ npx http-server -p 8080
＃ ↑ 再度 webpack を行って生成した楽曲を含めて、サーバーを実行
~~~

## 楽曲の追加方法

前提として、上記のビルド方法を使って inside-music が閲覧できる状態になっているものとします。流れは以下のようになります。

- 楽曲 (wav または mp3 のみ) を特定の場所に設置
- スクリプトで楽曲を30秒毎に分割
- src/Config.js に楽曲の設定を追加
- webpack で js を再構築
- サーバーを再起動

です。↑で書いたビルド方法の後半を参照してください。

以上です。どこかに使って良いの音源があればより分かりやすく書けるのですが、、、そもそも複数の音源から構成される音楽のファイルって手に入りにくいですね。