はじめに
SuperColliderはオーディオ合成とアルゴリズム作曲のためのソフトウェアで、独自の言語でシンセの自作などできます。
MacOS, Windows, Linuxで動作するのと、音質も良いため実際の作曲にも有用です。自分もSuperColliderのライブコーディングによって曲つくっているので是非聴いてください!
課題
ただ、このコードと一緒に音が鳴っているカッコ良さを他の人に体感してもらうためには、現状だと各環境に合わせてSuperColliderの環境構築をしてもらわなければならず、なんとかブラウザで共有できないかと考えていました。
SuperCollider runs in the browser!
そんなことを考えながらいろいろGithubなど徘徊していたらまさにな記事を見つけました。
Scissさんという方がWebAssemblyを用いてブラウザでもSuperColliderが動作(エディタは別で、再生部分のみ)するという画期的なことをしてくれていました。実際に彼のサイトで試せます。(Chromeで開いてください)
https://www.sciss.de/temp/scsynth.wasm/
以下の手順で再生が確認できるかと思います。
- Bootボタンクリック
- F12(検証ツールを開く)
- Console
- Consoleに
d_bubbles()
と打ち込みEnter(bubblesというシンセのパターン定義) - Consoleに
s_bubbles()
と打ち込みEnter(bubblesを再生) - Consoleに
cmdPeriod()
と打ち込みEnter(再生停止)
WebAssemblyでSuperColliderをbuildする
README_WASM.mdにしたがってbuildし、localhostでさきほどのサイトを確認するところまでいきましょう。
ちなみに環境は
OS: MacOS
Python: 3.8
です。
emcriptenのinstall
githubをcloneしてくる適当なディレクトリに移動してから以下のコマンドを実行
# Get the emsdk repo (ちなみに自分がcloneしたコードのコミットハッシュは060b48f0c69827f95c8433ee965656babf953e82でした)
git clone https://github.com/emscripten-core/emsdk.git
# Enter that directory
cd emsdk
# installできるversion確認
./emsdk list
# Download and install 2.0.13 (lastestでもよかったですが、このREADMEpush時点でのversionを取得しています)
./emsdk install 2.0.13
# Make the "2.0.13" SDK "active" for the current user. (writes .emscripten file)
./emsdk activate 2.0.13
# Activate PATH and other environment variables in the current terminal
source ./emsdk_env.sh
SuperColliderをwasm(WebAssembly)でbuild
上でemcriptenをinstallしたターミナルをそのまま用います
# githubをcloneしてくる適当なディレクトリに移動
cd ../
# supercolliderのリポジトリをclone(自分のclone時点でのコミットハッシュは89c431ad61adb5298a0086148e2191119f6824e7でした)
git clone git@github.com:Sciss/supercollider.git
# supercolliderディレクトリに移動
cd supercollider
# サブモジュールを取得
git submodule update --init --recursive
# build
mkdir build
cd build
../wasm/build.sh
Running
# wasmディレクトリに移動
cd ../wasm/
ここで、READMEの通りだとpython -m SimpleHTTPServer
(python3系ならpython -m http.server
)を実行してhttp://localhost:8000/ にアクセスすればよさそうですが、それだとSharedArrayBuffer is not definedというエラーがでて怒られます。(Chrome92以降で起こるエラーです。詳しくはこちらを確認してみてください)
その対策としてレスポンスヘッダーに
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
を追加してあげる必要があるので、このディレクトリに以下の.pyファイル(server.py)を追加します。
import copy
import datetime
import email.utils
import html
import http.client
import io
import mimetypes
import os
import posixpath
import select
import shutil
import socket # For gethostbyaddr()
import socketserver
import sys
import time
import urllib.parse
import contextlib
from functools import partial
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, HTTPServer, SimpleHTTPRequestHandler
class MyServerHandler(SimpleHTTPRequestHandler):
def send_head(self):
"""Common code for GET and HEAD commands.
This sends the response code and MIME headers.
Return value is either a file object (which has to be copied
to the outputfile by the caller unless the command was HEAD,
and must be closed by the caller under all circumstances), or
None, in which case the caller has nothing further to do.
"""
path = self.translate_path(self.path)
f = None
if os.path.isdir(path):
parts = urllib.parse.urlsplit(self.path)
if not parts.path.endswith('/'):
# redirect browser - doing basically what apache does
self.send_response(HTTPStatus.MOVED_PERMANENTLY)
new_parts = (parts[0], parts[1], parts[2] + '/',
parts[3], parts[4])
new_url = urllib.parse.urlunsplit(new_parts)
self.send_header("Location", new_url)
self.send_header("Content-Length", "0")
self.send_header('content-type', 'text/html')
# add header
# https://developer.chrome.com/blog/enabling-shared-array-buffer/#cross-origin-isolation
self.send_header(
'cross-origin-embedder-policy', 'require-corp')
self.send_header('cross-origin-opener-policy', 'same-origin')
self.end_headers()
return None
for index in "index.html", "index.htm":
index = os.path.join(path, index)
if os.path.exists(index):
path = index
break
else:
return self.list_directory(path)
ctype = self.guess_type(path)
# check for trailing "/" which should return 404. See Issue17324
# The test for this was added in test_httpserver.py
# However, some OS platforms accept a trailingSlash as a filename
# See discussion on python-dev and Issue34711 regarding
# parsing and rejection of filenames with a trailing slash
if path.endswith("/"):
self.send_error(HTTPStatus.NOT_FOUND, "File not found")
return None
try:
f = open(path, 'rb')
except OSError:
self.send_error(HTTPStatus.NOT_FOUND, "File not found")
return None
try:
fs = os.fstat(f.fileno())
# Use browser cache if possible
if ("If-Modified-Since" in self.headers
and "If-None-Match" not in self.headers):
# compare If-Modified-Since and time of last file modification
try:
ims = email.utils.parsedate_to_datetime(
self.headers["If-Modified-Since"])
except (TypeError, IndexError, OverflowError, ValueError):
# ignore ill-formed values
pass
else:
if ims.tzinfo is None:
# obsolete format with no timezone, cf.
# https://tools.ietf.org/html/rfc7231#section-7.1.1.1
ims = ims.replace(tzinfo=datetime.timezone.utc)
if ims.tzinfo is datetime.timezone.utc:
# compare to UTC datetime of last modification
last_modif = datetime.datetime.fromtimestamp(
fs.st_mtime, datetime.timezone.utc)
# remove microseconds, like in If-Modified-Since
last_modif = last_modif.replace(microsecond=0)
if last_modif <= ims:
self.send_response(HTTPStatus.NOT_MODIFIED)
self.end_headers()
f.close()
return None
self.send_response(HTTPStatus.OK)
self.send_header("Content-type", ctype)
self.send_header("Content-Length", str(fs[6]))
self.send_header("Last-Modified",
self.date_time_string(fs.st_mtime))
self.send_header('content-type', 'text/html')
# add header
# https://developer.chrome.com/blog/enabling-shared-array-buffer/#cross-origin-isolation
self.send_header('cross-origin-embedder-policy', 'require-corp')
self.send_header('cross-origin-opener-policy', 'same-origin')
self.end_headers()
return f
except:
f.close()
raise
if __name__ == '__main__':
MyServerHandler.extensions_map['.wasm'] = 'application/wasm;charset=UTF-8'
server = HTTPServer(('', 8000), MyServerHandler)
server.serve_forever()
ここで
python server.py
を実行し、http://localhost:8000/ にアクセスすればhttps://www.sciss.de/temp/scsynth.wasm/ と同様にブラウザでSuperColliderのシンセを再生できるはずです!!
現状の課題と今後
READMEのoverviewにも記載がある通り、現状(2022年1月現在)はまだ音声ファイルを使用できなかったり、Chromeでしか動かなかったり様々な問題はありますが、このプルリクがマージされれば開発が進んでいきそうな気がします。
ちなみに動作しない原因はSharedArrayBufferなので、SharedArrayBufferが動作するブラウザとそのバージョンについてはこちらを参照してください。
https://caniuse.com/sharedarraybuffer
また現状ではOSCを受け取ってその情報に合わせて音を再生する仕様なので、エディタ部分はsupercolliderjsなどを用いて実装すれば良さそうです。
ちなみにですが、単純にSuperColliderのようなことをブラウザでやりたい、という要望に対しては既にflockingjsというフレームワークが存在するので、いまからflockingjsを極めるというのも一つの手かもしれません。