From 77c1df8670114fcadd8d9258bd71a480c0b95db3 Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Thu, 11 Apr 2024 16:57:23 -0400 Subject: soxdial: moved to its own repo: https://slackware.uk/~urchlay/repos/soxdial --- soxdial | 730 ---------------------------------------------------------------- 1 file changed, 730 deletions(-) delete mode 100755 soxdial (limited to 'soxdial') diff --git a/soxdial b/soxdial deleted file mode 100755 index a97add4..0000000 --- a/soxdial +++ /dev/null @@ -1,730 +0,0 @@ -#!/usr/bin/perl -w - -# constuct and execute a sox command to dial a phone number with DTMF tones. - -$VERSION = "0.1.0"; -($SELF = $0) =~ s,.*/,,; - -$|++; - -=pod - -=head1 NAME - -soxdial - generate DTMF (touchtone or blue box) audio - -=head1 SYNOPSIS - -B [I] [ [I] [I] ... ] - -B [I] - -=head1 DESCRIPTION - -B uses B(1) to generate DTMF tones (aka touchtone -dialling) and/or "blue box" tones. Output can be played through the -system sound card, or saved to a file of any type supported by sox. - -By default, letters are accepted and converted to numbers, according -to the layout of a touchtone phone (e.g. A = 2, M = 6, etc). If the -extended touchtone digits A, B, C, and D are needed, they can be -enabled with the B<-x>, B<--extended> option (see below). - -The generated audio is monophonic by default, though stereo output -can be done with the B<-S>, B<--stereo>; B<-L>, B<--left>; or B<-R>, -B<--right> options (see below). - -The time each digit is pressed and the timing between the digits can -be controlled (B<-l>, B<--length>; B<-d>, B<--delay>, and can also be -randomized (B<-X>, B<--random>). Longer pauses can be added -between digits by including a comma, or using the B<-s>, B<--silence> -option. - -=head1 OPTIONS - -Note that option bundling is not supported. Use e.g. B<-v -n>, -not B<-vn>. Spaces are allowed but not required between options and -their arguments: B<-b16> and B<-b 16> are equivalent. Also, GNU-style -B<--option=value> is supported (and works the same as B<--option -value>). - -=head2 Utility Options - -=over 4 - -=item B<-V>, B<--version> - -Print the version number of B, then exit. - -=item B<--help> - -Prints this help text, via B(1), then exit. - -=item B<--man> - -Prints this help text as a man page, via B(1), then exit. 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 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>, 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 in hertz (or kilohertz if followed by B. Default is -8000 (or 8k). You should probably stick with standard bitrates such -as 22050, 44100, 48000, etc, although this is not enforced. If this -option is used, it B occur on the command line B any -dial string. - -=item B<-b>, B<--bits> I - -Set the bits per sample. Default is 8. The only other choice is 16. -8-bit samples will be encoded as unsigned, and 16-bit will be encoded -as signed. If this option is used, it B occur on the command -line B any dial string. - -=item B<-S>, B<--stereo> - -Output stereo audio. The left and right channels will contain the same -audio. Use this if you're importing the audio into some other piece of -software that expects stereo input files. - -=item B<-L>, B<--left> - -Output stereo audio, with the generated tones on the left channel and silence -on the right channel. - -=item B<-R>, B<--right> - -Output stereo audio, with the generated tones on the right channel and silence -on the left channel. - -=item B<-v>, B<--verbose> - -Print verbose information, including the generated B command, on standard -error. - -=item B<-n>, B<--no-exec> - -Do not execute the generated B command. This option also -enables B<-v>. - -=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>, B<--length> [ I | IB ] - -Sets the time each digit's tones are played. Default is 0.25 -or 250ms. Can be randomized with B<-X>, B<--random>. - -=item B<-d> I, B<--delay> [ I | IB ] - -Sets the delay between consecutive digits. Default is 0.1 -or 100ms. Can be randomized with B<-X>, B<--random>. - -=item B<-c> I, B<--comma> [ I | IB ] - -Sets the delay added by commas in the dial strings. Default is 0.5 -or 500ms. Can be randomized with B<-X>, B<--random>. - -=item B<-f>, B<--fast> - -Decreases the digit, delay, and comma times by 50%. - -=item B<-X>, B<--random> - -Randomize the timing. Digit length (B<-l>), inter-digit timing -(B<-d>), and the comma (B<-c>) will vary from 1x to 2x the amount -specified. This does a pretty good impression of a human dialling a -number manually. - -=item B<-Y>, B<--no-random> - -Disable randomized timing. This is the default; this option exists -to turn off a prior B<-X>, B<--random> option. - -=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. - -=item B<-t>, B<--dialtone> [ I | IB ] - -Play I (or I ms) of dialtone before the next dial string. - -=item B<-D>, B<--dialtone-type> [ I | I | I | I | I ] - -Set the type of dialtone to be played by B<-d>, B<--dialtone>. Default -is B. - -=item B<-s>, B<--silence> [ I | IB ] - -Play I (or I ms) of silence before the next dial string. - -=item B<-B>, B<--bluebox> - -Switch to bluebox dialling mode. See B below for details. - -=item B<-N>, B<--normal> - -Switch to normal touchtone dialling mode. This is the default; this -option exists to turn off a prior B<-B>, B<--bluebox>. - -=back - -=head2 Dial Strings - -These are the actual digits to be dialled. Each dial string consists -of one or more digits (as many as desired), and they will be played -consecutively. - -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 (and see the B<-c>, -B<--comma> option to set the length of the delay). - -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 BLUEBOX MODE - -A blue box is a phone phreaking device that generates tones (formerly) -used for in-band signaling within the telephone network. B -can generate these tones. - -To enable bluebox mode, use the B<-B>, B<--bluebox> option. - -Unlike touchtone dialling, bluebox signals have a timing -specification. When entering bluebox mode, the digit time and -inter-digit time are set to 60ms (except the kp and kp2 tones, which -sound for 100ms). - -To exit bluebox mode, use the B<-N>, B<--normal> option. This resets -the timing to whatever it was before entering bluebox mode. - -In bluebox mode, there is no letter-to-number conversion. - -The bluebox keypad has digits B<0-9>, which can be combined in a -single dial string. The keys B, B, B, B, and B -must appear alone as separate dial strings (meaning, put a space -before and after them). - -The alternate names for B<0> (B<10>), B (B<11>), and B -(B<12>) are not supported, since there's no way to tell an 11 from -two 1's. - -=head1 NOTES - -B<1.> B works by iterating over the words on the command -line, and building up an array of B commands for each dial string -or segment of dial tone. At the end, all the B commands are run -and their combined output (as raw samples) is piped to another B -command that writes the output as a single audio stream (raw, .wav, -or whatever format the B<-o>, B<--output> filename indicates). It has -to be done this way because B doesn't allow multiple B -arguments in the same command (or, it does, but they don't work as -expected, or at all). Because the final B command reads only raw -audio, it's impossible to change the bitrate or sample size in between -dial strings. - -B<2.> If anything on the command line starts with B<-> but isn't -a recognized option, it's not an error: it gets treated as a -dial string. This allows e.g. I<555 -1212> to work correctly, but -mistyped options will result in them being dialled as alphabetic -characters. This may be a bit surprising the first time it happens. -If you use B<-v>, B<--verbose>, you'll get warned about it at least. - -B<3.> I haven't been able to test this with a real land-line phone to -see whether it will actually dial out. - -B<4.> I know nothing about blueboxes other than what I've read on -Wikipedia, and there's no way I know of to test whether the bluebox -tones are correct (you'd need a time machine). - -=head1 AUTHOR - -soxdial was written by B. Watson and released -under the WTFPL: Do WTF you want with this. - -=head1 SEE ALSO - -B(1) - -The Wikipedia articles on DTMF, dialtone, and the Blue Box: - - https://en.wikipedia.org/wiki/Dtmf - https://en.wikipedia.org/wiki/Bluebox - https://en.wikipedia.org/wiki/Dialtone - -=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], -); - -%bluefreqs = ( - 1 => [70, 900], - 2 => [700, 1100], - 3 => [900, 1100], - 4 => [700, 1300], - 5 => [900, 1300], - 6 => [1100, 1300], - 7 => [700, 1500], - 8 => [900, 1500], - 9 => [1100, 1500], - 0 => [1300, 1500], - 11 => [700, 1700], - 12 => [900, 1700], - kp => [1100, 1700], - kp2 => [1300, 1700], - st => [1500, 1700], -); - -@freqs1 = (); -@freqs2 = (); -@delays = (); -$time = 0; -$pausetime = 0.5; -$digittime = 0.25; -$intertime = 0.1; -$bits = 8; -$encoding = "-eun"; -$rate = 8000; -$output = "-d"; -$dialtone_type = "us"; - -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; -} - -sub randomize { - my $time = shift; - return $time unless $random; - - my $newtime = $time * (rand() + 1); - warn "$SELF: randomized $time to $newtime.\n" if $verbose; - return $newtime; -} - -# 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 += randomize($pausetime); - } else { - push @freqs1, ($freqs{$d}->[0]); - push @freqs2, ($freqs{$d}->[1]); - - push @delays, $time; - - $time += randomize($digittime); - $time += randomize($intertime); - } - warn "$SELF: added digit '$d', time now $time\n" if $verbose; -} - -sub add_bluebox_digit { - my $d = shift; - if(!$bluefreqs{$d}) { - return; - } - push @freqs1, ($bluefreqs{$d}->[0]); - push @freqs2, ($bluefreqs{$d}->[1]); - - push @delays, $time; - - $time += $digittime; - $time += $intertime; - warn "$SELF: added bluebox 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. - -# each dial string (or --dialtone, or --silence) creates a -# subcommand. they get executed in a subshell, writing raw audio -# to stdout, and the final sox command reads all the audio from its -# stdin. looks like: -# ( ; ; ... ) | sox - -# --norm is needed because otherwise, the volume decreases as the number of -# digits (tones for "synth") goes up. --norm=-3 helps avoid clipping. -sub make_sox_subcmd { - return ":" unless @freqs1; - - my $length = shift || $digittime; - my $cmd = "sox -n -b$bits $encoding -r$rate -c1 -traw - --norm=-3 "; - my $synth = " synth $length "; - 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; -} - -# used to add the silence after the last digit in a dial string -sub silence_subcmd { - my $s = shift; - if(!defined $s || ($s + 0) <= 0) { - warn "$SELF: ignoring invalid --silence argument.\n"; - return; - } - warn "$SELF: adding $s sec silence.\n" if $verbose; - return "sox -n -b$bits $encoding -r$rate -c1 -traw - trim 0 $s"; -} - -sub dialtone_subcmd { - my $sec = shift; - if(!defined $sec || ($sec + 0) <= 0) { - warn "$SELF: ignoring invalid --dialtone argument.\n"; - return; - } - warn "$SELF: adding $sec sec of dial tone, type '$dialtone_type'.\n" if $verbose; - my $cmd = "sox -n -b$bits $encoding -r$rate -c1 -traw - synth $sec "; - for($dialtone_type) { - /^us$/ && do { $cmd .= "sine 350 sine 440" }; - /^uk$/ && do { $cmd .= "sine 350 sine 450" }; - /^eu$/ && do { $cmd .= "sine 425" }; - /^fr$/ && do { $cmd .= "sine 440" }; - /^jp$/ && do { $cmd .= "sine 400" }; - } - - return $cmd; -} - -# these are all the dialtone types I know about (or, that wikipedia knows about). -sub set_dialtone_type { - my $type = shift || ""; - $type = lc $type; - if($type !~ /^(?:us|uk|eu|fr|jp)$/) { - die "$SELF: invalid dialtone type '$type'.\n"; - } - $dialtone_type = $type; - warn "$SELF: set dialtone type to '$type'.\n" if $verbose; -} - -# final sox command, to which we pipe all the others. -sub make_sox_cmd { - # support quotes, spaces, etc in filenames. - # this can probably be fooled by a determined luser. - $output =~ s,",\\",g; - $output = "\"$output\""; - - if($output !~ /\./) { - $output = "-t raw $output"; - } - - my $ch = $stereo ? "channels 2" : ""; - - my $remix = ""; - if($left) { - $remix = "remix 1 1v0"; - } elsif($right) { - $remix = "remix 1v0 1"; - } - - my $cmd = "sox -traw -b$bits $encoding -r$rate -c1 - $output $ch $remix"; - my $subcmds = join(" ; ", @sox_subcmds); - return "( " . $subcmds . " ) | " . $cmd; -} - -# support either seconds (possibly with decimal point, e.g. 0.5) or -# milliseconds (e.g. 500ms). -sub parse_sec { - no warnings "numeric"; - $_ = shift || 0; - if(/^(.+)ms$/) { - $_ = $1 / 1000; - } else { - $_ += 0; - } - return $_; -} - -# support either Hz (e.g. 8000) or KHz (e.g. 8k or 8K). -sub parse_rate { - no warnings "numeric"; - $_ = shift || 0; - if(/^(.+)k$/i) { - $_ = $1 * 1000; - } else { - $_ += 0; - } - return $_; -} - -sub check_stereo { - die "$SELF: can't enable stereo after a dial string.\n" if @sox_subcmds; -} - -# main() -if(system("sox --version > /dev/null 2>&1") != 0) { - die "$SELF: can't execute sox, is it installed?\n"; -} - -if(!@ARGV) { - warn "$SELF: no dial strings. Try $SELF --help.\n"; - exit 1; -} - -# we can't use Getopt::Long here because we can apply the same dial options -# multiple times, between dial strings. -# preprocess @ARGV, convert e.g. -b16 to -b 16, --foo=bar to --foo bar. -@newargv = (); -for(@ARGV) { - if(/^(--?\w+)=(.+)$/) { - push @newargv, $1, $2; - } elsif(/^(--?[blcdts])([\d.]+(?:ms)?)$/i) { - push @newargv, $1, $2; - } elsif(/^(--?[r])(\d+(?:k)?)$/i) { - push @newargv, $1, $2; - } elsif(/^(--?[D])(\w\w)$/i) { - push @newargv, $1, $2; - } elsif(/^(--?o)(.+)$/) { - if($2 eq 'utput') { - push @newargv, $_; - } else { - push @newargv, $1, $2; - } - } else { - push @newargv, $_; - } -} - -@ARGV = @newargv; - -# this is a big ugly mess. should be refactored. works, though. -for ($argc = 0; $argc < @ARGV; $argc++) { - $_ = $ARGV[$argc]; - - if(/--?(?:V|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(?:utput)?$/) { - $output = $ARGV[++$argc]; - die "$SELF: missing argument for --output.\n" unless defined $output; - warn "$SELF: output set to '$output'\n" if $verbose; - } elsif(/^--?b(?:its)?$/) { - if(@sox_subcmds) { - die "$SELF: can't change bits after a dial string.\n"; - } - $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)?$/) { - if(@sox_subcmds) { - die "$SELF: can't change bitrate after a dial string.\n"; - } - $rate = parse_rate($ARGV[++$argc]); - die "$SELF: invalid bitrate.\n" unless $rate > 0; - warn "$SELF: bitrate set to '$rate'\n" if $verbose; - } elsif(/^--?l(?:ength)?$/) { - $digittime = parse_sec($ARGV[++$argc]); - die "$SELF: invalid --length argument.\n" unless $digittime > 0; - warn "$SELF: digit length 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 = parse_sec($ARGV[++$argc]); - die "$SELF: invalid --delay argument.\n" unless $intertime > 0; - warn "$SELF: inter-digit delay set to '$intertime'\n" if $verbose; - } elsif(/^--?c(?:omma)?$/) { - $pausetime = parse_sec($ARGV[++$argc]); - die "$SELF: invalid --comma argument.\n" unless $pausetime > 0; - warn "$SELF: comma delay set to '$pausetime'\n" if $verbose; - } elsif(/^--?(?:D|dialtone-type)$/) { - set_dialtone_type($ARGV[++$argc]); - } elsif(/^--?(?:t|dialtone)$/) { - push @sox_subcmds, dialtone_subcmd(parse_sec($ARGV[++$argc])); - } elsif(/^--?s(?:ilence)?$/) { - push @sox_subcmds, silence_subcmd(parse_sec($ARGV[++$argc])); - } elsif(/^--?(?:S|stereo)$/) { - check_stereo(); - $stereo = 1; - } elsif(/^--?(?:L|left)$/) { - check_stereo(); - $right = 0; - $stereo = $left = 1; - } elsif(/^--?(?:R|right)$/) { - check_stereo(); - $left = 0; - $stereo = $right = 1; - } elsif(/^--?(?:X|random)$/) { - # sorry I had to use -X for this, -R was already taken. - $random = 1; - } elsif(/^--?(?:Y|no-random)$/) { - $random = 0; - } elsif(/^--?f(?:ast)?$/) { - $intertime /= 2; - $digittime /= 2; - $pausetime /= 2; - } elsif(/^--?(?:B|bluebox)$/) { - @oldsettings = ($digittime, $intertime, $randomize); - $bluebox = 1; - $digittime = $intertime = 0.06; - $randomize = 0; - warn "$SELF: bluebox mode enabled.\n" if $verbose; - } elsif(/^--?(?:N|normal)$/) { - $bluebox = 0; - ($digittime, $intertime, $randomize) = @oldsettings if @oldsettings; - warn "$SELF: bluebox mode disabled (touchtone enabled).\n" if $verbose; - } else { - $_ = lc $_; - if($verbose && (/^--?[a-z]/i)) { - warn "$SELF: treating '$_' as a dial string (might be a typo?)\n"; - } - warn "$SELF: start dial string '$_'\n" if $verbose; - - my $time_override; - if($bluebox) { - if(/^(?:st[23]?|kp2?)/) { - $time_override = 0.1 if $_ eq 'kp' || $_ eq 'kp2'; - add_bluebox_digit($_); - } else { - add_bluebox_digit($_) for split "", $_; - } - } else { - 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); - } - } - push @sox_subcmds, make_sox_subcmd($time_override); - push @sox_subcmds, silence_subcmd(randomize($intertime)); - - warn "$SELF: end dial string '$_'\n" if $verbose; - } -} - -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); -} -- cgit v1.2.3