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);
	    }
	}
    }
}