ラベル JavaScript の投稿を表示しています。 すべての投稿を表示
ラベル JavaScript の投稿を表示しています。 すべての投稿を表示

2012年2月13日月曜日

Audio Data APIのサンプル

JavaScriptでサウンド合成

HTML5のおかげでFlashが不要になると言われています.では,これまでFlashを使わなければならなかった用途に何があるでしょうか?グラフィックス描画,フォント,アニメーション,通信などがメジャーなところですが,それ以外にもサウンド出力があります.合成した波形を音として出力する機能は,特に欲しい機能でした.厳密には,Javaでも合成サウンドの出力は可能だったのですが,それだけのためにJavaを使う人は私以外にいないはずなので,Flashの独壇場と言えるでしょう.

ところが,HTML5の旗印のもと,様々な機能がJavaScriptから利用できる時代になりました.Firefoxに実装された,Audio Data APIもその一つです.Audio Data APIとは何かと言うと,合成した波形を音として出力するAPIです.と言う訳で,今回はAudio Data APIを利用して,簡単な合成音を鳴らしてみたいと思います.

Audio Data API入門

Audio Data APIは非常にシンプルなAPIです.音を扱うAPIとして,Webkit系に実装されているWeb Audio APIも存在して,一括りにされることが多いのですが,狙いが全く異なります.Web Audio APIは,音源位置を指定したり,FFTしたり,一定間隔でバッファに記録する機能を持つなど,様々な機能を持っています.よって,これら2つのAPIは,別物と捉えておいたほうがすっきりします.

ではAudio Data APIの使い方ですが,必要な手順は5つです.

1. Audioのインスタンス作成

2. 1で作成したオブジェクトにチャンネル数とサンプリングレートを設定

3. Float32Arrayのインスタンスを生成し,波形データを生成

4. 3で生成したオブジェクトを1で生成したオブジェクトと結びつける

5. 再生する

言葉にするとイメージが掴みづらい箇所もありますが,手順としてはシンプルです.それでは実際のコードで見てみましょう.

Audio Data APIのサンプル

Firefox限定ですが,実際に耳で確かめてください.以下のボタンをクリックすると,ボタンに書いてある周波数の音を再生します.上3つと下3つは,鳴らすためのパラメータ指定方法を2つ作成したので,そのチェック用です.上は周波数を基準周波数と基準からの相対値で指定します.下は周波数を直接指定します.詳しくはソースを読んでいただければ幸いです.







このエントリーをはてなブックマークに追加

2012年1月27日金曜日

Mac OS Xにnode.jsなどをインストール

パッケージ管理システム

Macでは,macportsと言うパッケージ管理システムが有名だったのですが,root権限が必要などの理由により,最近はhomebrewが勢力を伸ばしてきました.そこで,homebrewを使用してnode.js関係のインストールを行う方法を示します.せっかくなので,インストールログをつけておきます.なお,ホスト名は「sahara」,ユーザ名は「suda」です.背景が濃いグレーの箇所が実際に入力する行です.

本日インストールするものは,

homebrew

node.js

mongoDB

npm

です.

インストール

まずはXcodeをインストールします.Lionを使用している人はMac App Storeからどうぞ.LeopardやSnowLeopardの人は付属CD-ROMに入っているようです.なお,Javaも必要なようなのでインストールしておいてください.

それでは,homebrewをインストールします.以下の1行目の$以降を入力してください.途中でenterキーの押下と管理者のパスワードの入力を求められるので指示に従ってください.

以上でhomebrewのインストールが完了です.ちなみに,コマンド名は「brew」です.さっそくnode.jsをインストールします.

ぼーっと待っているだけでインストール完了です.続いてbrewをアップデートして,mongoDBをインストールします.不要でしたらupdateのみ実行して,mongoDBのインストールは飛ばしてください.

起動方法や自動起動設定は上記ログに書かれているので,必要な方を実行してください.続いてnpmをインストールします.昔はhomebrewでインストールできたようですが,方針の違いから,現在はインストールできなくなっているので,以下に示すようにcurlとshでインストールします.

後はnpmを使用して必要なパッケージをインストールして下さい.以下,「Express」「Socket.IO」「EJS」「Mongoose」をインストールします.

npmのモジュールツリーが化けていますが,原因不明です.それでは,Macでnode.jsを楽しんでください.

追記

ブログのデザイン変更と,コマンドを打ち込む箇所を明示しました.(2011/2/2)

このエントリーをはてなブックマークに追加

2012年1月25日水曜日

Nylon iframe

Nylon iframeとは

昨日の日記に続いてXDMの話です.Nylonの仕組みを拡張して,iframeを超えてメッセージセンディングを行うモジュールを開発しました.プログラムはgithubで公開しています.

Nylon iframeの使い方

まずはNylonとnylon.iframe_parentを初期化します.idがdestFrameのiframeに,localhost配下のhtmlを読み込んでいるものとします.その場合の初期化は以下のように行います(変数名は手抜きです).通常,onloadイベント内に記述してください.

同様に子フレーム側でもnylon.iframe_childの初期化を行い,onconnectメソッドで接続時の関数を定義します.親フレームから接続されると,自動的にonconnectメソッドが呼び出されるので,その中でイベント受信時のコードを記述します.

このとき,フレーム間の通信は以下のように行います.

この通信内容は,お互いのフレームだけでなく,タグ"test"を受信する他のNylonモジュールにも伝達されます.このような作りになっているので,iframeの有無に寄らず動作するモジュールを開発することが可能です.

ソースコード

Nylon iframeによる,親子フレーム間で通信を行うサンプルを以下に示します.まずは親フレームです.好きな名前で保存してください.localhost以外のサーバに置く場合は17行目を変えてください.なお,通信相手のサーバのチェックは,17行目の第2パラメータに基づいて自動的に行われます.

続いて子フレームです.ファイル名はframe2.htmlで保存してください.ファイル名を変えたい場合は,親フレーム側の12行目を変更してください.さて,親フレーム側と同様,通信相手のチェックは自動的に行われます.Nylon iframeでは始めに接続してきたフレームのサーバ情報を記録しておき,そのサーバ以外からの通信であれば自動的に無視するようにしています.

これにより,子フレーム側はどのWebページから呼び出されても動作します.これは部品化(ガジェット化)を容易にするための仕様です.そのため,onconnectメソッドが存在し,少々トリッキーではありますが,利便性とセキュリティを確保しています.

Nylon iframeにより,iframe間の通信が容易になりました.しかも,何らかの機能がiframe上にあっても親フレームにあっても利用できます.これにより,機能のモジュール化が進み,最終的にモジュールを組み合わせたWebシステム開発が楽になります.

このエントリーをはてなブックマークに追加

2012年1月24日火曜日

XDMの基礎知識

XDM - Cross Document Messaging -

AJAXでは,セキュリティの都合により,そのWebページが格納されているサーバとしか通信を行えませんでした.これに対してJSONPを使って回避することも可能ですが,JSONPはscriptタグによるJavaScriptのロードそのものであり,トリッキーなコードになることは否定できません(jQueryを使うことで回避できますが,ここでは置いておきます).そんな時に使われるのが,XDMです.

XDMとは,大雑把にいってしまうと,iframeを作う技術です.そうです.frameは抹殺されたのですが,iframeは残っています.それで,iframeの特性として「表示上は同一ページだが,セキュリティ上の扱いは親フレームとは別に扱う」ことになっています.すなわち,親フレームとは異なるサーバに置いてあったとしても,子フレームは自身が格納されているサーバとは通信を行えることになります.偉そうに言ってますが,ここまでは昔から可能でした.

昔と違うのは,親フレームと子フレームの間の通信が(別サーバであっても)可能になったことです.ただし,悪意のあるページからiframeで呼び出されて好き勝手されては困るので,通信相手のサーバ情報を取得して,正規の通信であることを確認可能となっています.

XDMによる送信(親フレーム側)

フレーム間の通信には前回WebWorkersでも使用した「postMessage」と「addEventListener」を使います.レシーバの指定が少々異なるので,例を使って説明します.まずは,localhostにWebサーバがあり,以下のように,送信ボタン,iframe,受信データの表示領域が定義されているとします.

このとき,親フレームから子フレームへの送信は以下のように行います.

順に説明すると,1行目はボタン要素の取得,2行目はiframe要素.contentWindowによって,通常のページで言うwindowに相当するオブジェクトを取得しています.

3行目からは,ボタンクリックのイベント定義です.4行目でdataという名前のハッシュを作り,helloという文字列と現在時刻を収めます.

5行目が子フレームへの送信です.第1パラメータが送信内容で,先ほど作成したdataをJSON化したものです.今のところ,文字列でないと送信できないので,その対策です.そして,第2パラメータが子フレームが格納されているサーバを表します.つまり,プログラム作成者が子フレームのサーバをしっかりと把握している必要があります(プログラムで動的にサーバを取得する場合はこの限りではありませんが,どのような状況で動的に取得するのかは不明です).もし子フレームのサーバ指定を間違えると,例えば間違えて「http://hoge.com」と指定すると「Unable to post message to http://hoge.com. Recipient has origin http://hoge.com.」というエラーがコンソールに表示されます(Google Chromeの場合).

子フレーム側の記述

一方,子フレーム側でデータを受け取るには,addEventListenerを使います.ここでは,親フレームから送信された内容を,そのまま返信する例を使って説明します.

1行目はXDMにより,メッセージを受け取った際のイベント定義です.2行目は,通信相手のサーバ情報の確認です.この例で分かるように,event.originで通信相手のサーバ情報を確認できるので,セキュリティ面からきちんと確認しましょう.

3行目はデータの返信です.親フレームの要素を取得する方法が分からなかったため,event.source(送信してきた要素)に対してpostMessageしています.パラメータは先程と一緒です.あ,event.dataで送られてきた内容を取得できるので,取り出して加工したい場合はJSON.parse( event.data )などとして使ってください.

親フレーム側の受信方法

最後に,親フレーム側での受信方法です.やり方は子フレームの場合と全く一緒です.ここでは,送られてきた内容をパースして,表示領域に貼り付ける所まで一緒に書いておきます.

1, 2行目は子フレームと全く一緒の記述方法ですが,メッセージを受け取った際のイベント定義と,送信元のサーバ情報のチェックです.3行目は送られてきたデータをパースして変数dataに格納します.4行目,5行目は送信元サーバ情報と送信内容を変数outに収めます.そして,6行目で表示エリアにoutの内容を貼り付けます.

ソースコード

今回のサンプルコードを示します.まずは親フレームです.好きな名前で保存してください.localhost以外のサーバに置く場合は17行目と21行目を変更してください.

続いて子フレームです.ファイル名はframe.htmlで保存してください.ファイル名を変えたい場合は,親フレーム側の9行目を変更してください.localhost以外のサーバに置く場合は11行目を変更してください.

XDMにより,親フレームと子フレームの間の通信が(それなりに)簡単に記述できるようになりました.実際に使用する際には子フレーム側を別サーバに置くことがあると思います.その場合はpostMessageや送信元サーバのチェック部分を書き換えてください.いずれも,そのファイルが格納されているサーバではなく,通信相手のサーバを記述します.

このエントリーをはてなブックマークに追加

2011年11月9日水曜日

Nylon WebWorkersについて

WebWorkers

HTML5で導入されたWebWorkersは,JavaScriptで書かれたプログラムをバックグラウンドで動作させるAPIです.これがまたシンプルなAPIで,(1)Worker(バックグラウンドで動作するプログラム)を起動する,(2)Workerにメッセージを送る(postMessageメソッド),(3)Workerからメッセージが届いた際に実行する処理を記述する(onmessageやaddEventListener)のみでWorkerを制御します.メッセージがほとんど行き交わないような単純な処理であれば混乱は少ないのですが,メッセージが行き交うような処理になると,届いたメッセージの内容を元に処理を分岐させるなどの記述が多くなり,プログラム全体の見通しが悪くなります.

この問題点に対応したフレームワークとして,すでにAlexServiceが公開されています.(リリース記事:HTML5 Web Workersを超簡単に使えるようにするAlexService0.5を公開)一方,我々の開発しているNylonをWebWorkersに対応させたモジュールが「Nylon WebWorkers」です.

Nylon WebWorkers

Nylon WebWorkersはメイン側のモジュール「nylon.webworkers.js」とバックグラウンド側のモジュール「nylon.webworkers.client.js」から構成されたシンプルなモジュールです.これらのモジュールを使うと,Nylonの流儀に従って「キーワードに対する処理の記述」と「キーワード付きのメッセージ送信」のみによるモジュール間通信が可能になります.もちろん,nylon.coe.jsを併用した上でキーワードとして「server」を指定すればNylonサーバまでメッセージが届きますし,「broadcast」を指定すればNylonサーバを経由して,同時に接続されているWebブラウザ上のNylonモジュールまでメッセージが届きます.Nylonを通じてWorkerと通信するために必要なことはWorkerとして動作させるJSファイルを指定するだけです.以下に例を示します.

この記述だけで,worker.jsが読み込まれてバックグラウンドで実行され,キーワードベースの通信が可能になります.後はnylon.client.createで作成したオブジェクトにonメソッドでキーワードに対する処理を記述したり,emitメソッドでメッセージを送るのみです.

それでは,実際のプログラムを見てみましょう.今回のプログラムは,こちらのページに掲載されている,1から指定された数値までの和を計算するプログラムを改造したものです.

用意するファイルは以下のとおりです
worker_test.html・・・HTMLファイル
nylon.client.js・・・Nylon本体
nylon.webworkers.js・・・WebWorkersとやりとりするクラス
worker.js・・・わざわざWebWorkersを使うまでもありませんが,合計値を計算するプログラム
nylon.webworkers.client.js・・・ワーカ側のライブラリ(クラス化とか面倒なことはしていません)

以下に示すファイルはworker_test.htmlです.9〜11行目は「worker_set」を受け取った際の処理で,無事にWorkerと接続された場合に画面上に「connect」と表示しています.12〜14行目は「result」により計算結果を受け取り,アラートを表示します.18〜22行目はボタンがクリックされた際に「worker」と共に計算範囲をWorkerに渡します.16行目は,「worker.js」を読み込む処理です

続いて,以下に示すファイルは,Workerとして動作する「worker.js」です.4〜9行目はキーワード「worker」が届いた場合の処理です.具体的には入力された数値までの数値を足して,その結果を「result」で送っています.

11行目は「worker_set」を送っています.必須ではありませんが,通信できる状態になったことをメイン側に示します.

13行目はWorkerが受け取るキーワードをメイン側Nylonに送ってキーワード登録を行います.この処理が無いとWorker側にメッセージが届かないので重要なのですが,onメソッドに含める方がスマートなので近い内に削ります.(たぶん)

このように,onとemitのみでメイン側とWorker側の通信を記述できるので,通信が煩雑になっても混乱しなくてすみますし,通信するモジュールを限定しないので,様々なモジュールと組み合わせることが可能となります.今のところ,MeSHで使用するシミュレータ教材はSilverlight,Java,Flashを想定していますが,将来的にCPU速度が向上しJavaScriptでシミュレータ教材を記述できるようになった際には,Nylon WebWorkersを利用していこうと考えています.

動かし方

HTMLファイルをWebブラウザに放り込むと,URLの先頭が「file://」となります.この状態ではWebWorkersは使えません.必ずHTTP経由でアクセスしてください.今回の例ではすべての通信はクライアント内で完結しているのでNylonサーバは不要です.

HTTP経由で読み込むと,即座に画面上の「not connect」が「connect」に変わるはずです.この状態で入力欄に数値を入力してください.10や100ではすぐに結果が表示されてしまうので,もっと大きな数値が良いです.具体的な数値はCPUと相談して決めた上,お試しください. このエントリーをはてなブックマークに追加

2011年10月30日日曜日

Nylonサーバについて

Nylonサーバの役割

Nylonサーバは,あるWebブラウザから送られてきた通信内容を解析して,キーワードに「broadcast」が含まれていた場合には他のWebブラウザに転送する機能と,その他特定のキーワードに対応した処理を行う機能を持つものです.

理論上,言語は問いませんが,Socket.IOが便利なのでJavaScriptで記述するのが自然だと思います.実際,クライアント側の通信モジュール「nylon.coe.js」がSocket.ioを前提として組んでいます.

Nylonサーバのソースコード

以下にNylonサーバのソースコードを示します.できるだけコメントを付けているので,コメントを追いかけていけばなんとなく動作がわかると思います.21行目から31行目がメインの処理(=キーワードの解析・通信内容の転送など)となります.

動かし方

必要なものは,基本的にnode.jsとSocket.IOです.執筆時に使用したバージョンは,node.js 0.4.12とSocket.IO 0.8.5です.他にもexpressやejs(使っていますが,他のテンプレートエンジンでも構いません)も使用しています.

node.jsをインストールしたらディレクトリを作成して,上記プログラム(仮にnylon_server.js)をそのディレクトリに置きます.そのディレクトリの下にpublicディレクトリを作成し,前回紹介したプログラムを置いてください.ディレクトリの作成はexpressの機能を使ってもかまいません.

これで準備は整いました.後はWebブラウザからアクセスするだけで,リアルタイムチャットが動作します.複数のブラウザからアクセスして,動作を体験していただければ幸いです.なお,過去の書き込みの保存などは一切行なっておりません.リロードすると全書き込みがリセットされます.過去の書き込みの保存などを行いたい場合には,上記プログラムの29行目などに処理を追加することで対応可能です.追加するプログラムの例や,追加分のモジュール化などについては,そのうち紹介する予定です.

このエントリーをはてなブックマークに追加

2011年10月28日金曜日

Nylon入門

Nylonによるイベント送受信の流れ

前回はNylonの書き方について簡単に紹介しました.今回は,Webブラウザ間のやりとりについて,実際のコードを踏まえて説明したいと思います.下図のように,nylonサーバに複数のクライアント(Webブラウザ)が接続している状態です.ここで,obj_aは任意のオブジェクトです.COeはnylonサーバと通信するnylonモジュールで,正式なファイル名はnylon.coe.jsです.

Nylonのモジュールは,キーワードに従ってイベントを待つことができます.COeは,「broadcast」と「server」と言うキーワードで待ち受けをして,イベントの内容をnylonサーバに送ります.nylonサーバではキーワードを判別し,「broadcast」だった場合には,送られてきたイベントの内容を他のWebブラウザに送ります.「server」だった場合にはnylonサーバで何らかの処理を行います.続いて,各WebブラウザのCOeは送られてきたイベントを受け取り,ブラウザ内の他のオブジェクトにemitします.

例:リアルタイムチャット

リアルタイムチャットを例にとって説明します.リアルタイムチャットのオブジェクトがobj_aだと思ってください.obj_aはキーワード「append」に反応して書き込み欄に新たな内容を追加し,キーワード「clear」に反応して書き込み欄を消去するものとします.あるブラウザで発言があった場合,obj_aから「broadcast」と「append」という2つのキーワードを持ったイベントを送信します.擬似コードは以下のようになります.

イベントの登録

イベントの送信

実際のコード

リアルタイムチャットの実際のコードは以下のようになります.

6行目から8行目は,必要となるライブラリの読み込みです.サーバとの通信にはSocket.ioを使用しています.nylonサーバのプログラムは後日説明予定です.

22行目から32行目はイベントの登録です.先に説明したとおり,「append」と「clear」と言う2つのキーワードについて,その動作を登録しています

35行目から49行目はイベントの送信処理です.送信ボタンが押されたら名前と発言欄の内容を入れた,キーワード「append|broadcast」のイベントをemit(送信)しつつ,書き込み欄に追加します.

52行目から54行目は,接続した旨を表示するための確認欄の記述です.このプログラムではページ上部の「未接続」が「接続」に変化します.この辺は,格好良いインジケータを配置したいところですね.

その他

書き忘れていましたが,このソースプログラム及びNylonとNylonモジュールは無保証です.動作確認はChrome15.0.874.106,Safari5.1,Firefox7.0.1,iOS5のSafariで行っています

このエントリーをはてなブックマークに追加

2011年10月22日土曜日

Nylon

Nylonとは何か?

MeSHの開発に当たり,すべての機能を盛り込んだシステムとして開発すると,その後の変更作業が地獄と化すことが分かっています.そこで,e-Learningシステムを,個々の機能ごとに分割して開発し,統合する仕組みが必要となりました.その際,単にiframeなどで統合するのではなく,個別の機能を融合しつつ統合する仕組みが望まれました.今回は,その仕組として開発されたNylonについて説明します.

具体的に何が困るのか?

MeSHで目指しているWebによるリアルタイムe-Learningを実現することは,node.jsとsocket.ioのおかげで用意になりました.しかし,全ての機能を盛り込んだシステム(=モノリシックなシステム)を開発すると,機能追加や変更が面倒です.どこにバグが潜んでいるかヒヤヒヤです.

では,リアルタイムe-Learningを実現する機能をバラバラに見ていきましょう.まずは,スライド表示位があります.スライドを表示すると,そこにペンで書き込みしたくなります.そうすると,説明している姿を動画に残して,その動画に合わせてスライドやペンを再現したくなります.または,学生のペンによる書き込みを残しておくとか,「重要」などのマーキングもしたくなります.さらに,シミュレータについてもリアルタイムで操作情報を配信したり,後の自学自習に生かしたくなります.

これらの機能を融合しつつ統合することはできないでしょうか?そこで生まれたのがNylonという考え方です.具体的には,様々なモジュール単位で開発を行いつつ,モジュール間の動作を制御するイベント情報をやり取りするフレームワークです.

EventEmitter

ベースとなるヒントは,node.jsのソースコードに隠されていました.それを発見したのは当時学生だったi君です.どの様な仕組みかと言うと,個々のオブジェクト毎にキーワードとコールバック関数を定義する仕組みです.これにより,イベント駆動型のサーバ記述を実現しています.この仕組はEventEmitterと名付けられています.

EventEmitterをベースに,Webブラウザ上で動作する複数のモジュール間の通信を,共通するイベントマップで制御して,モジュール同士の「ゆるい」結合を実現したのがNylonです.Nylonとは,i君いわく,mesh(網目の織物)を実現するための素材から連想したとのことです.筆者は「モジュール同士を紡ぐ人工的な素材」と再定義しています.
ここで言うモジュールですが,Webブラウザ上で動作するモジュール + Webサーバサイドのモジュール + WebWorkersのモジュール + iframeに読み込まれたモジュール + Webサーバを通じて別のWebブラウザ上で動作するモジュールなどを含みます.

つまり,これまで面倒だった,Webブラウザ間の通信や,WebWorkersのworker側とメイン側の通信などを統一的に扱うことを可能とするフレームワークです.

Nylonの動作イメージ

obj_a, obj_b, guiの3つのオブジェクトがあり,これらが協調動作する様子を簡単に説明します.guiは,Webブラウザ上のボタンなどから制御されるオブジェクトと仮定します.obj_aはイベントとして,"A"を受け取り何らかの処理を行います.同様にobj_bはイベントとして,"B"を受け取り何らかの処理を行います.guiはAボタンが押されたらイベント"A"を送信します.同様にBボタンが押されたらイベント"B"を送信します.さらにCボタンが押されたらイベント"A"とイベント"B"を送信します.(プログラム中では"A|B"と表記)これらの動作を図にすると以下のようになります.
このように,キーワードをイベントとして送受信し合う仕組みがNylonです.この,「キーワードだけで動作する」というゆるい仕組みなので,モジュールのアップデートにも柔軟に対応できます.

Nylonの動作範囲と実際のプログラミング

先の例では,Webブラウザ内のモジュールを連想しますが,実際にはこれらのイベントを中継する仕組みも開発しています.現在のところ,WebWorkersによるworkerとの通信と,Webブラウザ同士の通信が実現されています.つまり,チャット(ピクトチャット含む)やカードゲーム,動画像の同期などに利用することが可能です.

実際のプログラミングで使用するメソッドはたったの2つで,onとemitです.onはキーワードとコールバック関数を定義するメソッドで,emitはキーワードと(必要があれば)パラメータを送信するメソッドです.例えば上の図の状況を定義する場合には以下のような記述となります. たったこれだけの記述でobj_a,obj_b,gui間のイベント処理が実現します.

次に,Webサーバを中継点として,Webブラウザ同士で上記のイベントをやり取りする場合のプログラミングについて説明します.キーワードは「broadcast」です.イベントキーワードに「broadcast」が入っていた場合,Webサーバを通じて同時に接続しているWebブラウザ上のオブジェクトに通知されます.実際には以下のようになります. ボタンが押された際の送信キーワードに「broadcast」を加えるだけで,Webブラウザ同士の通信を実現できます.この場合,接続しているどのWebブラウザ上でも良いのでボタンをクリックすると,各Webブラウザ上の該当する処理が実行されます.言い忘れましたが,キーワードは「|(パイプ記号)」で繋げることで,いくつでも指定できます.

Nylonはhttps://github.com/sudahiroshi/Nylon2で公開中です.
今後,何回かに分けてNylonについて説明していきます. このエントリーをはてなブックマークに追加

2011年2月28日月曜日

RubyとOLEでPowerPointを遠隔操作してみる

MeSHの枠組みを拡張する?

MeSHは、これまでWebブラウザ上で様々なものを動作・同期させることを念頭に置いてきました。しかし、「様々な」という概念を拡張していくと、ネイティブアプリケーションまで辿り着きます。せっかくプラットフォームに依存しないよう開発してきたのですが、「できない理由を説明するより、できることを証明してから悩もう」の精神に則り、ネイティブアプリケーションを操作してみたいと思います。今回は技術検証なので、Windows上にWeb簡易サーバを構築し、ネイティブアプリケーションを制御してみます。

RubyとOLE

OLEはObject Linking and Embeddingの略で、簡単に言うとWindowsアプリの連携を図るための根幹です。対応したプログラムであれば、プログラムを制御したり、プログラム中のイベントに応じて自作プログラム中のメソッドを実行することができます。このような便利な機能を使わない手はありません。早速プログラムを制御してみましょう。

普通は、Microsoft関連の便利な機能を使うためにはVisualStudioなどを使うことになるのですが、RubyにはWIN32OLEというありがたいクラスが作られています。通常、WIN32OLEの説明にはWordやExcelが用いられるのですが、実はPowerPointと連携するとプレゼンが便利になります。と言う訳で、RubyからPowerPointを制御しようというのが本日の記事になります。

基本的な制御

WIN32OLEが何であるかはRubyist Magazineの該当記事を読んでください。ここでは、あくまでPowerPointの制御のみ(簡単に)解説します。いや、深いところを訪ねられても解説できないというのが本音です。

事前準備として、Windowsを使っていて、PowerPointを持っていて、Rubyをインストールしていることが必要となります。それでは早速PowerPointで何かファイルを開いてみて下さい。次にirbを起動して以下の内容を打ち込んでみて下さい。

この3行を入力すると、PowerPointがプレゼンテーションモードになると思います。2つ以上のファイルを開いている場合には、どちらのファイルが対象となるか私には分かりませんので、1つだけ使用して下さい。

2行目は、OLEの仕組みを利用して、PowerPointとやり取りするための書き方です。3行目は、メソッドがチェーンしていますが、順番に「アクティブなプレゼン」の「スライドショー」を「実行」して「その状態」を得る。という(様な)意味です。細かく言うと間違っているかも知れませんので、鵜呑みにしないようお気を付けください。

それでは、ここで得られたオブジェクトviewを利用してPowerPointを制御してみます。制御のためのメソッドの中でも代表的なものを掲載します。上のプログラムの続きと思っておいてください。それぞれ、意味はコメントに書いておきます。


具体的にPowerPointを制御する

ここまで分かれば、後は適当なポート番号でHTTPを想定して待ち受けて、「次へ」や「前へ」のボタンを作成して、ボタンが押されたらview.Nextやview.Previousを実行してあげれば、Webブラウザから制御できます。つまり、iPhoneなどからプレゼンテーションの制御を行なうことが可能となります。

と言うことで作成したのが次のソースです。1つのファイルの中にRubyスクリプト、HTMLファイル、JavaScript、CSSが入っているので見づらいと思いますが、このファイルだけコピーすれば使えるという気楽さを念頭に置いているのでご容赦ください。(無駄に)AJAXを使っているので、ページ遷移などは発生しません。

前準備:
・以下のソースを保存する。
・WindowsのIPアドレスを調べておく

使い方:
1.PowerPointでプレゼンしたいファイルを開く。
2.rubyで以下のソースを実行する。
3.WindowsのWebブラウザ(動作検証はChromeで行ないました)からhttp://localhost:8080/に接続する。
4.ボタンをクリックして動作させる。
5.うまくいったらiPhoneなど、別ホストからも試してみる。

注意事項:
・セキュリティソフトの動作状況によっては、iPhoneなどから接続できない場合があります。その場合は一時的にセキュリティソフトをオフにしてお試しください。
・ユーザインタフェースは手抜きです。簡単なHTMLとCSSなので、気になった方は書き直してください。
・JavaScriptも冗長です。ご容赦ください。
・ctrl+CでRubyスクリプトが停止しない場合があります。対処法としてctrl+Cを3回ほど連打して、ブラウザ側でリロードなどをすると停止します。対処法をご存じの方、ご連絡いただければ幸いです。

将来的な拡張:
・現在のページ番号と全ページ数が手元に表示されるといいな
・ついでにストップウォッチ機能も欲しいな(スタート・ストップは手動で良いかな?)
・ページ番号をメニューから選択して飛ぶ機能も欲しいかも?
・現在表示しているページや次ページの内容が手元で確認できるといいかも

検証に使用したソフトウェアとバージョン:
・Microsoft Windows Vista ServicePack2 (Windows 6.0.6002)
・Microsoft Office PowerPoint 2007(12.0.6535.5002) SP2
・ruby 1.8.7 (2009-06-12 patchlevel 174) [i386-mswin32]
・Google Chrome 9.0.597.98
・iPhone 3GS (iOS4.2.1)

このソフトについて

ライセンス:GPLv3
免責事項:無保証です。このソフトを使用することによって生じた損害に対して、作者は一切の責任を負いません。

このエントリーをはてなブックマークに追加

2010年11月19日金曜日

WebSocketを試す

WebSocket

最近、WebサーバからWebクライアントにデータをPushする技術としてWebSocketが脚光を浴びています。従来だと、Comet(AJAXの返答を引き延ばす技法=ロングポーリング)を用いて擬似的にPushしていたのですが、(サーバサイドもクライアントサイドも)プログラムが面倒だったり、クロスドメインへの対応がイマイチだったり、リアルタイム性が乏しかったりなどの理由で、だんだんとWebSocketに移ってきています。気づくと、サーバ側ではpywebsocketやnode.websocket.js、Jetty、mojoなどの実装が出てきましたし、クライアント側ではChromeやFirefoxが対応しており、そろそろ手を出してみても良い頃合いになってきました。ruby好きなので、サーバ側にrev-websocketを使ってみたいと思います。

rev-websocketの準備

今回は、OSにDebian Squeezeを使用します。正直、aptの便利さと、Debianのパッケージの多彩さとGUIいらずで軽量なヴァーチャルマシンを多数用意できるポータビリティにメロメロです。

まずはruby関連でインストールするパッケージです。入れたパッケージのメモを取り忘れたので、dpkg -l | grep rubyした結果を示します。不要なモノもありますが、リストに載せておきます。
libruby1.8
libsqlite3-ruby1.8
ruby
ruby-dev
ruby1.8
ruby1.8-dev
rubygems1.8

次に、rubygem関連でインストールするパッケージです。確かこれだけで良かったと思います。
rev
rev-websocket

revを検索したところ「Rev is a high performance event library for Ruby built on top of libev.」「libevのrubyバインディング」「Rubyの高速なイベント駆動IOライブラリ」などと書かれていました。便利そうなので使います。

revのサンプルプログラムを読む

まずは、revのサンプルコードを試してみます。/usr/lib/ruby/gems/1.8/gems/rev-0.3.2/README.textileを覗いてみると、echo serverの例が載ってます。gemを使ってインストールしたので、そのままではrevが無いと言われ、1行目にrubygemsのrequireを追加してあります。
require 'rubygems'
require 'rev'
HOST = 'localhost'
PORT = 4321

class EchoServerConnection < Rev::TCPSocket
  def on_connect
    puts "#{remote_addr}:#{remote_port} connected"
  end

  def on_close
    puts "#{remote_addr}:#{remote_port} disconnected"
  end

  def on_read(data)
    write data
  end
end

server = Rev::TCPServer.new(HOST, PORT, EchoServerConnection)
server.attach(Rev::Loop.default)

puts "Echo server listening on #{HOST}:#{PORT}"
Rev::Loop.default.run
このソースを見ると、(1)Rev::TCPSocketを継承したクラスを作り、(2)接続されたり、接続が切れたりしたら「on_connect」「on_close」が呼び出され、データを受信したら「on_read」が呼び出され、(3)20行目にあるようにRev::TCPServerのオブジェクトを作る際にポート番号や作成したクラスを登録すれば良いことなどが分かります。あと細かいことでは、wirteメソッドを使うとクライアントにデータを送れることも分かります。 実際に「telnet localhost 4321」で接続してみると、確かに入力した文字列がそのまま返されることが分かります。また、最終行の「Rev::Loop.default.run」によりイベントループが回り出すのですが、プログラムの実行してはこれより下には行きません。もっと複雑なプログラムを組もうとしたら、スレッド化が必要かも知れません。

WebSocketのサンプルプログラムを読む

revのソースコードの書き方が分かったところで、WebSocketのサンプルも試してみます。こちらは/usr/lib/ruby/gems/1.8/gems/rev-websocket-0.1.3/README.rdocの中にサンプルがあります。
require 'rubygems'
require 'rev/websocket'

class MyConnection < Rev::WebSocket
  def on_open
    puts "WebSocket opened"
    send_message("Hello, world!")
  end

  def on_message(data)
    puts "WebSocket data received: '#{data}'"
    send_message("echo: #{data}")
  end

  def on_close
    puts "WebSocket closed"
  end
end

host = '0.0.0.0'
port = '8080'

server = Rev::WebSocketServer.new(host, port, MyConnection)
server.attach(Rev::Loop.default)

Rev::Loop.default.run
echoサーバのプログラムと比べても、ほとんど違いがありません。構造的に違うのは、2行目のrequireと、4行目で作成しているクラスのスーパークラスがRev::WebSocketであることと、23行目でRev::WebSocketServer.newとしている箇所です。 サーバができたので試してみましょう。ただし、telnetコマンドではWebSocketサーバと通信できません。いや、正確には可能なのですが、繋がった後に渡すパラメータが複雑なので現実的ではありません。そこで、とりあえずChromeのJavaScriptコンソールから接続してみたいと思います。下記のプログラムを入力してみて下さい。接続先ホスト名は必要に応じて変更して下さい。うまく動作すれば、3行目で送信したメッセージが、アラートウィンドウに表示されます。
var ws = = new WebSocket( 'ws://localhost:8080/' );
ws.onmessage = function(event){ alert( event.data) ; }
ws.send( "This is a test message." );

重要な部分は、WebSocketのオブジェクトを生成すること、データを受信した際にonmessageが実行されること、受信したデータはevent.dataで取得できること、データ送信はsendメソッドであることです。

このように、WebSocketを使うことは難しくありません。皆さんもお試し下さい。 このエントリーをはてなブックマークに追加

2010年10月20日水曜日

メモ: クロージャの書き方

クロージャとは?

JavaScriptに触れていると、クロージャという書き方に突き当たります。それ何?って思って検索しても、難しいことばかりで本質はなかなか分かりません。しかも、他の言語でどう書くかも掲載されないことが多いです。元々慣れない言語(JavaScript)を勉強しているときに、概念もあやふやで、他の言語の例もない・・・ってことで使えるようになるまで苦労しました。確かに、他の言語では書き表しにくい書き方です。

そうは言っても、避けてばかりでは進歩しないので、自己流で解説してみます。サンプルは、node.jsなどを想定して書いています。Debianのsqueeze(2010/10/20時点でtesting)では、すでにnode.jsが標準パッケージに入っているので、敷居はかなり低くなっています。

まずはJavaScriptでクロージャ

ゴタゴタ書くと、難しいという先入観が形成されてしまうので、まずは下記のソースをお読み下さい。
var test = function() {
  var i = 0;
  return function() {
    i += 1;
    return i;
  }
}

var count = test();
count();  // => 1
count();  // => 2
count();  // => 3
まず、変数countにはtest内の3行目以降で宣言したfunctionが代入されます。堅い言語に慣れている人は、まずこの書き方を許容して下さい。functionは変数に代入できます。
このfunction内で扱っている変数iは、functionの外で宣言されています。ここで、count()が実行されると、iが1増えます(iが1になります)。そして、このiの値は(プログラマから見て)裏側で保持され、再度count()が実行されると、今度はiが2になります。このように、スコープ外で宣言された変数を取り扱うのがクロージャです。

では、iはどこに保持されているかと言うと、概念的にはcountの一部として保持されています。例えば、別の変数も作ってみましょう。8行目までは上記と一緒で、9行目から先を以下のように改造したとします。
var count1 = test();
var count2 = test();
var count3 = test();
count1();  // => 1
count2();  // => 1
count1();  // => 2
count3();  // => 1

このように、count1, count2, count3でのiはそれぞれ別の値を保持しています。もちろん、test内でいくつもの変数を宣言することも可能です。

Rubyでクロージャ

次に、比較のためにRubyでクロージャを扱ってみましょう。例によって、まずは下記のコードをご覧下さい。
def test
  i = 0
  return lambda { i += 1 }
end

count = test
count.call  # => 1
count.call  # => 2
count.call  # => 3
count.callの文字数が多くて嫌いな人は、最新版のRubyならcount[]という書き方も許されます。

Rubyを知らない人のために、じっくりと説明していきます。まず、1行目のdefはメソッドを定義するための構文です。この例ではtestという名前のメソッドを定義しています。2行目で変数iに0を代入しています。変数宣言はRubyでは不要です(と言うか、できません)。3行目でlambdaを使って定義した一連の作業を返しています。lambdaは、「一連の作業をオブジェクト化する」ことが可能です。(Rubyでは、最後に評価された内容がそのまま返り値となるので、returnは省略可能です)

6行目で変数countにtestメソッドの実行結果として先ほど定義したlambdaが代入されます。「test」は「test()」の省略形です。7行目以降のcount.callは、lambdaの実行です。iがどこに保持されているかはともかく、iの値が1ずつ増えているのが分かると思います。もちろん、count1, count2などを定義すると、それぞれのiは独立した変数として保持されます。

ついでにカリー化の手前

上のサンプルでは、クロージャの良いところが伝わりきっていないと思いますが、そのままカリー化の手前まで突っ走りたいと思います。カリー化の手前とは「2つの引数を伴う関数f(x,y)をg(x)(y)のように記述すること」です。真のカリー化は、もう少し複雑なので説明は割愛します。こちらも早速サンプルをご覧下さい。
var test2 = function( x ) {
  return function( y ) { return x * y }
}

mul = test2(3);
mul(2);  // => 6
mul(5);  // => 15

def test2( x )
  lambda { |y| x * y }
end

mul = test 3
mul.call 2  # => 6
mul.call 5  # => 15
2つの引数x, yを受け取って積を返す関数の代わりに、一つ目の引数xを先に受け取りクロージャの要領で保持しておきます。その後、もう一つの引数yを受け取って積を計算し、返すという流れが記述できました。もう一つの引数の受け取り方ですが、Rubyでは2行目の「 |y| 」のように、パイプ記号で囲った変数に代入されます。

この例では、掛け算に使用する数値の一つを先に受け取り、以後はその数値を基準として、もう一つの数値は実際に計算する際に渡す形となっています。これ以外にも、初期値を指定してカウントダウンするような場合とか、出力フォーマットを定めておいて後で数値を渡すと指定したフォーマットで出力するなどの場合に便利ではないでしょうか?

クロージャまとめ

C言語では、スタティック変数を使用することで、一見似た様な記述が行えます。しかし、その場合はcount1とcount2のように二つ以上の状態を保持できません。だからと言ってグローバル変数を使用するのも気が引けます。そのような場合にクロージャのような書き方ができると、グローバル変数を使用することなく実装できます。

一方、「クロージャなんて使わずに、オブジェクト指向(クラスとかインスタンス)でいいんじゃない?」って考えもあると思います。もちろん、オブジェクト指向の記述方法でも同じことができます。しかしその場合、変数の値を裏で保持して欲しいだけなのに、大げさな記述が必要になってしまいます。Rubyでのサンプルを以下に示します。
class Test
  def initialize
    @i = 0
  end
  def count
    @i += 1
  end
end

c1 = Test.new
c1.count  # => 1
c1.count  # => 2
c1.count  # => 3
この程度のプログラムでしたら、クロージャでもクラスでも好きな方で記述して良いと思いますが、クロージャのシンプルさは魅力的です。そうは言ってもクラスを使ってもカリー化と同等の内容も簡単に記述できます。クロージャに慣れていない人は現時点では多いと思いますので、そう言う人に気を遣う場合にはクラスを使った方が良いと思います。 このエントリーをはてなブックマークに追加

2010年5月29日土曜日

JavaScriptで苦悩

苦悩する日々

MeSHのフレームワークには、今のところユーザ管理機能はありません。ユーザ管理などは他のシステムに任せて、コアな部分だけを実装する予定だからです。とは言っても、そろそろ何らかのシステムと連携して実践したいところです。そう思ってたら、棚からぼた餅が落ちてきました。なんと、大学にLMSが(試験的に)導入されました。元々大学にはとあるプロジェクトがあって、当時はLMSが動作していたのですが、現在は停止しております。プロジェクトの中心にいた私が言うので間違いないです。停止しています。

自前でLMSを立ち上げるなど考えていたのですが、ユーザ管理の手間はいかんともしがたいものがあります。どうしたものかと思っていたところに、タイミング良くLMSが導入されたので、さっそく認証情報をもらうことにします。とは言っても、ユーザ情報の入っているデータベースに接続するとか、セッション情報を貰ってくるなんてことは、どう考えても許可が下りるとは思えません。色々考えた結果、LMSにHTMLの教材をアップロードして、そこでスクリプトを動かして強引に外部ページにリンクしてしまえ!と言うことに決め、さっそく慣れないJavaScriptと格闘しました。苦悩の始まりです。

簡単・・・じゃ無い

まずはLMSにHTML教材もどきをアップロードして、何ができて何ができないか確認します。その結果、LMSが作成したページ内にiframeで表示されることが分かりました。面倒なので、LMSが作成したページを「親」、iframe内のページを「子」と呼びます。親にはユーザ名が表示されているので、この情報を取ってきて、Formに入れて、MeSHのサーバにSubmitすれば、少なくともユーザ名を取得できます。本当は学生番号のようなユニークな情報があれば良いのですが、親のソースをいくら探してもそのような情報はありません。邪道なことをおこなうので仕方ないと考えつつ、氏名がliタグに囲われているのが救いでした。とりあえず、同姓同名がいたらそのときに悩むことにして、まずは技術確認を行います。

子では、割と自由がききました。JavaScriptも使えますし、JQueryを読み込むこともできます。とりあえず、ブラウザ(Google Chrome)のJavaScriptコンソールで一行一行実行しながら試そうとしたとき・・・「ん?iframeの親の情報ってどうやって取得するんだ?」と言うJavaScript初心者の検索の旅が始まりました。結局、金曜日の23:00~翌3:00、土曜日の21:00~翌3:00までやって、なんとか親のliタグの情報を取得することができましたが、ページに埋め込むとエラーで止まってしまいました。仕方ないのでJavaScriptが得意な学生にメールでデバッグの要請をしたら、ものすごい初心者的なバグ(と言うか考え違い)を指摘されました。我ながら「おぃおぃ、こんなのが分からなかったのかい?」と問いかけたくなるレベルでした。

やっとプログラムの話

そんなこんなで、やっとこさ完成したのが以下のソースコードです。生のJavaScriptとJQueryのコードが混在しているあたりが初心者丸出しです。はい、JavaScriptは初心者です。やってることは、parent.document.getElementsByTagNameを使って、親のフレームの特定のタグ(ここではli)に書かれた内容を取得し、その内容をid="username"の入力欄に入れ、新しいウィンドウを開いて、formをsubmitするだけです。まぁ、ちゃんと使うためには、textタグじゃなくてhiddenタグを使うとか、画面デザインを考えるなど、やることは沢山あります。また、Webサーバxxx.xxx.xxxでは、HTTP_REFERERをチェックすることもセキュリティ上必要だと思ってます。でも、とりあえず動作確認のためなので、これで良しとします。



以下、上のソース内のJavaScript部分の抜粋です。
var name1=parent.document.getElementsByTagName('li')[0].firstChild.nodeValue;
$('#username').val(name1);
window.open('about:blank', 'starting');
var f=document.starting; f.action='http://xxx.xxx.xxx/';
f.method='post'; f.target='starting';
f.submit()
このエントリーをはてなブックマークに追加

2010年2月9日火曜日

JavaScriptとjQueryのメモ

検索すれば済む話ですが、あまりに何回も同じことを検索するのに疲れたので、メモとしてまとめておきます。

生のJavaScriptとjQuery

すでに有名な話ですが、JavaScriptの実装はブラウザによって若干の誤差があります。特に有名なのがInternetExplorerですが、Microsoft社が実験的に先行して拡張したにも関わらず後続が異なる書式を採用したものもあるので、一概に責めることはできません。もしかしたら、他のブラウザで同様の技術が実装されなかった可能性もあります。

とは言っても、いわゆるWeb屋さんから見ると、IEとそれ以外で(実際にはもっと細かく)処理を分けるのは、やりたくないけどやらなければならない仕事であることは、想像に難くないです。そう言うことが嫌いな人たちが、関数化(またはクラス化)された処理内でブラウザ毎に処理を分けるノウハウを蓄積してきました。ブラウザ毎に処理を分ける必要がなくなり、プログラム効率が向上したのを良いことに、さらに便利な処理を関数化(またはクラス化)して、より複雑な処理の記述に耐えるべく進化してきました。

ありがたいことに、このような関数(またはクラス)がライブラリとして公開されています。有名なところでは、jQueryや、prototype.js、YUI、Dojoなどが存在します。これらのライブラリを活用することによって、生のJavaScriptでは難しい、またはブラウザ毎に処理を分けなければならない部分のプログラムを省略し、高度なプログラムを作成することができます。

JQueryを使う

上に挙げたように、いくつものライブラリが存在します。その中で、ここではjQueryを取り上げます。まずは、jQueryを使うための宣言を行います。通常はjQueryをダウンロードして、htmlファイルと同じディレクトリに置くのですが、楽な方法としてGoogleがホストしているものを使います。headタグの中に以下のように記述します。google.loadメソッドの引数は、順にライブラリ名、バージョンです。他にも様々なライブラリがホストされているので、興味のある方はこちらをご覧ください。



次に、JavaScriptのプログラムを置く場所ですが、上記の1行目のような記述方法で、外部ファイルを読み込むのが良いとされています。ですが、ちょっとお試しの場合は、わざわざ別ファイルを作成するのも面倒なので、htmlファイルの中に記述できます。もっと言えば、FireBugやChromeのJavaScriptコンソールを使ってもお試しできますが、後に残るようにすると、何かの時に役立ちます。それでは、jQueryを使って、JavaScriptのコードを書いてみましょう。



2行目は

と同じ意味です。

DOMの取得

さて、jQueryによって、まずプログラマが楽をできるのがDOM要素の取得です。DOMをいい加減に解説すると、「HTMLで書かれた文章から特定の内容を取り出したり、内容を変更する際に場所を指定する方法」です。例えば、「htmlの中のbodyの中のformの中の3番目のinput」のように階層化された構造を辿ることで、内容を取得できます。さすがにこれは面倒なので、通常は「id」や「class」などを目印に指定することになります。

以下のようなhtmlがあるとします。

このとき、「No.1」と書かれている箇所の指定方法には、以下のようなやり方があります。


上記の例は全てidによる指定でしたが、タグによる指定も行えます。この場合、結果は配列で返されます。


このようにすると、jQueryオブジェクトからDOM要素を取り出すことができます。DOM要素をjQueryオブジェクトに変換するためには、$( )で括れば良いです。以下の例では、No.1と書かれている箇所をjQueryを使って赤くします。

cssメソッドは、CSSに相当する操作を行います。色を変えたり、サイズを変えたり、非表示にしたりと、CSSで指定できるパラメータは(たぶん)全て記述できます。 このエントリーをはてなブックマークに追加