Unity上で動作するライブコーディング環境を作成してみた。

概要

Unityで動作するライブコーディング環境を作ってみました。
現在α版的な位置づけですが、割と形になってきたので、忘れる前に技術的な仕様をここに記すものです。
開発動機等は別の機会にして、この文章では「どうやって実装しているか」を主に記述します。
機能の追記があれば別途書いていくのもいいかな、と考えてます。

動画

まずは動いてる所を見てください。

特徴

  • 現状シンセサイザー4音・サンプリング4音を同時発音が可能(もっと多くできる)
  • Miniscriptという言語を利用して楽曲を作成可能
  • リアルタイムにコードを変更して楽曲を作成可能

内部の仕組み(概要)

大きな要素が以下の4点。

  • テキスト処理(=エディタ)
  • 言語解釈・管理エンジン
  • オーディオエンジン
  • サウンドサーバ

あとは細かいモジュールを多数そろえてます。

テキスト処理

以下のアセットを利用。
https://assetstore.unity.com/packages/tools/gui/ingame-code-editor-144254
というよりもUnity上でテキストエディタ機能を利用できそうなのがこれしかありません。
このアセットに改修を行い各種機能を実装してます。
内部ではInputFieldを利用して、最終的にハイライトを別のTextに転送の上記述しているという形式です。
このため、EventSystem上ではInputFieldにフォーカスを合わせるようにし、以後他のボタンを押す場合、フォーカスをInputFieldに返す形にしています。
これでコンパイルを行ったりする場合もマウスでフォーカスをあわせたりする事無くプログラムを打ち込める環境を実現しています。

テキストについてはTextMeshProを利用しているため割と高速に描画されます。
フォントは以下を利用し、Unity上で利用可能なフォントとして生成。
https://github.com/yuru7/HackGen#%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB

言語解釈・管理エンジン

言語はMiniscriptを利用。
https://miniscript.org/

Unityへの組み込みが想定されており、stringを言語エンジン側に渡すだけで各種処理や連携が可能です。
以下要素を取得できるようにしました。

  • 譜面
  • ボリューム
  • 波形プリセット
  • その他各種パラメータ

エディタからソースコードを取得し適時コンパイル可能。
その際、ソースコードから譜面データ等各種パラメータを取得できるようにしました。

パラメータは必ずリストで持つ形とし、
再生タイミングに応じて、リストの要素数で分割した値を渡す処理を実装。
(ジャグ配列の場合、子要素として処理)

譜面の処理については、以下の対応が可能となります。

  • 要素数が2個なら、ループを2分割し値を変更可能
  • 要素数が3個かつ3個目の要素数が4個の場合、3個目だけ更に4分割可能

ここらへんは動画を見るのがわかりやすいです。

元々TidalCyclesという言語の仕様に影響を受けており、Miniscriptで同様の処理ができないか?と考えて実装してみました。

また、言語解釈・管理エンジンにてキーボード制御も管理しており、このモジュールを各オーディオエンジンが適時確認し、再生制御を行っています。

オーディオエンジン

ここで指すオーディオエンジンとは、「ループの時間管理・利用しているシンセサイザー・サンプリングの再生状況等を司るモジュール」と定義します。
オーディオエンジンは、UnityのOnAudioFilterRead関数上に作成してます。
各種ミキサーを通す形でシンセ・サンプリングパートごとに再生タイミングを制御および波形の書き込みを行ってます。
(言ってしまうとDSP処理全般を行っていることになります)

データは以下の構成。

  • サウンドキュー・シーケンス登録モジュールという2要素を用意
  • サウンドキューの機能・・・キューに入った譜面を再生すること。
  • キューには「譜面番号・再生時間リミット・再生済みカウンタ」を格納
  • シーケンス登録モジュールの機能・・・シーケンサデータは言語解釈・管理エンジン部分にて保持
  • シーケンス登録モジュールではループの長さや再生位置を保持
  • サウンドキューとは別にループの長さ等を管理
  • サウンドキューにおいて、再生済みカウンタが再生時間リミットを超えた場合に起動
  • シーケンサデータから現在のループ位置における譜面番号を取得し、サウンドキューに情報を格納する
  • 上記2モジュールを循環させてサウンド処理を成立させる

シンセサイザーについては毎回波形を生成しています。
発音形式は波形メモリ音源という形式。
これは、矩形の集まりと考えて有限的な要素を複数持ち、オシレータとして運用する音源。
リストとの相性は良いため自分で波形を書き込むことが可能です。

その他エンベロープやフィルタも実装済み。
エンベロープはADSR、フィルタはLPF/HPF/BPF等。
いずれもサウンドキューと連動し動作します。

シンセサイズも最初波形を生成するのにCPU負荷がかかって各種処理が間に合わないのでは?と考えましたが、Editor上でのIL処理の時点でも悪くない数値が出てますし、IL2CPPを利用すると更に動作が早くなるため、それほど悪くない選択肢かな?と思いました。

サンプリングについては、以下の機能を実装済みです。

  • サンプリング再生
  • サンプリング波形の途中再生
  • サンプリングの逆再生(途中再生も可能)

これらはシーケンスの指定だけで利用可能。
更に各種パラメータを指定することで、多様な演奏が可能になっています。

今後ユーザ波形の読み込みや波形の収録を多く行おうと思いますので、
演奏として楽しいものになるかと思います。

サウンドサーバ

波形については、現状のバージョンでは以下の管理を行っています。

  • 1個のGameObjectで管理
  • 波形はfloat配列にすべて格納
  • アプリケーション開始時にAudioClipからfloat配列にデータを変換し保持

よって、作成中のアプリケーションでは、AudioClipではなく、オーディオエンジン上でサンプリング波形の合成を行い再生しています。

当然サンプリングレートの違いが生じるため、基本的に48000Hzに決め打ち。
ユーザ波形も読み込めるようにする予定ですが、これも48000Hzに固定予定としています。

サンプリングレートの切り替えに対応できるかは検討課題とします。

最後に

当該ライブコーディングツールは現在鋭意開発中です。
よかったら使ってみてください。(ダイマ)

(itch.io)
https://machiaworx.itch.io/recode-livecoding
(Booth)
https://machiaworx.booth.pm/items/3007869