QCGainer : Quartz Composer Gainer plugin

June 6th, 2008  |  Published in Uncategorized

RubyCocoaでQuartzComposer プラグインをつくれるようになった ので、さっそくつくってみました。 みんな大好きGainer の入力を QuartzComposer からとれるようにしました。

ソースコード

github:qc_gainer に。MITライセンスです。 , osc を使っています。

短かいので、コードをはっておきます。ごさんこうに。

#

qc_gainer.rb



#

Created by mootoh on 6/6/08.


Copyright (c) 2008 deadbeaf.org. All rights reserved.



# require 'osx/cocoa' OSX.require_framework 'QuartzComposer' require 'funnel'
class QCGainer < OSX::QCPlugIn def self.executionMode 2 end
def self.timeMode 1 end
def initialize = false = Funnel::Gainer.new(Funnel::Gainer::MODE1)
 = [0,0,0,0]
 = [0,0,0,0]
 = [0,0,0,0]
 = [0,0,0,0]


end
def startExecution(context) Thread.new do sleep 0.1 4.times { |i| addInputPortWithType_forKey_withAttributes(OSX::QCPortTypeNumber, "aot_" + i.to_s, nil) } 4.times { |i| addInputPortWithType_forKey_withAttributes(OSX::QCPortTypeNumber, "dot_" + i.to_s, nil) } 4.times { |i| addOutputPortWithType_forKey_withAttributes(OSX::QCPortTypeNumber, "ain_" + i.to_s, nil) } 4.times { |i| addOutputPortWithType_forKey_withAttributes(OSX::QCPortTypeNumber, "din_" + i.to_s, nil) } end true end
def execute_atTime_withArguments(context, time, args) unless 4.times do |i| (i).on Funnel::PortEvent::CHANGE do |event| [i] = event.target.value end (i).on Funnel::PortEvent::CHANGE do |event| [i] = event.target.value end end = false end
4.times do |i|
  setValue_forOutputKey([i], "ain_" + i.to_s)
  setValue_forOutputKey([i], "din_" + i.to_s)
end

true


end
def stopExecution(context) true end end

使い方

  1. Gainer I/O をMacとつなげる
  2. Funnelサーバを起動
  3. Quartz Composerを起動
  4. Pluginを配置

デモ

ビデオに撮るのがめんどうなので、Ruby会議 に持っていきます。

まとめ

こんなふうに、ありものをくっつけてささっと何かをつくるというとき、glue言語としてRubyはグッドですね。

Happy Physical Hacking !

Tags: , ,

Leopard で Quartz Composer カスタム Plugin を RubyCocoa でつくる

June 6th, 2008  |  Published in Uncategorized

RubyCocoaで、QuartzComposerのカスタムプラグインをつくることができました on Leopard。 LeopardでのQuartzComposerカスタムプラグインづくりの続きです。あれから4ヶ月。

サンプル: qcplugin_rb

サンプルとして、2入力をとってその間に “Ruby” を挿入して後段に流すというプラグインを書きました。 githubにアップしてあります。MITライセンスです。

ソースコード: github:qcplugin_rb

スクリーンショット

screenshot

ポイント

  • Info.plistの書きかた
  • 入出力ポートの追加方法

Info.plistの書きかた

1.まず、Info.plist の NSPrincipalClass エントリに、RubyCocoaのBundleを初期化するObjective-Cのクラスを指定します。 RubyCocoaでMacのプラグインをつくるときの共通tipsですね。

2.それと、実際のプラグインの動作を記述するクラスを QCPlugInClasses に列挙します。QCPlugInクラスを継承したやつですね。

    NSPrincipalClass
    RBLoader    <-- 1
    QCPlugInClasses
    
        QCRubyPlugin <-- 2
    

入出力ポートの追加方法

LeopardのQuartzComposerから、ポートの指定は Objective-C 2.0 の property 機能を使って行うようになりました。 これをRubyCocoaからどう使うか分からなかったので、QCPlugInクラスのメソッドであるaddInputPortWithType:forKey:withAttributes:addOutputPortWithType:forKey:withAttributes: を使ってなんとかします。

では、どこにこれらのメソッドを書くか。QuartzComposerの制約で、executeメソッド内に書いちゃダメよ、とあるので困ります。 サンプルコードを漁ってみたところ、別スレッドを立ててそこで呼び出すのはOKぽい。 そこで、RubyのThreadをつかってやってみたところ、うまくいきました。ただし、ちょっとdelayを設けてやらないとQuartzComposerが死にます。なぜだ。

もっとうまいやり方をご存知のかたがいれば、ぜひ教えてください。 m(_ _)m

ひっかかりどころ

バッドノウハウがいくつかありますね。

  • s/QCPlugin/QCPlugIn/ なのです。見落しやすすぎる。
  • ポート追加のdelay。
  • メソッド名。execute が execute_atTime_withArguments になったりします。気づかずにいると、いつまでたってもメソッドが呼ばれずに悲しくなる。
  • イチからXcodeのプロジェクトをつくるよりも、サンプルを元に書き換えていくとはまりにくい。

おまけ

こう書くとすんなり動くようになったみたいですが、現実は泥のようなデバッグでした。 RubyCocoaのフレームワークをデバッガで追ったり、QuartzComposerの謎の挙動に悩まされたり。 フレームワークをつかった開発でのデバッグについては得るところがあったので、別に書くことにします。

関連エントリ

  • RubyCocoa で QuartzComposer CustomPatch をつくりたい
  • RubyCocoa で QuartzComposer CustomPatch (2)

まとめ

Happy Visual Hacking !

Tags: ,

LeopardでのQuartzComposerカスタムプラグインづくり

February 19th, 2008  |  Published in Uncategorized

Ruby会議2008CFPとして、RubyCocoaでインタラクティブにMacのプラグインをつくるよ、みたいなものを書いて出してみました。 出してから、そういえばLeopardになってからQuartzComposerで遊んでないなあと気づき、ちょっと触ってみたらえらく変わっていたので、今日調べたことをカスタムプラグインをつくるという観点でメモっときます。

カスタムプラグインがオフィシャルに

最大の変更点はこれです。 Appleの丁寧なドキュメント Introduction to Quartz Composer Custom Patch Programming Guide を読めば、たちまちつくれるようになるのではないかと。サンプルも充実してて、/Developer/Examples/Quartz\ Composer/Plugins/にごろごろ転がってます。 これまで、kineme.net:Xcode Template for Custom Quartz Composer Patches を使い、非公開のAPIでプログラミングしていたのに対して大きな進歩です。

Port指定にはpropertyをつかう

Objective-C 2.0で導入された、propertyという機能をつかって入出力Portの指定をコードでやるようになっています。 これまでは、インスタンス変数名の先頭にinput/outputがあれば、それがPortになっていました。

Portの増減が動的にできる

RubyCocoaでpropertyを使う方法を知らないのでどうしようかなあと思ってたのですが、 addinputPortWithTypeとかaddOutputPortWithTypeというメソッドを使えば動的にPortが加えられます。

注意すべきことは、これらのメソッドはプラグインのexecuteメソッドに入れてはいけないことです (例外があがってQuartzComposerが止まってしまいます)。んじゃどうするかということで、ExampleのCommandLineToolFreeFrameHostを調べてみると、どうやらQuartzComposerのインスペクタから受け取るイベントのハンドラで、これらのメソッドを呼ぶようにしています。なるほど。

インストール場所が変わっている

これまでは、/Library/Graphics/Patch にプラグインを置くのが流儀だったのですが、Leopardからは/Library/Graphics/Quartz\ Composer\ Plug-Ins/ になりました(~/Library以下でもOK)。


カスタムプラグインをつくるという点から、Leopardになって気づいたことをまとめました。次はいよいよ、RubyCocoaでカスタムプラグインづくりです。

関連エントリ

  • RubyCocoaでQuartzComposerパッチ
  • SocketReaderPatch
  • IRCのメッセージをQuartzComposerに表示
  • QuartzComposer の CustomPatch をつくるための Xcode template
  • QuartzComposer の CustomPatch で Signal を生成する方法
  • ApacheLogPatch 0.1
  • RubyCocoa で QuartzComposer CustomPatch をつくりたい
  • RubyCocoa で QuartzComposer CustomPatch (2)
Tags: , ,

RubyCocoa で QuartzComposer CustomPatch (2)

July 15th, 2007  |  Published in hack

とりあえず、できました。

スクリーンショット

サンプル

input, output ともにStringのPortを1つずつもち、入力にある”Objective-C”を”Ruby”に正規表現で置換するというだけのサンプルを書きました。

RubyPatch (binary & source) rev3 (i386 Binary)

RubyCocoaをUniversal Binary でビルドしていなかったため、いまのところIntel Mac だけで動きます。

RubyCocoa の svn trunk、 MacBook Kuroで動作確認をしています。

何をしているか

  1. Patchがロードされるとき、すなわち “registerNodesWithManager” が呼ばれるときに、RBBundleInitを呼んでRubyCocoaの準備をする
  2. Patchの動作を記述するObjective-CのクラスをProxyとして、実際の動作を記述するRubyクラスのインスタンスを保持させる
  3. Rubyクラスは、Objective-CクラスからPortをもらってくる
  4. Patchを実行するイベントがきたら、Rubyクラスのインスタンスに対して処理を移譲

といったところ。

実際のコードは、RubyQC::Trac にあるのでご覧ください。

このアプローチの問題点

  • いちいち全メソッドを Objective-Cのクラス → Rubyのクラス に投げるよう書くのがめんどう
  • Portの記述がObjective-Cのクラスにしか書けない (みたい) なので、Portを加えたり減らしたりするたびにObjective-C/Rubyコードともに変更しないといけない。わすれそう。

OSX::QCPatchクラスをRubyクラスで継承できて、さらにPortの情報もRubyクラスで書けるようになれば、これらの問題は解決しそうです。

課題

QCPatch.execute は、3つの引数をとるのですが、Rubyクラスのメソッドにどうやって3引数を渡すのか分からない…

18:03 追記

MLで、LimeChatの中の人に教えてもらいました。

objc_method :execute_time_arguments, %w|char id double id|
def execute_time_arguments(fp8, fp12, fp20)
  ...
end
のようにすればいいよ、とのことで、ばっちり動きました。ありがとうございます。


とはいえ、CustomPatch がRubyで書けるようになったのは大きな進歩です。ApacheReaderPatch とかも、ずいぶんと簡単に書けそう。

Tags: , , ,

RubyCocoa で QuartzComposer CustomPatch をつくりたい

July 14th, 2007  |  Published in Uncategorized

先に今の状態 : できそうな糸口がつかめたところで挫折 :(

SocketReaderPatch、ApacheLogPatch とQuartzComposerのカスタムパッチをつくってきたのですが、ここらでRubyで書くかと思い立ちました。 バベル案内ホワイの(感動的)Rubyガイド を読んで、こんなにもRubyが世界に受け入れらているのかと感動したことと、いくつかCustomPatchをつくってみて、CustomPatchj作成の概観がつかめてきたから。

どうやるか

RubyCocoaのsvn trunkを使います。 最近のリビジョンでは、QuartzComposer.frameworkが使えるようになっているのです。


require 'osx/cocoa' r=> true OSX.require_framework 'QuartzComposer' => true OSX::QCStructure => OSX::QCStructure OSX::QCPatch => OSX::QCPatch



ひゃっほう。
で、いつもどおりQuartzComposer CustomPatch の Xcode template をつかって、Xcodeプロジェクトをはじめます。
RubyCocoa.frameworkをリンクしておき、RubyPatchPrincipla.m を

のように書きました。
RubyCocoaのbundle生成用APIを用いています。


loadされるRubyスクリプトはこういうもの。
require 'osx/cocoa'
OSX.require_framework 'QuartzComposer'


class RubyPatch < OSX::QCPatch #attr_accessor :inputFoo, :o utputBar
def RubyPatch.logger=(l) @ = l end
def RubyPatch.executionMode @("executionMode") 2 end
def RubyPatch.allowsSubpatches @("allowsSubpatches") 0 # in objc.h, NO is defined as 0 end
def RubyPatch.timeMode @("timeMode") 1 end
def init @("init") = OSX::QCBooleanPort.alloc.init end
def initWithIdentifier(fp8) @("initWithIdentifier") init self end
def setup(fp8) @("setup %s, %s", fp8, fp8.class) fp8 end
def cleanup(fp8) @("cleanup") end
def enable(fp8) @("enable") end
def disable(fp8) @("disable") end
def execute(fp8, time, arg) @("execute") true; end end
OSX.init_for_bundle do |bundle, param, logger| logger.info("init bundle=%s param=%s", bundle, param) RubyPatch.logger = logger end


templateが生成するObjective-Cのコードを、Rubyに書き下したものですね。

実行



ビルドもすんなり通り、さっそく実行してみます。
qc-exception-rubycocoapatch.png
アウチ。 なんか例外が投げられてしまいます。 出しているログを眺めてみると…

正常なもの (Objective-Cで書いたカスタムパッチ)



2007-07-14 14:35:03.568 Quartz Composer[15580] RubyPatch.plugin (Quartz Composer): init bundle=NSBundle  (loaded) param=
2007-07-14 14:35:03.708 Quartz Composer[15580] initWithIdentifier
2007-07-14 14:35:03.708 Quartz Composer[15580] executionMode
2007-07-14 14:35:03.708 Quartz Composer[15580] timeMode
2007-07-14 14:35:03.710 Quartz Composer[15580] allowsSubpatches
2007-07-14 14:35:03.741 Quartz Composer[15580] allowsSubpatches
2007-07-14 14:35:03.741 Quartz Composer[15580] allowsSubpatches
2007-07-14 14:35:03.741 Quartz Composer[15580] allowsSubpatches
2007-07-14 14:35:03.785 Quartz Composer[15580] setup

だめなもの (RubyCocoaで書いた)



2007-07-14 14:36:11.300 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): init bundle=NSBundle  (loaded) param=
2007-07-14 14:36:11.301 Quartz Composer[15602] assert passed !
2007-07-14 14:36:11.342 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): initWithIdentifier
2007-07-14 14:36:11.342 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): init
2007-07-14 14:36:11.345 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): allowsSubpatches
2007-07-14 14:36:11.377 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): allowsSubpatches
2007-07-14 14:36:11.378 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): allowsSubpatches


よく見てみると、RubyCocoaで書いた方は、initWithIdentifier のあとに executionMode, timeMode が呼ばれることなく allowsSubpatches が呼ばれています。 executionMode, timeMode が分からないので、QuartzComposer としてはこのパッチをどの種類にして良いか分からず、レンダリングできませんよーという例外がきてるっぽい。

問題



ではなぜ executionMode, timeMode が呼ばれないか、考えてみます。
QCPatchのヘッダによると、executionMode, timeMode はそれぞれ int を返すメソッドのようです。 ところが、RubyCocoaで用意した executionMode, timeMode は返り値の型を指定していません。 、RubyCocoaではRubyのメソッドが整数型を返すとき、Objective-Cの世界にはNSDecimalNumberの型を返すようになっているようです。
その結果、executionMode, timeMode は別のメソッドとして認識されてしまい、QCPatchのメソッドをオーバーライドできず、呼ばれていないのではないかと推測しました。

解決案


  1. intで返すようにRubyCocoaでなんとかする
  2. QCPatchをRubyクラスで継承せずにObjective-Cのクラスで継承させ、そのメンバにRubyクラスのdelegateのオブジェクトをもたせて、実際の処理はdelegateオブジェクトに振る


2が手っ取り早そうですが、いまひとつかっこいくないなぁ。

別の問題



CustomPatchのテンプレートによると、QCPatchを継承したクラスのメンバに QCPortクラスのインスタンス (ex. inputEnable, outputString など) を書くことで、パッチのinput/outputポートを指定できるような仕組みになっています。
じゃあ、
attr_accessor :inputEnable, :o utputString
...
みたいにすればいいのか、というとどうもそうでもないみたいで…


やはり解決案2を使うしかないのかもです。

import


imporut "RubyPatchPrincipal.h"



RubyPatchPlugin + (void)registerNodesWithManager:(GFNodeManager*)fp8 { static bool loaded = false; if (!loaded) { if (RBBundleInit("/tmp/custom_patch.rb", self, nil)) { NSLog(@"[RubyPatchPlugin.registerNodesWithManager] RBBundleInit failed"); } else { loaded = true; } }
Class helperClass = NSClassFromString(@"RubyPatch"); [fp8 registerNodeWithClass:helperClass]; }
Tags: , ,

ApacheLogPatch 0.1

July 9th, 2007  |  Published in hack

ApacheLogPatch というものをつくりました。 Apacheのログを、Quartz Composer で派手に tail -f するというものです。 パス、リモートホストのアドレス、リファラー、検索キーワードが表示されます。

Apacheの設定ファイルを書き換えたりと動くようにするまでの敷居が妙に高いわりには、動いたときの「おー、ふーん」感がなんともいえませんが、動かしてみようという方はどうぞぜひ。

自宅サーバをMacで動かしている方という、たいへん狭いターゲットを狙いました。 スクリーンセーバーとして動かしておき、自分のサーバに人がどんな検索キーワードできているのか、なんてことを夕食どきにご飯を食べながら眺めてみるのも一興ではないでしょうか。

ビデオとスクリーンショット

ビデオは、本当はDVカメラで撮影したちゃんとしたものがあったのですが、どうもFirewireからの取り込みができず挫折して vnc2swf を使いました。ので、フレームレートが悲惨なことになっており、本当にこれ、動かして楽しいの? みたいなことになってます。楽しいんですよ。

Python版のvnc2swfで録り直しました。だいぶ見れるレベルになったのでは。

Tags: , , ,

ApacheLogPatch

July 9th, 2007  |  Published in Uncategorized

Apache の ログ を、リアルタイムに QuartzComposer で表示するものをつくりました。 tail -f /var/log/httpd/access.log の代わりにつかう、みたいな。

Download

httpd2qc package (Universal Binary, tested on Tiger)

Screencast

プライバシーをまもるために、リモートホストのIPアドレスは “xxx.xxx.xxx.xxx” にしています。実際にはばっちり表示されます。

Quick Start

  • ApacheLogPatch.plugin を、/Library/Graphics/Patches にコピー
  • Apacheの設定ファイル (httpd.conf とか) に、filter.rb へ カスタムログ を パイプするような設定を加える
    例:
      LogFormat "%h \"%{Referer}i\" %U" httpd2qc
      CustomLog "|/opt/local/httpd2qc/filter.rb" httpd2qc
  • ApacheLogDisplay.qtz を 開く
  • Apache を restart
  • 眺める

Detail

overview.png

  1. ApacheのカスタムログをパイプでRubyスクリプトに流しこむ
  2. Rubyスクリプト (filter.rb) は、文字列のURI デコードなどをして、QuartzComposerのカスタムパッチ用にデータをつくる (ここでもうちょっと賢いことができそう)
  3. QuartzComposerのカスタムパッチ (ApacheLogPatch) は、流れてきたデータをQCStructureのスロット (とりあえず3つ) に詰め込む

ApacheLogPatch は、QCStructureのオブジェクトを出力します。実際には、[リクエストパス、リモートホストのアドレス、リファラー、検索キーワード] の要素をもつQCStructure の配列になっています。

より詳しくはソースを。

ToDo

  • 背景に世界地図を描いておいて、ログが来たときにIPアドレスから地理情報を取得して、それらしき場所を点滅させる (IP2Location とかをつかえばよさそう)
  • もうちょっとカスタマイザブルに
  • もうちょっとかっこよく

きっかけ

Google本社では、検索キーワードがリアルタイムに画面を流れているのが見れるとか聞きました。僕が行ったときには、そんなディスプレイは見当たらなかったのだけれど。

せっかく自宅サーバをMacで動かしているのだし、なにか面白いことはできないもんかなー、と風呂でぼんやり考えていて、最近遊んでいる QuartzComposer の CustomPatch と組み合わせてみたら? と。

Tags: ,

QuartzComposer の CustomPatch で Signal を生成する方法

July 5th, 2007  |  Published in hack

QuartzComposer の Patch には、Signal という 一瞬だけ True になったあとまたすぐ False になるような Port をもつものがあります。たとえば、StopWatch とか、Counterとか。

では、どのようにして CustomPatch で Signal を生成すればよいか、いろいろ試行錯誤してみたので、メモを残しておきます。

Port

 SomePatch : QCPatch {
  QCBooleanPort *outputSignal;
  ....
}
のように、Boolean な Port を宣言しておきます。

Configuration

まず、CustomPatch の timeMode を 1 にして、常に Patch が実行されるようにしておきます。

+ (int)timeMode { return 1; }

としておけばよいでしょう。

ここらへんの詳細については、QCPatch Configuration に書かれてあります。

Signal を発生させる

1〜2回、execute が実行される間に outputSignal の値が True になっていれば、Signal が発生したとみなされるようです。

- (BOOL)execute:(id)fp8 time:(double)fp12 arguments:(id)fp20 {
  static int count = 0;


// signal hack if (TRUE == [outputSignal booleanValue]) { if (1 == count++) { [outputSignal setBooleanValue:FALSE]; count = 0; } }
return YES; }

みたいにしました。[outputSignal setBooleanValue:TRUE] にするのは、どこか別のスレッドにてやっています。

まとめ

俺Patchで Signal が送れるようになれば、データが変わったタイミングを後段のPatchに伝えることができて、なにかと使えそうです。RSS Feed Patch や、Image Downloader も、FeedやImageのダウンロードが終わったタイミングをSignalで通知しているわけで。

おまけ

QuartzComposer の CustomPatch をデバッグするには、NSLog が便利です。 というか、つくった Patch は plugin 形式なので、いつものようにデバッガを直接起動することができないので。

NSLogのログは、 /Library/Logs/Console/”自分の uid”/console.log に出力されるので、tail -f などとして眺めるとグッドです。

Tags: , , ,

QuartzComposer の CustomPatch をつくるための Xcode template

July 3rd, 2007  |  Published in Uncategorized

の0.2が公開されてました

なにが素晴らしいかって、

  • より多くの種類の Port ( Image, Structure, …)
  • ビルドしたら、自動的に /Library/Graphics/Patch につくったPatchがインストールされる

というところ。

以前のもの よりも格段に使えるものになっているので、QuartzComposerで俺パッチをつくるぜ! という方はぜひインストールしてみればよいと思います。

Tags: , ,

IRCのメッセージをQuartzComposerに表示

June 17th, 2007  |  Published in hack

SocketReaderPatchの応用例として、IRCで流れるメッセージを、QuartzComposerに表示させてみました。

qcbot

ScreenCast (QuickTime)

ダウンロード

qc.rb

インストール

  • を Rubygem などでインストールする
  • インストールしたrbotのディレクトリ (Rubygemの場合は /opt/local/lib/ruby/gems/1.8/gems/rbot-0.9.10) の /data/rbot/plugins/ に、qc.rb をコピーする

つかいかた

  • SocketReaderPatch を使うQuartzComposerのCompositionを開いておく (サーバ側のSocketをlistenしておく)
  • 適当なサーバ、チャンネルにrbotを常駐させる (rbotのマニュアルを参照)

これで、QuartzComposerにIRCのログが表示されるようになります。

コード

たったこれだけ。

    require 'kconv'

class QCPlugin < Plugin
  PORT = 12345

  def initialize
    super
     = TCPSocket.new('localhost', PORT)
  end

  def listen(m)
    return unless m.kind_of?(PrivMessage)

     Kconv.toutf8(m.message)
  end

  def help(plugin, topic="")
    "QuartzCompositor from IRC log"
  end

  def privmsg(m)
    #puts m.message
  end
end
plugin = QCPlugin.new
plugin.register("qc")


その他

元々は、Ruby会議2日目に思いついたネタだったので、かれこれ1週間たってしまいました。長かった。 RubyCococaでやろうとか、dRubyだとか何かと野心的にアプローチをとってみたのですが、けっきょくはSocketでごりごり書くというオーソドックスなスタイルに。

それにしても、SocketReaderPatchをつくるときにもqc.rbのときにも、オブジェクト指向の生産性の高さを実感しました。 既存の振舞いをほんの少しカスタマイズしたいときの継承の威力というか。

Tags: IRC, ,