くろっくのおぼえがき。

個人開発関係の話を書きます。

Unityで世界初のバーチャルモノサシストになる

f:id:clock_v:20201224104333p:plain

はじめに

こんにちは。くろっく(@clock_v)です。
先日Twitterの方で色々と情報を出しました、"MONOSIMULATOR"(通称:バーチャルモノサシ)に関して、開発時のこと、裏話などいろいろと書いておきます。
よく考えたらアプリの操作説明さえもどこにも載せてないのでちゃんと書かなきゃね(多分別記事にするけど・・・)。
ということで以下本題です。

※今回の記事ではソースコードの一部のみ、或いはそれらを少し改変したものを載せました。というのも一部変数に人の名前が入っていたりして、全文をそのまま掲載するのはあまり気が進まないためです。各機能に必要な部分だけ掲載しています。

モノサシストってなんだよ

一般人からするとモノサシストってなんだよっていう疑問が最初に来ますよね。こういう人たちです。


ちなみに2つ目の動画には私も出ています(小声)

こんな風に、定規(主にステンレス製)の台からはみ出した長さを変えながら弾いて音階を表現するのが定規演奏、そして定規演奏のパフォーマーのことをモノサシストと呼ぶわけです。

これスマホで出来たら面白くね?という話が一時期盛り上がりまして、おれおさん(@mono_oreo)メロスさん(@merosu_215)の協力により今回の開発に至りました。さらにはVtuber桃空しいなさん(@v_momosorasiina)に実際に使っていただいたりしてます。こちらでもプロトタイプ公開中です。

公開当時のTwitterでのお知らせがこちらです。スレッドも続いていますので良かったら覗いてください。

2020/12/25追記
各サイトにデモ動画をアップロードしました!
YouTube
https://www.youtube.com/watch?v=dA2EdaNoI4Q
ニコニコ動画
【定規】世界初のバーチャルモノサシストになってみた【作って演奏してみた】【くろっく】 - ニコニコ動画


開発前日譚

ちょうど夏休みの頭にUnityの教科書(いわゆる猫本)を2日で速習し、何か作りたいなあと思っていました。
実はバーチャルモノサシは数年前、先述の話が盛り上がったときに作ろうとして挫折した経験があり、そのほったらかしにしてたファイルを偶然見つけてしまったのです。
当時はちょっとC#の基本文法が読める程度の知識しかなく、プログラミングの腕はカスだったのでよく調べもせずに投げ出していました。

そもそも定規みたいに音程変更なんてできるわけないやろ・・・ゲームエンジンやぞ・・・と思いつつUnityの音周りを漁った結果、
pitchパラメータ
なんですかこれ。
いやどう見てもpitchって書いてあるじゃん。
これは作るしかねえな!!!!!!!!!!!!

開発開始

やったこと、実装した機能としてはこんな感じ。

  • 定規を作る
  • タップした時に音が鳴る
  • 定規を移動できる
  • タップ判定を定規に追尾させる
  • 定規の位置が音程に反映される
  • 2点タップ(定規を移動させる手と弾く手を区別して認識)
  • 音色を複数扱う
  • そのほか

定規を作る

そもそも定規無いじゃん!!!!!!!!!!ということで作ります。
私含め多くの場合、モノサシストは演奏台としてガラス製の鍋敷きを敷いています。ちなみに大体この定規と鍋敷きです。

初期投資1000円かからずに始められるんですよ。
こんなお手軽な趣味他にありませんよね?(巧妙なステマ)

というわけで両方blenderでつくりました。
制作した定規と鍋敷きのモデル
モデルと実物の比較
上がモデル、下が実物になります。
結構再現度はこだわりました。満足の出来です。
実際にはこの後これにMaterialで金属光沢を付けていきます。
これで必要なモデルはそろったので、コードを書き始めることにします。

タップしたときに音が鳴る

定規演奏の基本ですね。定規を弾いたら音が鳴る。
手順としては、

  1. タップを検出
  2. AudioSource.Play()の呼び出し
  3. AudioListenerで取得

の3ステップになります。プ〇アクティブみたいですね。
ではタップの検出から。スマホなどは画面が小さく、定規も必然的に面積が小さくなってしまう・・・そう思いUIのPanelを画面半分に配置しそれを利用してタップ判定をすることにしました。EventTriggerコンポーネントをPointerDownで使ってみたところだいぶいい感じに判定してくれてよかったです。
Panelを設置
ここから音を鳴らしていくわけですがUnityって便利ですね。.wavファイルを適用して.Play()するだけで音が鳴りました。やったー。
あとはAudioListenerを鍋敷きの縁、定規の真下の部分に置いてこの部分は完成です。

    public void soundPlay()
    {
        Debug.Log(mySlider.value);
        monoSound.Play();
    }

定規を移動できる

これについては一番簡単なやり方を取りました。UIのSliderの値を定規の座標(transform.position)に連動させるだけ。Slider自体がちょっと動かしにくかったのでつまみ部分の大きさをちょっと細長くしました。
Sliderを設置

    public Slider mySlider;
    float pos, initialPos;

    Vector3 myPos;

    // Start is called before the first frame update
    void Start()
    {
        myPos = this.transform.position;
        initialPos = this.transform.position.z;
    }


    // Update is called once per frame
    void Update()
    {
        pos = initialPos + mySlider.value;
        myPos.z = pos;
        this.transform.position = myPos;
    }

タップ判定を定規に追尾させる

これはどういうことかというと、演奏動画でもわかるように実際の定規演奏では定規の"先端部分"を弾くため、半面パネルどこでもOK!というのでは定規演奏感がちょっと薄れてしまうんですよね。というわけでモードを分けてどちらも選べるようにしてみた次第です。

方法としては定規の先端部分に透明にしたCubeを配置し、それに対して上のPanelのようにタップ判定を与える、という形です。これもEventTriggerで実現できました。便利。

で、そのCubeの移動についてなんですが、これはLocalPositionで常に定規に対して一定の位置にするような設定を使うことで実現できます。前に書いたtransform.positionっていうのは空間内での座標になるんですが、こっちは定規を原点として空間を考えたときの座標みたいな感じですね。

これでやってみたところ、無事追尾とタップ判定が実現できました。
Cubeを設置

定規の位置が音程に反映される

はじめに、で触れたpitchくん、ようやく登場です。優秀なUnityくんはpitchの値をいじれば読み込んだ.wavファイルをbpmを変えて流してくれます。つまり基本になる音を決めて、そのpitchの値(=再生速度)を上手く操作すればいい感じに音が変わってくれるわけです。

じゃあどうやってそのpitchの変え方を上手く定義するか。録音して分析するのがよさそうですね。
自分でも少しやってみましたが、近似できそうな雰囲気はありました。ただ今録音環境がなくなってしまったんですよね・・・

そんなわけで知り合いモノサシストの中でも音程が正確で録音音声もきれいな2人、おれおさん(@mono_oreo)メロスさん(@merosu_215)に連絡。「1cm,2cm,...,10cmまで1cm刻みで音録音してその.wavファイルください!」とお願いしたところOKを頂きました。本当にありがとうございました...!!!

頼む、こっちでもいい感じに近似できてくれ・・・と祈りながら周波数分析をかけます。
Excelをポチポチしながらグラフを描いてみたところ・・・

グラフ1
グラフ2

なんですかこの奇麗さは??????

あんまり奇麗にならなかったらどうしようとか思ってた自分の不安を返してほしいくらい奇麗な並びしてます。一発で決定係数0.9993って何だよ。たけーよ。無駄に洗練された無駄に正確な無駄のない無駄な動きみたいになってます。

超奇麗に並んでくれたのでここではとりあえず片方だけ、Excelくんが教えてくれた数式をTap.csのUpdate()内でピッチの値に適用し、ここの作業は終了です。

    void Start()
    {
        monoSound = soundPlayer.GetComponent<AudioSource>();
        cor = (float)(coeff * Mathf.Pow(5, ord)) - 1.0f; // 補正項
    }

    // Update is called once per frame
    void Update()
    {
        monoSound.pitch = (float)(coeff * Mathf.Pow(mySlider.value, ord)) - cor;
    }

ちなみに上での補正項というのは、どうしても生じるfloat範囲での累乗計算のずれを軽減するものです。xのa乗の形ゆえに変化が急になる部分ではこのずれが地味に操作感に影響するので・・・

 一応音が変になってないかテストしないと・・・と思い実行。結果は・・・

 

 

 

 

全 く 違 和 感 が あ り ま せ ん 。 最 高 。

 

 

 

Update()はフレームごとに中の動作を実行してくれるので、再生中に定規が動いても音程が変わってくれます。

ここまででほぼ定規になりました(?)。

2点タップ

ここは先人の知恵です。
qiita.com

音色を複数扱う

上でも述べたように2人分の音源を頂いたので、両方実装したいなということで。
勿論音程変更の近似式もそれぞれで設定します。

タップしたら音が鳴る、のところでAudioSourceっていうのがありましたが、アレに読み込ませるファイルをスクリプトとUIで指定するだけでした。ここはそんなに大きな問題ではなかったですね。
ToggleのON/OFFで音が変わるように分岐させてここは終了です。

    void Update()
    {
        if (!soundToggle.isOn)
        {
            monoSound.clip = soundA;
            // 音程変更、補正項などの処理
        }
        else
        {
            monoSound.clip = soundB;
            // 音程変更、補正項などの処理
        }
    }

 

そのほか

そのほかにやったことはこんな感じです。

  • スライダーの位置を変更できるToggleの設置
  • 画面サイズが変わったときの対応
  • タップしたときにエフェクトが出る

だいたいこの辺を実装してほぼ今公開してる状態になりました。

最後に残った問題

上で見てきた通りこれタップ操作前提なんですよ。
つまるところ、スマホアプリにしたかったんですよね。
でも実は配布ってお金かかるんですよ。
某有名な林檎さんでは年1万ちょいとか。きついなあ・・・
泥もラグが激しいから演奏向きじゃないし・・・

 

...........せや!!!

 

というわけでWebアプリとしてUnityroomに公開することとしました。
unityroom.com
MonoSimu!prototype | フリーゲーム投稿サイト unityroom

iPadなどでのアクセスがやりやすいと思います。
よかったら遊んでね。
(UnityのSwitch Platformめちゃくちゃ便利だった・・・)
感想などを #バーチャルモノサシ でツイートしてくれると嬉しいです。
 

今後について

まだまだ実装したい機能もあるので当然ながら開発は続けていきます。
あと実はAndroidなら配布できなくもない状況ではあるんですよね。そのうち配布するかもしれません。

最初の方のツイートにもある通り実はVtuberさんにも使っていただけるみたいでして・・・

開発者より上手です。
みんなもしいなちゃんを推そうな。

そんなわけでざっくりと、こんな感じで制作してたよーっていうのを書いてみました。
記事書くのも初めてなので分かりにくい部分はあるかもしれませんが大目に見てください。
また何か作ったりしたら記事書くかも。でわでわ。