らくとあいすの備忘録

twitter : lactoice251

Amebientの音楽体験設計 (導入編)

こんにちは。Amebient Advent Calendar 6日目の記事です。

もし、Amebientが何であるかがわからないという方は、先にVRChatで公開されているワールド と、phiさんの1日目の記事 を見て頂ければと思います。

さて先日、4日目の記事 で「Amebient」の音楽体験の設計の概要編を書きました。 今回からの記事は3回にわけて、「Amebientという楽曲」の「導入部」「展開部」「終末」を書いていきます。 今回は"導入編"と題しまして、ワールドにJoinしてから展開がはじまるまでの部分についての詳細を書いていきます。

今回からは、やや主観的な記述を増やして、自分から見た制作振り返り的な観点で書いていければと思っています。

雨部 作戦会議課への参加

私が雨部作戦会議課*1に参加した経緯を軽く書いてみようと思います。*2 VRAA02がスタートしてテーマが「Live/Frontier」と分かった日に、

と、思っていました。これは何かというと、Live/Frontierというテーマに対して、VR楽器を並べて自分で演奏したりする方向=ワールド(自分自身を含む) ではやれることがありそうと思い、かつめちゃくちゃ興味を持ちつつ、VRAA02のレギュレーションの範囲内では時間的にも能力的にも今回やれることはあんまりなさそうだなあ...というつぶやきでした。第一回の作戦会議

非メンバーとして見ていて、自分がやりそうな方向とすごく近かったのもあって、このレベルの構想がある中で個人力で何か出来るのか...みたいな気持ちがあったという背景もありましたしかし。しかし、2分後...

というエアリプを観測しまして、

と、参加する運びになったわけです。チョロいですね...。多分、内心めちゃくちゃ混ざりたかったんでしょうね...。

そして、雨部作戦会議課discordに参加します。

f:id:Raku_Phys:20201206182326p:plain

phiさんにこう言ってもらったことで、音を音楽にするのが自分のやることなのだと理解しました。それなら...と、前々から少し考えていたアイデアを共有してみました。

f:id:Raku_Phys:20201206183039p:plain

すると、ありがたいことに即時理解を得られたので、BGMだと思っていたものが、実は制御可能な環境音 (物理音) だったというスタート地点が決まりました。そして、まずはその曲を作ってみることろから始まることとなりました。

製作開始

「BGMだと思っていたものが、実は制御可能な環境音 (物理音) だった」というコンセプトを実現するための作曲にあたって、私はGLSL Sound LiveCodingをすることにしました。*3

f:id:Raku_Phys:20201206184150p:plain

画像内でも書いてますが、こうするのが今回の制作においてすごく素直な方法に思えたんですよね。ピアノでの作曲で行き詰った理由の大きな部分は、周期的な雨粒をメインとして制御する以上、ピアノのような表現力で曲を作ってしまうと、容易にものすごく不自然で大がかりなものになってしまいうるからです。作曲の段階から、雨粒が落ちるとか、それが色々な楽器に当たるみたいな構造をちゃんと入れてあげた方がやっぱり素直ですよね。加えて、各音サンプルについてもゆくゆく自作していこうと思っていたので、twiglで書いておくとサンプルの素材になりそうという算段もありました。ここから GLSLSoundで作られたAmebientのテーマを聴くことが出来ます。(実ははじめは、3拍子ではなかったのでこれは後の作業でちょっと修正されたものですが...。)

twiglにはせっかくライブ配信機能が備わっているので、phiさんとcapさんに見てもらっている中で、通話を繋ぎながらLiveCodingしていきました。これによって、リアルタイムで音色や曲のイメージのフィードバックしてもらいながら、コード上で微修正を加えていけるという理想的な環境が実現したわけです。

良い環境であったこともあって、1時間ほどで曲の第一案が出来ました。次は、このGLSLのコードをUnityでの実装をするphiさんに受け渡すため、雨粒の落下の構造への変換を含んだ譜面に落とし込むことにしました。

f:id:Raku_Phys:20201206190003j:plain

楽譜にあるように、曲は4つのパートからなります。楽譜を読める方だと分かるかもしれませんが、最終的なAmebientのテーマとはちょっと違いますが、第一案は譜面にあるような4拍子の曲でした。譜面中に書き込まれている青い丸は、別の音符と繋がっているのがわかると思います。これは、"1周"の中で2回出てくる音符です。つまり、雨粒を使うことを考えるとこの青い音符に対応する雨粒は、1/2の周期で落ちることになります。一方、赤い音符は、1周の中に1回出てくる音符です。この青い音符がなるべく多くなるようにしつつ曲を作っていくことで、雨粒の発生ポイント (それを受ける楽器) をなるべく少なくしつつメロディーを作っていくことが出来ます。

4拍子から3拍子へ

glslで作った音と、楽譜を使ってphiさんにワールドに組み込んでテストを行ったのが、制作開始から6日目のことです (すごくすんなり進んでいきましたね...)

この頃は、ネタバレを避ける意味もあって、"Part3"と"Part4の一部"のみを流していましたが、楽譜と見比べると対応が分かるのではと思います。

この時、実際に触ってみた感触でphiさんとこの次の展開を考えていました。 そこでわかってきたのが、メロディーに合わせて落下する雨音そのものを使って入れ替えて曲を作っていったりするのはなかなか難しそうという話です。 それよりは、比較的規則的に落下する、たとえば横並びに雨粒落下点が用意されていて左から順番に1拍おきに落ちていくなどすると演奏しやすそうだよねという話になりました。

そこで、空間を2つに分けて、Amebientのテーマが鳴っている入口付近と、その後の展開のための雨粒が配置されている外側の領域を用意することになりました。 この時、外側のリズムはもっとリズミカルにテンポ良くしたいと思ったわけですが、単純に速度を2倍にしてしまうとそれはそれで速すぎてしまうという問題にあたります。 この理由から、4日目の記事でも書いたようにAmebientのテーマを3拍子にして、Metric modulationを活用してテンポアップしようということに決まりました。

そうこうして、最終的に出来たの譜面がこんな感じになりました。

この時はもう、雨粒の構造をちゃんと考慮して作った曲のアレンジ (4拍子から3拍子) だけで良かったので、さくっと譜面を書いてしまいました。 また、このあたりで、音色は実収録音を使いたいなあと思い始めます...*4

まとめと告知

前回よりは、ちょっと軽めの記事になりましたが導入編以上になります。今回の話のポイントは、

  • BGMを制御可能なワールドのギミックで作るにあたっての作曲方法 (glsl sound/twiglは便利)
  • ワールド/体験の要件に合わせたアレンジ

あたりですかね。次回は、この先の展開部について書きたいと思いますので、よろしくお願いします。

*1:Amebientの制作チーム名。VRAA用に作ったものの認知率は低そう。

*2:phiさんの1日目の記事でも書いてありますね。

*3:GLSL Soundについては、前の記事を読んでもらえるとある程度わかるかと思いますhttps://raku-phys.hatenablog.com/entry/2020/04/19/002400

*4:詳細は12/23のAdvent calendarに書きます

Amebientの音楽体験設計 (概要編)

こんにちは。Amebient Advent Calendar 4日目の記事です。

今回はVRChatワールド「Amebient」の音楽体験の設計について書かせて頂きます。 特に今回は"概要編"と題しまして、全体をかいつまんで書いていきたいと思います。 細かい話については、以降の記事で書いていく予定です。

もし、Amebientが何であるかがわからないという方は、先にVRChatで公開されているワールド と、phiさんの1日目の記事 を見て頂ければと思います。

Amebientにおける音の役割

話声、音楽、通知音、雨音など、私達の住む日常の周りは、人為的なものにせよそうでないにせよ基本的に音に溢れています。VRの世界では、存在させようとしなければ音が存在しません。逆に言えば、望む世界になるように、望む音を存在させることが出来るのがVR世界だと思っています。

音・音楽をどのように用いるかは、製作者の意図その他によって異なっており、様々な使い方がなされます。VRChatのワールドで比較的スタンダードな音の使い方としては、ワールドの雰囲気を演出する Back Ground Music (BGM)、UI操作等に合わせた効果音などが挙げられます。また、シーンの環境に合わせた環境音や、モノの当たる音などの物理音などより現実に近いような音が使用されることもあります。Amebientで使われている音は、原則後者の2つの要素 (環境音・物理音) のみで構成されています。すなわち、VR世界の中では、比較的現実寄りを指向した音の使い方をしているといえるのではないでしょうか。

しかし、Amebientは、現実よりも少しだけ音楽的な世界です。 言い換えれば、音楽的となるように構造を与えられた世界です。一つ一つの音は、雨粒が缶にあたったり、パイプで菅を叩いたりする物理音や、風音や雨音といった環境音にすぎません。現実世界でもそのような音は日常の中に存在しているでしょう。ただし、現実世界においてそれらの音は、現実的な確率の範囲内においては、人間が音楽と呼ぶに足る構造を持ちうることはありません。しかしそこに、雨粒の音楽的規則を持った落下、環境音の連鎖的な相関、音楽的操作と世界進行の相関といった構造が加えられることで現実のようでありながらそうでない、少し楽しい世界が出来上がるわけです。

すなわち、Amebientにおいて音は、単体では現実を模倣するような物理音や環境音でありながら、それらが連なったものとしては、世界観を演出するBGM、展開を誘導する導線、演奏を奏でる楽器の音源などの多様な役割を担うものへと変化します。そして、その全体としてAmebientという楽曲が構成されます。

Amebientという楽曲

Amebientでは、行動に伴って世界が進行します。穏やかな雨が降りそそぐ廃墟に雨音の奏でるテーマがゆったりと聴こえてくるところから始まり、物を移動させ音を変化させていくに伴い次第に雲行きが怪しくなり、雷とともに電力が通じてより一層音も環境も勢いを増していき、そして最後には一瞬の静けさの後、海の底に沈んで行きます。この一連の流れは、プレイヤーの行動によって拓かれていくストーリーラインとも言うべきものですが、これは同時にAmebientという楽曲そのものとも言うことが出来ます。

実際、phiさんの1日目の記事でも触れられていますが、実際にワールドに組み込む前の段階においてAmebientの1つの形を1曲としてまとめました。ストーリーラインのおおまかな流れは、作戦会議でたくさんのアイデアが挙がっていきました。その流れに概ね従いつつ、人の動きや世界の進行を想像しながら、ワールドにJoinしたところから結末まで曲として構成していくことで、次のような曲としてAmebientのストーリーラインが現れてきました。

部分的に今のAmebientとちょっと違うのが面白いですね。ちょっと違う部分は主にVisualとか実装の要請で逆に曲の方に変更が加わった部分です。 この曲は大きく4つの場面に分かれています。それを踏まえて各場面の概要、設計意図や経緯などを簡単に説明します。*1

導入部 (~0:32) *2

導入部分は、Amebientのテーマとも言うべき3拍子のメロディーからはじまります。 ワールドにはじめて来た人は、これをBGMだと認識したのではないでしょうか?実際、VRAA02の公開審査配信でもぴったりのコメントを頂いていました:

今流れている曲、ワールドのBGMかと思っていたら、リアルタイムに発生している音だったんですね。 (届木ウカさん)

これは、実際完璧に意図通りでした。

BGMは空間の雰囲気をがらっと変えられてしまうほど強力なもので、Amebientの世界観表現にも欠かせないものであると考えていました。一方で、これは音を自分で弄って遊べるというコンセプトとはなかなか合流しがたいものでした。 そこで、プレイヤーが制御可能なワールド内のオブジェクトでBGMとなるテーマを奏でるという方法でこれらの相反する二つの要素を両立させることにしました。 これは、背景だと思っていたものが背景ではなかった、という驚きを与え続けるAmebient全体にわたる仕掛けにも通じるもので、何かこのワールドは尋常では無さそうということを悟ってもらえるファーストインパクトにもなったかなと思っています。

展開部1 (0:32~2:23) *3

導入部で、BGMだと思っていた音楽が実は制御可能であったことに気づいたプレイヤーが、オブジェクトを抜き差ししたり違う雨粒に当ててみたりする行動を取っていくと、徐々に音楽は変化していきます。 しかしここで、ただ変化していくだけではなかなか展開していきません。仮にここで、何らかの条件を満たして急に世界が進行しても説得力がない(因果がない)ですよね。そこで、単なる変化ではなく、明らかな展開を見せるような工夫が必要になってきます。ここで、導入部と展開部の音を比較してみて欲しいのですが、なんとなくテンポ感がはやくなっている感じがすると思います。これは、実際にBPMが変わっていて、はじめのBPMは99になのに対して途中から132になっています。しかし、段々とテンポをあげていくといったことをしているわけではありません。(仮にワールド内でそれをしたら不自然ですよね...?)

これには、Metric modulationという楽曲構成上のテクニックを使っています。はじめBPM99で奏でられるテーマは3拍子なのに対して、展開部を構成するリズムは4拍子のものであり、BPMではなく拍子も変わっています。ここで、BPMの数字の比に注目すると、132/99 = 4/3となっていることがわかります。すなわち、3拍子の導入部の 4/3倍速の4拍子の展開部への移行なので実は1小節の長さが変わっていないのです。 従って、これらが共存している0:32~1:00付近では、3拍子を基準に見たら4拍子の音は4連符、4拍子を基準にしてみたら3拍子の音は3連符としてポリリズム的に解釈され、音楽的に自然にBPMが変更されていくようになっているのです。

なぜ、こんな回りくどいようにも見えるBPMの変更方法を取っているのかというと、上記のBPM変更を曲から空間に落とし込む時にこの解釈が最も自然であったからです。 図には、Amebientのメインフロアである3Fと、各領域でメインで聴こえてくる音のBPMとテンポを示しています。 入口付近では、BGMとなっていたテーマが聴こえており、外側付近ははじめは無音ですが入口付近から楽器を持ち運んでいくことで4拍子のリズムが聴こえてきます。 単に曲であればそれで終わりなのですが、実際には物を運んだり中間領域でたたずんだりする過程で何度もこの間を行き来したり、時には同時に耳にしたりすることになります。 すなわち

  • 外にいくという行動を反映してBPMをあげるという展開を与えつつ、
  • いつでも前の音に自然に戻って来れるようにし、
  • それらが同時に聴こえることも許容する

という楽曲上、空間上、体験上の課題をクリアする必要があります。そのために、Metric modulationが導入され、いつでも混ざったり遷移したりできるBPMの違う音楽が空間領域ごとに存在している状態というものが設計されています。 これは、通常のステレオの音楽を扱う上では絶対に問題にならないことで、空間方向に自由度を持つインタラクティブな楽曲制作の難しさと楽しさを感じる部分でした。

f:id:Raku_Phys:20201204201757p:plain
導入部BGMから展開部へのBPM変化

展開部2 (2:23~4:30) *4

導入部から展開部1に向かっては、BPMの変化によって曲に展開をもたらしました。ここからもう一段の盛り上げ、にぎやかさ、自由度を与えるのがこの展開部2です。 展開部1から展開部2にわたっては、豪雨と落雷をきっかけとした多くの変化がワールドにもたらされます。

豪雨によってもたらされるのは、降ってくる雨粒のリズムの変化です。 展開部1よりもより多くのバリエーションの雨粒のリズムバリエーションを使用出来るほか、それぞれのリズムもより密なものとなっていて、自然に全体の音が増えて音圧とスピード感を増していきます。 落雷によってもたらされるのは電源の供給で、これによって電子ロッカーが開錠されることとなり、いままで制限されていた楽器が使用出来るようになります。 その一つが鉄パイプです。今までは、雨粒に頼るしかなかったリズム部分を鉄パイプを使うことで自由に演奏することが出来るようになります。

この動画めちゃくちゃ好きなんですが...雨粒によってもたらされる背景のリズムに上乗せして、鉄パイプで叩いたリズムや旋律を加えることで多様な音楽を他の人とセッションしたりすることが楽しめます。

これらの、ワールドの変化・楽器の変化は総じて、体験者・プレイヤーにより多くの選択肢・自由度を与えるものです。 この展開を作っていくにあたり、Amebientに訪れる人は、演奏者なのか?という議論をphiさんとしていました。私は、演奏者として意図を持って音楽を構成出来る体験を作りたいと考えていましたが、phiさんはもっと偶発的な偶々置いたものが雨粒に当たってそれが楽しいという体験をベースに考えていました。それら二つの体験を期待する人の双方に対してワールド側が幅を持てるように考えられたのが、この展開部1から展開部2への流れです。 展開部1では、使用できる楽器や雨粒は限られており、作れる音の幅はあまり広くはないものの、ある程度どんな風においても成立するように作られています。一方で、展開部2は非常に自由度の高い演奏が可能になります。多くの楽器を使っていけば、それだけうまく聴かせるのは難しくなっていきますが、意図をもって音を作っていけばより多様な音楽を作り出すことが出来るようになっています。このような展開を取ることによって、ある程度音楽の知識がある人でも、まったくの初心者でも共通の空間での音楽体験を楽しめるようになっています。

さらに、この先の展開を望まずもっと音楽を作りこみたいという人のためには、別途端末を使った操作から得られるエンドレスモードを用意して対応することにしました。これについては、誰かがどこかで書いたりするんでしょうか...?

終末 (4:30~) *5

あらゆる体験の先にあるのが、海面上昇に伴う建物の水没です。 海中では、もともとAmbient音楽が流れていますが、海面が上昇することによってこの音楽がワールド全域を覆うことになります。 これによって、世界的にも、音楽的にも穏やかにエンディングを迎えられるような構成がとられています。

ここで、上記の音源では海中Ambientがフェードアウトして終わりになっていますが、実際のワールドではもう少し違った音を聴くことが出来ます。 それが海上の音です。上記の曲が出来上がり、ワールドとしても組みあがった後、一度テストワールドに行ってみました。 曲を作った段階では、水没した後のことは海の中に沈んでいることしか考えていなかったのですが、実際にテストワールドに行ってみると、屋上部分がわずかに海上に出ていてそこから外の雨の音を聴くことが出来たのです。 これを見た時、完全に鐘の音が聴こえてきてしまって、曲には少し続きが出来ました。

遠景の建物の内部には、1つ1つ音程の異なる鐘が設置されており、2Fの機械を起動した後からこれがランダムに流れ始めます。*6 そして、ついに最後の雷が落ちて音が消えるとこの鐘が、はじめの入り口付近で鳴っていたものと同じテーマを奏で始めます。 ここまでは、元の曲にあったのですが、実際にはこの鐘を水没後も永遠に鳴り続けるようにすることで、雨・風の環境音の上に流れるAmebientのテーマを世界が演奏し続けるというエンディングを迎えることになりました。

まとめと予告

本記事では、Amebientではどんな風に音が扱われているか、そしてそれがどんな体験をもたらしているかといったことの概要を書かせて頂きました。 Amebientのような音が体験・空間と密に結びついたような体験、そして楽曲はまだまだ沢山検討の余地があると思っています。そして、色々な人の作ったものを見てみたい気持ちです...。 すごく大変ではあったのですが、普通の楽曲制作とはまったく違った刺激の得られる楽しい体験だったので、音楽を作る方やVRChatのワールドを作る方に是非興味を持っていただきたいなと思って記事を書いていました。

今後のAmebient advent calendarでは、この記事の各章の深堀り (導入編, 展開編, 終末編)、自作音サンプルの制作エピソード、Udonを使った立体音響の制御などについて書いていこうと思っていますので、よろしくお願いします。

*1:詳細な説明は今後のAdvent calendarに書いていきます。

*2:詳細は12/6 Advent calendar

*3:詳細は12/10 Advent calendar

*4:詳細は12/10 Advent calendar

*5:詳細は12/13 Advent calendar

*6:これ気づいている人が少ない気がします...

GLSLで音を作るために 番外編

こんにちは。らくとあいすです。 「GLSLで音を作るために」第二回の前にしてさっそく番外編となってしまいました。 今回は5/30に twigl.app及びVRChat上のクラブで配信したGLSLサウンドを用いたDJについて、記憶の新しいうちに書いておきます。

性質上、ここでは1mixしか聴けませんがライブ時に使用したURLを載せておきます。 これ自体を解読することはあまりオススメしませんが...。 twigl.app

やったこと

GLSLを用いて、僕の好きな曲や自分の曲を作りこみ、そしてそれをライブで繋いだり混ぜたりするDJのような形式で発表しました*1。 これは、1. 各トラックの作りこみ、2. 作ったトラックを使ったDJ風プレイの大きく二つの工程に分けられます。1については、書くことが膨大過ぎる上にケースバイケースなので、本記事では概略に触れるに留めます*2。2については、GLSL特有の面白く、わかりやすい部分が結構あるので、少し具体的なコードも交えつつ解説したいと思います。

各トラックの作りこみ

トラックの作りこみはおおよそ次のようなフローで行いました:

  1. 楽曲の聴きこみ
  2. 要素の取捨選択と代替
  3. 音色の作りこみ
  4. 音程とリズムの構成
  5. 展開の構成

楽曲の聴きこみは、まずさっと一通り聴いてから集中して聴く箇所を絞ります。各パートをザクっと紙に書き起こした後、それぞれのパートに集中して聞き返しながらディティールを写しとっていきます。 この時、パートをしっかりと最小単位まで分解しきることが恐らく後の作業をスムーズにするように思います。

次に取捨選択ですが、GLSLサウンドでやる都合上、明らかにコストがかかりすぎる音があったりします。例えば歌や、複雑な構造の生楽器などはなかなか再現が難しいものです。 そこで、それらの要素を落として曲が成立するか吟味した上で、それを引き算した時に空いた空間を別の要素で補填します。 生楽器演奏でも、同じ曲を表現するのにピアノで弾くのとトランペットで吹くのでは、再解釈が必要となります。GLSLサウンドへの変換でも同様に、原曲を損なわないために原曲と表現を変えるということが必要だと思っていて、ここが一番のポイントのような気がしています。

音色の作りこみは、感覚と測定の両面で進めていきます。まず、各楽器を聴いたときの感覚で、この音を表す最もシンプルだと思われる波形を数式に表します。ここでは、サインなのか、のこぎりなのか、倍音の多いFMなのか、といったぐらいの粒度です。次に生成した波形と原曲の波形を、Audacity等の波形編集ソフト上で比較します。また、スペクトログラムも比較していきます。ここで、おおよそ、どんな倍音成分が必要かがわかってくるので、試行錯誤して所望の波形に近づけていきます。最後に、音を聴きなおして原曲に合う合わないによらず、単体として良い音になるように感覚で微調整しておおよその音作りを終えます。

音程とリズムの構成は、ピアノロールに打ち込むように、音を譜面に落とし込んでいく作業です。ただ、コーディングベースのサウンド全般に言えるかもしれませんが不規則な動きや、一部分だけ連符になっていたりする楽譜に弱いので、ここでも再解釈が必要になることがあります。その不規則性が、曲の中でどうしても必要な要素と解釈されれば、コード量を尽くして再現しますが、別の表現で代替可能と思えば書きやすいように表現を少し変えたりします。このあたりは、ケースバイケースですね。

展開の構成は、曲の展開の中で使いたい部分を再現したり、実際に回すときに変化をつけやすいように曲を分解する作業です。 今回は、各曲をひとつの関数にまとめたので、そのインプットとして通常実時間のみを取れば良いのですが、展開切り替え用にintを一つ変数にとっています。 このint変数の値によって、組み合わせるパートを変化させて、曲の展開を作っています。

このように、音色の作りこみ方法を除けば、おおよそは通常のDAWや生楽器を使用した楽曲のカバーと、同じようなフローになっているように思います*3

作ったトラックを使ったDJ風プレイ

まず、突飛に聞こえるかもしれませんが、レコードを回すこととGLSLサウンドで曲を作った関数を呼び出すことは、本質的に同じだと思っています。 レコードは、事前に音の波形を溝の深さとして記録しており、それをターンテーブル上で回すことで、針が指す時刻の溝の深さを次々と読み取って音に変えていきます。 GLSLサウンドも同様で、各トラックを作りこんだ関数はレコードに相当し、そして針の代わりに時刻 (time変数) を入力することで返り値として、溝の深さに相当する波形の値を読み取ることが出来るわけです。

このような、気持ちで持ってコメントを付けたGLSL Sound DJの最小コードは次のようなものが考えられると思います:

#define bpm 132.0
#define pi2 6.2831
//フェードイン関数(a拍目~b拍目)
#define fin(a,b) smoothstep(a,b,time*bpm/60.0)
//フェードアウト関数(a拍目~b拍目)
#define fout(a,b) (1.0-smoothstep(a,b,time*bpm/60.0))

//A(440Hz)からの相対周波数計算関数
float calf(float i){
  return pow(2.0,i/12.0);
}
//一つ目のレコード(内容は一例です)
vec2 recordA(float time){
  float t = time*bpm/60.0;
  float bd = sin(3e2*time)*fract(-t);
  float hh = fract(sin(time*12.1)*4e5)*pow(fract(-t+0.5),20.0);
  return vec2(bd+hh)*0.5;
}
//二つ目のレコード(内容は一例です)
vec2 recordB(float time){
  float t = time*bpm/60.0;
  vec4 sl = vec4(0.,7.,9.,14.);  
  int st = int(mod(floor(t*2.0),4.0));
  float seq = sin(pi2*time*440.0*calf(sl[st]))*fract(-t*2.0);
  return vec2(seq)*0.5;
}
vec2 mainSound(float time){
  float t = time*bpm/60.0;
  //ターンテーブルを逆回転
  float itime = -time;
  //ターンテーブルを行き来(スクラッチ)
  float sctime = sin(pi2*t)*0.5;
  //ターンテーブルの回転速度を指数関数的に加速させる
  float etime = exp(time);
  //ターンテーブルを何回か引き戻す(スタッター)
  float stime=(t<2.0)?mod(time,pow(2.0,-floor(t))*15.0/bpm):time-120.0/bpm;
  //********************************************//
  vec2 table1 = vec2(0.0);//空のターンテーブル1
  vec2 table2 = vec2(0.0);//空のターンテーブル2
  table1 = recordA(time);//ターンテーブル1にレコードAを乗せる
  table2 = recordB(time);//ターンテーブル2にレコードBを乗せる
  vec2 master = table1 + table2;//masterアウトにmix
  return master;
}

以下は、上記のコードについてかいつまんで解説します。是非 twigl.app で実際に触りつつ読み進めてもらえればと思います。

twiglの基本操作のおさらい

念のため基本操作をおさらいします。まず、twiglにアクセスした後、SoundShaderを有効にし、上記コードをSoundShader用のエディタにコピー。次にalt+Enterを押すことでコンパイルされ、再生が始まります。alt+ctrl+Enterで再生ストップです。何か変更を加えた場合はalt+Enterで再コンパイルすることで、反映されます。

GLSL Sound DJ最小コードの解説

まず、ここでは二枚のレコードを作成します (recordA, recordB)。本来は各レコードについて前節でお話したような方法で作りこんでいきますが、ここではごく簡単なサンプルを入れてあります。 次に、メイン関数の下6行ですが、ここでレコードをターンテーブルに載せて再生・ミックスに相当する操作を行います。このサンプルでは、シンプルに二つのターンテーブルにレコードを載せてそのまま再生しています。BPMについては、#defineで指定しているので、元々合うようになっています。

DJにおいて、必須ともいえるフェーダーの再現は、#defineで冒頭に定義されているfin及びfoutを用いて行うことが出来ます*4。これを使うと例えば次のようにrecordAとrecordBをクロスフェードできます:

table1 = recordA(time)*fout(0.,16.);
table2 = recordB(time)*fin(0.,16.);

二つの因数は、フェードの開始と終了です。これを使って、2つのトラックをパキっと入れ替えることもできます:

table1 = recordA(time)*fout(7.5,8.5);
table2 = recordB(time)*fin(7.5,8.5);

多分、DJの方はフェーダーを上げ下げする速度も気を使っていると思いますが、そこまでパラメータを含めるとやはりまた煩雑になるので、ここではGLSLのsmoothstep関数で統一しています。 どうしてもやりたくなれば、その都度書けば良いという話ではあります。

次に、DJと言えばお皿を触って色々するイメージ(ド素人)なので、愚直に実装してみたのがmain関数の初めの数行 (itime,stime,etime,sctime) です。 レコードを触ること、すなわち相対的には針の位置を変えることは、GLSLにおいては時間 (time) を加工することに相当します。 ターンテーブルの逆回転は、time = -time と、時間を反転させることで再現されます:

table1 = recordA(time);//そのまま再生
table2 = recordB(itime);//逆再生

ターンテーブルをごしごしとその場でいったり来たりさせるスクラッチは、sin等の関数を使って時間をいったり来たりさせれば実装出来ます:

table1 = recordA(sctime);//スクラッチ
table2 = recordB(sctime);//スクラッチ

スタートから数拍だけ時間を折り返すと、スタッターと呼ばれる効果を作ることが出来ます(物理お皿でやることがあるのかはわかりません):

table1 = recordA(stime);//スタッター
table2 = recordB(stime);//スタッター

お皿をどんどん加速させることも、時間を指数関数などでどんどん早く進めることで実現可能です:

table1 = recordA(etime);//指数関数的にお皿を加速
table2 = recordB(time);//そのまま再生

他にも色々な操作をtimeに加えて、それを各レコードを読みだす時間 (針) に入れて上げることで、面白い効果を得ることが出来ます。 是非色々と組み合わせて遊んでみて下さい。

最後に

GLSL Sound DJはたしかにハードルが高くお気軽にオススメ出来るようなものではないのですが、作りこめば作りこむほど音になって返ってきてくれる楽しさがあります。 簡単なレコードを作って混ぜ合わせるところから、是非トライしてみて下さい。わからないことや間違えがあれば、Twitterかコメントにお願いします。

謝辞

GLSL Sound DJは、doxasさん(h_doxas (@h_doxas) | Twitter) の開発された、twiglのGLSLライブ配信環境に依拠するところが非常に大きいです。 維持、開発してくださり本当にありがとうございます。twiglを用いて度々セッションしてくれているブタジエンさん (ブタジエン (@butadiene121) | Twitter )、いつもありがとうございます。 ブタジエンさんの作る、Visualがアイデアのかなりの部分を支えてくれています。最後に、今回のDJで楽曲を使用させて頂いた方々にお礼の意を込めてセットリストとリンクを載せて終えたいと思います。

  • バーチャルユーチューバーのいのち (キヌ)

www.youtube.com

  • O.D.E.N (obake)

digitalghost.booth.pm

www.youtube.com

  • Tell Your World (kz)

www.youtube.com

  • TECHNO VINEGAR (obake)

www.youtube.com

  • Cloud Identifier: 集合知に遍在する残滓による自意識の再構成 (memex)

www.youtube.com

  • さざなみ (Sound: らくとあいす/VJ ブタジエン)

twigl.app

*1:僕はDJをやったことも、コントローラーに手を振れたことも、物理現実で見たこともないので、あくまでDJの"ような"ですが...。

*2:実際これは準備が相当に大変でゴールデンウィークの代休を利用して、木曜の夜から土曜の夜までぶっ続けで書き続けてようやく間に合った感じでした。

*3:DAWを使用した楽曲のカバーをしたことはないですが...

*4:普通の関数で書いても良かったのですが、ライブでやる都合上time変数を代入する文字数を書くのが大変なので、defineを使って省略しています。

第1回 GLSLで音を作るために

こんにちは。らくとあいすです。 以前「GLSLで音を作る」という記事を書いたところ、ありがたいことに色々と反響をいただきました。 raku-phys.hatenablog.com せっかくなので、より詳しい記事を書いていこうと思ったのですが、私も始めたばかりということもあって日々色々なやり方が変わっていきます。 なので、長編の記事として、「これを読めばわかる」といったようなものが書きづらいといった状況にあります。 そこで今回から、GLSLサウンドに関連したちょっとしたお話を何回かに分けて(思いついたときに...)書いていこうと思います。(続くことは保証しません) 例によって今回も twigl.appサウンドシェーダーを開きつつ読み進んでいただければと思います。

GLSLサウンドにおける時間の捉え方

GLSLサウンドでは、あらゆる部分が時間の関数となります。 GLSLで絵を作る場合は、縦横と時間の3軸、立体的な絵の場合は4軸考えたりすることもあると思いますが、サウンドは基本的に一軸です。 ただし、その一軸はいくつかの階層的な構造を持っていると、私は考えています。

ここでは、私なりに考えたGLSLサウンドにおける時間軸の階層性を、次の例で説明します。

#define pi2 6.2631
#define bpm 118.0
float fm(float t,float f,float i,float r){
  return sin(pi2*f*t+i*sin(pi2*f*t*r));
}
float calf(float i){
  return pow(2.0,i/12.0);
}
vec2 mainSound(float time){
  float tbpm = time*bpm/60.0;
  float[8] seq_line = float[](0.,7.,5.,10.,7.,3.,5.,-2.);
  float seq_time = mod(floor(tbpm),8.0);
  float seq_freq = 440.0*calf(seq_line[int(seq_time)]);
  float seq = fm(time,seq_freq,0.5,1.0)*fract(-tbpm);
  return vec2(seq*0.5);
}

上記のコードで生成した音を、次の再生ボタンから聴いてみて下さい:

8つの音が連続して流れたのがわかると思います。実際、上記のコードはこの8つの音を連続して繰り返すだけのシンプルなものです。 この8つの音のカタマリが一つめの時間の階層です。 フレーズ、パターン、リフ、シーケンスなど色々言い方はあると思いますが、このような数秒程度の音のカタマリは音楽を構成する上で良く使われます。 さて、この8つの音の波形は、次のようになっています。

f:id:Raku_Phys:20200520212015p:plain
8つの音の波形

8つの音の音程の変化は(波形上はささいな変化なので)この波形から読み取ることは難しいですが、各8音が規則的な音量変化をしていることがわかると思います。 この各音の時間変化が、二つ目の時間の階層です。一般に音は、鳴り始めから鳴り終わりまでの間に音色が変化します。 例えば、ピアノではハンマーが弦を叩く初めの瞬間、そして弦が強く振動し最後には次第に弱まっていきます。太鼓では、叩いた瞬間の鋭い打撃音の後に、太鼓の内部での音の響きが残ります。 このような、1音1音の持っている音の時間変化や音量の時間変化は、音色を決める重要な要素となっています。 さて、この波形をさらにクローズアップして見てみると次のようになっています。

f:id:Raku_Phys:20200520212340p:plain
拡大した波形

丸まったノコギリの歯のような形の波形の繰り返しが見えると思います。この一つ一つの波形が、三つ目の時間の階層です。 この波形が音を取り扱う上での(ひとまずの)時間的の最小単位で、音色を決めるもっとも根本的な要素になっています。

実装

時間の階層構造は捉え方の話ではありますが、私の場合実装上でも意識的に時間の階層を取り扱っています。

まず、1つ目と2つ目の階層の時間を扱う上で重要となるものがBeats Per Minute (BPM) です。 おそらくこの記事を読んでいる読者の方々であればBPMという言葉自体はご存じかと思いますが、その名の通り1分間あたりのbeatの数です。 では、このbeatがどのように決まっているかというと、実際のところ厳密な定義があるわけではありません。 とはいえ、そうそう意見が割れることもなく、例えば4つ打ちのダンスミュージックであればバスドラムがbeatとなることが多いですし、8beatであれば (その名の通り) 8分音符で刻まれるハイハットの倍の長さがbeatとなります。ざっくりとは体でノれるリズムの単位がbeatで、その1分あたりの数がBPMと思っておいて良いかと思います。 そして、曲を構成するあらゆる音はこのBPMを単位として、その1/2や1/4のの上におおよそ乗ることになります。 したがって、その1音1音の音の変化や、それらが集まったフレーズはこのBPMを単位とした時間で扱うと色々と都合が良いです。 そこで、まずはじめに、実時間 (time) をbpmを基準とした時間 (tbpm) に次のように変換します:

float tbpm = time*bpm/60.0;

mainSound関数の入力として与えられる時間は秒を単位としているので、そのまま1秒をbeatとするとBPM=60となります。そこで、その実時間を60で割った後bpmを書けることでBPMを基準とする時間に変換することが出来ます。

このBPMを基準とした時間 (tbpm) を用いて1つ目の階層 (フレーズ) の時間は次のように表しています:

 float seq_time = mod(floor(tbpm),8.0);

フレーズの時間は連続的な変化をするものではないので、まずはfloorで離散化し、そのあとmodで折り返すことで8つの音の繰り返しを作っています。

2つ目の階層 (1音の変化) の時間は次のように表しています:

fract(-tbpm)

音の変化 (ここでは音量のエンベロープ) は連続的なのでここでは、floorでつぶさずに連続的なまま扱うことにし、fractを使って1beatごとに折り返すことにします。

最後に波形を扱うための三つ目の時間としては、mainSoundの入力として与えられる実時間 (time) をそのまま使います。これは各波形の繰り返しの速さ (=周波数) はBPMなどに関係する量ではないからです。 コード上では、fm波形を生成するための関数の入力として実時間 (time) をそのまま与えています。

おわりに

今回はGLSLサウンドを書く上で普段意識している、時間の捉え方について書きました。 色々とごちゃごちゃと書いてしまった気がしますが...要は目的に応じて使いやすいように時間を加工しておくと便利ということだけ知ってもられば良いのかもしれません。 コード内には今回解説していない要素も色々と含まれていますが、今後もしかすると解説するかもしれません。 また、質問等あればお気軽に投げていただければと思います。 それでは、お読み頂きありがとうございました!

VRChatで強化学習しよう

こんにちは、らくとあいすです。

先日VRChatワールド"UDON AI -tic tac toe-"をパブリック化しました。今回はその内部で使われている強化学習と、UdonSharp*1を用いたVRChatでの実装の要点を記事にしたいと思います。 公開されているワールドではでは三目並べを題材としていますが、単純なサンプルとして経路探索を題材としたものをGitHubにて公開しました。実装の詳細などはこちらを併せて見て頂ければと思います。 MITライセンスにて公開しておりますので、改変等行って頂いても構いません。

github.com

強化学習

囲碁や将棋など、勝敗が付くまで今の行動が良かったのか悪かったのかわからないといった問題に直面した時、最適な行動を選択するにはどうしたら良いでしょうか? 人は、このような問題に対して過去の経験を頼りに最も良さそうな選択を選んでいくことが多いと思います。機械(AI)で同じことをやろうと思うと 、この"経験"や"良さそう"や"悪そう"をうまく教えてあげる必要があります。 強化学習は、過去に得られた良い経験、悪い経験から、今どのような行動をすべきかを学習する枠組みです。以降では簡単のために次のような単純な状況を考えます:

f:id:Raku_Phys:20200425205708p:plain
シンプルなダンジョン
Playerは地点0からスタートし、1-1から2-4の地点に移動していきます。各地点には正または負の得点が用意されており、通過した段階でPlayerは得点を手に入れます。ただし、Playerは事前に得点を知ることは出来ません。 以降ではPlayerのいる地点を状態 s、どちらの分岐に進むかの選択を行動 a、手に入れる得点を報酬 rと呼ぶことにします。

マルコフ決定過程

強化学習マルコフ決定過程と呼ばれる環境のもとに定義されます。 マルコフ決定過程とは、遷移後の状態 s'及び得られる報酬 rは、直前の状態と行動の組 (s,a)にのみに依存することを表します。 ダンジョンの例で言えば、次の状態と得られる報酬は、「今どこにいて」「どちらの道を選ぶのか」にのみ依存するということです。 ここで、時間と共に報酬が減っていったり、予期しない敵の襲来で違う地点に飛ばされたりする場合、これはマルコフ決定過程ではなくなります。 もちろん、これらの要因を状態 sに含めて s=(地点,時刻,敵の位置) などとすれば、上記の例もマルコフ決定過程となります。 すなわち、マルコフ決定過程は、結果に影響を与えるあらゆる条件がすべて観測可能である(=状態として表現されている) ことを表しています。

行動価値関数

マルコフ決定過程に従う時、現在の状態 sにおいてどのような行動 aを取るかによって得られる報酬 rは決められます。 では、最終的に最も多い報酬を得られる選択をするには、どのように考えるのが良いでしょうか? その指標となるのが行動価値関数 Q(s,a)です。 Qは、ある状況 sにおいて行動 aを取ることの価値を表します。 ダンジョンの例で言えば、地点0から地点1-2(down) を選んだ場合、直近の報酬は最大となります。しかし、次の手まで見通すと最終的に得うる報酬は地点1-1(up) を選んだ場合に最大となります。 従って、地点0における Q

 Q(0,up)>Q(0,down)

を満たすべきと考えられます。このように、直近の報酬だけではなく、遠くの報酬も見越して行動の価値 Qを設計する必要があります。

ベルマン方程式

最終的に得られる報酬を最大とする行動価値関数 Q(s,a)は、次のベルマン方程式を満たすことが知られています*2*3

 Q(s_t,a_t) = r_{t+1} + \gamma Q(s_{t+1},a_{t+1})

すなわち現在の行動の価値は、直近の報酬と次の行動の価値に割引率 \gammaを乗じたものの和として与えられます。 式を見るとわかるように、 \gammaは未来の報酬の価値を減衰させる効果があり、例えば \gamma=0とすると直近の報酬のみを見て行動を選択するような行動価値関数となります。 ダンジョンの例において、ベルマン方程式に従う Qを記入すると次のようになり、 Qの大きい行動を選択することによって、正しく報酬+10の地点にたどり着けることがわかります。

f:id:Raku_Phys:20200425230738p:plain
ベルマン方程式を満たす Q (割引率 \gamma=0.9)

Q学習

ベルマン方程式を満たすように Qを設計することで、 Qのより大きい行動を選択することが最終的な報酬を最大にすることがわかりました。 しかし、ベルマン方程式に従う Qは漸化式であるため、それ以降のすべての状態及び報酬を知らなければ計算することが出来ません。 例えば、はじめに地点0を出発するPlayerにとってupを選択すべきかdownを選択すべきかを決める手がかりは何もありません。 そこで、強化学習では実際に何度も行動してみて得られた報酬から、段階的に Qの値をベルマン方程式で表される最適な値に少しずつ近づけていく(学習する) ことを考えます。 その手法のひとつがQ学習です。Q学習では現在の Q(s_t,a_t)を、行動後に得られる報酬r _{t+1}及び、次の状態における Qの最大値\underset{a}{{\rm max}}Q(s_{t+1},a_{t+1})を用いて次のように更新します:

 Q(s_t,a_t)\leftarrow Q(s_t,a_t) + \alpha(r_{t+1}+\gamma\underset{a}{{\rm max}}Q(s_{t+1},a_{t+1})-Q(s_t,a_t))

ここで \alphaは学習率と呼ばれる量です。例えば \alpha=0においては、あらたに得られた経験によって Q(s_t,a_t)が変化せず全く学習が進みません。一方で \alpha=1においては、今までに得られた経験のみから Q(s_t,a_t)をベルマン方程式に従う形に急激に変化させます。 ダンジョンの例でQ学習の様子を示したのが次の図です。

f:id:Raku_Phys:20200426151415p:plain
Q学習の様子(学習率\alpha=0.3、割引率 \gamma=0.9)
まず初期状態では①のようにランダムなQ値がふられています(いわば初期の思いこみ・勘です)。その後Q値のより大きい1-2に進む選択をすると、そこで得られた報酬 r_{t+1} =1及び、次の行動で得うる最大のQ値(=0.1)により、Q値が更新されます。次もQ値のより大きい2-4に進む選択をすると、ここでは-10の報酬を得ます。次に取りうる行動はないので、ここでは報酬のみを用いてQ値を更新します。この時点で2-3に進むQ値と2-4に進むQ値が逆転したため、以降は2-4に進まないようになりました。このように行動を繰り返すことで、Q値を更新していきます。

 \epsilon-greedy法

ダンジョンの例では、一度目の探索の結果、2-4にたどり着くという最悪の結果を避けることが出来るようになりました。さて、ここでもう一度④を見てみましょう。

f:id:Raku_Phys:20200426154654p:plain
1度目の学習終了時点のQ値の割り当て
これを見ると、二回目の行動では[0→1-2→2-3]という経路を通り再びQ値が更新されます。そして三回目以降も同じルートを辿り続けることがわかります。一方で、Playerにとって最も高い報酬を得ることが出来るルートは、[0→1-1→2-1]です。このように、Q学習においては、より高い報酬を得る行動が存在するにも関わらず別の行動に囚われてしまうといった問題が生じることがあります。

そこで、確率 \epsilonでランダムな行動を取り、確率 1-\epsilonでQ値を最大にする行動を取ることを考えます。すると、ランダムな行動においてはいかなるルートも選択されうるため[0→1-1→2-1]のようなルートも探索されうることが期待されます。言い換えれば

"ちょっと向こうも見にいってみようぜ!"
"危ないけど \epsilonの確率でなら君の意見をきこう。"

ということです。このような手法を \epsilon-greedy法といいます。

UdonSharpを用いた実装の要点

さて、これまでに見てきたことをUdonSharp(U#)を用いて実装することで、VRChat上での強化学習を可能にすることを考えます。 とはいえ、U#が大変すばらしいために、ほとんどC#で書けば良いというだけになっていますので、ここでは要点のみ解説することにします。 実装の詳細はGitHubで公開しているコードを参照いただければと思います*4。 以降では、比較的シンプルに実装出来る次のようなタスク "From Corner To Corner"を題材とします:

  •  4\times4の16マスの盤面で行われる

  • Player(青丸) はGoal(赤丸) を出来るだけ短い手順で目指す

  • Player(青丸) は上下左右の4方向に移動できる。

  • 盤面の外に出たら失格

f:id:Raku_Phys:20200426174732p:plain
From Corner To Corner

このルールにおいて、Playerの持っている状態 sは盤面上での位置であり、行動 aは上下左右の4択となります。 これらはすべて観測可能である(=マルコフ決定過程に従う) ので、このタスクは強化学習で学習可能であると考えられます。

Qtable

Q学習の学習結果は、各行動と状態の組 (s_t,a_t)に対応するQ値です。これは最もシンプルには、 s_t\times a_tの行列 (二次元配列)に値を詰めていくことで実装可能です。 ただしU#(およびUdon) では(2020/04/26) 現在、多次元配列に対応していないため一次元配列で実装する必要があります。ここではこの一次元配列をQtableと呼びます。 取りうる状態と行動の組の数は状態数16と行動数4の積で64なので*5*6*7、Qtableを次のように宣言できます:

private float[] qTable = new float[64]; //(s,a)=>s*4 + a

ここで、特定の状態 sと行動 aに対するQ値は、 Q(4s+a)と定めておくこととします。

Start処理

学習の開始地点としてQ値を乱数で初期化する必要がありますが、U#においてStart関数が呼ばれるタイミングなどに不安定性があるので、Update関数内で一度だけ呼ばれる処理をしておいた方が安定するようです*8。次のコードはその一例です:

void Update()
    {
        if (first)
        {
            boardMat = boardObj.GetComponent<MeshRenderer>().material;
            InitQTable();
            first = false;
        } 
    ...
    ...
    }

学習部分

Q学習を行うにあたり、まずは報酬を定義する必要がありますが、今回は次のように報酬を定めました:

  • ゴールに到達→1点

  • 盤面を外れる→-1点

  • 毎ステップ→-0.1点

これによって盤面を外れず・ゴールを目指すような行動が優先されるようになります。さらに毎ステップ弱い罰則を科すことで、なるべく短い手順でゴールを目指すような行動が優先されるようになります。 次にこの報酬に従い、Q学習の更新式を実装します。Q学習の更新式は次に取りうる行動がある(ゴールや失敗をしていない) 場合と、次に取りうる手がない場合で変わるので、そこに注意して次のように実装します。

private void Train()
    {
        float reward = GetReward();
        int Qid = lastState * 4 + act;//Q(st,at)=>Q(st*4+at)
        
        if (goal | fail)
        {
            qTable[Qid] = qTable[Qid] + alpha * (reward - qTable[Qid]);
        }
        else
        {
            int nextQid = state * 4 + QTableSelect();//max(Q(st+1,at+1))
            qTable[Qid] = qTable[Qid] 
                         + alpha * (reward + gamma * qTable[nextQid] - qTable[Qid]);
        }
}

行動の選択

行動の選択は \epsilon-greedy法で行います。0から1の間の乱数が \epsilonよりも小さい場合にはランダムな行動を選択し、そうでない場合にはQ値を最大とする行動を選択します。 ただし本実装では、 \epsilonは学習の経過とともに小さくする \epsilon-decayという手法を用い、段々とランダム性を弱めていくようにしました*9

private int GetAct()
    {
        //epsilon-greedy
        int a = 0;
        if (Random.Range(0.0f, 1.0f) < epsilon)
        {
            a = Random.Range(0, 3);//ランダムで行動を選択
        }
        else
        {
            a = QTableSelect();//最も高いQ値となる行動を選択
        }
        return a;
    }

おわりに

本記事では、強化学習の一手法であるQ学習の概要を紹介し、U#を用いた実装例について要点を解説しました。 強化学習は、状態・行動・報酬をうまく設計することが出来れば様々なタスク・ゲームに応用することが可能です。 今回公開した経路探索のサンプルでも、途中に障害物をおいてそれを避けるような構成にしたり、妨害者がランダムに表れるようにしたりといったように色々な拡張を考えることが出来ます。 たかだか数十~数十万の数字の組み合わせにより、"知性"を獲得していく様子は、個人的に大変面白いと感じました。 本記事が、VRChat上に様々な"知性"を生み出すきっかけとなれば幸いです。

*1:https://github.com/Merlin-san/UdonSharp

*2:簡単のためPlayerは常にQ値が最大の行動を確率1で選択することを仮定しています

*3:簡単のため (s_t,a_t)に対して確率1で次の状態が決まることを仮定しています

*4:https://github.com/RakuPhys/Udon-Sharp-Scripts/tree/master/FromCornerToCorner

*5:ちなみに三目並べの場合、取りうる状態数は 3^9=19683、行動数は9なので一次元Qtableの長さは177147となります

*6:組み合わせ爆発によって、探索範囲が膨大になってしまうことがQTableを用いたQ学習の欠点の一つです

*7:これを解消するために、Q値を状態と行動の組 (s,a)を入力とする関数と見て、さらにこの関数をDeep Neural Network(DNN) で表現するというDeep Q Network (DQN)などが登場しました。

*8:ちゃんとした検証はしていませんが...。

*9:DecayTimeというパラメータで \epsilonが0になるまでの時間を変更できます

GLSLで音を作る

こんにちは、らくとあいすです。

つぶやきGLSLというというTwitterハッシュタグをご存じでしょうか?詳しくはハッシュタグを実際に見てもらうか、ブタジエンさんの記事*1をみると雰囲気をつかめると思いますが、簡単に言えば1ツイートの中に収まるGLSLシェーダーを書き、生成されたGIF画像と共にツイートする遊びです。 

さて、普通シェーダーは絵作りのために用いられるものですが実は音を作ることも出来ます(?) 正確には、音の波形を表す配列(=テクスチャ)を生成するのにシェーダーを用い、それを読み取って音を出す仕組みが提供されています*2。ここではこのようにGLSLシェーダーを用いて作られた音楽のことをGLSLサウンドと呼びます。

最近このGLSLサウンドをつぶやきGLSLでやってみたところ、ありがたいことに多くの反応を頂けたのでその解説やGLSLサウンドの初歩的なところを書いていこうと思います。

GLSLサウンドを作る環境

GLSLサウンドを作れる環境として私の知っている範囲では、ShaderToy*3、Veda*4、twigl*5などがあります。ここでは#つぶやきGLSLに特化して作られた環境であるtwiglを標準環境として進めます。こちらtwigl.appからtwiglを開き、触りつつ読み進めてもらえればと思います。

GLSLサウンドチュートリアル

ここではチュートリアル的にGLSLサウンドでの音の鳴らし方や作り方について初歩的な事項を解説します。

Sin波を鳴らす

まずは、GLSLサウンドHello World とも言えるSin波の出力をしてみましょう。twiglのエディタ右側のSound ShaderスイッチをオンにするとSound Shader用のエディタが展開されます。そして再生ボタンもしくはalt+Enterを押すことで音が再生されると思います。この初期コードに入力されている音が(指数減衰する)Sin波です。このコードについて少し詳しく解説します。

f:id:Raku_Phys:20200418132521p:plain
指数減衰するSin波を出すコード
f:id:Raku_Phys:20200418141922p:plain
指数減衰するsin波の出力波形
まず、mainSoundの入力はfloatの実時間で、出力はvec2です。このvec2(2次元ベクトル)は、ステレオ出力におけるLとRの2チャンネルを表します。今回はvec2の中に一つの値しか入っていないので、LとRがまったく同じ出力=モノラルの音が得られます。

続いてsin関数の中身ですが、周波数を f, 時間を tとして \sin(2\pi f t) の形をしています。つまり1秒間に f回振動する(= f{\rm Hz}の) Sin波です。ここでは f=440{\rm Hz}となっていますので音叉などで基準音として用いられるAの音が鳴ります。試しに fの値を色々と変えて音が変わることを確認してみましょう。

最後にexp関数についてです。これはsin関数に掛かる形をしているのでsinの振幅に影響を与えることがわかります。中身は \exp(-at)の形をしていて、指数減衰することと、 aがその速さを決めることがわかります。試しに aの値を色々と変えて減衰速度が変わることを確認してみましょう。また、expの項を取り除き音が減衰しないことを確認しましょう。

Sin波を混ぜる

では次にSin波を混ぜて和音を作ってみましょう。

vec2(sin(6.2831*440.*time)+sin(6.2831*440.*1.5*time))

一つ目のSin波は周波数440Hz、二つ目のSin波は周波数440*1.5=660Hzで、完全5度*6の響きとなる...はずですが鳴らしてみるとどうもSin波では無さそうな音が聴こえると思います。この時の出力波形は次のようになっています:

f:id:Raku_Phys:20200418145015p:plain
クリッピング波形
波形を見ると波形の上端と下端が平坦になっていることがわかります。これは、音声は-1から1の間のfloatで扱われるため、それを超える部分についてはクリップされてしまうことに起因します。これはこれでこういう音として利用しても良いですが、ここでは全体に係数をかけて綺麗なSin波を再生してみましょう。

vec2(.4*(sin(6.2831*440.*time)+sin(6.2831*440.*1.5*time)))

このように、音を混ぜる際には出力が意図しないclippingを起こしていないかを常に注意しておく必要があります。

周期的なエンベロープを作る

今までは一度減衰して消えてしまう音や、常になり続けるような音だけを作ってきました。次は周期的に鳴るような音を作ってみましょう。 ある波形に対してその振幅の変化 (包絡線) をエンベロープと言います*7。例えば"Sin波を鳴らす"で触れた例では、エンベロープとして指数減衰が使われています。 指数減衰は一度下がると上がることはないため、音が一度しか立ち上がりません。逆に言えば何度も立ち上がる=周期的な関数を使えば周期的に鳴るような音を作ることができます。 ここではまずfract関数、すなわち入力の少数部分を取る関数をエンベロープに使ってみましょう。

f:id:Raku_Phys:20200418150858p:plain
fract関数を使った周期的エンベロープ
グラフを見るとわかるように {\rm fract}(at)は0から1までを 1/a秒で上昇し、そして再び0に戻る関数です。したがってこれは周期 1/aエンベロープと言えます。これを使ってSin波を周期的に鳴らすコードは次のようになります:

vec2(sin(6.2831*440.*time)*fract(2.*time))

これでSin波が"ふわっと"持ち上がることを繰り返すようになりました。次にfractの中身を時間反転させてエンベロープを反転させてみましょう:

vec2(sin(6.2831*440.*time)*fract(-2.*time))

これで鋭い立ち上がりのSin波が繰り返されるようになりました。 この他にもゆっくりと振動するsin波やpowerで形を変形したfract、三項演算子で条件分岐したものなど様々なエンベロープを考えることが出来ます。ぜひ色々と試してみて下さい。

ノイズを使う

ここまでに揃ったSin波・加算・エンベロープでかなり多様な表現が出来るようになりました。最後にもう一つ重要な要素としてノイズを紹介します。空洞を持たない金属(ハイハットやシンバルなど)を叩く音は、通常整数倍音等決まった周波数が強調されず幅広い周波数の音が一斉に放出されます。Sin波をベースにした音の構成ではこのような、幅広い周波数を連続に持つ音を表現することが難しいため、ノイズ成分が重要となってきます。

ビジュアルを作るGLSLで良く使われるランダムノイズとしては、次のようなものがあります:

fract(sin(dot(co.xy,vec2(12.9898,78.233))) * 43758.5453);

ここではノイズの生成について (筆者は詳しくないので) 深く立ち入ることはしませんが、このランダムノイズの関数をベースにそれらしく聴こえる音のノイズ関数として次のようなものを作ってみました:

return vec2(fract(sin(time*1e3)*1e6)-.5);

高速に振動するsinに大きい値をかけてfractしたらrandomらしい挙動になるでしょうという適当なものですが、スペクトルを見るとおおよそホワイトなノイズになっているようです。

f:id:Raku_Phys:20200418162536p:plain
生成したノイズの周波数スペクトル
さてこのノイズに”周期的なエンベロープを作る”で作った周期的なエンベロープを付与し、ハイハットを作ってみましょう。例えば時間反転したfractをエンベロープとして用いると次のようになります:

return vec2((fract(sin(time*1e3)*1e6)-.5)*fract(-time*8.));

機械の動作音のようなものが出来ましたね。ここからエンベロープを工夫するだけでも色々な表現を作っていくことができます:

return vec2((fract(sin(time*1e3)*1e6)-.5)*pow(fract(-time*4.),mod(time*4.,2.)*8.));

色々と試して好みの音を作ってみて下さい。

つぶやきGLSL作品の解説

ここではGLSLサウンドの基本的な知識を前提として、冒頭に載せた作品についての解説をしていきます。

全体像

実際につぶやいたコードは次のようなものです

#define f fract 
#define s(a,b) sin(1e3*a+sin(3e2*a))*pow(f(mod(-a*8.,8.)/3.),6.-3.*b)
#define d(a)+exp(-3.*a)*vec2(s(8.*t+a*.3,a),s(8.*t+a*.5,a))
vec2 mainSound(float t){return .3*vec2(3.*sin(3e2*t)*pow(f(-t*2.),4.)+.5*sin(4e5*t)*f(-t*2.+.5)d(0.)d(.5)d(1.)d(2.));}

圧縮がかかっていて読みづらいため、字数制限を考えず同じ音がなるように展開したものが次のコードです:

float fm(float time){
  return sin(1000.*time+sin(300.*time));
}
float rhy(float time,float f){
  return pow(fract(mod(-time*8.,8.)/3.),6.-3.*f);
}
vec2 dfm(float time,float dt){
    return exp(-3.0*dt)*
        fm(8.*time)*
        vec2(rhy(time-.3*dt,dt),rhy(time-.5*dt,dt));
}
vec2 mainSound(float time){
  vec2 s;
  s += vec2(3.0*sin(3e2*time)*pow(fract(-time*2.),4.));
  s += vec2(0.5*sin(4e5*time)*fract(-time*2.+.5));
  s += dfm(time,0.0);
  s += dfm(time,0.5);
  s += dfm(time,1.0);
  s += dfm(time,2.0);
  return 0.3*s;
}

展開後のコードをベースに要点となる要素を見ていきます。全体はバスドラムハイハット・パーカッションの3つのパートから出来ています。

バスドラム

バスドラム4つ打ちは文字数もあまり使えないのでsin波のみで作っています。アタック感を出すためにエンベロープに用いているfractを4乗してカーブを急にしています。

return vec2(3.0*sin(3e2*time)*pow(fract(-time*2.),4.))

ハイハット

裏打ちのハイハットは上で述べたような乱数にエンベロープをかけた形で作ろうと考えましたが、乱数を生成する文字数が足りませんでした。そこでsin波にものすごく大きい値(サンプリング周波数よりも十分大きい値)を入れれば取り出される値が乱数的になるのではないかというゴリ押しで作っています。結果的には不安定な仕組みで作っているがゆえに、timeが大きくなるにつれ音が変わっていきなかなか面白い効果が得られました。

return vec2(0.5*sin(4e5*time)*fract(-time*2.+.5));

ちなみにツイートした動画の終盤ではこのハイハット部分を次のように改造して効果音的に用いています。なんでこんな音がなるのかは正確には良く分かりません

return vec2(0.5*sin(2e6*time));

パーカッション

パーカッションパートは、FM音源、不均等なリズム、クロスディレイという三つの要素からなっています。

FM音源

関数fmでは、周波数変調(Frequency Modulation) を用いて音を作っています。FM音源の波形は次の式で与えられます:

 \sin(2\pi ft+A\sin(2\pi f_M t))

この式の形から音を想像することは容易ではないと思いますが、明らかなこととしては A=0においてこの式は周波数 fのsin波と一致します。そしてAが大きくなるにつれて位相が”かく乱”されて音が変化しそうという見通しを立てることはできます。FM音源の音の性質についてここで詳しくは書きませんが*8、いくつか知られている性質を紹介します。FM音源の音色はもとのSin波の周波数 fと変調周波数f_Mの比r = f_M/fによって大きく性質を変えます。良く知られている比率は1,2,3,3.5等ですが、色々といじって好きな音にたどり着くまで探索してみると良いと思います。

vec2 mainSound(float time){
  float r = 3.0;// FM/F
  float freq = 440.0;//F
  return vec2(sin(6.4831*freq*time+sin(6.4831*freq*r*time))*fract(-time*1.));
}

実際のコードでは、パーカッション的なあまり音程の定まらない音を作る目的があったので、綺麗な比率ではなく3/10という半端な比率を用いることにしました。(あくまでも探索的に見つけた値です。)

不均等なリズム

fract等の周期関数を使って一定のリズムを刻む音を作ることは出来ますが、ユークリッドリズム*9等の不均等なリズムを作ることはなかなか難しいです。 今回のコード上の関数rhyでは次のリズムを作っています:

[10100100]

ここで1は音の鳴る拍、0は休符を表します。言葉で書けば「カッカッッカッッ」というリズムです。このリズムの肝は休符が一か所だけ詰まっている点で、今回はこれを作るためにmodとfractの組み合わせを用いました。つまり周期3のfractエンベロープfract(-t/3.)の中でtを周期8で折りかえすfract(mod(-t*8.,8.)/3.)ことでこのリズムを作っています。

f:id:Raku_Phys:20200418192339p:plain
fractとmodの組み合わせによる不均等なリズム
sin波に対してこのエンベロープを設定したサンプルコードを載せておきます。音を聴いたり値を色々と変えてみて下さい。

return vec2(sin(6.4831*440.*time)*fract(mod(-time*8.,8.)/3.));

クロスディレイ

FM音源と不均等なリズムの掛け合わせで作ったパーカッションに立体的な”響き”を持たせるために、クロスディレイというエフェクト処理を行いました。クロスディレイとは原音がLRにふれながら減衰しつつ繰り返されるエフェクトのことです。つまり、ある時刻の波形を f(t)として、

 \sum_{i=0}^{N}\exp(-a_i t){\rm vec2}( f(t-dt_{L_i}),f(t-dt_{R_i}))

などと書き表すことが出来ます。これをSin波に対して適用したミニマムなコードは次のようになります。

float rhy(float time,float fade){
  return pow(fract(-time),6.0-fade*3.0);
}
vec2 delay(float time,float dt){
    return exp(-2.0*dt)*sin(6.4831*440.0*time)*vec2(rhy(time-dt*.3,dt),rhy(time-dt*.5,dt));
}
vec2 mainSound(float time){
  vec2 s;
  s += delay(time,0.0);
  s += delay(time,0.5);
  s += delay(time,1.0);
  s += delay(time,1.9);
  return 0.5*s;
}

delayの関数の中身を見るとdtと共に減衰する指数関数と、sin波の掛け合わせの後に、vec2のrhy関数(ここではfract)が掛け合わされています。さらにrhy関数の中身は左右で異なるdt幅(L:0.3dt, R:0.5dt) をずらした時刻が入力されています。 rhy関数は時刻と"fade"の二つの入力を持ち、fade=dtとして遅い残響ほど音の立ち下りをゆるやかにする効果を作っています。

f:id:Raku_Phys:20200419001033p:plain
クロスディレイを適用したsin波の波形

まとめ

今回はGLSLサウンドの初歩的な取り扱い及び実際の作品の解説を行いました。GLSLサウンドは基本的に波形を数式に落とし込む作業なので、少しはじめのハードルを高く感じやすいように思いますが、Sin波が扱えるだけでもかなり色々な表現が出来ることが分かったと思います。本記事がたくさんの作品を作る出発点になりましたら幸いです。

VR音楽イベント "アルテマ音楽祭" を制作して気づいたこと

はじめまして。らくとあいすといいます。VRChat上でVR楽器演奏やワールド制作を行っています。今回は2019年1月19日に開催された音楽イベント"アルテマ音楽祭"でのパフォーマンス・演出作りに関わる過程で気づいたことや、演出の意図、僕の視点からみた舞台裏についてまとめてみたいと思います。

アルテマ音楽祭とは

アルテマ音楽祭とはカズユキ(https://twitter.com/kazuyuki_haruno?lang=ja)さんが主催した音楽イベントで、VRChat内に自分達で会場を作りオリジナルの楽曲を様々な方法で演奏しました。このブログは演出のネタバレを含みますので、まだご覧になっていない方はぜひ先に配信のアーカイブを見ていただければと思います。

 

おきゅたんbotさんのチャンネルでの配信

www.youtube.com

 

sunaさんのチャンネルでの3D動画配信

www.youtube.com

 

自分が主に関わった演目の概略

アルテマ音楽祭は大きくわけて2部構成になっており、前半は歌を中心とした"ライブステージ"、後半はVR楽器や立体音響を中心とした"水面ステージ"となっていました。私は、この後半部分の楽曲・ワールドギミック・演出制作などを主に行いました。"水面ステージ"はさらに次の5つの部分に分かれています。

  1. 水上ステージでのVR楽器演奏
  2. イルカに乗ってワールド移動
  3. VR楽器・立体音響等を利用した複数人演奏
  4. "音の生物"を捕まえるギミック
  5. 飛行パフォーマンスとパーティクルによる演出

以下ではこの中の1~3について、制作過程や、印象的だったやりとり、気づいたことなどを書き、最後にイベント全体を通した感想も書いていこうと思います。

 

 1. 水上ステージでのVR楽器演奏

ワールドに移ってきて一番はじめ、鍵盤楽器やドラムなどを組み合わせたVR楽器パフォーマンスを行いました。

f:id:Raku_Phys:20190120175505j:plain

水上におかれたVR楽器を演奏する

私一人でバックのオケに合わせてVR楽器を演奏するという試みは、2018年10月20日に行われた第三回VRアートイベント(https://www.youtube.com/watch?v=Ay32kFXWsJc&t=3790s)で初めて挑戦しました。今回使った楽器の一つ、スティックで鍵盤を叩くものは第三回Vアートで使ったものの改良版です。この楽器は曲のキーに合わせたマイナーペンタトニックスケールで並べた鍵盤を4度ずらしで3段配置しています。これによって、鍵盤による反発がないことを利用した自由なグリッサンド表現が可能になります。何故前回のイベントと同じ楽器を使ったのかというと、VR楽器の上達というものを示してみたかったからです。VR楽器は比較的新しい概念だと思うので、それ単体で注目されがちなところはあると思いますが、最終的にはそれで何が表現出来るかが重要だと思っています。上達の余地がないということは表現を広げる余地が少ないということですから、楽器としてはあんまり面白くないと思っています。前回のイベントでの演奏はいわば、突貫工事で楽器を作ってその楽器の"初心者"がとりあえずステージに立ってみましたというところです。それから二度のイベントの再演と練習を経て楽器の音をより良く聴かせるための工夫や奏法がだんだんと固まってきました。具体的には下記のような注意をおいて演奏しました。

  • スティックの動きがはやくなると同期が追い付かないので、なるべく鍵盤付近で動きをとめて演奏すると共に、左右の手での分担を意識する。
  • 現実のピアノやその他の鍵盤楽器で用いられるようなフレーズにこだわらず、積極的にグリッサンドを多用する。

前回のイベントではこれらの知見を得ていなかったので、曲のテーマをVR楽器で演奏することが出来ませんでした。現実の鍵盤を使って作った曲なので、必ずしもVR楽器での演奏に適していませんでした。そこで、今回は大方曲がまとまった時点でそれをワールドに持ち込み、実際にVR楽器を演奏しながら曲のテーマをまとめていきました。これによって、この楽器で美しく演奏出来る曲のテーマを作ることが出来たと考えています。

今回新しく導入した楽器は鍵盤楽器と向かい合うように設置されたドラムです。この楽器は鍵盤を作ったときの知見を活かして、垂直方向に面を叩くのではなく楽器を横切るようにスティックを流す奏法を意識して楽器を配置しました。(結果何かの兵器のような見た目に…。) この手法によって同期が安定する理由は、面を通過することで音を出した場合は、始点と終点が同期されていればその間を通過することは明らかなためということだと思います。 (ある面をたたいてスティックを上げた場合には、打面にスティックの先端が触れた瞬間の位置が同期されていなければ音はなりません。) とはいえ、それでも通常のドラムのように一定のテンポを刻み続けることはかなり困難です。そこを目指すのではなく、今回はドラムソロとして活用するために自由度の高い演奏が出来るように心掛けた配置にしました。(余談ですが、アルテマ音楽祭の一番はじめでぴぼさんもドラム演奏をしていて、やられた!という感じでした笑)

f:id:Raku_Phys:20190120182428p:plain

VRドラム

視覚演出面で今回一番心掛けたのは、矛盾するようですが視覚情報を減らすことです。2番目以降でのパフォーマンスでは、非常に華やかなパーティクル等々が空間を覆いますし、ここでは最大限情報を削りました。特に鍵盤楽器を用いたソロの最中はステージを照らすスポットライトを除いたワールドの全てのライトを消した上で、空を真っ暗にしました。ソロのバッキングもピアノ一本だけに絞り、とにかくその場にある鍵盤楽器の音への集中を高めることを意識した演出にしました。

その上で、Mikipomさん(https://twitter.com/cakemas0227?lang=ja)によるリアルタイムパーティクル演出をしてもらっています。これは楽曲の進行に合わせて、事前に仕込んであるパーティクルをMikipomさんが出していくというものです。今回は曲を途中で止めたりする事情もあり、楽曲の長さが正確には確定しておらず演出も直前まで変更続きだったのでリアルタイムで演出してもらえるありがたみがすごく大きかったです。また、これによって視覚情報の濃度に差が付き、よりソロ部分の静けさが際立ったと考えています。

2. イルカに乗ってのワールド移動

水上ステージでの楽器演奏の終盤で水中から巨大な三体のイルカを出現させました。二番目の演目はこれに乗って、水中へと移動するというものです。ここでは、この演目が生まれたいきさつを中心に書きたいと思います。

f:id:Raku_Phys:20190120185834j:plain

イルカの背中に乗って空を飛ぶ一同

まず、このシロイルカのモデルですが私がまだVRChatに来る前、2018年の3月にVTuberのファンアートとしてモデリングしたものです。というか実際にはペイント3DとBlenderを組み合わせてどんな感じのモデリングが出来るかちょっと試してみたくて作ったものです。

 

 このモデルは、VRChatをはじめてしばらくはアバターとして使用していました。その後2018年の9月頃光る水面のワールドを制作していた際に、このイルカに乗って空を飛んでみたいと思い実装してみました。

 これを見た主催のカズユキさんが、イルカに乗って水面に潜っていくという演出案を考え提案してくれました。また、ゆうのLv3さん(ゆうのLv3 / Yuji Hatada(@yunoLv3)さん | Twitter)がアルテマ音楽祭に提供してくれていた、「世界の語り方」という曲がこのシーンにぴったりだと思い、この曲に合わせた飛行演出を考えることにしました。こういう色んなきっかけが繋がって形になっていくのはやっぱり楽しいですね。

水面の中に潜っていくシーンは一番ドラマチックな部分なので、ここを曲の盛り上がりに持って来たいとまず考えました。したがって、それまでの間空を飛んでいる必要性が生まれました。しかし、何もない空をただ飛ぶのでは少し味気ないと思ったので、元怒さんの提案もあり輪をくぐっていく演出を加えました。この輪にはやぎりさんの販売しているパーティクルヒューリング(https://booth.pm/ja/items/1114058)を使わせて頂きました。

また、演出案が出来た段階では酔いの問題を非常に懸念してみましたが、テストの段階でも本番でもあまり強い酔いを感じたという意見は聞きませんでした。非常に大きいイルカが常に視界に入っていること、リングによって行先が示されていることなどが良いの軽減につながったのかもしれません。このあたりはあまり詳しくないので、今後調べていきたいですね。

3. VR楽器・立体音響等を利用した複数人演奏

イルカに乗って水中に移動したあとは、複数人でのVR楽器演奏を含む演目を行いました。曲は番匠カンナさん(番匠カンナ@バーチャル建築家 (@Banjo_Kanna) | Twitter)のオリジナル曲「なつおわ2011」です。

www.nicovideo.jp

アルテマ音楽祭に参加決定した際にカンナさんがこの曲を全体のdiscordに貼ってくれました。それを聴いた時にVRで立体音響使って複数人演奏したら絶対面白いと思ったので提案してみました。そして、カンナさんから音源のパラデータが送られてきて、それを元に第一案が固まっていきました。

f:id:Raku_Phys:20190120193735j:plain

なつおわ2011VR誕生の瞬間

f:id:Raku_Phys:20190120194025j:plain

なつおわ2011の演出案

実際には第一案から少し改良し、お客さんを取り囲む3人と周囲をただようシャボン玉に乗って演奏する2人、そして分身した3人のカンナさんで演奏するという案が固まりました。この時点で、分身したカンナさんのアニメーション制作をMikipomさんに、カンナさんが演奏する”なつがおわる”という声の楽器と、ピアノとアコーディオンの音を発しながら周囲を回る2つのシャボン玉のモデリングをカンナさんに依頼しました。お二人ともレスポンスがめちゃくちゃはやくさくさくと作業は進行していきました。ありがたい…。

その間に私は全体の演出をどうまとめるかを考えていました。曲の構成の全体像が掴みきれていなかったので、まずは譜面に起こしてみました(譜面1.0)

f:id:Raku_Phys:20190122020556j:plain

譜面1.0

全体を把握した上で、個性的で特に抽出したい音とバックグラウンドで流れているべき音に分けました。すなわち、人が楽器で演奏する音としない音を切り分けていきました。人が演奏する楽器として最終的に作られたのは次の画像のようなものです。このうち⑦の泡のはじける音の鍵盤と、⑨のパーカッション群は原曲にはない音ですが、演奏する上での自由度を上げるために新たに導入しました。これらの楽器を演奏手法によって大別すると、①,②,③,⑤,⑦はスティックによって叩くことで音を出す楽器、④,⑧は(コントローラーの)トリガーを引くことで音を出す楽器、⑥は投げることで音を出す楽器です。⑤の遠隔操作する"夏の終わり"の楽器は観客を取り囲むように3つ配置されており、①の楽器の付近に置かれたスイッチを叩くことでいつでもアニメーションを起動してカンナさんの分身を動かすことが出来ます。

f:id:Raku_Phys:20190126010454p:plain

なつおわ2011で使われた楽器群。①"夏が終わる"の言葉が入った楽器(モデル作成はカンナさん)②シンセ音が入った楽器③ベースの音が入った楽器④ハンドベル⑤遠隔で操作出来るアニメーションによって動く"夏が終わる"の楽器(アニメーション作成はMikipomさん)⑥投げて地面か人に当たると弾けて音が出る楽器⑦泡のはじけるような音の楽器⑧ピアノとアコーディオンの音を出しながら空中を旋回する2つのシャボン玉間を飛ぶ光の楽器(光にはナルさんのTrailParticleを使用)⑨パーカッション群

また、人が演奏しない楽器についても完全にバックグランドでなっているだけのものと、視覚と何か結び付けて音を鳴らすものの2パターンがあります。完全にバックグラウンドで鳴っている音はなつおわ2011の土台となっている2011年夏の環境音といくつかの効果音、さらに原曲にはない音ですがドラムやストリングス、シンセパッドの音を加えました。追加で加えた音については、水の中のワールドという世界観に合わせて、原曲のなつおわ2011という世界に一枚膜をかけてみたような神秘性を加えたいというコンセプトで制作しました。また、音源だけで聴く場合に比べて曲の展開が単調に感じやすいと思ったので、曲の起伏をより強調するという狙いもありました。視覚と聴覚の情報量のバランスを取ることが大切なのかもしれません。

視覚と結び付けて音を鳴らしているのは、観客の周囲を旋回するシャボン玉から鳴るピアノとアコーディオンの音と、曲の中盤に頭上から降りてくる泡のようなものが破裂して、パーティクルと共に鳴る音です。このパーティクルについては特にこだわりたかったので、Mikipomさんに別途パーティクルの制作を依頼しました。

f:id:Raku_Phys:20190122022229j:plain

自然が欲しい…というヤバそうなオーダーにこたえてくれるMikipomさんには本当に感謝しかない…。

そして出来上がったパーティクルがこちら。まさにイメージ通りでした…やっぱりMikipomさんはすごい…。

f:id:Raku_Phys:20190126011553p:plain

Mikipomさんによる"NatureParticle"

続いてこれらの楽器を複数人で展開にそって演奏する手法について考えました。拍感の強い曲ではないので、ほとんどの場面でシビアな同期は必要ありません。しかし少なくとも10秒スケール程度での展開の共有は必要ですし、曲の終盤には拍に合わせて複数人で演奏する必要のある場面もありました。

まず、曲の中で5人が具体的に何をするのかということを示した譜面(譜面2.0)を作成しました。多分これだけ見ても暗号か落書きにしか見えないと思いますが…。

f:id:Raku_Phys:20190126014727j:plain

譜面2.0

譜面の中で、曲の展開において重要な音の前には▼の印がついています。この部分で、ワールド上では本人のみに見えるように指示が表示されます。そしてその音をきっかけに他の人は自分のパートを演奏するという形になっています。はじめこの仕組みを作ったときは全員のすべての音に対して指示を出していましたが、あまりにも面白くないので辞めてこの方式にしました。この方式によって、曲の展開に沿いながらも各々の演奏には一定の自由度があるようにし、他の人の音を聴いて自分の音を演奏するという複数人演奏において自然な形式を採用することが出来ました。(二回以上同じことを演奏すると飽きる性格です。)曲の終盤では、地上で演奏している三人で音を合わせる必要のある場面が8小節間だけありました。この部分は曲を通して唯一拍感の強く出ている場面なので、ここだけはきっちり合わせたいと考えました。この同期にはすごくアナログな力技を使っていて、カンナさんにはハンドベルを振る際に同時に指揮を振ってもらい、らくとあいす・ぴぼの二名はそれに合わせて演奏するという方式を取りました。指揮を見る二人はカンナさんに対してほとんど対称な関係にあるため、これによってほとんど同期されます。しかし、これだけだとカンナさんだけはやや音が先行する形になるので、指揮を見る二人は実際の拍よりも1拍はやく演奏することで、お客さん視点ではほとんど同期するようにするという方式をとりました。とはいえ、とても難しく成功率は低かったです。本番でも完全にはうまくいってなかったように思います…悔しい…。こちらhttps://youtu.be/ORzh1JyMRY0?t=9760で該当部分から再生出来るので良かったらこれを踏まえて聴いてみて下さい。

 

ここまでは各パフォーマンスについて、制作過程や技術的ポイントを振り返ってきましたが、最後にイベントを通して思ったことについて書きたいと思います。

イベントを通しての感想

イベントを通して振り返ったときにまず触れたいと思ったのは、このイベントが全曲メンバーのオリジナル曲で構成されていたことです。アルテマ音楽祭には、VR空間でのライブ・VR楽器・パーティクル・ワールドギミック等々色々重要な要素があります。しかし核となっているのはやはり音楽で、その一曲一曲をメンバーの意思のこもったオリジナル曲で出来たというのはとても嬉しいことでした。

また、観客の皆さんの存在というのもあらゆる面で大きかったです。まず、VRChat内で見て下さった方々が空間に加わることで、ようやく作品が完成したと思わされるような点が多くありました。イベントの一曲目、memexのライブ中に飛び交った数々の声援や、なつおわ2011の会場で思い思いに体を動かして音を感じる皆さんの姿からは、準備段階で予想していた以上のものを感じさせられました。YouTubeLive等で見て下さっていた方からの感想でも気づかされる部分が多々ありました。特にこの音楽祭に"未来"や"可能性"を見たというコメントにはハっとさせられました。僕は少なくとも一年前までは"未来"を見せられてすごいなーと思うだけの人でした。その後、VRChatやVTuberの方々の世界に感じたその未来に少し手を伸ばしてみたいと思った結果が、今の活動に繋がっています。だからといって、僕は今回のイベントに関連したほとんどの分野でド素人で、今の自分が未来を作れるなんてことはあまり思ってはいませんでした。だからこそ、それらのコメントには驚かされました。そして何も知らない素人でも、とにかく作って形にして伝えた結果、誰かの”未来”を一段詰むことが出来るのかもしれないということに気付かされました。というのはさすがに思い上がりだとは思いますが、今はその感想を素直に受け止めておこうと思います。

 

想定以上に長いブログとなってしまいましたが、このあたりで締めたいと思います。ブログ初心者の駄文に最後までお付き合い頂きありがとうございました。この文章がほんの少しでも誰かの創作の一端につながるのであればとても嬉しく思います。