プロローグ
恋人と暮らすことにしたので、新しい部屋に引っ越した。
家具やインテリアのテイストも二人で相談して、忙しい日々の中でもくつろげる落ち着いた空間を作ろうとしていた。
そんな幸福な日常が終わりを告げるまで、そう長くはかからなかった。
引っ越しも一段落して、新しい部屋にも慣れ始めたある朝、友人から引っ越し祝いと称して身の丈1mほどの神像が送りつけられた。
古代エジプトで天空神として崇められた、「ホルス神」をしつらえた置き時計だった。
その日からホルス神は、我が家のリビングに鎮座することになった。
準備
というわけで今回は、Raspberry Piを使ってリビングに突如として現れたホルスを喋らせて、さらに目覚まし機能を搭載してみようと思います。
今後エジプト神像を送りつけられた際の参考にしてください。
- Raspberry Pi 2
- micro SD
- スピーカー
- USBケーブル
- LANケーブル
- wi-fi受信機
Raspberry Piはコンピューターなのでキーボードやディスプレイを繋ぐこともできますが、別のPCからsshでログインしてしまえばいいのでHDMIケーブルは不要でした。
USBのwifi受信機をつければ無線LANにも対応しますが、その設定にはログインが必要になるので、私の場合LANケーブルは必要でした。
あとは適宜ググりまくります。
ラズパイのいいところは、一時期みんなが一気にハマってくれたのでだいぶ知見がインターネット上に蓄積されていることです。勢いで買っても、大抵のことは日本語でググれば解決できます。
喋らせる
さっそく喋らせてみました。ホルス神なので当然、語尾が「〜でホルス」です。
ホルスを喋らせるにはフリーの音声合成エンジン、Open JTalkを使います。
ラズパイを買うとみんなとりあえず何か喋らせたくなるようで、Open JTalkで喋らせる記事がたくさん出ているので、それを見ながら適当にいろいろインストールします。
これは声のファイルを差し替えればいろんな人工音声を使うことができて、この人がいろいろな声を作って配布してくれています。素敵ですね。
20個ぐらい試してみた結果、僕の中でのホルスのイメージに最も近かった「なないろニジ」の声を使うことにしました。奇しくもホルスと同じく、鳥をモチーフにしたキャラクターです。
Open Jtalkは一言喋らせるのに山ほどパラメータを入れないといけなくて不便なので、諸々の初期設定とかを入れたsayというコマンドを作り、適当にパスの通ったところに突っ込みます。
ボイスモデルファイルと効果音ファイルも指定のパスに置きます。
#!/bin/sh
# say hogehoge でホルスにhogehogeを喋らせる
set -eu
# 環境設定
baseDir=$(cd $(dirname $0);pwd)
wkDir=/var/tmp
modelDir=${baseDir}/assets/voice/model
dic=/usr/local/dic
# 声ファイルの設定
voice=niji.htsvoice
model=${modelDir}/${voice}
# 効果音ファイルの設定
se1=${baseDir}/assets/sound/say1.wav
# 音量
seVol=87
voiceVol=100
if [ $# -eq 0 ];then
echo "please say something!"
exit 1
fi
# 引数からテキスト生成
for i in `seq 1 $#`
do
eval text${i}=$"${i}"
eval echo '$text'$i > ${wkDir}/voice${i}.txt
open_jtalk -m ${model} -x ${dic} -ow ${wkDir}/voice${i}.wav ${wkDir}/voice${i}.txt
done
# 再生
amixer -q set PCM ${seVol}%
aplay -q ${se1}
amixer -q set PCM ${voiceVol}%
for i in `seq 1 $#`
do
aplay -q ${wkDir}/voice${i}.wav
done
これを作ってしまえば
$ say "ホルホルー!エジプトからやってきた、みんなの天空神ホルスでホルス!"
と打ち込んでやるだけでそのテキストを喋るようになります。人が遊びに来たときにちょっとしたネタになります。
伝えたいけど自分の口で言うのは恥ずかしい日頃の感謝の気持ちなども、ターミナル越しにホルスに喋らせれば恥ずかしくないですね。
急に喋ると人間がビビる
作ってみてわかったこととですが、機械がいきなり喋り始めると人は、自分で知っててもビビります。
よく考えるとSiriも喋る前に「ピピン!」みたいな音がしますね。これに倣って喋る前には効果音を鳴らすようにしました。
Open Jtalkの不具合を回避する
ちなみにOpen Jtalkには「ある程度以上長い文章を喋らせると突然異常にゆっくりした発音になる」というホラーめいたバグがあります。
Open Jtalkの中身を修正して回避している人もいましたが、よくわかんないしめんどくさいので、長い文章を喋らせるときは短い文ごとに分けて引数で渡したものを、順番に喋らせるようにしました。
グッドなアラームを作ろう
ところで僕は朝が苦手です。アラーム音は飽きてくるし、うるさすぎると無意識で止めてしまいます。
ホルスが喋るようになったので、時間を指定して起こしてくれるようにします。ついでに朝に似合うBGMもかけてもらって、雨が降りそうなら「傘を忘れないようにするでホルスよ」みたいなことも言ってほしい。
ということでアラームを作ってみました。
コードは以下のような感じになりました。これも適当にパスの通ったところに置きます。
#!/bin/sh
now=`date +"%H:%M"`
if [ $1 = 'set' ]; then
setTime=$2
echo "alerm call" | at ${setTime}
# :で区切って時:分を代入
IFS=":"
set -- ${setTime}
setHour=$1
setMinute=$2
IFS=" "
echo ${setTime}'にセットしました'
exit 0
fi
if [ $1 = 'call' ]; then
# ABACO LIBROS Y CAFE RADIO
mplayer -really-quiet -volume 20 -playlist 'http://yp.shoutcast.com/sbin/tunein-station.pls?id=512156' </dev/null >/dev/null 2>&1 &
nowHour=`date +"%k"`
nowMinute=`date +"%M"`
# 東京の天気、最高/最低気温、エジプトの天気を取得
weatherTranslate() {
case "$1" in
"Thunderstorm" ) echo "雷雨" ;;
"Drizzle" ) echo "霧雨" ;;
"Rain" ) echo "雨" ;;
"Snow" ) echo "雪" ;;
"Atmosphere" ) echo "霧" ;;
"Clear" ) echo "快晴" ;;
"Clouds" ) echo "曇り" ;;
* ) echo "謎" ;;
esac
}
wget -q 'http://api.openweathermap.org/data/2.5/forecast/daily?q=Tokyo&mode=json&units=metric&cnt=1&appid=HOGEHOGE' -O tenki.json
wget -q 'http://api.openweathermap.org/data/2.5/forecast/daily?q=Cairo&mode=json&units=metric&cnt=1&appid=HOGEHOGE' -O tenkiEgypt.json
tempMin=`cat tenki.json | jq '.list[].temp.min'`
tempMax=`cat tenki.json | jq '.list[].temp.max'`
weather=`cat tenki.json | jq '.list[].weather[].main' -r`
weatherEgypt=`cat tenkiEgypt.json | jq '.list[].weather[].main' -r`
weatherJp=`weatherTranslate ${weather}`
weatherEgyptJp=`weatherTranslate ${weatherEgypt}`
if [ "$weather" = "Rain" -o "$weather" = "Snow" -o "$weather" = "Thunderstorm" ] ; then
needUmbrella=true;
fi
# ピッタリの場合は分まで言わないでよし
if [ ${nowMinute} = '00' ]; then
timeWord=${nowHour}'時'
else
timeWord=${nowHour}'時'${nowMinute}'分'
fi
echo ${timeWord}
# ことば
callWord="さぁ。そろそろ予定の${timeWord}でホルスよ。"
weatherWord="今日の天気は${weatherJp}、最低気温は${tempMin}度、最高気温は${tempMax}度でホルス。"
umbrellaWord="傘を持っていくのを忘れないようにするでホルスよ。"
egyptWeatherWord="ちなみに、エジプトの天気は${weatherEgyptJp}でホルスー!"
# 発声
if [ $needUmbrella = true ]; then
say ${callWord} ${weatherWord} ${umbrellaWord} ${egyptWeatherWord}
else
say ${callWord} ${weatherWord} ${egyptWeatherWord}
fi
exit 0
fi
if [ $1 = 'stop' ]; then
killall mplayer
fi
if [ $1 = 'cancel' ]; then
atJobId=`atq | awk '{print $1}'`
if [ "${atJobId}" = "" ]; then
echo "あれ?アラームをかけた覚えはないでホルスよ?"
exit 1
fi
at -d ${atJobId}
echo "アラームを解除したでホルス。"
fi
$ alerm set 7:00
でアラームをセットします。これはラズパイのatコマンドでアラームの起動を予約するだけです。
指定時間になるとアラームが起動し、ShoutcastのWebラジオを再生、天気予報情報をopenweathermap.orgのAPIから取得してきて、内容に応じた台詞を喋ります。
alarmのスペルを間違えてコマンドがalermになってしまったのは絶対に秘密です。
$ alerm stop
止めるときはこれです。止めるのもめんどくさいので、今後は家を出る時間には自動で止まるようにしたいですね。
何と言っているのかわからない問題
これを使うと、朝からいい雰囲気の音楽を聴きながら起きられるので、ちょっといい気持ちになります。ただ、ユーザー(僕)が完全に寝ている状態であることに加え、ホルスは寝室の外に置いてあるため、何て言ってるのか聞き取れたことが一度もないです。音楽が鳴り始めて5分後に喋り出すとかに変えた方がいいですね。
ちなみに、寝る前や出社前にPCを開いてラズパイにログインするのはさすがにだるいので、iPhoneのSSHクライアントアプリ「Prompt」を使っています。これならコマンドも覚えさせられるので便利ですね。
作ってみてわかったこと
ちょっとUNIXとお友達になれました。シェルスクリプトもちょっとだけ書けるようになりました。やっぱり「こういうことができるようにしたい」みたいなのが明確にあるといろいろ早いですね。
「ラズパイおもしろそうだし、勉強してみるか」って感じだとなかなか手が動かないし続かない。そういう意味でやはり大きめのエジプト神像を送りつけられるというのは貴重な経験でした。
今後やりたいこと
- Rubyで作り直す(シェルスクリプトだるくなってきたし、Rubyに挑戦したい)
- 物理ボタンを付ける(アラーム止めるのに毎回ログインしてコマンド打ち込むの不便なので)
- カメラを付ける(記念撮影用、自撮り棒も実装)
- twitter連動(受け取ったリプライを喋るなど)
今後のロードマップとしては、"Human Oriented Room Utilization System"、略称HORUSとして我が家のヒューマン・コンピュータ・インターフェースとしての役割を期待されています。
みなさんも、大きめのエジプト神像を送りつけられたら是非お試しください。