2013年11月20日水曜日

WEBサービスを作ってみよう 第4回 Slim3の最新版へのアップグレード


この辺りからは、WEBサービス構築の概要というよりは、具体的な開発にかかわる話になってきます。
今回は、必要に迫られてSlim3のアップグレードを行ったので、その辺について残しておきます。


まず、WEBサービスを作るにあたって、JSONが大活躍します。

検証はしていませんが、Ajax的な作りでサーバ側はJSONを吐くだけにして、なるべくクライアント側で作業するようにしたほうがコストが安そうです。

やはり、特に無料枠に抑えたいという期待を持って開発するのであれば、いちいちServletやJSPがページをゴリゴリ生成して吐き出すような作りは、コスト的にマズイような気がします。


そこで、JSONの取り扱いですが、Slim3にはModelをJSONに簡単に変換してくれるメソッドがあります。
そのModelMeta.modelsToJson()で以下のエラーが出ました。

java.lang.NoClassDefFoundError: com/google/appengine/repackaged/org/json/JSONObject

Google先生に色々聞いてみたところ、ModelMeta.modelsToJsonでrepackagedなクラスを使ってるために起きているそうです。
repackagedなクラスというのは、SDKの変更で頻繁に変更されるので、あまり使うべきではないみたいですね。


で、解消方法がSlim3のバージョンを「1.0.15」から「1.0.16」にアップグレードすることでした。
Slim3はEclipseからインストールしているんですが、なんで初めっから最新にしてくれないんですかね?

理由は謎のままですが、最新版は以下からダウンロードします。

https://code.google.com/p/slim3/downloads/list

slim3-blank-1.0.16.zip


開発中プロジェクトでライブラリのアップグレードをするなんて、もろもろ動かなくなったりしそうで怖いのですが、やるしかありません。
公式などを見つつビクビクしながら以下の通り実行しました。

1.新しいバージョンのファイルをフォルダに投入

 「slim3-1.0.16.jar」を<Project>/war/WEB-INF/libに投入。
 「slim3-1.0.16-sources.jar」を<Project>/libsrcに投入。

2.プロジェクトのプロパティ >Javaのビルド・パス > ライブラリー からCLASSPATHの設定を変更

 「slim3-1.0.16.jar」と「slim3-1.0.16-sources.jar」を追加。
 「slim3-1.0.15.jar」を削除。

3.古いファイルをそれぞれのフォルダからも削除

4.注釈処理に関連した新しいバージョンのファイルをフォルダに投入

 「slim3-gen-1.0.16.jar」を<Project>/libに投入。

5.プロジェクトのプロパティ > Javaコンパイラー > 注釈処理 >ファクトリーパス からファクトリパスの設定を変更

 「slim3-gen-1.0.16.jar」を追加。
 「slim3-gen-1.0.15.jar」を削除。

6.古いファイルをフォルダから削除。


これで今のところは、ちゃんと動いています。


Read More...

2013年11月10日日曜日

WEBサービスを作ってみよう 第1回 利用言語とインフラ


なんかWEBサービスを作ってみようと思います。
たいした知識も無いのですが、作りながら必要な知識は身につけていけば良いということで、とりあえずやってみます。

あえてタイトルに第1回と付けたのは、いつもこの手のことは継続出来ないので、自分を追い込むための儀式のようなものです。


まずは、どこのインフラを使うかということで、世の中のWEBサービスのインフラに何が使われているのかから見ていきます。

この辺を見てみると、どうやらAmazonさん一人勝ちな雰囲気です。

AWS(Amazon Web Services)はいわゆるIaaSというやつで、仮想化されたインフラをクラウドで提供するサービスです。
正確にはAWSサービス群の中のAmazon EC2がIaaSなんですかね?

AWSには無料枠もあって必要に応じてスケールアップすることが出来ます。

サイトを作ったところで人っ子一人訪れないであろう事を考えると、あまりお金をかけたくないですし、うっかり盛り上がってしまった時に手軽にスケールアップ出来るのは魅力です。

しかし、仮想マシンが無料で使えるなんてすごい時代になったものです。

サイトを見ても無料枠がどこまでなのか非常に分かりにくいのですが、月750時間分のマイクロインスタンスと書いてあります。
良く分かりませんが、色んな用途のインスタンスが用意されている中で、恐らく一番しょぼいやつ750時間分の稼動というところでしょうか。


次にPaaSも見てみます。
PaaSはIaaSにOSとミドルウェアを乗っけたものと考えればいいでしょう。

代表格はGoogle App Engineです。

こいつもなかなか優れたやつで、Python、Java、Goまで使えてしかも無料枠があります。

一昔前は無料でJavaを使おうと思ったら、自宅サーバしかなかった気がするのですが、PaaSでJavaいけちゃうんですね。
データベースがRDBではないなど一癖ありそうな感じではありますが。

1日28時間の無料インスタンス時間とありますので、月840時間ってとこですね。
こちらは日単位なので、日が変わるとリセットされるということでしょうか。


しかし、最大手がこれだけのサービスを無料枠ありで提供していて、各社が出しているVPSのサービスとかどうやって顧客を獲得しているんでしょうか。

無料枠のインスタンス単位というのが直感的に分かりづらいので、それが敬遠されて月額固定のサービスに流れているだけなのか、それともすぐ無料枠上限に到達してしまうということなんでしょうか。

まぁ良く分かりませんが、無料枠突破してくれるほどアクセスがあるならむしろ嬉しい悲鳴なので、とりあえずGoogle App Engineを使ってみようと思います。

言語はGo(笑)と言いたいところですが、Javaにしておきます。


Read More...

2013年11月4日月曜日

WEBサービスを作ってみよう 第3回 Slim3の概要


Slim3をいくらか触ってみたので、その概念について。


まず、Slim3の基本的な構成はController、Model、Serviceとあり、それぞれControllerは画面遷移、Modelはデータオブジェクト、Serviceはロジックの役割を持ちます。

Slim3におけるControllerは、URLに関連付けられたServletで、基本的な流れはまずControllerを呼び出して処理し、そこからjspを出力するという感じです。
ModelはDatastoreに永続化するクラスで、データベースオブジェクトの設計部分になります。
ビジネスロジックが書かれたServiceから、DatastoreのModel読み出しや、保存を実行します。


開発にはEclipseを使うのが一般的で、Slim3 Plugin for Eclipseをインストールして利用します。
Antが使えるので、Antタスクを使ってControllerを作成すると、ControllerとJSPとTestCaseを同時に生成出来、ControllerはURLに関連付くので、servlet-mappingの設定など面倒な作業は不要です。
当然、ServiceやModelを作成する際も、TestCaseを生成してくれます。


Slim3はTDD(Test DrivenDevelopment)という開発手法を推奨していて、コード本体が完成していない状態でまずテストを書いてテストが失敗することを確認し(テストファースト)、次にそれが動くコード本体を書くという流れを繰り返します。

コード本体の書き方も、まずは定数を返すなどの仮実装を行い、徐々に変数を用いた本来のコードに仕上げていって洗練していくという流れです。

この設計手法にはとても共感出来ます。
いっきにコードを書き倒して、もはやどこでしくじったのか訳が分からなくなり、数時間ハマるなんてことを良く経験するので、それを回避出来ます。

TestCaseの生成の重要性が良く分かっていませんでしたが、なるほどこれは重要ですね。


ちなみに、はじめにAntを動かしてみたところ、gen-controllerのところで思いっきりハマりました。

Antを実行すると

 java.lang.SecurityException: SHA1 digest error for org/eclipse/swt/graphics/GC.class

と言われてControllerが生成出来ません。

色々調べてみたところ、

実行 > 外部ツールの構成 > 「JRE」タブ > ランライム JRE > 「ワークスペースと同じ JRE で実行」にチェック

で解決しました。

どうやらJREのバージョンが異なることで起きていたようで、これで無事Controllerが生成されました。


さて、フレームワークを使うと開発が効率化されるというのがなんとなく分かってきましたが、さらにSlim3はHOT reloadingをサポートしています。
何それ?という感じですが、所謂HOT deployの別称で、アプリケーションサーバを立ち上げたままでも変更したクラスがリロードされるので、いちいち再起動が不要という代物です。

いくつかハマるケースもあるみたいですが、Servletを書き換える度に再起動するのに比べると恐ろしく効率的です。


世の中の人が便利と言ってるモノは、やっぱり便利ですね。
今後は素直に先人の知恵には従おうと思います。


Read More...

WEBサービスを作ってみよう 第2回 フレームワーク


前回、インフラはGoogle App Engineを使って、言語はJavaと決めました。

今回は次のステップとして、利用するフレームワークについて考えます。

効率的な開発にはフレームワークは欠かせないらしいので、なるべく楽に開発していくためにもここは重要なポイントになります。

どんなものがあるかとGoogle先生に伺うと、Google App Engineで動きそうなものだけでも大量にhitします。
この辺にもGoogle App Engineで使えるフレームワークのリストが載ってますが、まぁ色々な用途の色々なフレームワークがあるようです。

Spring
Tapestry
Wicket
DWR
Tiles
SiteMesh
Grails
Struts 2
...

正直、それぞれどんな用途でどれが最適なのか調べているだけで日が暮れそうです。

また、効率的な開発はとても重要ですが、そもそもフレームワーク無しで作ったことが無い人間が、フレームワークありで作ったところでそのありがたみは分からないでしょう。

ここでめんどくさくなって開発が止まってしまうぐらいなら、フレームワークなんて考えずにゴリゴリ書き始めたほうが良いのではないか?

なんて事を考えていたのですが、検索結果の中に気になるフレームワークがありました。

Slim3

Slim3は、「Google App Engine/Java 用に最適化されたフルスタックのMVC フレームワーク」であり、また「単にDatastoreのフレームワークとしても使用出来る」とのことで、この手のものでは珍しく日本発のフレームワークだそうです。

Slim3 Datastoreに関しては、Bigtableに特化したLow level APIの薄いラッパーで、Google App EngineのデータストアをJavaから利用する場合の一般的な選択肢であるJDO(Java Data Objects)よりも高速に動作するそうです。

開発の効率化に加えて、そもそもの動作も高速化するということであれば、これは使ってみるしかないでしょう。

ということで、フレームワークはSlim3を使ってみます。

Read More...

2013年7月28日日曜日

久しぶりのSecondLife!乗り物のSIM越え遅延原因を検証する



今回は久しぶりのSecondLifeで、乗り物でSIMを越える時の引っかかりについて検証します。



コレ結構致命的なレベルで発生していて、SIM越えの度に5秒とか10秒固まるケースもあります。

SIM越えの際にSIM側で何が起きているのかをちゃんと調べるのが正攻法だと思いますが、正直良く分かりません。メモリの確保やらなんやらあるんでしょうね。

まぁ細かいところは良く分からないので、取りあえず色々と実験してみます。


まずは基準としてx:2m y:2mにリサイズしたキューブを用意し、これに飛行機のスクリプトを突っ込みます。



こいつのサイズを変えたりプリム数を変えたり、色々なスクリプトを放り込んだりしながら、SIM境界を20回ぐらい行ったり来たりして、遅延の平均を取ります。

遅延の計測方法はllGetTimestamp()を使ってミリ秒単位で計測します。


■基準

 平均遅延:268ms


このぐらいだと少し引っかかるなぁぐらいで特に問題ありません。


■プリムサイズによる差異

・基準のプリムサイズをx:30m y:30mにリサイズ。

 平均遅延:275ms


 サイズを変えてもあまり変わらないようです。


■プリム数による差異

・基準のプリムを30リンクさせたオブジェクトを利用。

 平均遅延:1,000ms


一気に悪化します。


■スクリプトの量による差異

・基準にVICE関連のスクリプトを投入。

 平均遅延:998ms


リンクされたプリム数が30の場合と同じぐらい悪化します。


・30プリムのオブジェクトにVICE関連のスクリプトを投入。

 平均遅延:1,548ms


当然さらに悪化します。


■スクリプトのファイル数による差異

・基準に300個の変数を定義したスクリプトファイルを投入。

 平均遅延:448ms

・基準に30個の変数を定義したスクリプトファイルを10ファイル投入。

 平均遅延:744ms


ファイル数が増えると悪化するようです。



ということで、最後のファイル数の部分以外は、何が悪影響というよりは基本的には足し算で、何か実装すればそれだけ遅延が発生するという印象です。

結論としては、何でもかんでも実装するのではなく必要な機能を絞ることと、最適化のためにファイル数を最小にするというところでしょうか。

まぁサンプル数も少なく、SIM側のトラフィックなどの影響もあるでしょうし、どこまで正しいか分かりませんが、なんとなく遅延を減らすには何をすべきかは見えた気がします。

Read More...

2013年7月8日月曜日

最寄り駅を教えろ!Google Maps APIでマッシュアップ


今回は、Google Maps APIとHeartRails Expressの最寄駅情報取得 APIを連携させて クリックした場所の最寄り駅を取得してみます。


いわゆるマッシュアップというやつですね。

まずはGoogle Maps APIですが、これは思ったより簡単に使えます。
version3になってから、APIキーも無しで使うことが出来るようになり、何の準備もなくScriptを書き始められます。


ScriptタグでGoogle Map APIのJavaScriptを読み込みます。

<script type="text/javascript"
 src="http://maps.google.com/maps/api/js?v=3&sensor=false">
</script>

Body要素の中にMapを表示するためのdiv要素を配置しておきます。
<div id="map_canvas"></div>


Mapを表示するための関数を書きます。
この関数をbody要素のonloadなどに仕込んで利用します。
function initialize() {
 //表示位置
 var myLatitude = 35.147055906927214;//経度
 var myLongitude = 139.150131046772;//緯度
 //Mapの表示位置、表示方法などの設定値
 var mapOptions = {
  center: new google.maps.LatLng(myLatitude, myLongitude),
  zoom: 15,//初期のズームレベル
  mapTypeId: google.maps.MapTypeId.ROADMAP
 };
 //Mapを表示するdiv要素と設定値を渡す
 var map = new google.maps.Map(
  document.getElementById("map_canvas"),mapOptions);

 //Mapがクリックされた時のリスナーを登録
 google.maps.event.addListener(map, 'click', clickMap);
}



次に、HeartRails Expressの最寄駅情報取得 APIを利用します。

とてもシンプルなAPIで、URLに以下のように経度緯度のパラメータをつけてリクエストを投げると、最寄駅の情報を返してくれます。
レスポンスはXMLとJSONの二択でそれぞれURLが異なり、今回はJSONを使います。
http://express.heartrails.com/api/json?method=getStations&x=135.0&y=35.0


返ってくるJSONの構造は、responseフィールドの中に、stationフィールドがあり、その中に3つぐらいの駅情報が配列として入っています。
都市部などで多いと10ぐらい入っていたりもしますが、基準はよく分かりません。
駅までの距離が近い順に格納されていて、見つからない場合は空っぽのようです。

これをAjaxで拾ってくれば終わりかと思ったのですが、まったくうまくいきません。

NETWORK_ERR: XMLHttpRequest Exception 101

とか文句を言ってまったく相手にしてくれない。
結構な時間悩みに悩んだんですが、普通にクロスドメインはダメなんですね。

で、JSONPに行き着いた訳ですが、これ完全にscriptタグの悪用なのではないでしょうか。

クロスドメインでデータの読み込みが出来ないブラウザの仕様を回避するために、動的にappendChildしたscriptタグから外部scriptファイルを読み込むふりをして、外部データを拾ってきてしまうという。。
なんだか詐欺みたいな仕様です。

その詐欺師コードが以下。
function clickMap(event) {
 heartrailsURL = "http://express.heartrails.com/api/json?method=getStations&";
 heartrailsURL += "x=" + event.latLng.lng() + "&";
 heartrailsURL += "y=" + event.latLng.lat() + "&";
 heartrailsURL += "jsonp=getStation";//JSONPのコールバック関数
 var script = document.createElement('script');
 script.src = heartrailsURL;
 document.body.appendChild(script);
}

//コールバック関数
function getStation(result) {
 var resultStr = "";
 for(i = 0; i < result["response"]["station"].length; i++) {
  resultStr += result["response"]["station"][i].line + " ";
  resultStr += result["response"]["station"][i].name;
  resultStr += "駅までの距離:" + result["response"]["station"][i].distance + "\n";
 }
 alert(resultStr);
}

普通に動きました。

Google Maps APIがテーマだったのですが、何よりJSONPに感動しました。

Read More...

2013年5月18日土曜日

Androidで音声解析!Visualizerによる高速フーリエ変換(FFT)


今回は、音の周波数を解析して音階を特定するというのをやってみたいと思います。

音階の特定には、時間軸を周波数軸に変換するフーリエ変換という処理で周波数解析を行います。

\hat{f}(\xi) := \int_{-\infty}^{\infty} f(x)\ e^{- 2\pi i x \xi}\,dx

数式を載せてみたものの、これが本当にフーリエ変換の式なのかすらよく分かりません。
数学的な知識はまったく無いので、フーリエ変換についての説明や考察は一切しません。いや、出来ません。

基本的に勘で進めているので、間違った記述もあるかもしれませんが、取りあえず動いたので完全に間違いという事ではないでしょう。


まずはシンプルに440Hz(Aの音)のsin波を鳴らして、それが440Hzであることが分かればゴールとします。


フーリエ変換を行うには、波形を表示したりといったことにも活用出来るandroid.media.audiofx.Visualizerを利用します。
android.media.audiofxはAPIレベル9(Android2.3)で追加されたパッケージで、イコライザ機能などを簡単に実装するものです。


public Visualizer (int audioSession)

 int audioSession 分析対象のMediaPlayerやAudioTrackのセッションID



流れとしては、Visualizerのコンストラクタに解析したいMediaPlayerなどのセッションIDを渡してやり、setCaptureSize()メソッドでキャプチャサイズを設定し、setDataCaptureListener()でリスナーを登録します。

キャプチャサイズを設定するときに利用するVisualizer.getCaptureSizeRange()で返ってくる配列はlengthが2で、index[0]は128、index[1]は1,024です。
つまり、キャプチャサイズは128か1,024しか選択出来ないようです。
129や2,048など別の数字を入れてもエラーにはなりませんが、VisualizerのgetCaptureSize()で確認すると、1,024になっていたので、自動で修正されているようです。

public int setDataCaptureListener (Visualizer.OnDataCaptureListener listener, int rate, boolean waveform, boolean fft)

Visualizer.OnDataCaptureListener listener 登録するリスナー
int rate         キャプチャ更新のレート
boolean waveform trueならonWaveFormDataCapture()が呼ばれる
boolean fft      trueならonFftDataCapture()が呼ばれる


キャプチャ更新レートの最大値はVisualizer.getMaxCaptureRate()で20,000です。

登録するリスナーはVisualizer.OnDataCaptureListenerインターフェースを実装したもので、以下二つの抽象メソッドがあります。

public abstract void onFftDataCapture (Visualizer visualizer, byte[] fft, int samplingRate)

public abstract void onWaveFormDataCapture (Visualizer visualizer, byte[] waveform, int samplingRate)


onFftDataCapture()がフーリエ変換後のデータを利用するもので、onWaveFormDataCapture()は生のWaveデータを利用するものです。


それではいよいよonFftDataCapture()についてみていきましょう。

まずドキュメントでVisualizerのgetFft (byte[] fft)部分を見ると、index[1]を除くと、byte配列には実数部と虚数部が交互に格納されているようなことが書いてあります。

ソースをちゃんと見ていませんが、onFftDataCapture()で渡されてくるbyte配列も恐らくこれと同じでしょう。

実数?虚数?という感じですが、複素数平面に拡張するには訳があります。

ある波を複数のsin波の合計で表現することがフーリエ変換なのですが、位相のズレを表現するためには虚数が必要なんです。
位相のズレたsin波をsinやcosをフル活用して表現する的な感じですかね。

はい。

あんまり良く分かりません。

まぁここで重要なのは、onFftDataCapture()のbyte配列の実数部がcos波、虚数部がsin波を表すってところです。

※という割には実数部を見ても虚数部を見ても同じところにピークが来ていました。また、ためしにcos波を混ぜてみたらまったく想定していない結果になり、謎は深まるばかりです。


ちなみにどの程度細かく分析が可能かに触れていませんでしたが、実はそんなに細かく分析出来ません。

サンプルレートが44,100Hzであれば、いわゆるナイキスト周波数は22,050Hz(44,100Hz / 2)で、キャプチャサイズが1,024であれば、分解能は21.5332031Hz(44,100Hz / 2 / 1,024)となります。

これはG♭3とG3ぐらいの差なので、音階を完全に特定するには十分とはいえない気がしますが、これが最大値なようなので仕方が無いです。
低周波になってくると音階の区別がつかなくなるってことでしょう。


つまり、サンプルレートが44,100Hzで、キャプチャサイズが1,024であれば、分解能は21.5332031Hzとなり、FFTの結果のbyte配列は恐らく以下のよう解釈すれば良いと思われます。

index[0]  実数部 0Hz~10.76660155Hz
index[1]  実数部 10.76660155Hz~21.5332031Hz
index[2]  実数部 21.5332031Hz~43.0664062Hz
index[3]  虚数部 21.5332031Hz~43.0664062Hz
index[4]  実数部 43.0664062Hz~64.5996093Hz
index[5]  虚数部 43.0664062Hz~64.5996093Hz




440HzのAの音であれば、以下のindex[41]にピークがくるということです。

index[41]  虚数部 430.664062Hz~452.1972651Hz

では実際にやってみましょう。
まずはFFTの結果を拾うところまで。


AudioTrack mAudioTrack;
Visualizer mVisualizer;

//サンプルレート
static int SAMPLE_RATE = 44100;
int bufSize = SAMPLE_RATE * 5;

//ストリームモードでAudioTrackを利用します
mAudioTrack = new AudioTrack(
  AudioManager.STREAM_MUSIC, SAMPLE_RATE,
  AudioFormat.CHANNEL_OUT_MONO,
  AudioFormat.ENCODING_DEFAULT,
  bufSize, AudioTrack.MODE_STREAM,
  420);//セッションID
mAudioTrack.play();


mVisualizer = new Visualizer(420);

// 1024
int captureSize = Visualizer.getCaptureSizeRange()[1];
mVisualizer.setCaptureSize(captureSize);


mVisualizer.setDataCaptureListener(
  new Visualizer.OnDataCaptureListener() {
   // Waveデータ
   public void onWaveFormDataCapture(
     Visualizer visualizer,
     byte[] bytes, int samplingRate) {
   }

   // フーリエ変換
   public void onFftDataCapture(
     Visualizer visualizer,
     byte[] bytes, int samplingRate) {

    //このbytesがFFT後のデータ
    //何度も呼ばれるのでこのデータを分析する

   }, Visualizer.getMaxCaptureRate(),
   false,//trueならonWaveFormDataCapture()
   true);//trueならonFftDataCapture()

mVisualizer.setEnabled(true);

byte[] audioData = new byte[bufSize];

// A単音
double freqA = 440;
double t = 0.0;
double dt = 1.0 / SAMPLE_RATE;
for (int i = 0; i < audioData.length; i++, t += dt) {
 audioData[i] = (byte) (Byte.MAX_VALUE * (Math.sin(2.0 *
   Math.PI * t * freqA)));
}
mAudioTrack.write(audioData, 0, audioData.length);


これでフーリエ変換後のデータを取得出来ました。
あとはピークがどこに来ているか確認し、分解能を元に周波数を特定すれば良いだけです。

ただし、結果を見てみると概ね正しく解析出来ますが、おかしな数字が出てくることもあります。

index[41] : 441.4306640625
index[41] : 441.4306640625
index[41] : 441.4306640625
index[73] : 785.9619140625
index[73] : 785.9619140625
index[141] : 1518.0908203125
index[169] : 1819.5556640625
index[123] : 1324.2919921875
index[41] : 441.4306640625
index[41] : 441.4306640625
index[41] : 441.4306640625
index[41] : 441.4306640625
index[49] : 527.5634765625
index[41] : 441.4306640625
index[149] : 1604.2236328125
index[149] : 1604.2236328125
index[41] : 441.4306640625
index[41] : 441.4306640625
index[41] : 441.4306640625

正しいindex[41]以外はバラバラですね。

虚数部しか見ていなかったので、位相のズレなどが正しく解釈出来ていないということでしょうか??

まぁちょっと勉強が必要ですね。

Read More...