http://www.perlmonks.org?node_id=237578

This uses Audio::Wav to create a WAV file of DTMF tones. If you are running Windows, you have the option of playing the file after creating it (just add a second command line parameter of 1). I found the required calculations here and modified the example script listed in the Audio::Wav docs.

Usage: $0 [DTMF tones] [play]
i.e. dtmf.pl 18005551212 1

#!/usr/bin/perl -w use strict; use Audio::Wav; my @tones; my %dtmf = ( '1' => [ 697, 1209 ], '2' => [ 697, 1336 ], '3' => [ 697, 1477 ], 'A' => [ 697, 1633 ], '4' => [ 770, 1209 ], '5' => [ 770, 1336 ], '6' => [ 770, 1477 ], 'B' => [ 770, 1633 ], '7' => [ 852, 1209 ], '8' => [ 852, 1336 ], '9' => [ 852, 1477 ], 'C' => [ 852, 1633 ], '*' => [ 941, 1209 ], '0' => [ 941, 1336 ], '#' => [ 941, 1477 ], 'D' => [ 941, 1633 ], ); if (@ARGV > 0) { @tones = split'',$ARGV[0]; } else { @tones = sort keys %dtmf; } my $play = $ARGV[1] || 0; my $sample_rate = 8000; my $bits_sample = 8; my $num_channels = 1; my $pi = 4 * atan2 1, 1; my $duration = 0.5 * $sample_rate; my $wav = new Audio::Wav; my $details = { 'bits_sample' => $bits_sample, 'sample_rate' => $sample_rate, 'channels' => $num_channels, }; my $write = $wav -> write( 'dtmf.wav', $details ); for my $tone (@tones) { my @hz = map { 2 * $pi * $_ } @{$dtmf{$tone}}; add_tone(@hz); } $write -> finish(); Win32::Sound::Play('dtmf.wav') if $play && $main::can_play; sub add_tone { my (@hz) = @_; for my $pos ( 0 .. $duration ) { my $time = $pos / $sample_rate; my $val = 63 * sin($time * $hz[0]) + 63 * sin($time * $hz[1]); $write -> write( $val ); } } BEGIN { if ($^O =~ /MSWin32/) { require Win32::Sound; our $can_play = 1; } }

Update: Moved some of the calculations outside of the subroutine. Adjusted the calculation of pi.

Updated much, much later: I really don't know if the tones produced are indeed at the right frequencies... It sounds right to me but I'm no expert.

Third update: Fixed the formula as pointed out in arhuman's reply.

Replies are listed 'Best First'.
Re: DTMF Tone Generator
by zengargoyle (Deacon) on Feb 21, 2003 at 22:03 UTC
    $ perl -e ' local $/; $w=<>; open(F,">","/dev/dsp") && print F $w; ' /usr/local/mozilla/res/samples/test.wav

    plays a wav on my linux box. YMMV

    you could also move some of your calculations that don't change out of the add_tone sub.

      Why not just a simple `play /usr/local/mozilla/res/samples/test.wav` ??


      If the above content is missing any vital points or you feel that any of the information is misleading, incorrect or irrelevant, please feel free to downvote the post. At the same time, please reply to this node or /msg me to inform me as to what is wrong with the post, so that I may update the node to the best of my ability.

Re: DTMF Tone Generator
by hardburn (Abbot) on Feb 22, 2003 at 04:32 UTC

    We were just talking about this in the CB this morning. You beat me to it, Muskrat (:

    Here's an Audio::Beep version I was working out. I think I have the math worked out, but it's kinda hard since I'm not sure of the sample rate of a PC speaker.

    #!/usr/local/bin/perl use strict; use warnings; use Audio::Beep; # Generate DTMF tones # use constant SAMPLE_RATE => 8000; use constant PI => 3.14159; use constant DURATION => 500; my %tones = ( 1 => [ 1209, 697 ], 2 => [ 1336, 697 ], 3 => [ 1477, 697 ], a => [ 1633, 697 ], 4 => [ 1209, 770 ], 5 => [ 1336, 770 ], 6 => [ 1477, 770 ], b => [ 1633, 770 ], 7 => [ 1209, 852 ], 8 => [ 1336, 852 ], 9 => [ 1477, 852 ], c => [ 1633, 852 ], * => [ 1209, 697 ], 0 => [ 1336, 697 ], '#'=> [ 1477, 697 ], d => [ 1633, 697 ], ); my $number = pop; $number =~ s/[-\(\)]//g; my $i = 1; foreach (split //, $number) { my $time = $i / SAMPLE_RATE; my ($f1, $f2) = @{ $tones{$_} }; my $freq = 63 * sin($time * 2 * PI * $f1); $freq += 63 * sin($time * 2 * PI * $f2); $freq += 128; print "$_: $freq\n"; beep($freq, DURATION); $i++; }

    ----
    Reinvent a rounder wheel.

    Note: All code is untested, unless otherwise stated

Re: DTMF Tone Generator
by grantm (Parson) on Feb 22, 2003 at 03:24 UTC
    I did a cruder version of this a few years back on an intranet. The web server was running SunOS 4.1.3 which came with an audio file for each of the DTMF tones - 0.au through to 9.au. The phone numbers on the contact database screen were hyperlinked to a CGI script which simply concatenated all the digit files and returned one long audio stream (with the appropriate Content-type header). If the user held their phone receiver near the speaker then the number would be dialled. That was in the days of Netscape 1.0 :-)

      That gives me an idea. Instead of having the user hold the phone up to their speaker, what if the webserver dialed for them? If the webserver had a modem hooked up to it and this was for use by people in the office, it could dial the number and transfer the call to the person that requested the number as soon as it was dialed. Then the person who had requested it would have their phone ring and when they picked up, the phone on the other end would be ringing as well!

      Though you'd have to have some way of figuring out the extension of the person that requested the number, either by having them type their own extension in (which could be prone to abuse), have them sign in to the phone number database, or give them static IP's and link each IP to an extension number. Don't know how practical it would be, but I thought it sounded interesting anyway. :-)

        and pay long distance charges from Outer Estonia to Brooklyn!

        somewhat off-topic, but if you're in an office where you can transfer calls back and forth between phones then you have a PBX (Private Branch Exchange) of your own or are getting the same service from your Telco. in this case it's much easier to just tell the PBX to connect Phone A to Number B than to have a machine dial and transfer. you can find PBX cards form your PC for not much money that will do really neat things like mailboxes/transfers/menus/music-on-hold/just about everything. a couple hundred dollars and 2 POTS (Plain Old Telephone System) lines and you can have your own personal Operator, a bit more money and a fractional T1 or more and you can start doing really neat things.

        IP phones are really really really cool over cool networks. i've talked to the middle of Africa from Southern California and it was cleaner than any other Overseas call i've been on before. the lag was minimal, less than your normal cell-phone conversation.

        i'm in the process of setting up some IP phones for an I2 project to link various NOC (Network Operations Center) across the world. when we have a problem on The Net, pick up the phone, dial the AS (Autonomous System) number of the NOC you need to talk to and BAM connected.

Re: DTMF Tone Generator
by arhuman (Vicar) on Jun 27, 2003 at 13:02 UTC
    I really don't know if the tones produced are indeed at the right frequencies... It sounds right to me but I'm no expert

    I'm not an expert too, but the generated tones are quite different from those generated by "dtmfdial"...
    Based on what I found on http://www.hut.fi/~then/mytexts/dtmf_generation.html, I modified your prog :
    for my $tone (@tones) { my @hz = map { 2 * $pi * $_ } @{$dtmf{$tone}}; add_tone(@hz); }
    (note the factor '2')

    and now I got (what I think to be) similar tones...
    I also had to modify $duration to to have the exact same output as dtmfdial but that's just a detail


    "Only Bad Coders Code Badly In Perl" (OBC2BIP)

      ++arhuman. Good catch! I remember copying and pasting the formulas in. So I really do not know when I lost the 2.

Re: DTMF Tone Generator
by Anonymous Monk on Jun 20, 2008 at 04:25 UTC
    The sounds from the above scripts were not working when I actually tried to use them, so I wrote it again using Audio::Data. This script has worked great so far on real telephone lines and other devices. This save as an au file, but if you need a wav you can use something like ffmpeg to convert it.
    #!/usr/bin/perl use strict; use Audio::Data; my(@tones) = qw(1 2 3 4 5 6 7 8 9 0 * #); my %lut = ( '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] ); my $rate = 44000; my $audio1 = Audio::Data->new(rate => $rate); my $audio2 = $audio1->clone(); foreach my $tone (@tones) { $audio1->tone(${$lut{$tone}}[0],.3,.8); $audio1->silence(.05); $audio2->tone(${$lut{$tone}}[1],.3,.8); $audio2->silence(.05); } my $final = $audio1 + $audio2; open(my $fh, '>dtmf'.join('',@tones).'.wav'); binmode $fh; $final->Save($fh); close($fh);
Re: DTMF Tone Generator
by Anonymous Monk on Dec 14, 2009 at 09:15 UTC
    Thanks for your useful code. I found it necessary to add a pause after each tone for effective dialling in UK
    There must have been an inter-digit interval in which there is no energy detected at any of the DTMF frequencies. For compatibility with ANSI T1.401-1988, the minimum inter-digit interval shall be 45msec, the minimum pulse duration shall be 50msec, and the minimum duty cycle shall be 100msec.
    (From http://nemesis.lonestar.org/reference/telecom/signaling/dtmf.html)
Re: DTMF Tone Generator
by smichal (Initiate) on Aug 16, 2012 at 21:46 UTC
    Dear Sirs, Sorry I cannot find any direct contact info for Mr. Muskrat. Would you please let me know your copyright or licensing info on this perl script? Can anybody copy and paste for any purpose or do you use GPL or Common, etc.?

      smichal:

      There was a discussion about this on the site some time ago. (You might find it with a search.)

      I believe that the long and the short of it is: If someone posts some code here, it's (probably) intended for general use.

      IINAL, you keep the pieces if it breaks, void unless used as directed, I'm not a Doctor, but I play one on TV, etc.

      ...roboticus

      When your only tool is a hammer, all problems look like your thumb.

      smichal, I've just updated my home node with licensing information.

      Update: By the way, you contact me via /msg same as anyone else on Perl Monks. If you can't figure that out, there's a link on my home node to send me a private message.