にんげんとふえの技術的な話

2015.05.20

河本です。
制作メモのつもりで書きました。
知ったかぶりしてる所もあると思いますので突っ込みを入れて下さい。

企画については作品レポートを読んで下さい。
にんげんとふえをナレッジキャピタルフェスティバルですると子供が集まる

作品の概要

マイクから入力した笛の音に合わせて、36体の人間が、首を回し、目を光らし、声を出す。
120[bpm]のBGMは表拍で笛を吹くと裏拍でレスポンスが帰って来るので、吹いて楽しい。

システムの概要

ざっくりとした構成
・マイク
・スピーカー
・MacBook Pro
・テーブル(Arduino) 9台
・首デバイス 36台


マイクから入力した音声信号をPureDataのバンドパスフィルタで笛の成分を抽出、音量の数値(笛信号)に変換する。
笛信号をopenFrameworksへ取り込み、笛のHIGH/LOWに落とし込む。
LOWからHIGHになったときに笛を吹いたと判断する。

シーケンスデータには、首の角度、目の色、音声のデータが格納されている。
笛入力時、再生完了時の動作パラメータも格納されている。

音声はopenFrameworksで再生し、イヤホンジャックからスピーカーへ出力される。
目の色と首の角度はEthernet越しにArduinoへOSCメッセージとして送信する。

ArduinoではOSCメッセージを解釈し、目と首を制御する。
テーブルの中心にあるArduinoには4隅の首と繋がっていて、テーブルは交換可能なモジュールとなっている。
目はNeoPixel(マイコン入りフルカラーLED)を使っている。
首はラジコン用サーボモータで動作する。

openFrameworksアプリ上で簡単なシミュレータを実装しているため、MacBook単体で確認できる。

使った部品

設備系

・電源タップ
・スイッチングハブ
・ミキサー(お借りしました)
・スピーカー(お借りしました)

Arduino単位(9セット+予備1セット)

Arduino UNO R3
Arduino イーサネットシールド R3
Adafruit I2C接続16チャンネル12ビットPWM/サーボ シールド
ProjectBox for Arduino

Arduinoシールド用ピンソケットのセット(R3対応)2つ(イーサネット端子と干渉するため)
・USB電源(制御用電源)
・ACアダプタ 5[V] 6.5[A](サーボとNeoPixelの駆動用電源、オーバースペックでした。)
φ2.1mmDCジャック-2P端子台 変換コネクタ
ディップスイッチ 4極(IPアドレス、MACアドレス設定用)
・カーボン抵抗 220[ohm]4つ(NeoPixel用ダンピング抵抗)
・電解コンデンサ 470[uF] 25[V](駆動用電源のバイパスコンデンサ)
・LANケーブル
・USBケーブル(ABタイプ)

首単位(36セット+予備4セット)

フタバ S3003バルク版
フタバ/F-304422/サーボ用延長コード 大電流50芯タイプ
スチロール マネキン 顔付ホワイト女性
NeoPixel スルーホールLED(半透明, 8mm)2つ
カラフルブレッドボード 白(首の根元でNeoPixelを直列に繋ぐ)
QIケーブル3S-3P3つ(Arduinoのサーボシールドに後付けしたヘッダピンから首の根元のブレッドボードまで)
QIケーブル4S-4P2つ(首の根元のブレッドボードからそれぞれの目へ)
QIコネクタ04P(2Px2列)QIケーブルの4S側と交換し、LEDソケット代わりにする

システムの詳細

音声入力


音声信号処理はPureData(ofxPdでアプリに組み込み)で処理させる
コリスのフエラムネの周波数を計測、4500[Hz]ぐらい。
Qファクターはよく分かってないけど500にした。
バンドパスフィルタでざっくりと笛の成分(笛信号)を抽出する。
env~ で100[dB]に正規化した数値を笛信号としてopenFrameworksへ送信する。


笛信号は不安定なので、HIGHレベルとLOWレベルを定義し、
LOW状態の時はHIGHレベルを一定期間超え続けた場合にHIGH状態へと遷移させる。逆も同じ。

LOW状態からHIGH状態への遷移をシーケンス動作のトリガーにする。

シーケンス制御

シーケンスはn個のキーフレームで構成されている。
目の色や首の角度は補間しないことにした。

シーケンス終了時、笛入力時をイベントにし、シーケンスを切り替えて行く。
イベント時パラメータはint型で、イベント発生時の遷移先シーケンスオフセットとなっている。
イベント時パラメータ値がIGNORE_JUMP(=INT_MIN)の場合のみイベントを無視する。

シーケンスデータを外部データ化しようとしたが、データを作成するのは自分しか居らず、しかも規則的な動きがほとんどのため、初期化時に呼び出されるシーンごとのファクトリメソッドにハードコーディングすることにした。
乱数やパーリンノイズを使えたり、仕様変更に対しても定数の書き換えで一括適用できたのでよかった。

シミュレータ

ofBoxPrimitiveを組み合わせただけの3Dオブジェクトを実際の位置に配置する。
首振りの速度は計測して合わせておく。

パラメータ設定画面


画面から設定可能なパラメータには
・ThresholdUpper(笛入力トリガのHIGHレベル)
・ThresholdLower(笛入力トリガのLOWレベル)
・ThresholdTime(笛入力トリガ状態のチャタリング回避用パラメータ)
・ThresholdLongTime(長音判定用、未使用)
・PdparamsFreq(バンドパスフィルターの周波数)
・PdparamsQ(バンドパスフィルターのQファクター)
・Delay(ワンテンポ遅れて返事を返すためには、処理遅延とこのパラメータを合わせて0.5秒になるようにする)
がある

現地では、赤色のThresholdUpperと緑色のThresholdLowerを操作する。
子どもの場合は肺活量の関係で音量が小さいため、波形を見ながら下げる。
フエラムネ自体に不慣れなために音量が小さい人もいた。

音声出力

音声用キーフレームには実行タイミング、ファイル名とコントロール指示(Play or Stop)が指定されているため、シーケンス制御の一環としてBGMとSEの制御を行っている。

システムにはあらかじめ4拍繰り返しで構成されたシーケンスデータを用意してあり、
120[bpm]のBGMのため、笛の入力を0.5秒遅れで発音させることで、表拍の笛で裏拍の発音になる。
また、120[bpm]は行進曲にも使われるテンポで、歩くテンポとほぼ同じ、普遍的なテンポのため、楽にリズムをとれる。

OSCメッセージ

4つ首を一つのArduinoで制御しているが、一度のパケットで500[Bytes]の情報を送ってしまうと、
Arduino UNOは変数領域が2[KB]しかなく、1[KB]であったため、簡単にスタックあふれを起こしてしまった。
メッセージを見直し、 68[Bytes]のOSCメッセージ(110[Bytes]のイーサネットフレーム)程度にした。

OSCでは到達を保証しないUDPプロトコルを使用したが、
後から上書きできるステートを送信しているので、
パケットロスしてもなんとかなっている。

スイッチングハブがすぐにMACアドレスを忘れてしまうようで、送り始めのUDPパケットが必ずロスしてしまう現象が起きた。
対策として1秒に一回、無意味なUDPパケットをブロードキャストしている。

DIPスイッチでのIPアドレスとMACアドレスの設定

同じスケッチで動くようにしたかったため、IPアドレスとMACアドレスはDIPスイッチで設定できるようにした。

出番は無いかと思っていたが、たびたび役に立った。
テーブル(Arduino)のIPアドレスは正面から見て以下のように設定した。

192.168.0.106 192.168.0.107 192.168.0.108
192.168.0.103 192.168.0.104 192.168.0.105
192.168.0.100 192.168.0.101 192.168.0.102

テーブル内の配線


中央のArduinoからサーボモータとNeoPixel用の配線が伸びている。(NeoPixel用のQIケーブルは首用テーブルの穴を通して上へ)
DCケーブル、USBケーブル、LANケーブルは中央の穴からテーブルの下へ。

テーブル内のHeadNoは以下の通りにした。

/2 /3
/0 /1

以下、IPアドレスとHeadNoの組み合わせ。

192.168.0.106
/2
192.168.0.106
/3
192.168.0.107
/2
192.168.0.107
/3
192.168.0.108
/2
192.168.0.108
/3
192.168.0.106
/0
192.168.0.106
/1
192.168.0.107
/0
192.168.0.107
/1
192.168.0.108
/0
192.168.0.108
/1
192.168.0.103
/2
192.168.0.103
/3
192.168.0.104
/2
192.168.0.104
/3
192.168.0.105
/2
192.168.0.105
/3
192.168.0.103
/0
192.168.0.103
/1
192.168.0.104
/0
192.168.0.104
/1
192.168.0.105
/0
192.168.0.105
/1
192.168.0.100
/2
192.168.0.100
/3
192.168.0.101
/2
192.168.0.101
/3
192.168.0.102
/2
192.168.0.102
/3
192.168.0.100
/0
192.168.0.100
/1
192.168.0.101
/0
192.168.0.101
/1
192.168.0.102
/0
192.168.0.102
/1

首ふりサーボ制御

ラジコン用サーボモータを使うことにし、
最大手(?)メーカーのフタバ社製のエントリーモデルS3003を選んだ

==自信の無い計算ここから==
首の質量0.18[kg] 半径0.12[m]の円柱
とした時、
Y軸慣性モーメントは
0.001296[kg*m^2]

静止状態から0.5*PI[rad]の角度を0.125[s]で等加速回転させる場合の角加速度は
(2*0.5*PI[rad])/(0.125[s])^2
=201.0619298[rad/s^2]

必要なトルクは
0.001296[kg*m^2]*201.0619298[rad/s^2] =0.2605762611[N*m] =26.05762611[N*cm] =2.658941439[kgf*cm] ==自信の無い計算ここまで==

s3003は4.1[kg*cm]のトルクを発生できるため、十分と判断。

実際にはそんなに素早く動作しなかったので、もうすこし余裕があったようだった。

サーボホーンにはレーザーカッターでカットした2.5[mm]厚MDFを首用テーブルとして固定し、そこに首をマジックテープで固定した。

角度指定するタイプのサーボのため、慣性による振動が発生したが、
簡易ダンパとしてスポンジ素材の隙間テープを貼ることでおさまった。

実測の消費電流は 400[mA]ぐらい。負荷をかけると1[A]を超えることもあるため、電源に余裕を持たせた。

目のLED制御




首の根元にブレッドボードを設置し、
左右のNeoPixelを直列で繋いでいる。
壊れやすいという話も聞いたため、LEDを交換可能にしている。

Arduino側でガンマ補正を行ってから出力する。

消費電流はフル点灯で1個あたり60[mA]程度らしい。

Arduinoのピンアサインなど


イーサネットシールド+サーボシールド+追加ロジックという構成。
サーボモータとNeoPixelの電源は共用し、並列にした。(コンデンサの足から分岐させた)

以下、ピンアサイン

D0 Serial RX
D1 Serial TX
D2 未使用
D3 未使用
D4 Ethernet Shield SDCS
D5 未使用
D6 NeoPixels /3
D7 NeoPixels /2
D8 NeoPixels /1
D9 NeoPixels /0
D10 Ethernet Shield ETHCS
D11 Ethernet Shield MOSI
D12 Ethernet Shield MISO
D13 Ethernet Shield SCK
A0 DIPSwitch 1(8の位)
A1 DIPSwitch 2(4の位)
A2 DIPSwitch 3(2の位)
A3 DIPSwitch 4(1の位)
A4 Servo Shield SDA
A5 Servo Shield SCL

未使用のピンが3つになってしまった。DIPSwitchに4ピン使うのは贅沢かもしれない。

まとめ

今回はひたすら無難にまとめた。
数で勝負するような作品と決まった段階で、スケールアウト可能な構成にし、モジュール単位での検証ですむようにした。

部品も既製品を多用し、マージンも大きめに取ったため。4日間故障する事無く乗り切ることができた。