# "perl de aiff"	Surfing on sine waves with perl
#	Copyright (C) 2004  Haruka Kataoka
#	
#	This program is free software; you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by
#	the Free Software Foundation; either version 2 of the License, or
#	(at your option) any later version.
#	
#	This program is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	GNU General Public License for more details.
#	
#	You should have received a copy of the GNU General Public License
#	along with this program; if not, write to the Free Software
#	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


# USAGE
# %perl perldeaiff.pl 440 > output.aiff
#
# 夏きたる暑さしのぎに正弦波

# ていうか	遅くてイライラするけど

# 下の設定をいろいろかえる。関数 function を変更して波形をHackしよう


#出力ファイルフォーマット設定
$number_channel = 2; #1 or 2
$bits_per_sample = 16; #8 or 16
$sample_rate = 44100; #ex) 44100   22050

#出力データの設定
$duration = 2; #出力ファイルの長さ(秒)
#$frequency = 440; #関数functionに与える位相の周波数
$frequency = $ARGV[0]; #関数functionに与える位相の周波数 $ARGV[0] の時引数

#出力波形の設定
#入力: 0.0以上の位相値(ラジアン)
#出力: -1.0～1.0の値の、チャンネル数分の配列(1チャンネルの場合もサイズ1の配列を返す事)
sub function {
	my $phase = shift;

	my $data = sin($phase);
	return ($data, $data);	#正弦波

	#my $data = sin($phase);
	#my $pan = (sin($phase/1000)+1.0)/2.0;
	#return ($data*$pan, $data*(1.0-$pan));	#左右に振る

	#my $data = (sin($phase) >= 0) ? 1: -1;
	#return ($data, $data);	#矩形波

	#return (rand(2)-1.0, rand(2)-1.0);	#ノイズ
}

#定数
$pi = 3.14159265;

#--------------------------------------------------------main
#設定
local $data_max = (1 << ($bits_per_sample-1));        #出力データの最大値
my $data_format;
$data_format = "s*" if ($bits_per_sample == 16);
$data_format = "c*" if ($bits_per_sample == 8);

#ヘッダの出力   #出力を標準出力以外にするにはここで設定
my $r2a = Raw2Aiff->new(*STDOUT, $duration*$sample_rate*$number_channel*($bits_per_sample/8));
$r2a->writeHeader($number_channel, $bits_per_sample, $sample_rate);

#書き出し開始
for($frame=0; $frame<$duration*$sample_rate; $frame++) {
	@data = map {
		my $data = int($_ * $data_max);
		($data == $data_max) ? $data-1 : $data;
	} function(2.0*$pi*($frame*$frequency)/$sample_rate);
	$r2a->write(pack($data_format, @data[0..($number_channel-1)]));
}

#--------------------------------------------------------main end
#変換オブジェクト
package Raw2Aiff;

	#$r2a = Raw2Aiff->new(*FILE, $datasize);
	#コンストラクタ
	sub new {
		my $pkg = shift;
		bless {
			FILE => shift, 
			size => shift, 
			written => 0
		}, $pkg;
	}
	
	#$r2a->writeHeader($numberOfChannel, $bitsPerSample, $sampleRate);
	#AIFFヘッダを書き出す
	sub writeHeader {
		my $self = shift;
		local *FILE = $self->{FILE};
		my $channel = shift; 
		my $bits = shift;
		my $rate = shift;
		# AIFFヘッダーを出力(12 byte)
		print FILE "FORM";
		print FILE pack("L", $self->{size} + 46);
		print FILE "AIFF";
		# COMM chunkを出力(26 byte)
		print FILE "COMM";
		print FILE pack("L", 18);
		print FILE pack("S", $channel);
		print FILE pack("L", $self->{size} / (($bits/8)*$channel)); #number of frames
		print FILE pack("S", $bits);
		print FILE &ULong2Float80($rate); #Extended 80-bit floating-point format
		#SSND chunk($filesize + 16 byte)
		print FILE "SSND";
		print FILE pack("L", $self->{size} + 8);
		print FILE pack("L", 0); #offset (comment size)
		print FILE pack("L", 0); #block size
	}
	
	#$r2a->write($data);
	#データを書き込む
	#残りの未書き込みデータバイト数を返す
	sub write {
		my $self = shift;
		local *FILE = $self->{FILE};
		my $data = shift;
		print FILE $data;
		$self->{written} += length($data);
		return $self->{size} - $self->{written};
	}
	# convert
	# unsigned long -> 80 bit IEEE Standard 754 floating point number
	sub ULong2Float80 {
		my $value = shift;
		my ($exp, $val1, $i, $buf);
	
		$val1 = $value;
		$val1 >>= 1;
		for ($exp=0; $exp<32; $exp++){
			$val1 >>= 1;
			if (!$val1) {last;}
		}
	
		for ($i=32; $i; $i--){
			if ($value & 0x80000000) {last;}
			$value <<= 1;
		}
	
		return pack("C2L2", 0x40, $exp, $value, 0);
	}
1;#end package Raw2Aiff

#end of program

