せっかなくので、やってみた。

日々のあんなこと、こんなこと、せっかくなのでやってみた

Z-MUSIC for the Webを使いたくてemsdkの環境をwindows10に入れてみた

もう3年以上前の話ですが、X68ユーザならご存じSION2をブラウザで動作するSION2 HDとして移植された @toyoshim氏がZ-MUSIC for the WebというJavaScriptでZ-MUZICを鳴らしてしまおうというモノを発表されていました。

qiita.com

去年あたりに発見し、これは素晴らしいということで自分でも使ってみたいと試してみたのですがドキュメントの通りJSを書いていても動作しない。。。再生方法が分からず、そのまま放置となってしまっていました。

かくして時は流れ、最近TwitterでMSXPlayというMSXの音源をWebで再生するサイトのシェアをちょいちょい見かけるようになりました。

msxplay.com

サイトに記載されている、"C言語版の libkss を EmscriptenJavaScript に変換"というあたりはZ-MUSIC for the Webと同じような技術を使用しているっぽい。
Z-MUSIC for the Webもエディタと組み合わせれば同じようなことができるのかなぁ、羨ましすぎるぞ、MSX勢!ということで、Z-MUSIC for the Webに再度トライしていました。

GitHubの説明を読み返し、zmusic.jsをビルドする必要があるとの事で(後で分かりましたが実は不要だったのですが。。。)以下を試してみました。

git clone https://github.com/toyoshim/z-music.js.git
cd z-music.js
git submodule update --init --recursive
git: 'submodule' is not a git command. See 'git --help'.

なぜかgitにsubmoduleが無いと言われてしまいます。
私の使っているgitはgnupack-pretest_develの2018.11.25に含まれているものでバージョンは2.17.0でした。
apt-cyg install gitとかやってみましたが状況は変わらないので、SourceTreeに内蔵されているgitにパスを通してこちらを使用したところ、無事git submoduleが起動しました。
ちなみにSourceTreeのgitは$USERPROFILE/AppData/Local/Atlassian/SourceTree/git_local/binにありました。
この下にはgitだけでなく、bashやshも含まれているのでcygwinのコマンドのパス(/usr/bin)より前に設定するとapt-cygなどが動作しなくなりました。git submoduleを使用した後は元に戻した方が良いでしょう。
とにかくこれでmakeが実行できるようになりました。

make
mkdir -p out
emcc -DFNC_TRACE -DENV_FROM_INI -DEMSCRIPTEN_KEEPR -include third_party/run68as/mod/preinc.h -I third_party/run68as/third_party/run68/src -I third_party/X68Sound/X68Sound -I src/compat -Oz -MM third_party/run68as/third_party/run68/src/ansicolor-w32.c third_party/run68as/third_party/run68/src/calc.c third_party/run68as/third_party/run68/src/conditions.c third_party/run68as/third_party/run68/src/disassemble.c third_party/run68as/third_party/run68/src/eaaccess.c third_party/run68as/third_party/run68/src/exec.c third_party/run68as/third_party/run68/src/getini.c third_party/run68as/third_party/run68/src/key.c third_party/run68as/third_party/run68/src/line0.c third_party/run68as/third_party/run68/src/line2.c third_party/run68as/third_party/run68/src/line5.c third_party/run68as/third_party/run68/src/line6.c third_party/run68as/third_party/run68/src/line7.c third_party/run68as/third_party/run68/src/line8.c third_party/run68as/third_party/run68/src/line9.c third_party/run68as/third_party/run68/src/lineb.c third_party/run68as/third_party/run68/src/linec.c third_party/run68as/third_party/run68/src/lined.c third_party/run68as/third_party/run68/src/linee.c third_party/run68as/third_party/run68/src/load.c third_party/run68as/mod/line4.c third_party/run68as/mod/linef.c third_party/run68as/mod/mem.c third_party/run68as/mod/run68.c src/doscall.c src/iocscall.c src/memop.c src/zmusic.cpp src/compat/compat.cpp src/x68sound.cpp> out/depend
/bin/sh: emcc: コマンドが見つかりません
make: *** 'all' に必要なターゲット 'out/depend' を make するルールがありません.  中止.
/bin/sh: emcc: コマンドが見つかりません

が、今度はemccが無いと怒られてしまいます。
どうやらemsdk (Emscripten) の環境も必要という事で、今度はemsdkのリポジトリを覗いてみました。

github.com

emsdkとはCやC++のコードをJavaScriptに変換して動作させる為のツール群の様です。
Z-MUSIC for the Webではrun68やx68soundをJavaScriptに変換してさらにエミュレーションを追加して動作させているようです。 まさに超絶技術ですね。

一応Windowsでもビルドできる記述があったのでcygwin上から以下を試しました。

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk/
./emsdk install latest
Traceback (most recent call last):
  File "./emsdk.py", line 3073, in <module>
    sys.exit(main())
  File "./emsdk.py", line 2781, in main
    load_sdk_manifest()
  File "./emsdk.py", line 2336, in load_sdk_manifest
    add_sdk(sdk)
  File "./emsdk.py", line 1972, in add_sdk
    raise Exception('Duplicate sdk ' + str(sdk) + '! Existing:\n{' + ', '.join("%s: %s" % item for item in vars(find_sdk(str(sdk))).items()) + '}, New:\n{' + ', '.join("%s: %s" % item for item in vars(sdk).items()) + '}')
Exception: Duplicate sdk sdk-upstream-master-64bit! Existing:
{version: upstream-master, bitness: 64, uses: ['llvm-git-master-64bit', 'node-12.18.1-64bit', 'python-3.7.4-64bit', 'emscripten-master-64bit', 'binaryen-master-64bit'], os: win, id: sdk, name: sdk-upstream-master-64bit, is_old: False, is_sdk: True}, New:
{version: upstream-master, bitness: 64, uses: ['llvm-git-master-64bit', 'node-12.18.1-64bit', 'emscripten-master-64bit', 'binaryen-master-64bit'], os: linux, id: sdk, name: sdk-upstream-master-64bit, is_old: False, is_sdk: True}

Exception: Duplicate sdk sdk-upstream-master-64bit! から察するにpythonが怪しいけどよく分からん。
cygwinpythonは2.7.14でemsdkの推奨バージョン以上だけど、cygwinで動かすのがそもそもサポートされていないかも。。。
という事で、DOSからemsdk.batを起動するため、まずはDOSを起動してpythonコマンドをたたいてみました。
するとMicrosoftStoreが勝手に起動してPythonをおすすめしてくれたのであまり考えずにインストール。
DOSから再度pythonをたたくと3.7.8がインストールされたので、以下を実行。

> .\emsdk.bat install latest
Installing SDK 'sdk-releases-upstream-e7e39da9c81faecd9ecf44065cee864d76e4e34d-64bit'..
Skipped installing node-12.18.1-64bit, already installed.
Skipped installing python-3.7.4-pywin32-64bit, already installed.
Skipped installing java-8.152-64bit, already installed.
Installing tool 'releases-upstream-e7e39da9c81faecd9ecf44065cee864d76e4e34d-64bit'..
Downloading: C:/hoge/emsdk/zips/e7e39da9c81faecd9ecf44065cee864d76e4e34d-wasm-binaries.zip from https://storage.googleapis.com/webassembly/emscripten-releases-builds/win/e7e39da9c81faecd9ecf44065cee864d76e4e34d/wasm-binaries.zip, 551241164 Bytes
Unpacking 'C:/hoge/emsdk/zips/e7e39da9c81faecd9ecf44065cee864d76e4e34d-wasm-binaries.zip' to 'C:/hoge/emsdk/upstream'
Done installing tool 'releases-upstream-e7e39da9c81faecd9ecf44065cee864d76e4e34d-64bit'.
Running post-install step: npm ci ...
Done running: npm ci
Done installing SDK 'sdk-releases-upstream-e7e39da9c81faecd9ecf44065cee864d76e4e34d-64bit'.

無事インストールできました。次に環境の設定を行うため以下を実行。

> .\emsdk.bat activate latest
Setting the following tools as active:
   node-12.18.1-64bit
   python-3.7.4-pywin32-64bit
   java-8.152-64bit
   releases-upstream-e7e39da9c81faecd9ecf44065cee864d76e4e34d-64bit

Adding directories to PATH:
PATH += \hoge\emsdk
PATH += \hoge\emsdk\node\12.18.1_64bit\bin
PATH += \hoge\emsdk\python\3.7.4-pywin32_64bit
PATH += \hoge\emsdk\java\8.152_64bit\bin
PATH += \hoge\emsdk\upstream\emscripten

Setting environment variables:
EMSDK = C:/hoge/emsdk
EM_CONFIG = \hoge\emsdk\.emscripten
EMSDK_NODE = \hoge\emsdk\node\12.18.1_64bit\bin\node.exe
EMSDK_PYTHON = \hoge\emsdk\python\3.7.4-pywin32_64bit\python.exe
JAVA_HOME = \hoge\emsdk\java\8.152_64bit
EM_CACHE = C:/hoge/emsdk/upstream/emscripten\cache
The changes made to environment variables only apply to the currently running shell instance. Use the 'emsdk_env.bat' to re-enter this environment later, or if you'd like to permanently register this environment globally to all users in Windows Registry, rerun this command with the option --global.

こちらも無事インストールされました。(のかな?)
これでやっとzmusic.jsがビルドできるようになりましたが、DOSにはmakeの環境が入っていません。
なので、cygwin(bash)の環境に上記のパスや環境変数をセットします。

~/.bash_profileに追記

export EMSDK=/hoge/emsdk
export EM_CONFIG=$EMSDK/.emscripten
export EMSDK_NODE=$EMSDK/node/12.18.1_64bit/bin/node.exe
export EMSDK_PYTHON=$EMSDK/python/3.7.4-pywin32_64bit/python.exe
#export JAVA_HOME=$EMSDK/java\8.152_64bit
PATH=$EMSDK/:$EMSDK/node/12.18.1_64bit/bin:$EMSDK/python/3.7.4-pywin32_64bit:$EMSDK/upstream/emscripten:$PATH
#$EMSDK/java/8.152_64bit/bin

JAVA_HOMEとjavaのパスは既に別のjava1.8がインストールしてあったので、省略しています。
bashを起動し、やっとzmusic.jsのmakeが通りました!
ということで、ディレクトリ配下にできあがったzmusic.jsと以下のファイルを用意し、chromeで表示してみました。

test.zms

(i)
(m1,3000)(aFm1,1)
(@1,28,  1,  7,  9,  0, 28,  0,  1,  0,  0,  0
 30,  1,  8, 10,  0,  1,  0,  1,  0,  0,  0
 29,  1,  8,  9,  0, 19,  0,  1,  3,  0,  0
 31,  1,  8, 10,  0,  1,  0,  1,  3,  0,  0
 4, 7)
(o127)
(t1) @01o4l8 cdefgab
(p)
<html>
  <head>
    <!-- <script src="zmusic.asm.js"></script> -->
    <script src="zmusic.js"></script>
    <script>
      // Make XHR to support Promise.
      function xhr (url) {
         return new Promise(function (resolve, reject) {
             var xhr = new XMLHttpRequest();
             xhr.open('GET', url, true);
             xhr.responseType = 'arraybuffer';
             xhr.addEventListener('load', e => {
                 resolve(xhr.response);
                 xhr.abort();
             }, false);
             xhr.send();
         });
      }

      Promise.all([
         ZMUSIC.install(),
         xhr('./test.zms')
      ]).then(results => {
         ZMUSIC.play(results[1]);
      });
    </script>
  </head>
</html>

するとブラウザに色々エラーが出てきました。たしか以前もこれで止まってしまっていたのだと思います。

both async and sync fetching of the wasm failed
RuntimeError: abort(both async and sync fetching of the wasm failed). Build with -s ASSERTIONS=1 for more info.
Access to XMLHttpRequest at 'file:///C:/hoge/test.zms' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.

CORSのエラーも出ているので、そもそもWEBサーバ経由で表示しないとだめっぽいです。
WEBサーバ(nginx)のドキュメントルート配下にファイルを置いて、再度アクセス。
上記のエラーは全て解消されましたが、zmusic.wasm 404 (Not Found)のエラーが。
なるほど、z-music.js/dist/の中にzmusic.wasmがありがましたが、こちらも必要だったようです。
あらためてアクセスすると今度は以下のエラーが。

wasm streaming compile failed: TypeError: Failed to execute 'compile' on 'WebAssembly': Incorrect response MIME type. Expected 'application/wasm'.

nginx(多分apacheも同様)のデフォルトのmime-typeにはapplication/wasmが無かったので、追加する必要があります。
/etc/nginx/mime.typesに次の行を追加して、nginxを再起動しました。

application/wasm                      wasm;

まだエラーが残っていて再生されませんが、最後のエラー

The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page

は、どうやらJSでいきなり音を再生することは許可されていなくて、(ボタンクリックなど)何らかのユーザからのインタラクションが必要な様です。(後で確認したら、Chromeで発生し、Firefoxでは再生されました)
なのでHTMLにbuttonタグを追加し、onclickでZMUSICの処理を起動したところ、遂に再生されました! 長かったですが、やっと再生させることができました。

で、ここまできてそういえばz-music.js/dist/の下に初めからzmusic.jsやzmusic.wasmあったなーと思い返し、一旦z-music.js/dist/の下のファイルを削除、再度リモートからチェックアウトしたファイルで確認したところ、問題無く動作しました。(汗

ということで、emsdkを使用したビルド済みのファイルが初めから提供されていましたというオチですが、emsdkとは何かが分かったし、今後z-music.jsに修正が入っても自分でビルドできるぞという事で、無駄ではなかったかなと思います。
最後に、こんな素晴らしいJSを公開してくださった@toyoshim氏に大変感謝いたします。