#!/usr/bin/perl -w # constuct and execute a sox command to dial a phone number with DTMF tones. $VERSION = "0.0.1"; ($SELF = $0) =~ s,.*/,,; $|++; =pod =head1 NAME soxdial - generate DTMF (touchtone) audio =head1 SYNOPSIS B [I] [ [I] [I] ... ] =head1 DESCRIPTION =head1 OPTIONS Note that option bundling is not supported. Use e.g. B<-v -n>, not B<-vn>. Also, spaces are required between options and their arguments. Use e.g. B<-b 16>, not B<-b16>. =head2 Global Options These options affect the entire output. They should only be given once, before any dial strings or dial options. =over 4 =item B<-o> I, B<--output> I Write sox's output to a file, rather than playing it. The file format is determined by the filename extension. Use B<.wav> for RIFF WAVE output, B<.flac> for FLAC, B<.ogg> for Ogg Vorbis, or anything else (including no extension) for raw audio samples. The special filename B<-> writes raw samples to standard output. =item B<-r>, B<--rate> I Set the bitrate. Default is 8000. You should probably stick with standard bitrates such as 22050, 44100, 48000, etc, although this is not enforced. =item B<-b>, B<--bits> I Set the bits per sample. Default is 8. The only other choice is 16. =item B<-v>, B<--verbose> Print verbose information, including the generated B command on stdout =item B<-n>, B<--no-exec> Do not execute the generated B command. This option also enables B<-v>. =item B<--help> Prints this help text, via B(1). =item B<--man> Prints this help text as a man page, via B(1). Suggested use: soxdial --man > soxdial.1 Then B can be installed in e.g. /usr/man/man1 or /usr/share/man/man1 or wherever your OS keeps its man pages. =back =head2 Dial Options These options can be mixed freely with dial strings, and are applied as they're found on the command line. Each one affects the rest of the dial strings, until the same option is seen again. =over 4 =item B<-l> I, B<--length> I Sets the time each digit's tones are played. Default is 0.25. =item B<-d> I, B<--delay> I Sets the delay between consecutive digits. Default is 0.1. =item B<-c> I, B<--comma> I Sets the delay added by commas in the dial strings. Default is 0.5. =item B<-x>, B<--extended> Allows the extended touchtone pad keys A, B, C, and D. B letter-to-number conversions. =item B<-a>, B<--alphabet> Disables extended touchtone pad keys A, B, C, and D; re-enables letter-to-number conversions. This is the default; this option exists to turn off a prior B<-x>, B<--extended> option. B<-t> I, B<--dialtone> I Play I of dialtone. =back =head2 Dial Strings These are the actual digits to be dialled. DTMF digits 0 to 9, #, and * are supported. By default, letters are also supported, and will be converted to digits according to the standard layout of touchtone phones (e.g. A through C convert to 2, D through F are 3, etc). Q and Z weren't present on classic phones, but they will be converted to 7 and 9, respectively (like modern cell phones). To add an extra delay between digits, use a comma. To play the extra DTMF tones for the extended 16-digit keypad (which has A, B, C, and D keys), use the B<-x>, B<--extended> option. This disables letter-to-number conversion, but it can be re-enabled later on the command line with the B<-a>, B<--alphabet> option. All characters that aren't mentioned above, will be silently ignored. This allows you to paste a phone number in the form B<(555) 555-1212> and have it work correctly. =head1 AUTHOR soxdial was written by B. Watson and released under the WTFPL: Do WTF you want with this. =cut %freqs = ( 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], 0 => [941, 1336], '#' => [941, 1477], A => [697, 1633], B => [770, 1633], C => [852, 1633], D => [941, 1633], ); @freqs1 = (); @freqs2 = (); @delays = (); $time = 0; $pausetime = 0.5; $digittime = 0.25; $intertime = 0.1; $bits = 8; $encoding = "-eun"; $rate = 8000; $output = "-d"; sub letter2number { my $ret; my $l = uc shift; return $l unless $l =~ /[A-Z]/; for($l) { if(/[A-O]/) { $ret = int((ord($_) - 65) / 3 + 2) . ""; } elsif(/[P-S]/) { $ret = "7"; } elsif(/[T-V]/) { $ret = "8"; } else { # /[W-Z]/ $ret = "9"; } } warn "$SELF: letter2number('$l') => '$ret'\n" if $verbose; return $ret; } # sox -n -d synth 0.25 sine 697 sine 1209 sine 770 sine 1477 delay 0 0 .35 .35 remix - # ...plays DTMF 1 and 6, for 0.25 sec each, with a 0.10 sec delay between them. sub add_digit { my $d = shift; if($d eq ',') { # pause push @freqs1, 0; push @freqs2, 0; push @delays, $time; $time += $pausetime; } else { #print "time $time\n"; #print "freq1 " . ($freqs{$d}->[0]) . "\n"; #print "freq2 " . ($freqs{$d}->[1]) . "\n"; push @freqs1, ($freqs{$d}->[0]); push @freqs2, ($freqs{$d}->[1]); push @delays, $time; $time += $digittime; $time += $intertime; } warn "$SELF: added digit '$d', time now $time\n" if $verbose; } # ( sox -n -p synth 2 sine 350 sine 440; sox -n -p synth 2 sine 697 sine 1209 ) | sox -G -p -d # ...plays 2 sec of dialtone followed by 2 sec of DTMF key 1. Change the -d # to 1.wav to save to a file. Prefix it with -rXXXX -sX if needed. sub make_sox_subcmd { my $cmd = "sox -n -b$bits $encoding -r$rate -c1 -traw - "; my $synth = " synth $digittime "; my $delay = "delay "; for(0..$#freqs1) { my $f1 = $freqs1[$_]; my $f2 = $freqs2[$_]; my $d = $delays[$_]; $synth .= "sine $f1 sine $f2 "; $delay .= "$d $d "; } $time = 0; @freqs1 = @freqs2 = @delays = (); return $cmd . $synth . $delay; } sub silence_subcmd { return "sox -n -b$bits $encoding -r$rate -c1 -traw - trim 0 $intertime"; } sub dialtone_subcmd { return "sox -n -b$bits $encoding -r$rate -c1 -traw - synth $_[0] sine 350 sine 440"; } sub make_sox_cmd { if($output eq '-') { $output = "-t raw $output"; } my $cmd = "sox -traw -b$bits $encoding -r$rate -c1 - $output "; my $subcmds = join(" ; ", @sox_subcmds); return "( " . $subcmds . " ) | " . $cmd; } # main() if(!@ARGV) { warn "$SELF: no dial strings. Try $SELF --help.\n"; exit 1; } for ($argc = 0; $argc < @ARGV; $argc++) { $_ = $ARGV[$argc]; if(/--?version$/) { print "$SELF $VERSION\n"; exit 0; } elsif(/^--?man$/) { exec "pod2man --stderr -s6 -cUrchlaysStuff -r$VERSION -u $0"; exit 1; } elsif(/^--?(?:\?|h)/) { exec "perldoc $0"; exit 1; } elsif(/^--?v(erbose)?$/) { $verbose = 1; } elsif(/^--?n(oexec)?$/) { $verbose = 1; $noexec = 1; } elsif(/^--?o(?:output)?$/) { $output = $ARGV[++$argc]; warn "$SELF: output set to '$output'\n" if $verbose; } elsif(/^--?b(?:its)?$/) { $bits = $ARGV[++$argc]; if($bits == 8) { $encoding = "-eun"; } elsif($bits == 16) { $encoding = "-esig"; } else { die "$SELF: bad -b/--bits, only 8 or 16 is allowed.\n"; } warn "$SELF: bits set to '$bits'\n" if $verbose; } elsif(/^--?r(?:ate)?$/) { $rate = $ARGV[++$argc]; } elsif(/^--?l(?:ength)?$/) { $digittime = $ARGV[++$argc]; warn "$SELF: digittime set to '$digittime'\n" if $verbose; } elsif(/^--?(?:x|extended)$/) { $extended = 1; warn "$SELF: extended DTMF (ABCD) enabled.\n" if $verbose; } elsif(/^--?a(?:lphabet)?$/) { $extended = 0; warn "$SELF: extended DTMF (ABCD) disabled.\n" if $verbose; } elsif(/^--?d(?:elay)?$/) { $intertime = $ARGV[++$argc]; warn "$SELF: inter-digit delay set to '$intertime'\n" if $verbose; } elsif(/^--?c(?:omma)?$/) { $pausetime = $ARGV[++$argc]; warn "$SELF: comma delay set to '$pausetime'\n" if $verbose; } elsif(/^--?(?:t|dialtone)$/) { push @sox_subcmds, dialtone_subcmd($ARGV[++$argc]); } else { warn "$SELF: start dial string '$_'\n" if $verbose; for (split "", $_) { my $digit = uc $_; if($extended) { next if $digit !~ /[0-9,#*A-D]/; } else { $digit = letter2number($digit); next if $digit !~ /[0-9,#*]/; } add_digit($digit); } warn "$SELF: end dial string '$_'\n" if $verbose; push @sox_subcmds, make_sox_subcmd(); push @sox_subcmds, silence_subcmd(); } } if(!@sox_subcmds) { die "$SELF: no digits or dialtone to generate.\n"; } my $cmd = make_sox_cmd(); if($verbose) { warn "$SELF: sox command is:\n $cmd\n"; } else { $cmd .= " 2>/dev/null"; } if(!$noexec) { system($cmd); }