7/5 プログラミング(3)


前回までの補足


今回の目標


複数の音を順に出す

/home08/ktanaka/bin/txt2wav.plは 入力データを5秒のWAVデータになるまで繰り返し出力するので,1周期分の データを出力するだけで5秒の音ができる.時間によって,変わる音を出す 場合は,5秒分以上(44100*5個以上)のデータを出力する必要がある. 以下に例を示す.
class doremi{
    // 周波数hzの音をsec秒分出力する.
    // 開始時の位相をパラメータradで渡して,終了時の位相を返す
    static double sinWave(double rad,double hz,double sec){
	int i;
	for(i=0;i< sec*44100;i++){
	    System.out.println(10000*Math.sin(rad));
	    rad=rad+2*Math.PI*hz/44100;
	}
	return rad;
    }
    // ドレミの歌
    public static void main(String[] args){
        double rad=0.0;
	// http://www.digion.com/dspark/waza/vol4/5.htm
        // 各音の周波数
	double DO=261.6; // 小文字にすると予約語doと重なる
	double RE=293.6;
	double MI=329.6;
	double FA=349.2;
	rad=sinWave(rad,DO,0.75); // ドの音を0.75秒
	rad=sinWave(rad,RE,0.25); // レの音を0.25秒
	rad=sinWave(rad,MI,0.75);
	rad=sinWave(rad,DO,0.25);
	rad=sinWave(rad,MI,0.5); 
	rad=sinWave(rad,DO,0.5); 
	rad=sinWave(rad,MI,1.0); 
	rad=sinWave(rad,RE,0.75);
	rad=sinWave(rad,MI,0.25);
	rad=sinWave(rad,FA,0.5); 
	rad=sinWave(rad,MI,0.25);
	rad=sinWave(rad,RE,0.25);
	rad=sinWave(rad,FA,1.0); 
    }
}

和音を出すプログラムをメソッド化

以下のようにsinWave2メソッドを定義して使う.
class t1{
    // 周波数hzの音をsec秒分出力する.
    // 開始時の位相をパラメータradで渡して,終了時の位相を返す
    static double sinWave(double rad,double hz,double sec){
	int i;
	for(i=0;i< sec*44100;i++){
	    System.out.println(10000*Math.sin(rad));
	    rad=rad+2*Math.PI*hz/44100;
	}
	return rad;
    }
    // 周波数hz1, 周波数hz2の和音をsec秒分出力する.
    // 開始時の位相をパラメータradで渡して,終了時の位相を返す
    static double sinWave2(double rad1,double hz1,double hz2, double sec){
	int i;
	double rad2=0.0;
	for(i=0;i< sec*44100;i++){
	    System.out.println(10000*(Math.sin(rad1)+Math.sin(rad2)));
	    rad1=rad1+2*Math.PI*hz1/44100;
	    rad2=rad2+2*Math.PI*hz2/44100;
	}
	return rad1;
    }
    public static void main(String[] args){
        double rad=0.0;
	// http://www.digion.com/dspark/waza/vol4/5.htm
        // 各音の周波数
	double DO=261.6; // 小文字にすると予約語doと重なる
	double RE=293.6;
	double MI=329.6;
	double FA=349.2;
	rad=sinWave2(rad,DO,MI,1);
	rad=sinWave2(rad,DO,FA,1);
	rad=sinWave(rad,RE,1);
	rad=sinWave2(rad,DO,MI,1);
	rad=sinWave2(rad,DO,FA,1);
    }
}

アニメーション

アニメーションを行うには,複数のppmファイルを作成してから,convert コマンドでアニメーション GIFファイルを作ると良い.ファイルをプログラム内で 名前をつけて保存することもできるが,ここではパラメータを受け取ることのできるJava プログラムを作って,パラメータを変更しながら各フレームの画像ファイル を作っていくことにする.

前回のマンデルブロー集合の例でやってみる.以下の プログラムは「赤い」部分 以外は前回のプログラムと一緒(クラス名は違う)である.

class MandelAnimation{
    // 実数を引数(パラメタ)に持つメソッド mandel の定義
    static int mandel(double x, double y){
	double zr=0, zi=0,new_zr;
	int i;
	for(i=0;i<50;i=i+1){
	    if(zr*zr+zi*zi>4.0) { // |Zi|>2.0 の時は発散と判断
		return i;
	    }
	    // 複素数の計算
	    // (zr+zi*i)*(zr+zi*i)+x+y*i=(zr*zr-zi*zi+x)+(2*zr*zi+y)*i
	    new_zr=zr*zr-zi*zi+x;
	    zi=2*zr*zi+y;
	    zr=new_zr;
	}
	return 50;
    }
    public static void main(String[] args){
	int width=256, height=256;
	int frame=Integer.parseInt(args[0]);
	System.out.println("P3");
	System.out.println(width);
	System.out.println(height);
	System.out.println(255);
	int i,j,count;
	double x, y;
	for(i=0;i< height;i=i+1){
	    for(j=0;j< width;j=j+1){
		// -1.7 <= x < 0.7, -1.2 <= y < 1.2の範囲で計算
		// 整数(int)から実数(double)への変換は「(double)整数式」
		// のようにおこなう.
		x= -1.7+2.4*((double)j/(double)width)+0.1*frame; // (1)
		y= -1.2+2.4*((double)i/(double)height); // (2)
		// 0 <= r <= 50の値が返る
		count=mandel(x,y);
                int r,g,b; 
		// r=50の時に黒(0,0,0), r=0の時にほぼ白(250,250,250)とする.
                r=250-count*5; // 
                g=250-count*5; // (4)
                b=250-count*5; // 

                r=Math.min(255,Math.max(0,r)); // R の範囲を0から255までに
                g=Math.min(255,Math.max(0,g)); // G の範囲を0から255までに
                b=Math.min(255,Math.max(0,b)); // B の範囲を0から255までに
                System.out.println(r); // R
                System.out.println(g); // G
                System.out.println(b); // B
	    }
	}
    }
}
これをMandelAnimation.javaという名前で保存する.
javac MandelAnimation.java
として,コンパイルして,
java MandelAnimation 0 > mandel0.ppm
java MandelAnimation 1 > mandel1.ppm
....
java MandelAnimation 9 > mandel9.ppm
と実行する.なお,このような繰り返しは,
for i in 0 1 2 3 4 5 6 7 8 9; do java MandelAnimation $i > mandel$i.ppm; done
のように書くこともできる(28.8シェルスクリプト参照).この後で,
convert -loop 5 -delay 15 mandel0.ppm mandel1.ppm mandel2.ppm mandel3.ppm mandel4.ppm mandel5.ppm mandel6.ppm mandel7.ppm mandel8.ppm mandel9.ppm mandelanimation.gif
を実行すると, mandelanimation.gifができる.ワイルドカードを使って,
convert -loop 5 -delay 15 mandel*.ppm mandelanimation.gif
としてもよいが,2桁以上の数字の場合は,こうすると,
mandel0.ppm
mandel1.ppm
mandel10.ppm
mandel11.ppm
..
mandel19.ppm
mandel2.ppm
...
のような順に並んでしまうので,
convert -loop 5 -delay 15 mandel[0-9].ppm mandel[1-9][0-9].ppm mandelanimation.gif
のようにすると良い.

変更部分の簡単な説明をおこなう.java コマンド実行時のパラメータ( 12.3 パラメータ 参照) がmainメソッドのargsに文字列配列( 26.1.6.5配列登場 )として 渡されてくるので,Integer.parseInt (26.1.8.14 文字列から値へ(2)) を使って数値に変換する.


(1), (2)の行を
            x= -0.5+2.4*((double)(j-width/2)/(double)width)*(1.0-frame*0.07);
	    y= 0.0+2.4*((double)(i-height/2)/(double)height)*(1.0-frame*0.07);
のようにすると, mandelanimation1.gifのように,どんど ん拡大していくアニメーションが作れる.

また,それに加えて(4)の行を

                g=250-count*5; // (4)
から,
                g=(int)((250-count*5)*frame/9.0); // (4)
のように変更すると,mandelanimation2.gifのように,色がマゼンタから白に変わっていくのが分かる.