musicLineアプリ開発日記

作曲を誰でも楽しく簡単に♪

Webで聴く!MIDIプレイヤーの実装

今回はWebで快適にMIDIを再生する話。
WebでのMIDI再生は動作が重かったので、MP3に変換して再生しました。

プレイヤー動作



はじめに

musicLineでは、コミュニティでユーザーの投稿した曲を再生できます。
またコミュニティ曲のURLを共有して、他のユーザーに曲を勧めることができます。
しかし、musicLineのアプリを持ってないユーザーは曲を聴くことができず、Playストアに飛ぶようになっています。
もちろんPlayストアでアプリをインストールすると良いですが、そこまでしないと曲を聴けないです😅
そこまでして欲しいと願っていますが。。そもそもiOSはアプリすらまだないです😭

他の方法として共有動画を作成することもできますが、それもちょっと気軽に勧めたいって場合にはレンダリング時間が気になるところです。
それでもXで共有動画で曲を紹介してくれている mL民 に感謝😆✨

なのでもっと気軽に、アプリを超えて、曲を共有することを目指して、
WebでmusicLineコミュニティ内の曲を再生できるようにしました。



実装

WebでのMIDI再生の課題

はじめにWeb(Javascript)で動作するMIDIプレイヤーのライブラリを試してみました。
サーバーからMIDIファイルをDLして、Javascriptで再生するシンプルな実装だったのですが、

  • 再生処理が重くて音が途切れる
    特にモバイルでの動作時は止まることが多い
  • コミュニティの音と大きく異なる
    そもそもサウンドフォント(sf2)が使えない

の課題がありました。

そのため、MIDIファイルを一度音楽ファイル(WAVやMP3)に変換してから、再生する方法に変更しました。


MIDIファイルを音楽ファイルに変換

サーバーから直接MIDIファイルをDLせずに、サーバー(Ruby)で音楽ファイルに変換してDLします。
MIDIからWAVへの変換はFluidSynthを使いました。さらにWAVからMP3への変換にFFmpegを使っています。

この変換する時間はどうしても数十秒は掛かってしまうので、変換したMP3はしばらくキャッシュします。キャッシュしたMP3を使うことで、次訪れるユーザーはすぐに再生できます。
そのため、WAVファイルのままDLしても再生できますが、キャッシュサイズを小さくしたかったので、MP3に圧縮しています。

以上でWebでのMIDI再生の課題が解決できました。

こちらの方法でもWebにアクセスする最初の人はファイルを変換する時間を待たなければいけない問題はあります。
でもアプリをインストールしないと曲を聴けないよりは良いですねえ。


Herokuに外部ライブラリをインポート

より専門的な話になりますが、musicLineのサーバーにFluidSynthライブラリを取り入れるところにも課題があったので紹介します。
musicLineのサーバーはHerokuを使っています。そのため、Herokuの環境下に外部ライブラリを構築する必要があります。FFmpegはHerokuの拡張機能で簡単に取り込みができましたが、FluidSynth拡張機能のBuildPackが用意されていないため、自分でカスタムBuildPackを作る必要がありました。

ちなみに、違う方法(DockerContainerを使った方法)でも環境構築ができるので、こちらも試してみましたがこの方法は辞めました。
ローカルで仮想サーバーを試すときにはDockerContainerを使っているので、そのままHerokuの環境に適用できれば便利だと考えたのですが、

  • 他のBuildPackも使えなくなる
    Herokuが用意しているBuildPackも自分で環境を整えなければならない
  • デプロイ(環境構築)が遅い
    デプロイの際にキャッシュ等を使って最適化されているそう

ということがあり、Herokuを使う利点が失われると思い辞めました。

カスタムBuildPack

devcenter.heroku.com
このページを参考に作ります。
以下の3ファイル作る必要があるのですが、compileファイル以外は他のBuildPackと同じファイルを使って良さそうです。

  • detect​: この buildpack がアプリに適用するか判定
  • compile​: アプリで変換手順を実行するために使用
  • release​: メタデータをランタイムに戻す

compileファイルでライブラリのソースをDL・インストールする処理を記載します。
C++のソースをコンパイル・リンクして、環境変数を設定しています。
なかなか難しい部分でしたが、CMAKE等のC++ビルドツールに使い慣れていれば簡単だったかもしれません。
普段なにげに使っているapt-get等のパッケージ管理ツールはとても便利なんだと思い知らされました。

#!/bin/sh 

BUILD_DIR=$1
CACHE_DIR=$2

# midi->wavへ変換する依存ライブラリlibsndfileをインストール
LIBSND_CACHE_DIR=$CACHE_DIR/libsnd
LIBSND_DIR=$BUILD_DIR/vendor/libsnd

# $LIBSND_CACHE_DIR が存在する時キャッシュを使用
if [ ! -d "$LIBSND_CACHE_DIR" ]; then
    echo "-----> Installing libsndfile"

    LIBSND_SRC_DIR=/tmp/libsnd
    SND_URL="http://www.mega-nerd.com/libsndfile/files/libsndfile-1.0.28.tar.gz"

    mkdir -p $LIBSND_SRC_DIR
    curl -L $SND_URL | tar xz -C $LIBSND_SRC_DIR --strip-components=1

    mkdir -p $LIBSND_CACHE_DIR

    cd $LIBSND_SRC_DIR || return
    ./configure --prefix=$LIBSND_CACHE_DIR
    make
    make install
    echo "-----> libsndfile installation complete"
else
    echo "-----> libsndfile use cache"
fi

mkdir -p $LIBSND_DIR
cp -r $LIBSND_CACHE_DIR/* $LIBSND_DIR


# fluidsynthをインストール
FLUIDSYNTH_CACHE_DIR=$CACHE_DIR/fluidsynth
FLUIDSYNTH_DIR=$BUILD_DIR/vendor/fluidsynth

# $FLUIDSYNTH_CACHE_DIR  が存在する時キャッシュを使用
if [ ! -d "$FLUIDSYNTH_CACHE_DIR" ]; then
    echo "-----> Installing Fluidsynth"

    # FLUIDSYNTH_VERSION="2.3.5"
    FLUIDSYNTH_SRC_DIR=/tmp/fluidsynth
    FLUIDSYNTH_VERSION="2.1.7"
    FLUIDSYNTH_URL="https://github.com/FluidSynth/fluidsynth/archive/refs/tags/v$FLUIDSYNTH_VERSION.tar.gz"

    mkdir -p $FLUIDSYNTH_SRC_DIR
    curl -L $FLUIDSYNTH_URL | tar xz -C $FLUIDSYNTH_SRC_DIR --strip-components=1

    FLUIDSYNTH_BUILD_DIR=$FLUIDSYNTH_SRC_DIR/build
    mkdir $FLUIDSYNTH_BUILD_DIR

    mkdir -p $FLUIDSYNTH_CACHE_DIR

    cd $FLUIDSYNTH_BUILD_DIR || return
    cmake .. -Denable-libsndfile=ON  -DCMAKE_PREFIX_PATH=$LIBSND_DIR
    make
    make install DESTDIR=$FLUIDSYNTH_CACHE_DIR
    echo "-----> Fluidsynth installation complete"
else
    echo "-----> Fluidsynth use cache"
fi

mkdir -p $FLUIDSYNTH_DIR
cp -r $FLUIDSYNTH_CACHE_DIR/* $FLUIDSYNTH_DIR


# 環境変数の追加
mkdir -p $BUILD_DIR/.profile.d
echo "export PATH=\$PATH:$HOME/vendor/fluidsynth/usr/local/bin:$HOME/vendor/libsnd/bin" > $BUILD_DIR/.profile.d/fluidsynth.sh
echo "export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:$HOME/vendor/fluidsynth/usr/local/lib:$HOME/vendor/fluidsynth/usr/local/lib64:$HOME/vendor/libsnd/lib" >> $BUILD_DIR/.profile.d/fluidsynth.sh



おわりに

今回はWebでMIDIを再生する際に、再生を最適化するためにMP3に変換して再生しました。
WebでMIDIをそのまま再生すると処理が重くモバイルだとまともに再生されないことが誤算でしたが、MP3に変換することでなんとかWebでも再生することができました。
ちなみに同じサウンドフォントを使ってますが、音響ライブラリがアプリと異なるため、音も少し変わります😅

このURLでmusicLineを持っていなければ、Webプレイヤーに移ります。
https://3musicline.com/community/176751
(musicLineを持っていれば、アプリを開きコミュニティに移ります。)

https://3musicline.com/player/176751
(musicLineを持ってるユーザーはこちらのURLでWebプレイヤーを閲覧できます。)

実装の部分は試行錯誤した内容をごちゃごちゃと書いてしまいましたが、試したことを忘れないように備忘録として残しておきます。

ちなみに、最初にアクセスしたユーザーはMP3に変換する時間を待ってもらう必要があります。 頻繁にWebプレイヤーを使うユーザーはアプリをインストールして欲しいですね♪

なので待っている時間はアプリインストールを促す画面を出すようにしています。
ちゃっかり😋

アプリインストール促す画面