Talking clock, written in bash using common utilities

screen shot of mhwaveedit in action

Contents

Overview

What we want to do is very simple (in principle), just automate a speaking clock.
Take a string "07:24PM" and build a set of commands that would recreate the spoken version, 'oh','seven','twenty','four',PM'.
After we have this basic talking clock we want to add some fun, like delayed once an hour utterances and random voice modulation.

Setup

The main tools required for this project are a working sound card/microphone, a sound recorder and a sound player.
You could skip the recording part if you had all the samples from another source, like a text to speech program, but where's the fun in that ?
Any Linux/*inx setup should have access to the '
date' command and the bash shell of a reasonable version.

Sampling

Most Linux systems now a days use the ALSA sound system, if you setup uses something else (like oss) then please check your documentation.
Plug in a your microphone and with the speakers turned up fair way, if you hear nothing out of the speakers you most likely need to unmute your microphone channel.
Run your favorite audio mixer program or alsamixer.
To unmute the microphone channel in alsamixer use the Left and Right cursor keys to select the channel and press 'm' to mute/unmute.
ALSA also comes with amixer that can be automated
amixer sset Mic unmute unmutes and
amixer sset Mic mute mutes the microphone.
I also had a 'Mic boost' channel on my sound card that had to be unmuted to provide a decent volume.

Sockssox (SOund eXchange) is the swiss army knife of sound processing programs and can convert almost all sound sample formats. It also comes with the ability to process effects into samples and play and record sounds.
sox has been around for many many years and should be available to just about any platform including windows and the Atari ST.
Saying that, if you have favorite sound recording program you are free to use that. I found the graphical mhwaveedit very useful.

We need to collect 28 samples, but do not worry they are all short and it will take no time at all.

File names are important for the next stage.
To record a sample with sox type rec 0.wav when you finish recording just hit CTRL+c

We are using .wav files simply because they are a non-compressed lossless format that is well know to a large number of sound programs. Feel free to use .mp3, .ogg or any other format that you wish, but remember compressed files have to be uncompressed before they can be used and this may take time or/and excessive CPU processing.

Finally make sure the samples sound as you wish by playing them back.
To play back a sample with sox use play 0.wav, we can also chain samples together
play 0.wav; play 7.wav; play 20.wav; play 4.wav; play pm.wav (the play command has a ability to play multiple files per inception, but we will not be using that)

If you are boring you may want to try the VERY poor voice samples I made, located in the Downloads and links section.

Basic talking clock script

The programming language we are going to use to provide the speaking clock with automation is Bash shell script.
To start all bash shell scripts we have a "shebang", which is a comment telling any program that is interested that this file should be interpreted with /bin/bash.

01#!/bin/bash
We can also add some comments so we know what this file is supposed to do.
03# basic.sh - a talking clock using voice samples
04# more information available at http://www.jumpstation.co.uk/scripts/talkingclock/
The 'date' command simply prints the date and time (try it)date. If only we could get that result in a more handy format ...
date --help shows there are many ways of formating the output.
06hours=`date +"%-l"`
Will assign the variable [hours] with the result of the date +"%-l" command. Note the use of back-ticks `, not single quotes'. we do the same with [mins] and [ampm] and we end up with
01#!/bin/bash
02
03# basic.sh - a talking clock using voice samples
04# more information available at http://www.jumpstation.co.uk/scripts/talkingclock/
05
06hours=`date +"%-l"`
07mins=`date +"%-M"`
08ampm=`date +"%-P"`
Add some magic 'play' commands and we have a very basic talking clock.
01#!/bin/bash
02
03# basic.sh - a talking clock using voice samples (basic version)
04# more information available at http://www.jumpstation.co.uk/scripts/talkingclock/
05
06hours=`date +"%-l"`
07mins=`date +"%-M"`
08ampm=`date +"%-P"`
09
10play $hours.wav
11play $mins.wav
12play $ampm.wav
Save this as
basic.sh and run sh basic.sh if the voice samples are in the same directory you should hear the time spoken.

But wait, this only works if the minutes are 0-21,30,40 or 50, OK time to make this a bit more complex.
Other values will give an error sox: Can't open input file '.wav': No such file or directory

Advanced talking clock script

When writing complex code it is a good idea to modularise as much as possible, this makes reading and reusing code much easier.
We want a function for "hours","mins","AM/PM" and one to handle the actual playing of the samples, this makes it easy to change for the
Voice modulation section.

06# no processing required for hours
07function sayHours() {
08  wavplay $1.wav
09}
[$1] represents the first value passed to the function, this will make more sense later.
11# no processing required for AM/PM
12function sayAMPM() {
13  wavplay $1.wav
14}
Looks familiar, now we need that [wavplay] function
39# use the play command to reproduce the requested sound
40function wavplay() {
41  play $1
42}
hmmm, that was all very simple, lets look at how the script looks at the moment
01#!/bin/bash
02
03# lessbasic.sh - a talking clock using voice samples (less basic version)
04# more information available at http://www.jumpstation.co.uk/scripts/talkingclock/
05
06# no processing required for hours
07function sayHours() {
08  wavplay $1.wav
09}
10
11# no processing required for AM/PM
12function sayAMPM() {
13  wavplay $1.wav
14}
15
--
39# use the play command to reproduce the requested sound
40function wavplay() {
41  play $1
42}
43
44hours=`date +"%-l"`
45sayHours $hours
46mins=`date +"%-M"`
47#sayMins $mins
48ampm=`date +"%-P"`
49sayAMPM $appm

Wait a minute why is the sayMins $mins line commented out #sayMins $mins ? Because the [sayMins] function is a bit more complex.
Lets add it now
16# have to split the number and deal with 00-09,10-19, then 20-59
17function sayMins() {
18  tens=${1:0:1}
19  units=${1:1:1}
20  # check length
21  if [ ${#units} = 0 ]; then
22    units=$tens
23    tens="0"
24  fi
25  if [ $tens -lt 1 ]; then
26    wavplay $tens.wav
27    wavplay $units.wav
28  elif [ $tens -lt 2 ]; then
29    wavplay $1.wav
30  else
31    tens=$tens"0"
32    wavplay $tens.wav
33  if [ $units -gt 0 ]; then
34      wavplay $units.wav
35    fi
36  fi
37}
Woh! thats a bit more complex, never fear, I will break it down. Now as the comment says, we need to deal with three circumstances.
  1. 0-9
  2. we want to utter an 'oh' before each number, e.g. 'oh','seven' for seven minutes past the hour.
  3. 10-19
  4. each of these exist as an individual file so we do nothing extra.
  5. 20-59
  6. we want to utter the correct decimal, e.g. 'twenty','four' for 24 but only 'twenty' for 20 not 'twenty','oh'
First job is to take the number and split it into its possible components
18 tens=${1:0:1}
19 units=${1:1:1}
The ${} implies an action on an existing variable, the first 1 is the $1 we see in the other functions, the second value (0 for $tens and 1 for $units) is the starting point in the string, the third value is the length of the string.
so word=${word:0:3} when $word is 'google' the result is 'goo'
Because the value passed into the function from the 'date' command might only be one character i.e. 0-9 the first thing we need to check is if anything is in the $units variable. The ${#units} returns the length of the string value contained in $units, if this is zero then the variable is empty.
The spacing in bash script if statements is VERY important if [ ${#units} = 0 ]; then

When we find the $units is empty it will be because the single value fitted in $tens. to get what we want we swap it around a bit.

22 units=$tens
23 tens="0"
At this point, we have a value in $tens and a value in $units, time to get to work.
if [ $tens -lt 1 ]; then will be true if $tens is less than '1' i.e. '0'
in which case, play the 'oh' followed by the unit value.
26 wavplay $tens.wav
27 wavplay $units.wav
elif [ $tens -lt 2 ]; then will be true if $tens is less than '2' i.e. '0' or '1', but as the code already deals with '0', the condition only returns true if $tens is '1'
in which case, play the original value passed to the function as it will be 10-19
29 wavplay $1.wav
else will be true if all the previous conditions returned false.
So play the tens part, 2,3,4 and 5 become 20,30,40 and 50
31 tens=$tens"0"
32 wavplay $tens.wav
If there are any units greater than '0' then we need to say them as will otherwise our job is done.
33if [ $units -gt 0 ]; then
34  wavplay $units.wav
35fi

Here is the full code, save it as lessbasic.sh
run sh lessbasic.sh to test it.
You can change the call 'sayMins $mins' at the end to test it works with all values rather than waiting for the right time of day.

sayMins 0
sayMins 9
sayMins 10
sayMins 11
sayMins 19
sayMins 20
sayMins 32
sayMins 58

Voice Modulation

Time to spice things up a bit.
This is specific to the
sox 'play' command, but you may find other playback systems have similar options.
The standard sampling rate for .wav files is 8000hz, if we play back the sample at the same 8000hz then it should sound like the original. But if we instead play it back at 10000hz or 6000hz it will sound speeded up or slowed down respectively.

# use the play command to reproduce the requested sound at a random speed
function wavplay() {
  rate="--rate="$(($RANDOM%1600))
  play $rate $1
}
$(($RANDOM%1600)) returns a random number between 0 and 1599.
If for example it returned '10000' then the resulting 'play' command for 0.wav would be
play --rate=10000 0.wav which makes your sample sound like a chipmunk!.
The problem is that a random value of 10 is going to be painfully slow, even 3000 is unpleasant(sounds like a long belch!).
To get around this I added a minimum value, thus.
rate="--rate="$(($((basicrate - variance)) + $change ))
Where [basicrate] starts as '8000' and because it sounds funnier if the voice is speeded up rather than slowed down I added a bias.
biased=2000
basicrate=$((basicrate + $biased))
variance=4000
change=$(($RANDOM%$((variance * 2)) ))
Which gives the result of a range of values from 6000 to 14000, in the final script I also added handy flag to turn all this voice changing on or off.

Automating

So we have our voice changing talking clock that runs on command, this final section adds automation so that the talking clock is called upon once and hour after a random number of minutes.

The 'cron' daemon commonly has the task of controling scheduled tasks.
To add a new task use the command crontab -e
Add a new line 0 * * * * sleep $(($RANDOM\%60))m; /pathToTheScript/timeis.sh
This will run at '0' minutes past the hour but will sleep for a random number of minutes 0-59, once the sleep command has finished the next command is our 'timeis' script, note: you must use the full path to the script.
The '\' before the '%' percent sign is required in contab files, but not at the command line.
The semicolon';' can be used to chain commands together, try.
date ; sleep $(($RANDOM%60))s ; date
man crontab will tell you more about controling the 'cron' daemon.

Final notes

I run xmms most of the time continuosly with 3000+ songs, so I added a quick pause when ever the talking clock kicked in.

xmms --play-pause
hours=`date +"%-l"`
sayHours $hours
mins=`date +"%-M"`
sayMins $mins
ampm=`date +"%-P"`
sayAMPM $ampm
xmms --play-pause
xmms --play-pause will pause the player if it is playing or resume if it is already paused.

This is all very simple stuff and it should be very easy to bend the script to do various other things, like speak the date or warn you if you are low on disk space.

Above all, have fun with it, I did !

Downloads and links

basic.sh - The basic example.
lessbasic.sh - follows on from the basic example.
timeis.sh My final version of the timeis.sh.
voice.bzip2 (81k) My voice files, VERY poor quality, I am sure you can do better.

ALSA
bash shell
date command
sox (SOund eXchange)
mhwaveedit
audacity
glame
xmms
cron

contact

root




Disclaimer:
This page is by me for me, if you are not me then please be aware of the following
I am not responsible for anything that works or does not work including files and pages made available at www.jumpstation.co.uk I am also not responsible for any information(or what you or others do with it) available at www.jumpstation.co.uk
In fact I'm not responsible for anything ever, so there!

[Pay4Foss banner long]