#!/usr/bin/perl

use Getopt::Std;
use Time::HiRes qw( time alarm sleep );

getopts('msjtha?', \%opts);

if ($opts{'?'}) {
    print STDERR <<EOT;
Usage: $0 [-msjtha?]
    m : play morse time code
    s : morse time code for short
    j : append 'JST' after time
    t : play time signal tone
    h : higher tone at zero second
    a : automatic higher tone
EOT
    exit(0);
}

# Initialization of
# Morse to MML conversion table
#
%codetab = ( ' ' => ' ',

             '.' => '._._._', '-' => '_...._', '=' => '_..._',  '+' => '._._.', '/' => '_.._.',
             '?' => '..__..',

             'a' => '._'    ,  'b' => '_...' , 'c' => '_._.' ,  'd' => '_..'  , 'e' => '.'    ,
             'f' => '.._.'  ,  'g' => '__.'  , 'h' => '....' ,  'i' => '..'   , 'j' => '.___' ,
             'k' => '_._'   ,  'l' => '._..' , 'm' => '__'   ,  'n' => '_.'   , 'o' => '___'  ,
             'p' => '.__.'  ,  'q' => '__._' , 'r' => '._.'  ,  's' => '...'  , 't' => '_'    ,
             'u' => '.._'   ,  'v' => '..._' , 'w' => '.__'  ,  'x' => '_.._' , 'y' => '_.__' ,
             'z' => '__..'  ,

             '0' => '_____' ,  '1' => '.____', '2' => '..___',  '3' => '...__', '4' => '...._',
             '5' => '.....' ,  '6' => '_....', '7' => '__...',  '8' => '___..', '9' => '____.' );


# Make upper and lower case equal
#
foreach $chl (keys %codetab) {
    if ($chl =~ /^[a-z]$/) {
        ($chu = $chl) =~ tr/a-z/A-Z/;
        $codetab{$chu} = $codetab{$chl};
    }
}

# Calculate the duration of each character
#
foreach $ch (keys %codetab) {
    foreach $dd (split('', $codetab{$ch})) {
        $durtab{$ch} += 2 if $dd eq '.';
        $durtab{$ch} += 4 if $dd eq '_';
        $durtab{$ch} += 5 if $dd eq ' ';
    }
}

# Convert dot and dash into MML notes
#
foreach $ch (keys %codetab) {
    $codetab{$ch} =~ s/\./C-P/g;
    $codetab{$ch} =~ s/_/C-16.P/g;
    $codetab{$ch} =~ s/ /PP/g;
    $codetab{$ch} .= 'PP';
}

# Get morse tones of given text
#
sub str2morse {
    my($text) = shift;
    $text
        ? 'T100L32ML' . join('', map {$codetab{$_}} split('', $text))
        : '';
}

# Get the duration of morse tones
#
sub str2dur {
    my($text) = shift;
    my($dur)  = 0;

    for my $ch (split('', $text)) {
        next unless $codetab{$ch};
        $dur = $dur + $durtab{$ch} + 2;
    }
    $dur -= 3;
    return $dur < 0 ? 0 : $dur ;
}

# Generate Time Signal tone
#   if true argument given,
#   zero second tone will be higher freq.
#
sub timetone {
    $_[0] 
        ? 'T120C-16P16P4.C-16P16P4.C-16P16P4.>C-2'
        : 'T120C-16P16P4.C-16P16P4.C-16P16P4.C-2';
}

sub waituntil {
    my($waitsec) = shift;
    my($now) = time;
    my($sec) = $now % 60;
    return 0 if ($waitsec-$sec) < 1.0;
    my($nowf) = $now - int($now);
    sleep $waitsec-$sec-$nowf;
    return 1;
}

#=======================
# active code from here
#=======================

# HHMM for next zero second
#
if ($opts{'m'}) {
    open(SPK, ">/dev/speaker") || die $!;
    $fh = select(SPK); $| = 1; select($fh);

    $now=time;
    # 0    1    2     3     4    5     6     7     8
    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($now+60);

    $timestr = sprintf("%02d%02d %02d%02d", $hour, $min, $hour, $min);
    $timestr =~ y/0123789/TAUVBDN/ if $opts{'s'};
    $timestr .= ' JST' if $opts{'j'};

    $startsec = int(52 - (&str2dur($timestr) / 14.5));

    if (&waituntil($startsec)) {
        print SPK &str2morse($timestr);
    }
    close(SPK);
}

# generate time tone
#
if ($opts{'t'}) {
    open(SPK, ">/dev/speaker") || die $!;
    $fh = select(SPK); $| = 1; select($fh);

    $now=time;
    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($now+60);
    $outstr = &timetone($opts{'h'}||$opts{'a'}&&!($hour%6)&&!$min) . "\n";

    &waituntil(55);
    if (&waituntil(57)) {
        print SPK $outstr;
    }
    close(SPK);
}
