SuperColliderを使って(意識の高い)キックをWOMBのスピーカーで流す

先日開催されたSuperCollider Workshop at WOMB LOUNGEに参加してきました。
内容はかなり濃く、SuperColliderで出来ることを筆頭に外部アプリケーションとの連携や通信を使った講義もあり充実した内容になっていました。
音を鳴らすことから始まり、音の加工やシンセを作りループで再生、シーケンスを作ったりと駆け足ではありましたが出来ることを洗いながらお腹いっぱいな内容でした。
OSCを使ったProcessingやMax/MSPとの通信は初心者にとってはかなり踏み込んだ話しだったのではないかと感じています。
個人的にはインスタレーションやメディアアートと言った分野で非常に有効な手段であると考えているのでワクワクしながら講義をうけていました。

 

五日目最終日には受講者によるSuperColliderを使った発表がありもちろん私も参加しました。
皆さんそれぞれSuperColliderを使ったユニークな音や曲を発表し、幅の広い発表会だったと感じています。
そんな中、私は「どうやったら面白い発表が少しでも出来るだろうか?」 と考え、
せっかくなら普段鳴らすことのできないクラブのスピーカーでキックを鳴らそう!と思い立ったわけです。

 

なのでキックしか作りませんでした。


(
  SynthDef(\SinKick, {
    arg freq = 440, gate = 0.1, amp = 0.5, source, pan = 0.0;
    source = Pan2.ar(
      SinOsc.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 1, 1)
      * EnvGen.kr(Env.perc(0, 0.1, 1, 8));
    );
    Out.ar(0, source);
  }).store;

  SynthDef(\SawKick, {
    arg freq = 440, gate = 0.1, amp = 0.5, source, pan = 0.0;
    source = Pan2.ar(
      Saw.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 1, 1)
      * EnvGen.kr(Env.perc(0, 0.1, 1, 8));
    );
    Out.ar(0, source);
  }).store;

  SynthDef(\FSinKick, {
    arg freq = 440, gate = 0.1, amp = 0.5, source, pan = 0.0;
    source = Pan2.ar(
      FSinOsc.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 1, 1)
      * EnvGen.kr(Env.perc(0, 0.1, 1, 8));
    );
    Out.ar(0, source);
  }).store;

  SynthDef(\SquareKick, {
    arg freq = 440, gate = 0.1, amp = 0.5, source, pan = 0.0;
    source = Pan2.ar(
      Pulse.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 0.5, 1, 1)
      * EnvGen.kr(Env.perc(0, 0.1, 1, 8));
    );
    Out.ar(0, source);
  }).store;

//distortion

  SynthDef(\FSinKick_dist, {
    arg freq = 440, gate = 0.1, amp = 0.5, source, pan = 0.0;
    source = Pan2.ar(
      FSinOsc.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 1, 1).distort * Line.kr(2, 0, 0.9)
      * EnvGen.kr(Env.perc(0, 0.1, 1, 8));
    );
    Out.ar(0, source);
  }).store;

//+noise

  SynthDef(\FSinKick_noise, {
    arg freq = 440, gate = 0.1, amp = 0.5, source, pan = 0.0;
    source = Pan2.ar(
      FSinOsc.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 1, 1).distort * Line.kr(2, 0, 0.9)
      + LFNoise0.ar(freq * 6, 0.01, amp)
      * EnvGen.kr(Env.perc(0, 0.1, 1, 8));
    );
    Out.ar(0, source);
  }).store;

//+ saw

  SynthDef(\FSinKick_saw, {
    arg freq = 440, gate = 0.1, amp = 0.5, source, pan = 0.0;
    source = Pan2.ar(
      FSinOsc.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 1, 0.5).distort * Line.kr(2, 0, 0.9)
      + Saw.ar(freq * 2, 0.0025, 0.25)
      + LFNoise0.ar(freq * 6, 0.01, amp)
      * EnvGen.kr(Env.perc(0, 0.1, 1, 8));
    );
    Out.ar(0, source);
  }).store;

//+ square

  SynthDef(\FSinKick_square, {
    arg freq = 440, gate = 0.1, amp = 0.5, source, pan = 0.0;
    source = Pan2.ar(
      FSinOsc.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 1, 0.5).distort * Line.kr(2, 0, 0.9)
      + Pulse.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 0.5, 0.03, 0.5)
      + Saw.ar(freq * 2, 0.0025, 0.25)
      + LFNoise0.ar(freq * 6, 0.01, amp)
      * EnvGen.kr(Env.perc(0, 0.1, 1, 8));
    );
    Out.ar(0, source);
  }).store;

//+ high

  SynthDef(\FSinKick_high, {
    arg freq = 440, gate = 0.1, amp = 0.5, source, pan = 0.0;
    source = Pan2.ar(
      FSinOsc.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 1, 0.5).distort * Line.kr(2, 0, 0.9)
      + Pulse.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 0.5, 0.03, 0.1)
      + Saw.ar(freq * 2, 0.0025, 0.25)
      + LFNoise0.ar(freq * 8, 0.01, 0.05)
      * EnvGen.kr(Env.perc(0, 0.1, 1, 8));
    );
    Out.ar(0, source);
  }).store;

//+ high++

  SynthDef(\FSinKick_high_saw, {
    arg freq = 440, gate = 0.1, amp = 0.5, source, pan = 0.0;
    source = Pan2.ar(
      FSinOsc.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 1, 0.5).distort * Line.kr(2, 0, 0.9)
      + Saw.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.2, 2), 0.5, 0.05).distort * Line.kr(-2, 0, 0.5)
      + Pulse.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 0.5, 0.03, 0.1)
      + Saw.ar(freq * 2, 0.0025, 0.25)
      + LFNoise0.ar(freq * 8, 0.01, 0.05)
      * EnvGen.kr(Env.perc(0, 0.1, 1, 8));
    );
    Out.ar(0, source);
  }).store;

//+ release

  SynthDef(\FSinKick_release, {
    arg freq = 440, gate = 0.1, amp = 0.5, source, pan = 0.0;
    source = Pan2.ar(
      FSinOsc.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 1, 0.5).distort * Line.kr(2, 0, 0.9)
      + Saw.ar(EnvGen.kr(Env.adsr(0, 0.5, 0, 0, 1, -20), gate, freq, 0, 0.2, 2), 0.5, 0.005).distort * Line.kr(-2, 0, 0.5)
      + Pulse.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 0.5, 0.03, 0.1)
      + Saw.ar(freq * 2, 0.0025, 0.25)
      + LFNoise0.ar(freq * 8, 0.01, 0.05)
      * EnvGen.kr(Env.perc(0, 0.3, 1, 8));
    );
    Out.ar(0, source);
  }).store;

//+ noise

  SynthDef(\FSinKick_noise2, {
    arg freq = 440, gate = 0.1, amp = 0.5, source, pan = 0.0;
    source = Pan2.ar(
      FSinOsc.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 1, 0.5).distort * Line.kr(2, 0, 0.9)
      + Saw.ar(EnvGen.kr(Env.adsr(0, 0.5, 0, 0, 1, -20), gate, freq, 0, 0.2, 2), 0.5, 0.005).distort * Line.kr(-2, 0, 0.5)
      + Pulse.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 0.5, 0.03, 0.1)
      + LFNoise0.ar(EnvGen.kr(Env.perc(0, 20, 1, 100)), 1, 1)
      * EnvGen.kr(Env.perc(0, 0.1, 1, 12));
    );
    Out.ar(0, source);
  }).store;

//+ Kick!!!!!!

  SynthDef(\FSinKick_last, {
    arg freq = 440, gate = 0.1, amp = 0.5, source, pan = 0.0;
    source = Pan2.ar(
      FSinOsc.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 1, 0.1).distort * Line.kr(2, 0, 0.9)
      + FSinOsc.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq / 2, 0, 0.5, 2), 1, 0.1).distort * Line.kr(2, 0, 0.9)
      + FSinOsc.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq * 2, 0, 0.5, 2), 1, 0.05).distort * Line.kr(2, 0, 0.9)
      + Saw.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.2, 2), 0.5, 0.05).distort * Line.kr(-2, 0, 0.5)
      + Pulse.ar(EnvGen.kr(Env.adsr(0, 1, 0, 0, 1, -20), gate, freq, 0, 0.5, 2), 0.5, 0.03, 0.1)
      + Saw.ar(freq * 2, 0.0025, 0.25)
      + LFNoise0.ar(freq * 8, 0.005, 0.05)
      * EnvGen.kr(Env.perc(0, 0.12, 1, 8));
    );
    Out.ar(0, source);
  }).store;
)

 

~sinKick = Synth(\SinKick);
~sawKick = Synth(\SawKick);
~fsinKick = Synth(\FSinKick);
~aquareKick = Synth(\SquareKick);
~distKick = Synth(\FSinKick_dist);
~noiseKick = Synth(\FSinKick_noise);
~saw2Kick = Synth(\FSinKick_saw);
~square2Kick = Synth(\FSinKick_square);
~highKick = Synth(\FSinKick_high);
~highSawKick = Synth(\FSinKick_high_saw);
~releaseKick = Synth(\FSinKick_release);
~noise2Kick = Synth(\FSinKick_noise2);
~lastKick = Synth(\FSinKick_last);

 

SinやSawでとりあえずそれっぽい音を作り、それに重ねていく形でコーディング。
ただSuperColliderの知識が足りないので、高音域にSawやNoiseを重ねてなんとなくで作るという形で進めました。
抜けが悪いから高音域、中音域に追加しつつ余韻が足りないからReleaseを伸ばしたりと、DAWであればサンプリングしたキックをEQで加工していくところをコードで書くことがとても難しく、どうやったら再現できるのだろうかというところはとても楽しく作ることができました。

ただ満足にいくものが出来ていないので、原理や出力、SuperColliderの機能を理解してまた挑みたいと思ってます。

※発表の最後に、参考としてみんな大好きVengeanceのキックを鳴らしましたがやっぱり良かったです。

気圧センサで光るボールをつくってみました

 

ユニバでは今期から新領域チームを設け、これまでの仕事にとらわれず、かつそれらを更に押し広げるような技術の開拓にチャレンジしています。

 

最初の活動の成果となったこの”Uniball”(ゆにぼーる)では、「押す」「抱く」といった人間の自然な動作、またそういった「感触」をどうインタラクションやサービスに結びつけるか、という点を模索しています。

 

球体には気圧センサとLEDが内蔵されており、無線によって外部からのパラメータのコントロールが可能となっています。

体験者が強く抱きしめれば抱きしめるほど、球体は光を増し、それはさながら何がしかの感情の発露であるかのような、不思議な印象を抱かせます。

 

技術的な点は本当に簡単なもので、それよりも「素早く作って感覚を掴む」ことを重視して制作した感があります。

 

ラピッドプロトタイピングの定番であるArduinoを使用し、Messenger( http://arduino.cc/playground/Code/Messenger )を介したMax/MSPからのシリアル通信で値の取得・送信を行なっています。

I2C接続の気圧センサモジュールBMP085の使用にあたっては、galileo7様のWeatherduino3用ライブラリG7Weather3( https://sites.google.com/a/galileoseven.com/galileo-7/home/weatherduino3 )を使用させて頂きました。この場を借りて感謝致します。

 

ちなみにこの球体、実はバランスボールなのです。
直径15cmほどのタッパーの底面を切り取って、バランスボールにタッパーの直径より一回り小さい穴を空けてタッパーをねじこみ、ボンドで固定しました。
デバイスを内部に設置したらタッパーの蓋を閉め、反対側の空気穴から空気を入れて膨らませれば完成です。

 

使用デバイス、ソフトは以下の通りです。
- XBee シリーズ1
- 気圧センサモジュール BMP085
- Arduino Pro Mini 5V
- RGB LED
- Max/MSP

 

今後もこのような活動のご報告をこの場で行なっていければと思っています。ご注目ください!

Express で JSONP をスマートにレスポンスする方法

Express にて JSONP をレスポンスする時、今まではこんなふうにやっていました。

app.get('/awesome/your/api', function(req, res) {
  var response = { sugar: "sweet", salt: "spicy" }
    , callback = req.query.callback;

  if (callback) {
    // コールバックが指定されていたら JSONP をレスポンス
    res.send(callback + '(' + JSON.stringify(response) + ');');
  } else {
    // コールバックが指定されていなければ JSON をレスポンス
    res.send(response);
  }
});

マニュアルにはありませんが、実はフレームワーク側で JSONP のレスポンスがサポートされています。

// res.send() にて JSONP を有効にするオプション
// GET パラメータに callback の指定があると JSONP でレスポンス
// app.config に書いておくと良さげです。
app.enable('jsonp callback');

app.get('/your/awesome/api', function(req, res) {
  var response = { sugar: "sweet", salt: "spicy" };
  // 設定が有効になっているのでフレームワーク側で自動で判別してくれる
  res.send(response);
});

/your/awesome/api

{"sugar":"sweet","salt":"spicy"}

/your/awesome/api?callback=fn

fn({"sugar":"sweet","salt":"spicy"});

フレームワークの機能を使うと、余計な if 文を書かなくてもいい上に、危険な文字もエスケープしてくれるのでこちらを使ったほうが良いでしょう。なお、コールバック関数名を指定するパラメータ名は変更できないみたいです。

以上、ちょっとした小ネタでした。

RMagick のインストールでコケる問題

昨日から、自分の Lion 環境上で rmagick を使う Rails プロダクトの Bundle install 時に、

/Users/xxx/.rvm/rubies/ruby-1.9.2-p290/bin/ruby extconf.rb
checking for Ruby version >= 1.8.5... yes
checking for /usr/bin/gcc-4.2... yes
checking for Magick-config... yes
checking for ImageMagick version >= 6.4.9... yes
checking for HDRI disabled version of ImageMagick... yes
checking for stdint.h... *** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of
necessary libraries and/or headers.  Check the mkmf.log file for more
details.  You may need configuration options.

Provided configuration options:
        --with-opt-dir
        --without-opt-dir
        --with-opt-include
        --without-opt-include=${opt-dir}/include
        --with-opt-lib
        --without-opt-lib=${opt-dir}/lib
        --with-make-prog
        --without-make-prog
        --srcdir=.
        --curdir
        --ruby=/Users/xxx/.rvm/rubies/ruby-1.9.2-p290/bin/ruby
/Users/xxx/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/mkmf.rb:368:in `try_do': The complier failed to generate an executable file. (RuntimeError)
You have to install development tools first.
        from /Users/xxx/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/mkmf.rb:452:in `try_cpp'
        from /Users/xxx/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/mkmf.rb:834:in `block in have_header'
        from /Users/xxx/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/mkmf.rb:693:in `block in checking_for'
        from /Users/xxx/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/mkmf.rb:280:in `block (2 levels) in postpone'
        from /Users/xxx/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/mkmf.rb:254:in `open'
        from /Users/xxx/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/mkmf.rb:280:in `block in postpone'
        from /Users/xxx/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/mkmf.rb:254:in `open'
        from /Users/xxx/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/mkmf.rb:276:in `postpone'
        from /Users/xxx/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/mkmf.rb:692:in `checking_for'
        from /Users/xxx/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/mkmf.rb:833:in `have_header'
        from extconf.rb:193:in `<main>'

というエラーで rmagick のインストールに失敗するという現象に悩まされていたのですが、
単純にググってもそれらしい情報に行き当たらず、苦労していたので原因と解決方法をメモしておきます。

まず、この環境ですが、ImageMagick を brew で入れた時から怪しいところがありました。

% brew install imagemagick
==> Downloading http://downloads.sf.net/project/machomebrew/Bottles/imagemagick-6.7.1-1-bottle.tar.gz
File already downloaded in /Users/xxx/Library/Caches/Homebrew
==> Pouring imagemagick-6.7.1-1.bottle.tar.gz
==> Caveats
Some tools will complain unless the ghostscript fonts are installed to:
  /usr/local/share/ghostscript/fonts
ln: Wand.pc: Permission denied
Error: The linking step did not complete successfully
The formula built, but is not symlinked into /usr/local
You can try again using `brew link imagemagick'
==> Summary
/usr/local/Cellar/imagemagick/6.7.1-1: 1389 files, 32M

良く見ると、途中でエラーを吐いています。シンボリックリンクが作れなかったから、手動で brew link しろと。
単純に実行しただけだと ” ln: Wand.pc: Permission denied ” を解決できず同じ様に死んでしまうものの、 sudo で実行すると完了し、一見問題ないように見えます。
しかし、肝心の rmagick のインストール時には上記のようなエラーになってしまいました。

bundle が悪いのかと思って、単純に

% gem install rmagick

しても、同じでした。冒頭のエラーを良く見ると

Could not create Makefile due to some reason, probably lack of
necessary libraries and/or headers. Check the mkmf.log file for more
details. You may need configuration options.

とあるので、mkmf.log を読んでみる事に。

% less ~/.rvm/gems/ruby-1.9.2-p290/gems/rmagick-2.13.1/ext/RMagick/mkmf.log
checking for Ruby version >= 1.8.5... -------------------- yes

--------------------

find_executable: checking for /usr/bin/gcc-4.2... -------------------- yes

--------------------

find_executable: checking for Magick-config... -------------------- yes

--------------------

checking for ImageMagick version >= 6.4.9... -------------------- yes

--------------------

checking for HDRI disabled version of ImageMagick... -------------------- yes

--------------------

"/usr/bin/gcc-4.2 -o conftest -I/Users/xxx/.rvm/rubies/ruby-1.9.2-p290/include/ruby-1.9.1/x86_64-darwin11.0.1 -I/Users/xxx/.rvm/rubies/ruby-1.9.2-p290/include/ruby-1.9.1/ruby/backward -I/Users/xxx/.rvm/rubies/ruby-1.9.2-p290/include/ruby-1.9.1 -I.  -I/usr/local/Cellar/imagemagick/6.7.1-1/include/ImageMagick  -I/usr/local/Cellar/imagemagick/6.7.1-1/include/ImageMagick -fopenmp conftest.c  -L. -L/Users/xxx/.rvm/rubies/ruby-1.9.2-p290/lib  -L/usr/local/Cellar/imagemagick/6.7.1-1/lib -L/usr/X11/lib    -L/usr/local/Cellar/imagemagick/6.7.1-1/lib -lMagickCore -llcms -ltiff -lfreetype -ljpeg -L/usr/X11/lib -lfontconfig -lXext -lSM -lICE -lX11 -lXt -lbz2 -lz -lm -lgomp -lpthread -lltdl  -lruby.1.9.1-static  -lpthread -ldl -lobjc "
ld: library not found for -llcms
collect2: ld returned 1 exit status
checked program was:
/* begin */
1: #include "ruby.h"
2:
3: int main() {return 0;}
/* end */

-llcms を見つけられないのが原因だったようです。
liblcms とはなんぞ、と思ってググったらこのページに引っ掛かりました。Libraries for the Little CMS Engine というものらしいので、 再度この名前でググると、どうやらカラーマネジメントシステムのライブラリみたい。

しかし、必要なら brew が依存性解決に入れてくれているだろうと見てみると

$ brew list
cmake          imagemagick     jpeg          little-cms     mysql          nkf          readline     zsh
gdbm          jasper          libtiff          mongodb          netcat          pidof          wget

案の定、little-cms というソレらしいのが居ます。
が、ここで先程、brew で ImageMagick 入れようとしたときにエラーが出ていたのを思い出しました。
依存性解決のために自動で入れる途中で、同じようなエラーに遭遇して、little-cms パッケージが正常にインストールされていないのではないかという仮説を思いつき、この腐った brew 環境を叩き直すために、お医者様の力を借りる事にします。

% brew doctor
Unbrewed dylibs were found in /usr/local/lib.

If you didn't put them there on purpose they could cause problems when
building Homebrew formulae, and may need to be deleted.

Unexpected dylibs:
    /usr/local/lib/libmacfuse_i32.2.dylib
    /usr/local/lib/libmacfuse_i64.2.dylib
    /usr/local/lib/libosxfuse_i32.2.dylib
    /usr/local/lib/libosxfuse_i64.2.dylib

Unbrewed .pc files were found in /usr/local/lib/pkgconfig.

If you didn't put them there on purpose they could cause problems when
building Homebrew formulae, and may need to be deleted.

Unexpected .pc files:
    /usr/local/lib/pkgconfig/osxfuse.pc

Unbrewed .la files were found in /usr/local/lib.

If you didn't put them there on purpose they could cause problems when
building Homebrew formulae, and may need to be deleted.

Unexpected .la files:
    /usr/local/lib/libosxfuse_i32.la
    /usr/local/lib/libosxfuse_i64.la

/usr/local/lib/pkgconfig isn't writable.
This can happen if you "sudo make install" software that isn't managed
by Homebrew.

If a brew tries to write a .pc file to this folder, the install will\nfail during the link step.

You should probably `chown` /usr/local/lib/pkgconfig

Some "config" scripts were found in your path, but not in system or Homebrew folders.

`./configure` scripts often look for *-config scripts to determine if software packages
are installed, and what additional flags to use when compiling and linking.

Having additional scripts in your path can confuse software installed via Homebrew if
the config script overrides a system or Homebrew provided script of the same name.

/usr/X11R6/bin
    freetype-config libpng-config libpng15-config

brew doctor は概ね、勝手に /usr/local 以下に入ったosxfuseがお気に召さないようですが、

/usr/local/lib/pkgconfig isn’t writable.
This can happen if you “sudo make install” software that isn’t managed
by Homebrew.

If a brew tries to write a .pc file to this folder, the install will\nfail during the link step.

You should probably `chown` /usr/local/lib/pkgconfig

大事なのはおそらくここでしょう。
見ると osxfuse インストール時に作られたたらしいこのディレクトリは

drwxr-xr-x 4 root wheel 136 9 27 22:02 pkgconfig

と、root 権限で置かれていました。これを自分に chown し、

% brew uninstall little-cms
% brew install little-cms

としたら、案の定、/usr/local/lib/pkgconfig 内に、先程までは存在しなかった、lcms.pc というファイルが作られました。little-cms の環境がやはり不完全だったようです。
この状態で、ImageMagick も再インストールすると

% brew uninstall imagemagick
% brew install imagemagick

bundle install で RMagick のインストール時に失敗する事は無くなりました。
環境系のトラブルは面倒ですね。

CodaのZen-CodingをHTML5 Boilerplate的なアレで

普段コーディングで使っているエディタがCoda(Mac専用)なのですが、
プラグインのTEA for Codaを入れるとZen-Codingが使えるようになります。
(もしかしたらVer.古いかもしれません

Zen-Codingとはなんぞやというところで、


div#container>div.logo+ul#nav>li.navList0$*5>a

でこれを解釈というか展開というかおまじないをすると

<div id="container"></div>

こんな感じに展開してくれます。
サイトの骨組みを作る時に非常に助かっています。
これ以外にも様々なテンプレートのようなものが存在し、自分でも作ることが可能です。

よく使う一例として


html:5

を展開すると


 

と展開できます。

と思いきや、lang=”en”になってたりしませんか?
きっと日本語圏の人は「lang=”ja”になって欲しい!」と勝手に解釈して無理矢理進めます!

ということで変更してみましょう!

変更するファイルは
/User/hogehoge/Library/Application Support/Coda/Plug-ins/TEA for Coda.codaplugin

これでパッケージの内容を表示から開くとFinderで開くと思いますので
Support/Library/zencoding/zen_settings.py
を開きます。

そうすると10行目あたりに

'variables': {
'lang': 'en',
'locale': 'en',
'charset': 'UTF-8',
'profile': 'xhtml',

というのがあるので変更して保存します。
保存後は一度Codaを再起動しないと反映されませんのでご注意を。

これで基本的なhtmlができるので


div#container>div#header+div#content+div#footer
<div id="container"></div>

こんな風に展開していって構築していきます。

HTML5 Boilerplateを組み込んでみました!
というところで長くなりすぎてしまったため

まずZen-Codingの話をしなくてはならないというところで力尽きました
説明しようとすると割と内容が多いことに気づいたので
次回に・・・
持ち越し・・・
たいな・・・

draw3DLine : 3D drawing on 2D canvas (Processing.js)

最近、3Dになったユニバ社のロゴ
3Dの描画機能だけを切り出してみたので、皆さん使ってね。

ユニバの新ロゴはcanvas+Processing.jsで作られていますが、Processingの3D系の命令は使っていません(WebGLに対応しているブラウザがまだ少ないので…)。3Dのオブジェクトをスクリーン(canvas)に投影する計算を行って、2D系の命令だけで描画しています。
その描画系を切り出したのが以下の関数です。

使い方

draw3DLine(x1, y1, z1, x2, y2, z2, xRotate, yRotate, zRotate)

6つの引数(XYZ座標を2つと、XYZ軸の回転角度)を渡すと、canvasに2座標を結ぶ線を描画します。原点は画面の中央です。
カメラはZ軸上に固定です。カメラの視野角(radS)、カメラの位置(camZ)、スクリーン位置(scZ)だけ、変更することができます。線の太さ、色はweight、strokeH、strokeS、strokeBで持っていますが、2Dで線を描画するパラメータなので、太い線ではパース感の矛盾がよりはっきり見えてきます。細い線でも厳密に言えば奥行き感ゼロ。そこは見せ方でがんばってね。

サンプルでは、線を12本引き、XYZ軸それぞれを1度ずつ回転させて、直方体が回転するアニメーションを作っています。
http://hascanvas.com/draw3DLine

//透視変換用の変数
//視野角θ
float radS = TWO_PI/360*35;
//カメラの原点からの距離
float camZ = -4500;
//スクリーンの原点からの距離
float scZ = -1000;
//回転
float xRotate = 0;
float yRotate = 0;
float zRotate = 0;

//描画用の変数
//線の太さ
float weight = 0.5;
//線の色
int strokeH = 0;
int strokeS = 0;
int strokeB = 0;

//頂点座標を格納する配列 : x1, y1, z1, x2, y2, z2
int[][] va = {
  {-200,-200,-20, 200,-200,-20},
  { 200,-200,-20, 200, 200,-20},
  { 200, 200,-20,-200, 200,-20},
  {-200, 200,-20,-200,-200,-20},
  {-200,-200, 20, 200,-200, 20},
  { 200,-200, 20, 200, 200, 20},
  { 200, 200, 20,-200, 200, 20},
  {-200, 200, 20,-200,-200, 20},
  {-200,-200,-20,-200,-200, 20},
  { 200,-200,-20, 200,-200, 20},
  { 200, 200,-20, 200, 200, 20},
  {-200, 200,-20,-200, 200, 20}
};

//初期化
void setup()
{
  size(300, 300);
  frameRate(30);
  colorMode(HSB);
  noSmooth();
  noFill();
  background(255);
}

//メインループ
void draw()
{
  background(255);
  //描画ループ
  for(int i=0; i<va.length; i++) {
    //描画
    //x1, y1, z1, x2, y2, z2, rotateX, rotateY, rotateZ
    draw3DLine(va[i][0],va[i][1],va[i][2],va[i][3],va[i][4],va[i][5], xRotate, yRotate, zRotate);
  }
  //回転
  xRotate += TWO_PI/360;
  yRotate += TWO_PI/360;
  zRotate += TWO_PI/360;
}

//透視変換 x1, y1, z1, x2, y2, z2, rotateX, rotateY, rotateZ
void draw3DLine(float x1,float y1, float z1, float x2, float y2, float z2, float rx, float ry, float rz)
{
  //Y軸の回転量を反映
  float z1cash = z1;
  float z2cash = z2;
  z1 = x1 * sin(ry) - z1cash * cos(ry);
  z2 = x2 * sin(ry) - z2cash * cos(ry);
  x1 = x1 * cos(ry) + z1cash * sin(ry);
  x2 = x2 * cos(ry) + z2cash * sin(ry);
  //X軸の回転量を反映
  z1cash = z1;
  z2cash = z2;
  z1 = y1 * sin(rx) - z1cash * cos(rx);
  z2 = y2 * sin(rx) - z2cash * cos(rx);
  y1 = y1 * cos(rx) + z1cash * sin(rx);
  y2 = y2 * cos(rx) + z2cash * sin(rx);
  //Z軸の回転量を反映
  float x1cash = x1;
  float x2cash = x2;
  float y1cash = y1;
  float y2cash = y2;
  x1 = x1 * sin(rz) + y1cash * cos(rz);
  x2 = x2 * sin(rz) + y2cash * cos(rz);
  y1 = y1 * sin(rz) - x1cash * cos(rz);
  y2 = y2 * sin(rz) - x2cash * cos(rz);
  //透視変換
  float x1b = width/2  + x1 * (camZ + z1) * tan(radS/2) / (scZ + z1) * tan(radS/2);
  float y1b = height/2 + y1 * (camZ + z1) * tan(radS/2) / (scZ + z1) * tan(radS/2);
  float x2b = width/2  + x2 * (camZ + z2) * tan(radS/2) / (scZ + z2) * tan(radS/2);
  float y2b = height/2 + y2 * (camZ + z2) * tan(radS/2) / (scZ + z2) * tan(radS/2);
  //描画開始位置を初期化
  translate(0,0);
  //線の色・太さ
  stroke(strokeH, strokeS, strokeB);
  strokeWeight(weight);
  //描画
  line(x1b,y1b,x2b,y2b);
}

Redis 使ってみました。

会社に転がってた、↓ の本をつらつらと眺めていて

今まで、よくわかっていなかった NoSQL という物に興味が湧いてきました。
上記の本は、そろそろ出揃った感のある NoSQL 系のデータストアエンジンを一通り並列に紹介してくれる本で、見て、軽くわかった気になるには最適です。

ただ、結局のところ、いつも使っている SQL と何が違うのかまでは読んだだけでは充分に理解できないよね、やっぱり試してみなくちゃね、という事で、使ってみました。

NoSQL と言えども、データストア!データを保存しなくては。入れるデータの元ネタに困りましたが、困った時は他人のふんどしを借りようと Twitter から適宜引っ張ってくる事にしました。キャズムを越えたらしいという噂の Twitter から URL を含んだツイートを引っ張りだして集計したら、世間で話題になっているネタが拾えそうかなと。誰かが勝手にデータをたれ流してくれるのは収集に持ってこいですし。この辺を思い付きだけでやってしまったのが後で祟る事になるのですが、そこは後述。

保存用のスクリプトは Ruby で書く事にしたので、Redis へのアクセスに redis の gem 、また、Twitter からデータを取ってくるのにTwitter の gem を使います。

Twitter はクライアントなどが自動的に URL を短縮した状態で投稿する事が多いので、それを元 URL に戻す処理も入れる事にしました。

#!/usr/bin/ruby

require "rubygems"
require "redis"
require 'twitter'
require 'uri'
require 'net/http'

$lang = "ja"

# 短縮 URL を元に戻すための処理
def extract_url(url)
  begin
    uri = URI.parse(url)
    # 引数で与えられた URL に対して HEAD メソッドを実行
    Net::HTTP.start(uri.host, uri.port) do |http|
      response = http.head(uri.path, 'User-Agent' => "Redirect Tracker")
      # 実URLだと判断する条件は、以下のいずれかに合致する事
      # ・レスポンスコードが200
      # ・レスポンスヘッダにLocationが含まれない
      # ・レスポンスヘッダLocationが、元URLと同一
      if (response.code == "200") or (response['Location'] == nil) or (url == response['Location']) then
        return url
      else
        moved_to = response['Location'].match(/^https?:\/\//) ? response['Location'] : "#{uri.host}/#{response['Location']}"
        # 二重に短縮されている場合などのために再帰処理
        extract_url(moved_to)
      end
    end
  rescue => e
    puts " === ERROR ( #{url} ) ==== "
    puts e
  end
end

begin
  # ローカルホストの Redis に接続
  redis = Redis.new(:host => "localhost")
  # 前回取得時の最終 Tweet ID を拾う
  last_id = redis.get "meta:last_id:#{$lang}"
  result = nil

  # 前回取得した以降の、日本語のツイートの中から、http という文字列で検索
  Twitter::Search.new.containing("http").since_id(last_id).lang($lang).per_page(100).filter.reverse_each do |result|
    # URL らしき文字列の切り出し
    result.text.scan(/https?:\/\/[-_.!~*'()a-zA-Z0-9;\/?:@&=+$,%#]+/).each do |url|

      # 短縮URL を元に戻す
      exturl =  extract_url(url)
      if (exturl != nil) then
        uri = URI.parse(exturl)
      else
        next
      end

      # 集計系は incr メソッドでアトミックに処理
      id = redis.incr "data:count:whole"
      host_count = redis.incr "data:count:#{uri.host}"
      url_count = redis.incr "data:count:#{uri.host}:#{uri.path}"

      # URL情報をリスト型で記録
      redis.lpush "list:whole", exturl
      redis.lpush "list:#{$lang}", exturl
      redis.lpush "list:hosts", uri.host

      # 念のため、ツイート情報も取っておく / Marshal.dump でオブジェクトを serialize する
      redis.set "data:tweet:#{id}", Marshal.dump(result)
    end
  end
rescue => e
  puts e
ensure
  # 最後に処理した Tweet ID を記録しておく
  redis.set "meta:last_id:#{$lang}", result.id
end

上記スクリプトを、cron で定期実行して、情報を収集しておきます。
読み返してみたら、検索結果を Reverse_each した上でリストに lpush してるけど、素直に each & rpush したらよかったんじゃないだろうか…。

表示系は Sinatra で間に合わせ。

require 'rubygems'
require 'sinatra'
require 'redis'
require 'uri'

set :haml, {:format => :html5}

before do
	@redis = Redis.new(:host => "localhost")
end

get %r{/([0-9]+|all)?} do
        # 全データ件数を取得
	@total_count = @redis.get "data:count:whole"
	@datas = []
        # リスト長を取得(アレ?上の全データ件数と同一では?)
	length = @redis.llen "list:whole"

        # データ表示件数を取得。デフォルトは 100 件で、指定があれば、その件数。all なら全件
	limit = 100
	if params[:captures] != nil then
		if params[:captures].first == "all" then
			limit = length
		else
			limit = params[:captures].first.to_i
		end
	end
        # downto に渡す都合で加工。リスト長が指定件数に満たない場合はリスト長を限界にする
	limit = length - (length < limit ? length - 1 : limit - 1)

        # 表示用データを配列に突っ込む
	length.downto(limit) do |i|
		url = @redis.lindex("list:whole", length - i)
		begin
			uri = URI.parse(url)
			count = @redis.get "data:count:#{uri.host}:#{uri.path}"
		rescue
			count = 0
		end
		@datas << { :url => url, :count => count }
	end
	haml :index
end

__END__

@@ layout
%html
	%head
		%title twitter url counter
	%body
		= yield

@@ index
%h1 twitter url counter
%a{:href => "/hosts"} host oriented
%div
	There are #{@total_count} datas here.
%div
	- @datas.each do |item|
		%div
			#{item[:count]} :
			%a{:href => "#{item[:url]}"} #{item[:url]}

所定の件数を Redis から取得して表示するだけです。

これで、こんな表示が出来るようになりました。

しかし、KVS ほぼ初体験な自分には「ほぼハッシュだからそのつもりで使えよ」という概念がどうも理解されていなかったようです。こんなランキング系のコンテンツなのにソートの機能を持たない list 型に突っ込んでいるのが致命的。ダラダラ出すのには使えますが、それ以外の表示方法の可能性がほぼスポイルされてしまいました。

上記の本の中にあったベンチマークのデータでは、”スピードの向こう側”に突き抜けそうなブッチ切りの爆速を示していた、Redis の速度も、集計時に、URL のリダイレクトを辿るケースが頻発する今回のプログラムではネットワークの遅延が問題になってしまって、あまり恩恵受けれず…。結局何のための Redis なのか、というところが全く見えませんでした。使ってみた、というのが唯一の成果。ちゃんとアドバンテージとディスアドバンテージを見極めた上で新技術を導入しましょうね!というお話でした。

次回は今回の反省を活かして、データストアを MongoDB とかにしてみる予定!!


参考文献

jQueryでグラフを書く

集計データをグラフ化したいなー、ということでどういう道具が使えるの探してみました。
以前、河合さんがFlashで描くライブラリについて取り上げていたのだけれど、iOS系で表示できないとやだね、ということで、Flashじゃないものを探してみました。

で、まずは、Google Chart API
Googleさんが、Web baseのAPIとして提供しているもの。Analyticsの中でも使われているとか。
GETのQUERY STRINGで値を渡すらしい。描画はサーバサイドの画像処理ですね。
データが外に漏れるのを嫌うシチュエーションでは使いにくそうです。

JavaScript系はどんなかなというところで、出てきたのがflot
こちらは、jQuery系プラグインで、シンプルにまとまっていてよさそう。Canvasに描画するんですね。
使うのも比較的簡単。divのプレースホルダをもってきて、$.plot() というメソッドに、データと
オプションの配列と一緒に渡す。シンプル。

コードはこんな感じ

$(document).ready(function() {
	var data_results_cumulative = [[1301270400000,0],[1301875200000,8.25],[1302480000000,22],[1303084800000,29],[1303689600000,36.5]];
	var data_plan_cumulative = [[1301270400000,12],[1301875200000,24],[1302480000000,36],[1303084800000,48],[1303689600000,60]];
	var data_firstplan_cumulative = [[1303689600000,16]];
	var ticks = [1301270400000,1301875200000,1302480000000,1303084800000,1303689600000];

	$.plot($("#graph"), [
		{ label: "results", data: data_results_cumulative, color: 2},
		{ label: "plan", data: data_plan_cumulative, color: 0},
		{ label: "first plan", data: data_firstplan_cumulative, color: 1},
	],
	{
		series: {
			lines: { show: true },
			points: { show: true }
		},
		legend: {
			position: "nw"
		},
		xaxis: {
			mode: "time",
			timeformat: "%y-%m-%d",
			tickSize: [7, "day"],
			ticks: ticks
		},
		yaxis: {
			ticks: 10,
			min: 0,
		},
		grid: {
			backgroundColor: { colors: ["#fff", "#eee"] },
		}
	});

});

Titanium Mobile 勉強会に参加してきました

どうもこんにちは!みなさま iPhone アプリしてますか?!僕は未だにガラケーユーザです (涙) iPhone 5 が出たら本気をだそうかと思ってます。

そんなわけで 2/25 に行われた株式会社コンテンツワンさん主催の勉強会 Titanium Mobileで始めるiPhone / Androidアプリ開発 に参加してきました。

下書きのまま放置してしまい今更感もあるのですが、会場で取ったメモにちょっと加筆したものを公開します。


  • 講師 masuidrive (blog)
  • Titanium Mobie の実績
    • WikiWalker (iPhone)
      • 位置情報からゆかりの地を Wikipedia から検索してくれる
    • なごや乗換ブラウザ (iPhone)
      • 乗り換え案内アプリ
    • Android 映画情報「myシアター」 (Android)
      • もともとは iPhone 向けに Objective-C で書かれた
      • Android 版を製作するに当たって Titanium Mobile で書かれる (3 週間で完成)
    • TOKINOWA
      • ちょっとおしゃれな時計アプリ
    • はてなカウンティング
      • はてな謹製
    • MogSnap: 食べ物の写真を投稿
      • 食べ物の写真を撮影して投稿
      • ソーシャルゲームっぽい
      • 相手に食べたいなあと同調してもらえるとポイントアップ
      • アニメーションがリッチ
    • AKB前田敦子 Maeda-1 グランプリ (Android)
    • Follow me now (iPhoneAndroid)
      • 位置情報を使ってアプリを起動しているユーザ同士をフォローできる
      • masuidrive 謹製
      • 本当は今日のデモ向けにつくっていたが作り込みしていって話しきれない量になってしまった
      • 近々オープンソース化する予定
  • Titanium Developer
    • JavaScript で iPhone/Android クロス開発
    • IDE じゃないよ
    • Aptana 社買収して Full feature IDE を 3 月後半リリース予定
    • 今は printf デバッグぐらいしかできない。今後 IDE にデバッガが搭載される予定 (時期未定)
    • 原則 iPhone SDK の Beta 版には対応しない
    • SDK 複数バージョン入れてる場合は xcode-select で最新版を選んで
    • Android SDK Tools R8 以降では adb をコピー
    • C/Java で書かれたモジュールをロードする場合 manifest, tiapp.xml を編集
  • JavaScript を Objective-C/Java にトランスレートしているわけではなくインタプリタで実行している
    • OS の機能を JavaScript から簡単に呼び出せるところが Titanium のコアとなる技術
    • iPhone 版のインタプリタは WebKit 由来の JavaScriptCore (規約避けの意味もあった)
    • Android のインタプリタは Rhino を搭載
  • Titanium SDK 1.6
    • つい先日出た
    • 1.5 以前は iPhone に比べると Android のサポートはいまいちだった
    • Titanium SDK 1.6 から Android サポートがかなり良くなった、おすすめ
  • MogSnap の事例
    • 99% は JavaScript のコード
    • 残りは JPEG の圧縮率を変える処理 (Objctive-C で 10 行ぐらいのコード)
    • アニメーション・エフェクトは 2D Transform, 3D Transform が使える
    • Ti.UI.createAnimation(); / Ti.UI.create2DMatrix();
    • Ti.UI.createView() div タグみたいなやつ
  • Titanium Mobile の弱点
    • サンプル不足
      • リファレンスは比較的充実しているがチュートリアルがあまり無い
      • デモアプリの KitchenSink のコードが参考になる
    • リアルタイム性の高いものは厳しい
      • ゲームなどで大量のパーティクル飛ばすとか将棋の思考ルーチンとか
    • 画像処理 API が無かったり
      • ない部分、苦手な部分は Java/Objective-C で Module として書けば OK
      • masuidrive 氏が以前作ったコミックビュアーは通信部分などは JavaScript, UI は Objective-C で書いた
      • モジュールとして作れば JavaScript から Objective-C を呼び出したりその逆も出来る
  • 開発スタイル
    • 1 Window = 1 Source: Window 間の連携は苦手。複数人開発だと有利。変数の共有は Ti.App で。
      • Ti.UI.createWindow({ title: “..”, url: “hoge.js” }); で hoge.js に書いた Window のコードを呼び出し
    • 1 Project = 1 Source: 変数の共有や Window 間の連携が得意。複数人開発には向かない。
    • 併用も出来るっぽい?
  • 通信系
    • XHR のクローンとして Ti.Network.HTTPClient というものが用意されている
    • YQL 標準搭載 (XML -> JSON の変換も容易)
    • 余談: Echofon とかの引っ張ってリロードするやつは Pull to Refresh というらしい
  • Android と iPhone ではアイコンのサイズが違う
    • それぞれ専用で作ってあげると良い
    • フリーの Android アイコン配布サイト Android Icons
  • ローカルストレージ
    • SQL Database (SQLIte)
    • ファイルにも保存できる
  • コーティングインジケータを通信していない時でも回したい → モジュールを書く
  • RouteMe オフラインで地図見れる
  • AirPrint 対応したい → Titanium でサポートしていない → モジュールを書く
  • 足りない機能はモジュールを書く
  • Modern JavaScript
    • Titanium Mobile は JavaScript 1.6 が使える
    • CommonJS
      • hoge.js: exports.foo = function() { … }
      • var hoge = require(“hoge”); hoge.foo();
      • createHogehoge = function() { if (android) return new createAndroidHogehoge(); else if (iphone) return new …
    • Deferred, Promise, Emitter
      • callback でもネストが深くならない
      • でぃふぁーれっど (でぃふぁーどじゃないらしい)
      • JSDeferred は Titanium でも使えるらしい
  • ドキュメントやリソースなど
    • http://code.google.com/p/titanium-mobile-doc-ja/
      • API ドキュメントの日本語の翻訳
      • 何故か本家のコードの間違いが翻訳では直っていることがある
      • 何故か本家よりスクリーンショットが充実していることもある
    • http://tidocs.com/
    • WebDB Press Vol. 61 の特集
    • ぐぐる
      • 情報の賞味期限は 4〜6ヶ月
      • それより古い情報は食べたらおかなをこわします (笑)
  • Q. jQuery の便利な機能を使うことはできるか。jQuery.each(), 等の DOM に依存しないものとか。
    • A. Titanium の JavaScript は 1.6 相当なので、Array.prototype.filter (jQuery.grep に相当), Array.prototype.map (jQuery.map に相当) など、標準化されているものを使えば OK
  • Q. 描画系でリアルタイム処理するときはどうする?
    • A. Objective-C で UIView を継承したクラスに描画してモジュールにして Titanium から呼び出せば良い
    • コミックビュアーを作ったときはそうした

懇親会のかつサンド美味かった!というわけで以上!

iOS 4.3 で使えるようになったthird-party appからのAirPlay呼び出しを試してみる

iOS 4.3の目玉機能の一つ、third party appからのAirPlay利用について、コードを書いてテストしてみました。

といっても、難しいプログラミングが必要な訳ではなく、今まで動画を再生するのに使っていた MPMoviePlayerConntroller に追加された allowsAirPlay というプロパティをYESに設定するだけ。
これで、動画再生のコントローラの並びにあのAirPlayボタンが追加され、それをクリックすると出力先のリストが表示されるようになります。

今回はとりあえず試してみようということで、以下のようなコードを、ViewControllerのloadViewのところに直接書いてみます。

- (void)loadView {
CGRect bounds = [[UIScreen mainScreen] applicationFrame];

NSURL* url=[NSURL URLWithString:@"http://dev.uniba.jp/~jun/apt/test.m4v"];

MPMoviePlayerController *movie=[[MPMoviePlayerController alloc] initWithContentURL:url];

movie.scalingMode=MPMovieScalingModeAspectFit;
movie.allowsAirPlay = YES;

[movie.view setFrame:bounds];
self.view = movie.view;

[movie play];
}

これでビルドしてSimulatorで実行してみましたが、AirPlayボタンが出てきません。どうやら、SimulartorはAirPlayに対応していないようです。(リファレンスにも対応したDeviceで、と書かれています)

気を取り直して、実機をUSBでつないで今度はターゲットをDeviceにしてビルド。しばらく待った後に、実機でアプリが起動し、プレイヤーが起動、AirPlayボタンが表示されました。

AirPlayボタンをクリックして、AppleTVを選択してみましたが今度は「Cannot play video on “Apple TV”」というアラートが表示されます。もしやと思って確認してみると、Apple TV側でソフトウェアアップデートのガイダンスが表示されています。Apple TV側のソフトウェアバージョンが4.1系だったのですが、こちらも最新のバージョンにアップデートする必要がありました。

Apple TV側のソフトウェアをアップデートすると、ちゃんと先ほどのコードで、映像を飛ばすことができました。

アプリからのAirPlayの呼び出しは、MPMoviePlayerController のプロパティということで、今のところAirPlay経由で飛ばせるのは、MPMoviePlayerControllerがサポートしている動画ファイルということになるようです。AirPlayのプロトコルは、静止画もサポートしているようですが、こちらはAPIとして利用できる状態にないようです。

preload preload preload