Talking clock, written in bash using common utilities
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.
- 'zero' or 'oh' - 0.wav
- 'one' - 1.wav
- 'two' - 2.wav
- 'three' - 3.wav
- 'four' - 4.wav
- 'five' - 5.wav
- 'six' - 6.wav
- 'seven' - 7.wav
- 'eight' - 8.wav
- 'nine' - 9.wav
- 'ten' - 10.wav
- 'eleven' - 11.wav
- 'twelve' - 12.wav
- 'thirteen' - 13.wav
- 'fourteen' - 14.wav
- 'fifteen' - 15.wav
- 'sixteen' - 16.wav
- 'seventeen' - 17.wav
- 'eighteen' - 18.wav
- 'nineteen' - 19.wav
- 'twenty' - 20.wav
- 'thirty' - 30.wav
- 'forty' - 40.wav
- 'fifty' - 50.wav
- 'PM' - pm.wav
- 'AM' - am.wav
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.
04# more information available at http://www.jumpstation.co.uk/scripts/talkingclock/
date --help shows there are many ways of formating the output.
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"`
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
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.
07function sayHours() {
08 wavplay $1.wav
09}
12function sayAMPM() {
13 wavplay $1.wav
14}
40function wavplay() {
41 play $1
42}
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
Lets add it now
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}
- 0-9 we want to utter an 'oh' before each number, e.g. 'oh','seven' for seven minutes past the hour.
- 10-19 each of these exist as an individual file so we do nothing extra.
- 20-59 we want to utter the correct decimal, e.g. 'twenty','four' for 24 but only 'twenty' for 20 not 'twenty','oh'
19 units=${1:1:1}
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.
23 tens="0"
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.
27 wavplay $units.wav
in which case, play the original value passed to the function as it will be 10-19
So play the tens part, 2,3,4 and 5 become 20,30,40 and 50
32 wavplay $tens.wav
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 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.
function wavplay() {
rate="--rate="$(($RANDOM%1600))
play $rate $1
}
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.
basicrate=$((basicrate + $biased))
change=$(($RANDOM%$((variance * 2)) ))
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.
hours=`date +"%-l"`
sayHours $hours
mins=`date +"%-M"`
sayMins $mins
ampm=`date +"%-P"`
sayAMPM $ampm
xmms --play-pause
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