こんにちは!新しい音楽体験研究所 “チームサイリウム” です。チームサイリウムは今回のMaker Faire Tokyo 2023出展に向けて、「サイリズム」という音ゲーのプロトタイプを制作し、出展しました。今回はこのサイリズムの内部の仕組みについてチームメンバー全員で解説する記事を書いてみました!
- サイリズムとは (河野)
- システム構成 (河野)
- センサー (寺島)
- 描画 (澁谷)
- 3Dモデル (塚本)
- 実現できなかったこと (全員)
- まとめ (全員)
サイリズムとは
サイリズムとは「音楽 × サイリウム × ジェネラティブアート」をコンセプトにした体験型音楽ゲームのプロトタイプ作品です。プレイヤーが楽曲に合わせてサイリウムをテンポよく振ると、振りに応じてジェネラティブアートと呼ばれるデジタルアートをベースにしたグラフィックがディスプレイ上に生成されていきます。振りのテンポ感がアートの生成過程に反映される仕組みとなっているため、プレイヤーはサイリウムを振りながら楽曲を楽しみ、同時に唯一無二のアートができていく様子を楽しむことができます。
写真は株式会社ジュニさんが来訪された際のものになります。
Xアカウント:@junni_jp
従来の音楽ゲームでは、譜面と呼ばれる正解パターンのものが用意され、その正解パターンに合わせて正確に(そしてスマートに)アクションをしていくことが醍醐味かと思いますが、サイリズムでは譜面に当たるような正解パターンを用意せず、サイリウムが振られるテンポからのみをゲーム結果(アート)に反映するという仕組みを採用しました。この仕組みによって熟練したスキルや慣れがなくても、自分なりのリズムでサイリウムを振りながら、誰でも楽しめるゲームを目指しました。実際、Maker Faire Tokyo 2023当日は小さなお子様から大人の方まで幅広い層の方にゲームをプレイしてもらうことができました。
システム構成
サイリズムのシステム構成は以下の通りです。
- サイリウムの内部にM5StickC Plusを内蔵し、加速度センサーとジャイロセンサーから移動量を取得
- M5StickC Plusのセンサーから取得した移動量から高速フーリエ変換(FFT)により、周波数成分(=振りのテンポ)を抽出する
- 直近の代表周波数とそれまでの周波数移動平均との差分から”盛り上がり度”を内部的に算出する
- ”盛り上がり度”に応じて、ディスプレイ上に描画するアートを生成する
以下のセクションで各パーツについて解説します。
センサーについて
サイリウムの内蔵されていたセンサーでは、以下の3つを行いました。
- ジャイロの値取得
- 盛り上がり度合いの計算
- サイリウムの発光色の変更
これらはすべてM5StickC Plusを用いて取得した値をもとに処理を行っています。
ジャイロの値取得
M5StickC Plusには、以下の値を取得することのできるセンサーが備わってます。
- 加速度(X, Y, Z)
- ジャイロ(X, Y, Z)
今回はジャイロの値のみを使用するので、ジャイロについて簡単に説明します。
M5StickC Plus とジャイロ3軸(X, Y, Z軸)
画像出典元: Plus ESP32-PICOミニIoT開発ボード iotキット フィンガーコンピューターカラーLCD
これらの各軸の角速度を取得できるのが、ジャイロセンサーになります。
M5StickC Plusから角速度を取得するのに、Arduinoを使用しています。Arduinoを使用して、ジャイロセンサーの値を取得するには、以下のコードを書く必要があります。
float gyroX = 0, gyroY = 0, gyroZ = 0; M5.IMU.getGyroData(&gyroX, &gyroY, &gyroZ); |
取得した値は、シリアル通信を用いて、Processingに送っています。Processingに値を送るときの処理は以下になります。
Serial.printf("%.2f,%.2f,%.2f, o/s \r\n", &gyroX, &gyroY, &gyroZ) |
※ ここではあくまでジャイロの値をシリアル通信で送る例を示してます。
M5StickC Plus は、常にループ処理を行っています。以下の作業を繰り返し行うようになってます。
- ジャイロセンサーの値を取得
- 盛り上がり度合い計算のための値を計算
- サイリウムが光るようにジャイロの値から色を計算
- シリアル通信でProcessingに必要な値を送信
シリアル通信で送った文字列は、Processing側で処理し、値として使えるようにしています。また、盛り上がり度合いの計算については次の章で説明します。
実際にジャイロセンサーを取得する際に役に立った記事は以下です。
また、シリアル通信については以下を参考にすると良さそうです。
ここまでがジャイロの値取得についてです。次は盛り上がり度合いの計算方法について説明していきます。
盛り上がり度合いの計算
盛り上がり度合いは、一定のテンポでサイリウムを振れている事を目安にして、計算することにしました。曲などのテンポとは関係なく、一定のリズムで振れれば高得点が出るように取り決めました。Maker Faire Tokyo 2023では、小さなお子様が多く来場します。したがって、曲のテンポが分からなくてもある程度高得点を出せるようにしたいという思惑がありました。
「一定のテンポでサイリウムを振れている」ことを検知するために、振っているサイリウムの周期性を取得する事になりました。
X軸を軸として振った時に周期性がみれるグラフ
上記のように、実際にサイリウムを振った時のジャイロセンサーの値の変化の仕方を見ると、周期性が表れていました。そこで、今回は高速フーリエ変換を使用して、周期性を取得する事になりました。Arduinoには、高速フーリエ変換を行うことができるライブラリがあります。そのライブラリ ”arduinoFFT” を使用して、指定の値をフーリエ変換しました。
周期性を見るために以下の手順を踏みました。
- ジャイロのXYZの値を取得する
- 各軸の値を足し合わせる
- 足し合わせた値を高速フーリエ変換する
- 変換した結果の代表周波数を振りのテンポとする
この時のテンポが一定であればあるほど、高得点になるという仕組みにしていました。
任意のフレームごとにテンポを出し、次のテンポと比較をして、差が小さければ小さいほどスコアは高くなるように補正をかけました。この時、スコアは 0 〜 100の値になるように調整しています。
Maker Faire Tokyo 2023本番も上記の実装で遊んでもらうことになりました。良かった点・悪かった点があったため、それぞれ列挙します。
〜良かった点〜
- サイリウムを振ると点数が上がる
- 振っているか否かで、高得点と低得点がはっきり分かれたため、アートに良し悪しが出た
- 振ったことは検知できるような仕組みになっていた
〜悪かった点〜
- 厳密に一定のテンポで振ることで、点数が高くなる仕組みでは無かった
- 自分が実装した方法が本当に意図していた点数になっているか分からなかった
- 実装上でFFTを使用した処理がコード的にあっているか担保できなかった
- 採点よりは描画の仕組みに助けられている感が強い
今後の課題として、「どのように振ったか?」や「曲のテンポに合っているか?」を認識できる機能を追加し、サイリウムを振ることでアートを描いているという感覚をより強く感じられるようにすることが挙げられます。
サイリウムの発光色の変更
さらにサイリウムを自作しているということもあり、光る色も振りに合わせて光るように改造しています。こちらの光り方もセンサーの値を使用して、変更しています。
ジャイロのX, Y, Z軸の値 → ライトのR, G, Bの値に変換しています。
またライトは常に白く光っていて、各軸の角速度の値が閾値よりも大きくなった時に光るようにしています。例えば、X軸の角速度が閾値以上の値になると、赤く光ります。これはジャイロのX軸の値を、ライトのRedの値として使用しているからです。
動画では振り方でライトの色が変わることもはっきりしているかと思います。M5StickC Plusのセンサーの軸と光り方の色が対応していることも分かるようになってます。
実際にMaker Faire Tokyo 2023でライトが振りによって光り方が変わることを伝えると、色んなサイリウムの振り方を試してくれるようになりました。ライトの色に興味津々なお子様もいて、少し嬉しかったです。
以上、M5StickC Plusのセンサーを用いて、行っていた処理についてでした。
描画について
下記についてお話しします。
- 開発環境
- 大まかなファイル構成
- 画面遷移について
- エフェクトの描画について
開発環境
ファイルの大まかな構成
下記のように、メインスケッチから主要画面を呼び出しています。
さらに、主要画面内の状態に応じて様々なファイルを呼び出しています。
表示する主要画面やその内部状態は、それぞれ enum を作成して管理を行なっています。
画面遷移について
Processing では1枚のスケッチへの上書きと削除の操作を繰り返して描画を行います。
そのため画面遷移を実装するには、下記のような手順で画面を描画する必要があります。
- 1画面を描画する
- 目的の操作が完了次第画面全体を塗りつぶす
- 次の画面を描画する
下記のような enum を作成して主要画面の状態管理を行いました。
enum PageStatus { Start, MusicSelection, EffectSelection, Play, End } |
メインスケッチの draw() の中で、switch することで表示する画面を変えることができます。
void draw() { switch (page) { case Start: startPageView.draw(); break; case MusicSelection: musicSelectionPageView.draw(); break; case EffectSelection: effectSelectionPageView.draw(); break; case Play: playPageView.draw(); break; case End: endPageView.draw(); break; } } |
主要画面内の状態管理の一例として、プレイ画面での管理を紹介します。
下記のような enum を作成しました。
enum PlayPageStatus { First, Count, Play, Finish } |
それぞれ下記の役割を持っています。
- First
- 1フレーム目だけで実行したい内容を記載する
- Count
- プレイ開始前のカウントダウンを行う
- Play
- プレイ中の処理を記載する
- Finish
- 終了画面へ遷移する前の1フレームで行うことを記載する
- 保持されている主要画面の case を、次の画面の case へ更新する
このように、case で管理することで下記のようなメリットがありました。
- isCountFinished などのBoolean値管理による if文の乱立を防ぐことができた
- 状態毎に処理が明確になっているので、可読性が向上した
- 1フレーム目だけ行う case を設けることで、一度だけ描画すれば良いものとそうで無いものを明確に分けることができ、描画パフォーマンスが向上した
プレイ画面の draw() 内で switch して状態の切り替えを行います。
各 case 内での目的の処理が終了するタイミングで、保持している case を 次の状態の case へと書き換えます。
そうすることで、次フレーム描画時に次の case の処理が行われます。
void draw() { switch (status) { case First: (省略) minimManager.setupPlayer(); status = PlayPageStatus.Count; break; case Count: background(0); (省略) if (count < 0) { background(0); status = PlayPageStatus.Play; break; } break; case Play: (省略) if (minimManager.isFinished()) { status = PlayPageStatus.Finish; break; } break; case Finish: (省略) page = PageStatus.End; background(0); break; } } |
Processing で複数画面を切り替るような実装をすることはあまり無いとは思いますが、
一例として誰かの助けになればと思います。
エフェクトの描画について
エフェクトは、まず100作るGenerative Art|おかず|note を参考に実装を行いました。
noise 関数や random 関数を用いて、滑らかなエフェクトや動きの激しいエフェクトを作成しました。
そのエフェクトに対して、センサーの値を用いて変化を与えました。
一例として、「色の変化」をご紹介します。
「良い感じに振れている」を視覚的にわかりやすく表現するために、良い感じに触れている場合には虹色の鮮やかな色で描画されるようにしました。
実際の処理内容は下記になります。
public color getHSBColor(float score) { colorMode(HSB, 360, 100, 100); // 初期状態のHue(青気味の色からスタートする) float originalHuePitch = 210; float maxHuePitch = 400; float frameTotal = frameRate * selectedTrackDuration; // 1 float currentProgressRate = (frameCount - playStartFrameCount) / frameTotal; // 2 float mappedScore = map(score, 0, 100, -6, 6); // 3 float hueDelta = (currentProgressRate * (1 / (1 + exp(-mappedScore))) * maxHuePitch) % 360; // 4 float hue = random((originalHuePitch + hueDelta + 20), (originalHuePitch + hueDelta - 20)) % 360; return color(hue, 100, 100, 80); } |
関数の中で行なっていることは下記になります。
- 曲の進捗を計算する
- 0 – 100 の間に変換したセンサーの値(score)を、-6 から 6の数値へ変換する
- Hueの変化量を計算する
- Hueの値を基準値の周辺で乱数を用いて決定する
「良い感じに振れている」「盛り上がってきた」という感覚を視覚的に伝えるにはどうすれば良いかをかなり悩みました。
「盛り上がり」を実感するためには、「盛り下がり」を感じさせるのが一番わかりやすい手段でした。
盛り下がりを「アートが完成しない」という形で最初に実装したのですが、楽しんでもらうためには最低限「アートが完成すること」が必要なのでは無いかという結論に至りました。
結果として、色の変化や線の太さといった細かい部分での変化でしか表現することができませんでした。しかしながらMaker Faire Tokyo 2023本番では、完成したアートを持って帰って頂くという体験を提供できたと共に、お客様に楽しんで頂けたので良かったかと思っています。
自作サイリウムについて
快適にゲームを体験するために、サイリウムの内部にM5StickC Plusを内蔵する必要があったため、自作サイリウムを作成しました。
- 開発環境
- プロトタイプ1号 ~ 完成品までの道筋
- 本番での使用
開発環境
3Dモデル作成:Fusion 360
3Dプリンター:AnkerMake M5
プロトタイプ1号 ~ 完成品までの道筋
初めての3Dモデルと3Dプリンターの使用だったため、写真の左から右へと向かう順序で、3ヶ月かけて少しずつ形を変えながら製作を進めました。
まずはM5StickC Plusがぴったり入る箱をひたすら作成し、モデル作成とプリントに慣れていきました。
その後、購入したサイリウムの上部にフィットするような柄を作り始めました。
- PCにつなげるコード、ライトにつなげるコードの通り道を作る
- 購入したサイリウムの上部にぴったりはまるネジ山を作る
- サイリウムを振った際にM5StickC Plusがガタガタ揺れないようにしっかり固定させる
という条件をクリアしたものを作成するのに、2ヶ月ほどかかりました。
最終的には以下のことを意識して改良し、完成しました。
- 小さいお子様でも握りやすいように、できるだけ細くする
- ストラップをつけられるようにする
- 振ってる間に壊れないように、厚くする
- 尖ってる部分を丸くし、なめらかな持ち心地にする
最終的に完成した3Dモデル
理想のモデルが完成しましたが、使用した際に脆く、すぐ壊れてしまう問題点がありました。
(写真左:本番で使用した最終形、写真右:印刷に失敗したサイリウム)
握った際にかかる力の向きと、印刷時に積み重ねられるフィラメントの層の向きの関係から、写真の左に示すような横向きの状態よりも、写真の右に示すような縦向きの状態で印刷した方が、耐久性が高まることが分かりました。
しかし、縦向きで印刷しようとすると、印刷途中で倒れてしまうなど、失敗が多く大変でした。
最終的にはサポート材を使用して印刷することで、無事に完成させることができました。
本番での使用
(写真左:市販のサイリウム、写真右:作成したサイリウム)
本番で使用する際には、どのような振り方にも耐えられるように、市販のゴムを使用して補強しました。
本番の2日間にわたり、サイリウムが壊れることなく、来場者が安全かつ快適にゲームをプレイする様子を見ることができました。
特に印象的だったのは、お子様が無邪気に思い切り振っても全く壊れなかったことです。(少しだけドキドキしました)
サイリウム制作は、地道で細かい作業が多く、進捗が見えづらく、大変な部分もありましたが、来場者の方に「自分で作ったの?!すごい!」とたくさん言っていただき、とても嬉しかったです。
実現できなかったこと
開発時間と優先度から実現できなかったタスクが多くありました。その一部をご紹介致します。イベント当日にお客様から頂いた声の中に該当するタスクがいくつかあり、悔しい気持ちになりました。しかしながら、お声を頂いたことで自分たちが考えていた機能が必要とされていたという確認になったので自信にも繋がりました。今後、開発の機会があれば改善したいと思っています。
センサー
- Maker Faire Tokyo 2023では他の展示物と展示物との混線を防ぐため、有線でMacとM5StickC Plusを繋いでいたが、無線でのやり取りができてもよかった
- 無線が不安定になった際に、有線へ切り替える選択肢を取れる
- 振りに合った描画ができる
- 例えば、画面に対して水平に振ったら、画面上にも水平に線が引かれる
- ゲームの操作をサイリウムで出来るようにする
- 「サイリウムを振って、選曲できる?」という来客の声があった
描画
- センサーの値に応じてアートが変化するパターンを多彩にする
- 描画の変化が大きいアートや全体を埋めるように描画されるアートなど、お子様が喜びそうなアートの種類を用意する
- 3秒前のカウントダウンが3秒間で終わらない
- 遊んでいない場合に表示する画面を作成する(描画のデモ・楽曲のランダム再生)
3Dモデル
- 市販のゴムを使用して補強せずとも、100%安心してゲームをプレイできるものを作ること
今回はM5StickC Plusを収納するために2部品に分けて設計しましたが、そのために安全性が100%保証できませんでした。市販のゴムで補強することで安全性を確保しました。 今後は2部品を分けずに1部品で設計し、電池ボックスのようにM5StickC Plusを収納して蓋をする設計にしたいと考えています。
- M5StickC Plusの電源供給を組み込む
今回は有線でMacとM5StickC Plusを繋いでいたため、必要ありませんでしたが、無線化する際は、サイリウム内に電池を組み込み、コードレス化をしたいです。
- サイリウムをもっと細くする
未就園児などの小さなお子様の場合、サイリウムの大きさが持ちづらいような場面が見受けられました。小さなお子様がより容易に持てるようにするためには、M5StickC Plusよりも小さいセンサーを使用し、サイリウムの細さを改善する必要があります。
まとめ
今回の展示物を制作するにあたって、様々な機能や改善案を検討しました。限られた時間の中で開発を進めるため、Jiraなどのツールを活用してタスクの整理や優先順位付けを行いました。これにより、チーム内の意思疎通を図りながら、品質追求を徹底することができました。
その結果として、当日のお客様からの否定的なフィードバックを最小限に抑え、楽しんで頂けたのではないかと考えています。
限られた時間の中で、プロダクトの方向性とお客様の反応の両方を考えながら機能の洗い出し・タスク管理を行うことの難しさを知る事ができました。1から物を作り上げることのできた今回の経験を糧に、以降も「新しい音楽体験」を創出できればと思います。
記事をご覧頂きありがとうございました。