携帯でSkype

携帯でSkype
スマートフォンだとSkypeが使えるらしい。
なんだか悔しいから普通の携帯からSkype(チャットのみ)を使える様にしてみた。

携帯Skypeの構成

1. 起動しているSkypeapplescriptから操作/情報取得出来る。
2. phpからapplescriptを使い、掲示板風の携帯サイトでSkypeを操作出来るようにしたい。
3. phpにはapplescriptを実行する権限が無いから、権限を持って起動させたjavaプログラムを仲介役に使う。
 ということで、次のような構成に。

PHP -[socket]- Java -[exec]- AppleScript -[send command]- Skype

phpから直接applescriptを実行出来る様に権限を与えておくとか
javaから直接Skypeを操作する(Skype API For Java)ことも可能かな

Skype from AppleScript

AppleScriptからSkype APIを使う方法

tell application "Skype"
	set result1 to (send command "SKYPEコマンド1" script name "ほげ")
	set result2 to (send command "SKYPEコマンド2" script name "ほげ")
end tell

これでOK。
send commandがどういった仕組みなのかはよくわからない。

SKYPEコマンド
今回使ったSKYPEコマンドについて
詳細は http://www.google.com/search?q=SKYPE%20API%20PDF から

SEARCH FRIENDS
コンタクトリスト(skype nameの羅列)を取得する

GET USER [skypename] [property]
ユーザーの詳細情報を取得する

  • GET USER tompng_fake_skypename ONLINESTATUS
  • GET USER pngtomfakename FULLNAME

SEARCH RECENTCHATS
最近のチャットリスト(チャットIDの羅列)を取得する

GET CHAT [chatid] [property]
チャットの詳細情報を取得する

  • GET CHAT #hoge/$piyo;12345678 FRIENDLYNAME
  • GET CHAT #piyo/$hoge;98765432 RECENTCHATMESSAGES
    • 最近の発言(メッセージIDの羅列)を取得

GET MESSAGE [messageid] [property]
メッセージの詳細情報を取得する

  • GET MESSAGE 63 STATUS
  • GET MESSAGE 1025 PARTNER_HANDLE
  • GET MESSAGE 8182 TIMESTAMP
  • GET MESSAGE 66636 BODY

SET MESSAGE SEEN
メッセージを既読にする

CHATMESSAGE [chatid] [message]
チャットIDを指定して発言

  • CHATMESSAGE #tom/$png;89abcdef このチャットは侵略したでゲソ!

MESSAGE [skypename] [message]
ユーザーを指定してメッセージ送信

  • MESSAGE exampleskypename_ikamusume 伊勢エビの当選おめでとうございます!

必要な機能
上記のコマンドを組み合わせて
・最近のチャットの[チャットID チャット名 未読メッセージ数]を取得する機能
・コンタクトリスト(skype名 表示名 状態)を取得する機能
・チャットIDを指定して、チャットの情報(参加者、最新メッセージ(発言者,時間,本文))を取得する機能
・チャットIDもしくは送信先ユーザーを指定してメッセージを送る機能
を作る。

Java

ServerSocketで待機して、applescriptをexecした結果を返す

class SkypeSession extends Thread{
	public static void main(String args[]){
		while(true){
			new SkypeSession(serversocket.accept()).start();
		}
	}
	public void run(){
		引数=socket.read();
		cmdarray=["osascript","スクリプトファイル.applescript",引数]
		Process proc=Runtime.getRuntime().exec(cmdarray);
		socket.write(procの結果文字列);
		socket.close();
	}
}

PHP

socket経由でjavaに要求を出す

function skypeCMD($arg){
	global $PORT;
	$fp=fsockopen("localhost",$PORT);
	fwrite($fp,$arg);fflush($fp);
	$retval="";
	while($s=fread($fp,1024))$retval.=$s;
	fclose($fp);
	return $retval;
}

セキュリティ

Javaではsocketの接続先IP(loopbackのみとか)で制限したり
PHPではIP帯域と携帯の識別番号とか普通のパスワード認証とかをかけてやればいい。

そしてついに


いたって普通の地味な掲示板っぽい見た目だけども

これ俺もでスマフォ(疑似)ユーザーの仲間入り!

模様生成2 『雪』

雪の結晶模様

模様シリーズその2 雪の結晶の成長シミュレーションっぽい事をやってみようー

六角形の格子で雪の結晶を成長させる。
プログラムは
1. ほっとくと勝手に六角形に成長する
2. 先端が成長しやすい(パラメータで制御)
を満たしていればいいはず。


氷の量の配列iceの更新処理

1. ほっとくと六角形に成長する式
 iceが1を越えないように、そして結晶に隣接した点でしか成長しないようにする

var max=点(i,j)の周囲6点でのiceの最大値;
ice[i][j]+=(1-ice[i][j])*max*max;

2. 先端が成長しやすいように修正

var av=点(i,j)の周囲の半径p以内でのiceの平均値;
ice[i][j]+=(1-ice[i][j])*max*max/(1+Math.exp(32*av-4));

だいたいこんな感じ。見ての通りすごいテキトー。


あとは、iceの初期値として、数カ所だけゼロでない値をいれてやると・・・


こんなかんじで出来る。


動くサンプル

http://www.geocities.jp/flyinpng/snow20110531/
マウスの位置に応じて成長する。





処理はやたらと重い。
この方法の他に、水蒸気代わりの粒子を沢山ばらまいて、引力と斥力を設定して、くっついて結晶化するのを待つ、という手段でも雪の成長シミュレーションっぽい事が出来るかも。

領域表示

簡単な領域表示 正負で色分け

f(x,y)=x^3+y^3-xyが正の部分と負の部分を色分けして図示したい。
描画領域の全てのピクセルで値を計算すれば出来るけども、それじゃ芸が無いから別の方法を試す。


演算子の再定義

実数の四則演算(実数×実数→実数)を、範囲の演算(範囲×範囲→範囲)に変更する。
ある範囲でのf(x,y)値の正負を一度に評価して計算量削減できるはず。


[3〜4]+[10〜20]=[13〜24]
[5〜8]×[-1〜2]=[-8〜16]


コードにするとこんな感じ

function add(x,y){return [x[0]+y[0],x[1]+y[1]];}
function sub(x,y){return [x[0]-y[1],x[1]-y[0]];}
function mult(x,y){
	var e,a=x[0]*y[0],b=x[0]*y[1],c=x[1]*y[0],d=x[1]*y[1];
	if(a>b){e=a;a=b;b=e;}if(c>d){e=c;c=d;d=e;}
	return [a<c?a:c,b<d?d:b];
}

これを使って、f(x,y)を書き換える。
演算子オーバーロードが使えたら楽なんだけど・・・

//function f(x,y){return x*x*x+y*y*y-xy}
function func(x,y){
	var xxx=mult(x,mult(x,x));
	var yyy=mult(y,mult(y,y));
	var xy=mult(x,y);
	return sub(add(xxx,yyy),xy);
}

このfuncはf(x,y)の範囲(値域)をゆるーく評価する関数になってる。

描画手順

1. ある広い領域x,yでfunc(x,y)を実行
2. 必ず正もしくは必ず負になるのなら色を塗って終わり
3. 正負判別できない時は、4分割した小さい領域で同じ操作をする

コード全体

<script>
function add(x,y){return [x[0]+y[0],x[1]+y[1]];}
function sub(x,y){return [x[0]-y[1],x[1]-y[0]];}
function mult(x,y){
	var e,a=x[0]*y[0],b=x[0]*y[1],c=x[1]*y[0],d=x[1]*y[1];
	if(a>b){e=a;a=b;b=e;}if(c>d){e=c;c=d;d=e;}
	return [a<c?a:c,b<d?d:b];
}
function func(x,y){
	var xxx=mult(x,mult(x,x));
	var yyy=mult(y,mult(y,y));
	var xy=mult(x,y);
	return sub(add(xxx,yyy),xy);
}

function start(){
	var canvas=document.getElementById("canvas");
	var context=canvas.getContext("2d");
	
	var queue=[[0,0,512]],index=0;
	function recursive(x,y,s){
		var xx=2*x/512-1,yy=1-2*y/512,d=2*s/512;
		var z=func([xx,xx+d],[yy-d,yy]);
		if(z[1]<0)context.fillStyle="red";
		else if(z[0]>0)context.fillStyle="blue";
		else{
			if(s!=1){
				queue.push([x,y,s/2]);
				queue.push([x+s/2,y,s/2]);
				queue.push([x,y+s/2,s/2]);
				queue.push([x+s/2,y+s/2,s/2]);
			}
			var c="0123456789abcdef",zm=-z[0]+1e-16,zp=z[1]+1e-16,zz=zm+zp;
			context.fillStyle="#"+c.charAt(parseInt(16*zm/zz))+"0"+c.charAt(parseInt(16*zp/zz));
		}
		context.fillRect(x,y,s,s);
	}
	
	function loop(){
		for(var i=0;i<256;i++){
			var p=queue[index++];
			if(!p)return;
			recursive(p[0],p[1],p[2]);
		}
		setTimeout(loop,0);
	}
	loop();
}

</script>
<body onload="start()">
<canvas width=512 height=512 id="canvas">


簡単な構文解析っぽいのを付けたサンプル
http://www.geocities.jp/flyinpng/calc20110528/


特徴
・計算完了する前に概形が見える
・計算オーダーはだいたいピクセル数の平方根に比例
・けどそこまで速くなくてしらみつぶしのほうがましな場合も

いつかどこかで何かに使えるかも。

マウスホイールの横スクロール検出

タッチパッドとかマウスホイールとか、最近は360度全方向にスクロールできるようになってる。
これをjavascriptで検出して使おう。

実現方法

element.onmousewheel=function(e){
	//use e.wheelDeltaX and e.wheelDeltaY
}

これだけ。


別の手段-駄目な例

実は今までe.wheelDeltaしかなくて出来ないんだと思い込んでいて、次の方法で無理矢理実現してた。

1. <div style="overflow:scroll">のスクロールバーを隠す
2. onscrollでスクロール検出(scrollLeft,scrollTop)
3. scrollLeft,scrollTopを元の位置に戻す

コードはこんな感じ

<script>
function start(){
	var scrollDiv=document.getElementById("scroll");
	var box=document.getElementById("box");
	var left=scrollDiv.scrollLeft=80;
	var top=scrollDiv.scrollTop=80;
	scrollDiv.onscroll=function(e){
		e.deltaX=scrollDiv.scrollLeft-left;
		e.deltaY=scrollDiv.scrollTop-top;
		if(e.deltaX==0&&e.deltaY==0)return;
		scrollDiv.scrollLeft=left;
		scrollDiv.scrollTop=top;
		if(scrollDiv.onmousescroll)scrollDiv.onmousescroll(e);
	}
	var x=190,y=190,vx=0,vy=0;
	scrollDiv.onmousescroll=function(e){
		vx=0.98*vx+e.deltaX*0.1;
		vy=0.98*vy+e.deltaY*0.1;
	}
	setInterval(function(){
		vx*=0.98;vy*=0.98;
		x+=vx;y+=vy;
		if(x<0){x=0;vx*=-1;}if(x>380){x=380;vx*=-1;}
		if(y<0){y=0;vy*=-1;}if(y>380){y=380;vy*=-1;}
		box.style.left=x;box.style.top=y;
	},50);
}
</script>
<body onload="start()">
<div style="border:1px solid red;position:relative;width:400px;height:400px;overflow:hidden;">
<div style="position:absolute;width:100%;height:100%;top:0;left:0" id="main">
	<div style="position:absolute;width:20;height:20;background:blue;left:190;top:190" id="box"></div>
</div>
<div style="overflow:scroll;width:100%;height:100%;left:-40px;top:-40px;padding:40px;position:relative;" id="scroll">
<div style="width:100%;height:100%;padding:80px;position:absolute;left:0;top:0;">
</div>
</div>
</div>

サンプル http://www.geocities.jp/flyinpng/scroll20110527/index.html


もはや何の役にも立たないけど、wheelDeltaX/Yが使えないブラウザがあれば。

模様生成

昔見かけた記事でプレコの模様を作るプログラムが紹介されてた。
http://www.geekpage.jp/blog/?id=2007/11/7
それをほんのちょっと自分なりに変えて作ってみた。

模様生成の手順
 1. 2次元画像にランダムに色を付ける
 2. 各ピクセルで、そのピクセルを中心とした小円と大円の内側でそれぞれ色の平均値を計算し、
   その差によってそのピクセルの色を新たに決める
 3. 2の操作を何度か繰り返していくと模様が出来ていく
という事らしい。

黒が広範囲にあると、反復操作の結果その中央部分が白くなる。
逆に、白が広範囲にあると、その中央部が黒くなる。
その結果、白と黒がある程度のスケールで偏在した模様が出来る、という理屈。

皮膚の細胞が状態に応じて物質を2種類生成して、拡散&減衰(それぞれの物質の到達距離が大円と小円の半径に相当)して、
さらに周囲から拡散してきた物質の濃度に応じて状態が変化する・・・みたいな状況を考えているんだろう。



プログラム
当然、円の半径が大きくなるほど計算は重くなる。
そこで、今回は円内部の平均値のかわりに全領域でテキトーに重み付け平均した値を使う。
あと、2値の代わりに0〜1の実数を使用。

import java.io.*;
import java.awt.image.*;
import javax.imageio.*;
class GenTex{
	public static void main(String args[])throws Exception{
		int S=512;
		double map[][]=new double[S][S];
		double tmp[][]=new double[S][S];
		double out1[][]=new double[S][S];
		double out2[][]=new double[S][S];
		double param1[][]=genParam(0.8,0.7);//range parameter(bigger)
		double param2[][]=genParam(0.75,0.65);//range parameter(smaller)
		double area1=areaParam(param1);
		double area2=areaParam(param2);
		double patternParam=1.00;//0.99~1.01
		
		if(args.length==1){
			BufferedImage img=ImageIO.read(new File(args[0]));
			int w=img.getWidth(),h=img.getHeight();
			for(int x=0;x<S;x++)for(int y=0;y<S;y++){
				int c=img.getRGB(x*w/S,y*h/S);
				map[x][y]=(0.8*(((c>>16)&0xff)+((c>>8)&0xff)+(c&0xff))/3/0xff+0.2*Math.random());
			}
		}else{
			for(int x=0;x<S;x++)for(int y=0;y<S;y++)map[x][y]=Math.random();
		}
		
		for(int i=0;i<10;i++){
			smooth(map,tmp,out1,S,param1);
			smooth(map,tmp,out2,S,param2);
			for(int x=0;x<S;x++)for(int y=0;y<S;y++){
				double d=patternParam*out2[x][y]/area2-out1[x][y]/area1;
				map[x][y]=(Math.tanh(100*d)+1)/2;
			}
			System.out.println(i);
		}
		
		BufferedImage img=new BufferedImage(S,S,BufferedImage.TYPE_INT_RGB);
		for(int x=0;x<S;x++)for(int y=0;y<S;y++)img.setRGB(x,y,0x10101*(int)(0xff*map[x][y]));
		ImageIO.write(img,"png",new File("out.png"));
	}
	static double areaParam(double[][]p){
		int n=p.length;
		double area=0;
		for(int i=0;i<n;i++)for(int j=0;j<n;j++){
			area+=p[i][1]*p[j][1]/Math.log(p[i][0])/Math.log(p[j][0]);
		}
		return 4*area;
	}
	//force weight(0,0)=1 and d/dx{weight(0,0)}=d/dy{weight(0,0)}=0
	static double[][]genParam(double pa,double pb){
		double pp=(pa*pa-4*pa+3)/(pa-pb)/(4-pa-pb);
		double param[][]={{pa,1+pp},{pb,-pp}};
		return param;
	}
	//average with weight(x,y)=sigma{p[i][1]*pow(p[i][0],|x|)}*sigma{p[i][1]*pow(p[i][0],|y|)}
	static void smooth(double[][]map,double[][]tmp,double[][]out,int S,double p[][]){
		sum(map,tmp,S,p);
		for(int x=0;x<S;x++)for(int y=0;y<S;y++)out[x][y]=tmp[y][x];
		sum(out,tmp,S,p);
		for(int x=0;x<S;x++)for(int y=0;y<S;y++)out[x][y]=tmp[y][x];
	}
	static void sum(double[][]map,double[][]out,int S,double p[][]){
		int n=p.length;
		double tmp[]=new double[n];
		for(int y=0;y<S;y++){
			for(int x=0;x<S;x++)out[x][y]=0;
			for(int i=0;i<n;i++){
				for(int x=0;x<S;x++){
					tmp[i]=tmp[i]*p[i][0]+map[x][y];
				}
				for(int x=0;x<S;x++){
					tmp[i]=tmp[i]*p[i][0]+map[x][y];
					out[x][y]+=tmp[i]*p[i][1];
				}
			}
			for(int i=0;i<n;i++){
				for(int x=S-1;x>=0;x--){
					tmp[i]=tmp[i]*p[i][0]+map[(x+1)%S][y];
				}
				for(int x=S-1;x>=0;x--){
					tmp[i]=tmp[i]*p[i][0]+map[(x+1)%S][y];
					out[x][y]+=tmp[i]*p[i][0]*p[i][1];
				}
			}
		}
	}
}

反復していくと・・・

とまぁこんな感じで収束していく。
処理は結構重い。



プログラムが無駄に長くなったのは技術不足か?
生成した模様とフラクタルノイズを組み合わせてやると、本物っぽい毛皮のテクスチャを自動で作れそうな気がする。

効果音作成

早速ネタが切れたので過去に作ったツールの紹介を。

効果音作成ツール「おでーん」

http://www.geocities.jp/flyinpng/oden/

使い方は簡単。
周波数特性(音の高さ事の成分 上の曲線)と、音程(中)と音量(下)の時間変化のパラメータ曲線をマウスドラッグでいじるだけ。
左クリックで制御点移動or追加、SHIFT+左クリックで制御点削除、SPACEキーで再生
思った通りの音を作るには若干慣れが必要。



軽く技術情報を
★パラメータ編集部分にはcanvas要素を使用。これのおかげでjavascriptの表現力が格段に上がってくれてうれしい。
★WebWorkers(バックグラウンド処理)で音声データ(dataスキーム data:audio/wav;base64,[バイナリデータ])を生成。
 詳細は以下の通り
  1. 周波数特性に乱数を掛ける
  2. FFTをかけて、元となる波形データ(ループ)を作る
  3. 指定された音程になるように再生速度を変化させたデータを作る
  4. 音量を掛けて出来た波形データにwavヘッダを付けて完成
★audio要素に渡して、再生制御。
HTML5万歳

パラメータ曲線編集方法のタッチパネル版を考えていて、iPod/Androidに移植したいと思ってるけどもそれはまた別の話。



爆発音/破裂音/摩擦音とかなら簡単に作れるはず。
ゲーム等の効果音探しに困ったら試してみては?

GLSLでレイトレーシング

メンガーのスポンジ』
今日は、昔作ったプログラムを修正したりした。

立方体をくりぬいて出来る、自己相似なフラクタル図形。

これを普通にポリゴンで描画しようと思ったら、
n段階のスポンジは20のn乗の立方体で出来ているから・・・段階数増やすとちょい厳しい。


描画方法
3D描画には、キャンバスに図形(ポリゴンとか)を描いて行く方法と、
キャンバスの各ピクセルから視線を逆向きに辿って色を決める方法がある。
今回は、後者のレイトレーシングを使う。


プログラム概略は下図の通り。

フラクタル図形の隙間(空間 立方体)の大きさ計算して、効率的に視線を逆に辿っていこうという作戦。
図では赤→橙→黄→緑 と4段階で立体まで視線を辿っている。


GLSLにするとこんなかんじ
vertex shader
ここでは特に何かやってるわけじゃない

varying vec3 pos;
varying vec3 view;
void main(){
	vec3 trans=gl_ModelViewMatrix[3].xyz;
	view=gl_Vertex.xyz+vec3(dot(gl_NormalMatrix[0],trans),dot(gl_NormalMatrix[1],trans),dot(gl_NormalMatrix[2],trans));
	pos=gl_Vertex.xyz;
	gl_Position=ftransform();
}


fragment shader
レイトレーシングを行う部分

varying vec3 pos;
varying vec3 view;
uniform int N;
uniform float SIZE;
const int MAXLOOP=100;
int inblock(float x,float y,float z){
	if(x<0.||y<0.||z<0.||x>SIZE||y>SIZE||z>SIZE)return 0;
	float S=SIZE/3.;
	int retval=int(S);
	for(int n=0;n<N;n++){
		int cnt=(x<S||x>2.*S?1:0)+(y<S||y>2.*S?1:0)+(z<S||z>2.*S?1:0);
		if(cnt==0||cnt==1)return retval;
		x-=floor(x/S)*S;x*=3.;
		y-=floor(y/S)*S;y*=3.;
		z-=floor(z/S)*S;z*=3.;
		retval/=3;
	}
	return -1;
}

vec3 move(inout vec3 p,vec3 b,vec3 v,float s){
	vec3 t1=(b-p)/v;
	vec3 t2=t1+s/v;
	float M=SIZE*65536.;
	if(t1.x<=0.)t1.x=M;if(t1.y<=0.)t1.y=M;if(t1.z<=0.)t1.z=M;
	if(t2.x<=0.)t2.x=M;if(t2.y<=0.)t2.y=M;if(t2.z<=0.)t2.z=M;
	
	float min1=min(min(t1.x,t1.y),t1.z);
	float min2=min(min(t2.x,t2.y),t2.z);
	if(min1<min2){
		p+=min1*v;
		if(min1==t1.x){p.x=b.x;return vec3(-0.5,0,0);}
		else if(min1==t1.y){p.y=b.y;return vec3(0,-0.5,0);}
		else{p.z=b.z;return vec3(0,0,-0.5);}
	}else{
		p+=min2*v;
		if(min2==t2.x){p.x=b.x+s;return vec3(0.5,0,0);}
		else if(min2==t2.y){p.y=b.y+s;return vec3(0,0.5,0);}
		else{p.z=b.z+s;return vec3(0,0,0.5);}
	}
}
vec3 trace(vec3 p,vec3 v){
	vec3 bv=vec3(0,0,0);
	if(v.x>0.&&p.x<=0.){p-=p.x*v/v.x;p.x=0.;bv=vec3(0.5,0,0);}
	if(v.y>0.&&p.y<=0.){p-=p.y*v/v.y;p.y=0.;bv=vec3(0,0.5,0);}
	if(v.z>0.&&p.z<=0.){p-=p.z*v/v.z;p.z=0.;bv=vec3(0,0,0.5);}
	if(v.x<0.&&p.x>=1.){p+=(1.-p.x)*v/v.x;p.x=1.;bv=vec3(-0.5,0,0);}
	if(v.y<0.&&p.y>=1.){p+=(1.-p.y)*v/v.y;p.y=1.;bv=vec3(0,-0.5,0);}
	if(v.z<0.&&p.z>=1.){p+=(1.-p.z)*v/v.z;p.z=1.;bv=vec3(0,0,-0.5);}
	p*=SIZE;
	
	for(int i=0;i<MAXLOOP;i++){
		vec3 pp=p+bv;
		int size=inblock(pp.x,pp.y,pp.z);
		if(size==0)return vec3(0,0,0);
		if(size<0)return p/SIZE*(0.9+0.05*dot(vec3(1,2,3),bv));
		float s=float(size);
		vec3 b=floor((p+bv)/s)*s;
		bv=move(p,b,v,s);
	}
	return p/SIZE/2.;
}

void main(void){
	gl_FragColor.a=1.;
	gl_FragColor.rgb=trace((pos+vec3(1,1,1))/2.,normalize(view));
	if(gl_FragColor.a==0.)gl_FragColor.rgb=vec3(1,1,0);
}

inblock 再帰関数(for文に展開済) フラクタルの形を決定付けている
move 隙間の境界まで視線を辿る関数
trace inblockとmoveを繰り返して視線を逆に辿って行く
場合分けが多くて嫌な感じ。




MacOSX付属のOpenGLShaderBuilderに読み込ませて、
Nに段階数(例 4)、SIZEに3のN乗(例 81)を設定してやれば描画できる。

他のボリュームレンダリングとかにも応用できたり。


P.S.
目指せ3日坊主回避