The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long qw(:config posix_default bundling);

sub usage {
	print STDERR <<USAGE;

Usage: $0 [-duration] events ..
generates audio data in PCMU/8000 format for dial codes and
prints them to STDOUT

 duration: time in ms, default 100
 events:   string of dial codes 0123456789*#ABCD
           any other string will be used as pause of duration

duration and events can be given multiple times.


}


my $speed = 8000;

{
	my %event2f = (
		'0' => [ 941,1336 ],
		'1' => [ 697,1209 ],
		'2' => [ 697,1336 ],
		'3' => [ 697,1477 ],
		'4' => [ 770,1209 ],
		'5' => [ 770,1336 ],
		'6' => [ 770,1477 ],
		'7' => [ 852,1209 ],
		'8' => [ 852,1336 ],
		'9' => [ 852,1477 ],
		'*' => [ 941,1209 ], '10' => [ 941,1209 ],
		'#' => [ 941,1477 ], '11' => [ 941,1477 ],
		'A' => [ 697,1633 ], '12' => [ 697,1633 ],
		'B' => [ 770,1633 ], '13' => [ 770,1633 ],
		'C' => [ 852,1633 ], '14' => [ 852,1633 ],
		'D' => [ 941,1633 ], '15' => [ 941,1633 ],
	);

	my $tabsize = 256;
	my $volume  = 100;
	my @costab;
	my @ulaw_expandtab;
	my @ulaw_compresstab;

	sub dtmftone {
		my $event = shift;

		my $f = $event2f{$event};
		if ( ! $f ) {
			# generate silence
			return sub { return pack('C',128) x shift() }
		}

		if (!@costab) {
			for(my $i=0;$i<$tabsize;$i++) {
				$costab[$i] = $volume/100*16383*cos(2*$i*3.14159265358979323846/$tabsize);
			}
			for( my $i=0;$i<128;$i++) {
				$ulaw_expandtab[$i] = int( (256**($i/127) - 1) / 255 * 32767 ); 
			}
			my $j = 0;
			for( my $i=0;$i<32768;$i++ ) {
				$ulaw_compresstab[$i] = $j;
				$j++ if $j<127 and $ulaw_expandtab[$j+1] - $i < $i - $ulaw_expandtab[$j];
			}
		}
		
		my ($f1,$f2) = @$f;
		$f1*= $tabsize;
		$f2*= $tabsize;
		my $d1 = int($f1/$speed);
		my $d2 = int($f2/$speed);
		my $g1 = $f1 % $speed;
		my $g2 = $f2 % $speed;
		my $e1 = int($speed/2);
		my $e2 = int($speed/2);
		my $i1 = my $i2 = 0;

		return sub {
			my $len = shift;
			my $buf = '';
			while ( $len-- > 0 ) {
				my $val = $costab[$i1]+$costab[$i2];
				my $c = $val>=0 ? 255-$ulaw_compresstab[$val] : 127-$ulaw_compresstab[-$val];
				$buf .= pack('C',$c);

				$e1+= $speed, $i1++ if $e1<0;
				$i1 = ($i1+$d1) % $tabsize;
				$e1-= $g1;

				$e2+= $speed, $i2++ if $e2<0;
				$i2 = ($i2+$d2) % $tabsize;
				$e2-= $g2;
			}
			return $buf;
		}
	}
}


##### MAIN
my $duration = 100;
my $samples4ms = $speed/1000;
for my $arg (@ARGV) {
	if ( $arg =~m{^-(\d+)$} ) {
		$duration = $1;
	} else {
		for my $ev (split('',$arg)) {
			my $sub = dtmftone($ev);
			my $samples = $duration * $samples4ms;
			for( my $i=0;$i<$samples;$i+=160 ) {
				print $sub->(160);
			}
		}
	}
}