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...

2013年4月22日月曜日

テストも簡単!Intel XDKで書いたアプリをAndroidでテストする


前回エミュレータ上では、AndroidでもiPhoneでも動いたthree.jsのサンプルですが、今回は実際に実機でテストしてみます。

XDKのエミュレータの右上にある「App Tester」をクリックします。

作成したProjectがクラウドに転送されます。

転送完了。
右矢印をクリックすると手順の説明が出てきます。

ここからはappMobiのAndroidアプリ「app•lab」をインストールし、Android側で作業します。

アプリを起動し、「enter the lab」から先へ進みます。

「log in now」からブラウザを起動します。
どうやらはじめから画面の右上に表示されている「MY APPS」からでも同じく飛べるようです。

XDKに登録した際のIDとPWを入力してログインします。

先ほど転送したProjectを選択します。

「App Tester」を選択します。
「Test Local」からでも起動出来ますが、その差は良く分かりません。

「Launch」から起動します。

はい、見事動きません。

html5の互換性の問題なのか何なのか。。。

他のサンプルアプリを飛ばしてみたところ普通に動いたので、AndroidのChromeブラウザとthree.jsの問題かもしれません。

たしかにthree.jsで書いたアプリがAndroidで動いているサンプルってあまり見ないんですよね。

そもそもこのXDKのエミュレータは、ブラウザによる対応状況の差異まで考慮されていないのかもしれません。画面サイズによる差異を見る程度の話で、HTML5の互換性を100%と過程しているのだとするとあまり意味無いですね。

何にせよ、3Dで物理演算までの道のりはまだまだ長いです。。


Read More...

2013年4月21日日曜日

ブラウザで3D!Intel XDKでthree.jsを動かしてみる


では今回はIntel XDKを使って、three.jsのサンプルを動かしてみます。

この辺からthree.jsのサンプルなど含めて一式ダウンロードします。

「examples」フォルダに様々なサンプルが入っているので、Chromeで表示させながら、簡単そうなのを選んでみます。

今回選んだのは、立方体がドラッグするとクルクル回るだけの簡単なサンプル「canvas_geometry_cube.html」です。

今回、XDKの画面で新しく利用するのは、画面左側にある「Reload app」ボタンとエミュレータの右上にある「Show/Hide Debug Console」ボタンです。


前回触ったXDKのエディタ画面で、index.htmlに必要そうなところをコピペしていき、エミュレーションモードに戻ります。

「Show/Hide Debug Console」ボタンでコンソールを開き、「Reload app」ボタンで更新します。

。。。。

動きません。

コンソールを見てみましょう。


なにやら404(Not Found)が出ていますね。
それもそのはず、three.jsのライブラリをコピーするのを忘れていました。

ファイルをコピーし、パスも整えて再度リロードしてみます。


ちゃんと動きました。
マウスでドラッグするとクルクル回ります。

ちなみにリロードボタンの下に「Rotate」ボタンがあって、切り替えられます。


さらに画面右側に「DEVICE EMULATION」という欄があり、デバイスも切り替えられます。


XDKもなかなかお手軽ですが、three.jsもかなりお手軽ですね。
そのうち3Dで物理演算ぐらいまではやってみたいと思います。

Read More...

ブラウザで開発!初めてのIntel XDK


もともとappMobiが開発していたHTML5開発ツールで、Intelがスタッフごと買収して「Intel XDK」としてリリースしました。
ぱっと見た感じ便利そうだったので、軽く触ってみることにします。


まず立ち上げるとdemoアプリのHello Worldが表示されます。
新しくアプリを作成するには、左上のファイル名の左側にある「START NEW」から新規作成します。


「Project Name」を適当に決めて、今回はウィザードなどは使わずにゼロから作るので「Create your own from scratch.」を選択します。


ちなみに、Project IDは「ID.ProjectName」という規則なのですが、これID変更出来ないんでしょうか??登録に利用したメアドの@前がそのまま表示されます。
なんとなく気持ち悪いので画像は編集しつつ次へ進みます。

「Use a blank project.」を選択します。


画面に「Application Created Successfully!」と表示されて、まっさらなプロジェクトが表示されます。
以下で見えているのはエミュレーションモードで、エディタモードへ遷移するには左上にある先ほどの「START NEW」ボタンの左側「EDITOR」ボタンをクリックします。


開いた画面の左側メニューで編集したいファイルを選択すると画面右側にソースが表示されます。


生成されたindex.htmlのソースを見ると、ブランクというわりには、以下のような何やらテンプレ的なものが並んでいます。
<script type="text/javascript" charset="utf-8" src="_appMobi/appmobi_local_bootstrap.js"></script> 
<script type="text/javascript" charset="utf-8" src="http://localhost:58888/_appMobi/xhr.js"></script> 
<script type="text/javascript">
/* This function runs once the page is loaded, but appMobi is not yet active */
var init = function(){

};
window.addEventListener("load",init,false);  

/* This code prevents users from dragging the page */
var preventDefaultScroll = function(event) {
    event.preventDefault();
    window.scroll(0,0);
    return false;
};
document.addEventListener('touchmove', preventDefaultScroll, false);

/* This code is used to run as soon as appMobi activates */
var onDeviceReady=function(){
    //Size the display to 768px by 1024px
    AppMobi.display.useViewport(768,1024);
 
 //hide splash screen
 AppMobi.device.hideSplashScreen(); 
};
document.addEventListener("appMobi.device.ready",onDeviceReady,false);    
</script>

ちなみに上記を削除してリロードしてみると、以下のように怒られます。


コピペを指示されたコードは以下。
<!-- the line below is required for access to the appMobi JS library -->
<script type="text/javascript" charset="utf-8" src="http://localhost:58888/_appMobi/appmobi.js"></script>

<script type="text/javascript" language="javascript">
        // This event handler is fired once the AppMobi libraries are ready
        function onDeviceReady() {
            //use AppMobi viewport to handle device resolution differences if you want
            //AppMobi.display.useViewport(768,1024);

            //hide splash screen now that our app is ready to run
            AppMobi.device.hideSplashScreen();
        }

        //initial event handler to detect when appMobi is ready to roll
        document.addEventListener("appMobi.device.ready",onDeviceReady,false);
</script>

もともと入っていたコードと内容が違う理由は謎です。

エディタを触った感じ、「Ctrl+C」「Ctrl+Z」「Ctrl+S」などのコマンドもそのまま使えて、特に不便は感じませんでした。

ちなみに保存するとこんなポップアップが出てきます。


後は何か書いてみるだけということで、次回は実際に何か作ってみます。


今回はここまでです。

なぜ中途半端なところで終わっているかというと、調子に乗って「three.js」と「Physijs」で物理演算のサンプルを動かそうと試み、そして動かず時間だけが過ぎていき、無かったことにしたからです。。


Read More...

2013年4月14日日曜日

Chrome拡張で朝鮮半島に平和を!Browser ActionからScriptを実行する


 前回はツールバーのアイコンクリックでポップアップを表示させましたが、今回はアイコンクリックで直接スクリプトを実行します。

 拡張のアイコンをクリックすると、innerHTMLを書き換えるスクリプトを仕込みます。


 前回と違う点は「permissions」の設定と、「background」の設定です。

 「permissions」については必要なものを配列で指定します。
 今回はchrome.tabsを利用するために「tabs」を指定するのと、どのサイトでも動くように「http://*/*」(httpから始まるすべてのサイト)を指定。

 「background」については、スクリプトを登録しておくとChrome起動時に呼ばれます。
 関数を定義しておいたり、リスナーを登録しておいたりなどに利用します。

 「browser_action」の「default_popup」は、今回は使わないので削除します。

{
 "name": "南北統一",
 "version": "1.0",
 "manifest_version": 2,

 "description": "朝鮮半島を平和にします。",

 //必要なパーミッションを指定する
 "permissions": [
  "tabs", "http://*/*"
 ],

 //バックグラウンドで動く処理
 "background": {
  //スクリプトファイルを登録
  "scripts": ["background.js"]
 },

 "browser_action": {
  "default_icon": "icon.png",
  "default_title": "統一"
 }
}


 続いてスクリプトファイルを作成します。

 「chrome.browserAction.onClicked」はツールバーのアイコンがクリックされた際に呼ばれるイベントで、その「addListener」でリスナーを登録しておきます。

 「chrome.tabs.executeScript」でタブ内でスクリプトを実行します。
 第1引数はスクリプトを実行するtabIdで、現在のタブの場合はnullを指定します。
 第2引数は実行するスクリプトの詳細情報オブジェクトで、codeプロパティの場合は「:」の後ろに実行するスクリプトを文字列で指定します。

 長々と実行内容を文字列変数に格納しておいてから、executeScriptに渡していますが、もっとうまいやり方がありそうです。
 事前に関数を定義しておいてその関数を文字列で渡せば良いかと思ったのですが、やってみたら動きませんでした。

 まぁ、ここは動けば良いということで。

unification = "";
unification += 
 "document.body.innerHTML = " +
 "document.body.innerHTML.replace(/北朝鮮/g,'朝鮮');" +
 "document.body.innerHTML = " +
 "document.body.innerHTML.replace(/韓国/g,'朝鮮');";

chrome.browserAction.onClicked.addListener(function() {
 chrome.tabs.executeScript(null, {code: unification});
});

 ちなみにタグやスクリプトも無慈悲に置換されますので、「a」⇒「h1」なんてことも出来ます。


 さて、作成した拡張をChromeに登録して実行してみましょう。

 これが、

こうなる。

うーん。。

半島の統一には成功したのですが、内戦が激化しましたね。


Read More...