A musical 12-tone alarm for the Raspberry Pi (part 3)

This is the third and final part of an article which started by examining a short 12tone tune which could be used as an alarm clock on the Pi, but which has grown into a project to generate larger archives of tunes for display on a web page.

A diversion…

Just after publishing part 2, which dealt with creating and displaying archives of the shorter tunes, I decided to try and generate a longer sequence of 12 of the shorter tunes, minus the three chords, stuck together, with two crotchet rests in between. Not only does this sound more sonorous, but it gives a chance to appreciate the range of 12note tunes the program can create, within the confines of fitting 3 bars and an upbeat of 3/4.

It also shows, how, if you write a program initially on function modules, you can fairly easily amend it by, for example, just not calling the chords function, and by amending the function to put together the tune, replacing it with three functions, one to deal with the first tune, with the starting header information for the lilypond source file, then a newtune function to generate the notes for the following tunes, called repeatedly, and then one to add the final tail to the lilypond source file. The script called longplay.sh  has the chords function removed, the make-lily-file function replaced with make_top_lily_file, newtune and make_bottom_lily_file, the runlily function replaced with runlilyandcrop, which generates an A4 png image of the output, which is now several lines of music, and crops off the unused bottom part of the page. To do this you need to install the program imagemajick which is done by typing apt-get update followed by apt-get install imagemagick This provides the convert command used in the function which crops off the South or bottom end of the picture.
If you don’t want to install imagemagick you can still use the program, commenting out the last three lines in the runlilyandcrop function shown below. The program will still run, but the image in the webpage will be a bit larger than it needs to be.

#echo ‘cropping…’
#convert tonerow.png -gravity South -chop 0x250 tonerowcropped.png
#mv tonerowcropped.png tonerow.png

You can see the program below by clicking on its name. As before, if you refresh this page the listing will collapse down again.

#!/bin/bash
#===========================================================================
#					File longplay.sh
#	Can be run in LXDE terminal with ./longplay.sh
#	Or from a remote ssh connection
#	This version adds random rhythms
#   Option verbose FALSE or TRUE to control information out
#	Option archivesave TRUE or FALSE enables each tune to be saved to an archive
#	Set archive path appropriately if used, (will be created if necessary)
#	Written by Robin Newman, Aug 2013, based on a script by Jonathan Kulp (debug 10Mar2014)
#===========================================================================

#five manually defined parameters
verbose=FALSE #FALSE or TRUE. TRUE allows full output of process
prefbrowser=midori #select midori or netsurf
quitbrowser=TRUE #TRUE forces browser to quit at end of script or not if FALSE
archivesave=FALSE #set TRUE or FALSE to save archived tunes in archivepath
archivepath=~/long/archive/ #set path to save timestamped archives (end in /)
#------------ set up variables for filenames ------------
pitches=/tmp/pitches.ily #pitches in 12 tone scale
random=/tmp/random.ily #pitches in scale in random order
lilyfile=/tmp/tonerow.ly #completed source music file for lilypond
midifile=/tmp/tonerow.midi #midifile created by lilypond
mp3file=/tmp/tonerow.mp3 #mp3 file created from the midi file by timidity

stem=$(readlink -f $lilyfile | sed -e 's/\..*$//') #prefix path for tone* files

withrhythms=/tmp/withrhythms.ily #used to hold assembled lilypond tune code
#following files used in generation of random rhythms for the notes
allowed3bars=/tmp/allowed3bars.ily #3 bar patterns of 3/4 bars 11 notes in total
barnames=/tmp/barnames.ily #names of the 28 bar structures
barlist=/tmp/barlist.ily #all the possible 3/4 bar rhythms
#for 1 to 6 notes restricted to lengths 1 2 3
subbarlist=/tmp/subbarlist.ily #used to hold all the bar choices for a given number of notes in the bar
chosenlist=/tmp/chosenlist.ily # 3 bar list chosen from barlist
rbars=/tmp/rbars.ily #used to hold the chosen 3 bar lengths in random order
barnotes=/tmp/tune.ily #bar note values for each of the three bars in turn
randombarnotes=/tmp/randomtune.ily #assemble chosen note durations inc upbeat
# ------------ end of file variable setup ---------------

# ------------ start definition of functions ---------------

pvar(){
#used to print a text string, a variable name and cat the contents (if a file)
# or echo if not. Uses two supplied arguments $1 and $2
if [ $verbose = TRUE ] #allows suppression if not verbose mode
then
	echo -e '==========\n'$1' (variable name '$2')';
	if [ -f ${!2} ] #variable name passed as parameter is dereferenced to get the value
					#using the format ${!2}
		then
			cat ${!2}
		else
			echo ${!2}
		fi
	echo -e '==========\n'
fi
}
# ------------ end of pvar ---------------

getpitches (){
#makes a file of the pitches in the 12 tone scale and then puts in random order
# first make a list of all 12 chromatic pitches
# using lilypond's naming conventions
# one per line b/c it's easier to shuffle, add rhythms, etc
cat > $pitches << EOFallpitches
c'
cis'
d'
dis'
e'
f'
fis'
g'
gis'
a'
bes'
b'
EOFallpitches
# now run them through the shuf command to put them in random order
shuf $pitches > $random
pvar 'The 12 note pitches in random order' random
} # ------------ end of getpitches ---------------

getallowedbars () {
#read in list of allowed bar patterns for 3 bars of 11 notes total
cat > $allowed3bars << EOFallowed3bars
146
155
236
245
335
344
EOFallowed3bars
pvar "allowed 3 bar lengths" allowed3bars
} #------------ end of getallowedbars ------------

getbarlist () {
#generates a file containing the structure of the 28 possible distinct
#bar types  in the structure name n n n x
#first build as a string
b="1n1 2. x "
b+="2n1 2 4 x 2n2 4. 4. x "
b+="3n1 4.. 4 16 x 3n2 4.. 8. 8 x 3n3 4. 4 8 x 3n4 4. 8. 8. x \
3n5 4 4. 8 x 3n6 4 4 4 x "
b+="4n1 2 8 16 16 x 4n2 4.. 8. 16 16 x 4n3 4.. 8 8 16 x 4n4 4. 4 16 16 \
x 4n5 4. 8. 8 16 x 4n6 4 4 8. 16 x 4n7 4 4 8 8 x 4n8 8. 8. 8. 8. x "
b+="5n1 2 16 16 16 16 x 5n2 4.. 8 16 16 16 x 5n3 4. 8. 16 16 16 \
x 5n4 4. 8 8 16 16 x 5n5 4 4 8 16 16 x 5n6 4 8. 8 8 16 x 5n7 4 8 8 8 8 x "
b+="6n1 4. 8 16 16 16 16 x 6n2 4 4 16 16 16 16 x 6n3 4 8. 8 16 16 16 \
x 6n4 4 8 8 8 16 16 x"
echo $b |sed -e 's/\s\s*/\n/g' > $barlist
#convert string to file with one entry on each line (spaces replaced by LF)
pvar 'list of bar structures' barlist
} #------------ end of getbarlist ------------

make_top_lily_file (){
# determine lilypond version for later inclusion
version=$(lilypond --version | grep LilyPond | cut -d " " -f3)
# make it cat the random list of pitches
lilypitches=$(cat $withrhythms)
# assemble the lilypond source file, sticking the
# pitches in at the right place
cat > $lilyfile << EOFscore
\\score {
{
\\version "$version"
\\time 3/4
\\tempo 4 = 100
#(set-accidental-style 'dodecaphonic)
#(set-global-staff-size 22)
\\set Staff.midiInstrument = "violin"
\\partial 4
$lilypitches
EOFscore
}

next_tune (){
#add next tune to lilypond file
lilypitches=$(cat $withrhythms)
cat >> $lilyfile << EOFnotes
r 4
r 4
$lilypitches
EOFnotes
}

make_bottom_lily_file (){
# determine lilypond version for later inclusion
version=$(lilypond --version | grep LilyPond | cut -d " " -f3)
# make it cat the random list of pitches
lilypitches=$(cat $withrhythms)
# assemble the lilypond source file, sticking the
# pitches in at the right place
cat >> $lilyfile << EOFscore
\\bar "|."
}
\\layout {} \\midi {}
}
EOFscore
pvar 'The completed source lilyfile' lilyfile
}

runlilyandcrop(){
# compile the score redirecting all console output to /dev/null
lilycmd="lilypond -fpng"
$lilycmd $lilyfile &> /dev/null
echo 'cropping...'
convert tonerow.png -gravity South -chop 0x250 tonerowcropped.png
mv tonerowcropped.png tonerow.png
} # ------------ end of runlily ---------------

html-long(){ #generate html file
web="$stem".html
image="$stem".preview.png
midi="$stem".midi
mdate=`date +%Y-%m-%d-%H-%M-%S` #for archive date
mdate2=`date` #for html date
cat > $web << EOFhtml
<meta http-equiv=Content-Type content="text/html; charset=UTF-8">
<title>12-Tone Tune of the Day</title>
<h2>Multiple Random 12-Tone Tunes, with random rhythms</h2>
<img src="tonerow.png" alt="randomly generated 12-tone
rows"
title="randomly-generated 12-tone rows">

<a href="tonerow.midi">Midi file</a>
Generated with bash and <a href="http://lilypond.org"
target="_blank">Lilypond</a>
generated on $mdate2
Modified and developed by Robin Newman from an original script
by <a href="http://jonathankulp.org/archives/662">
Jonathan Kulp</a>

EOFhtml
pvar 'The html page' web
} # ------------ end of html ---------------

initrandombarnotes () {
#put 4 (crotchet duration) as first note value of randombarnotes
cat > $randombarnotes << EOFnotes
4
EOFnotes
} # ------------ end of initrandombarnotes ----------------

getbarnames() {
#generate list of barnames from barlist
cat $barlist |sed -n /n/p > $barnames #generate list of barnames from barlist
pvar 'list of barnames from barlist' barnames
} # ------------ end of getbarnames ----------------

makechosenbarsfile() {
#generate a file containing the number of notes in each of the three tune bars
shuf $allowed3bars > $chosenlist #get allowed bars in random order
pvar 'Shuffled list' chosenlist
cat $chosenlist |sed q > $rbars #select 1st line of randomised allowed bars list
pvar 'Chosen 3 bars' rbars
#this is the number we want. Need individual digits shuffled
cat $rbars |sed -e 's/\(.\)/\1\n/g'| sed -e '$d' >$chosenlist
#write 3 digit number to a file 1 digit per line
pvar 'Chosen 3 bars in a list' chosenlist
shuf $chosenlist > $rbars #put thes number in random order
pvar 'Chosen 3 bars in random order list' rbars
}
# ------------ end of makechosenbarsfile ----------------

fillbar() {
#add random rhythm for bar with number of notes in supplied arg $1
action=`cat $rbars |sed -n "$1{p;q;}"` #select no of notes from the list
echo bar $1 has $action notes
sed -n /${action}n/p $barnames > $subbarlist #get list of possible barnames
pvar 'possible bar names' subbarlist
read -r firstchoice < <(shuf $subbarlist) #shuffle and take first one as choice
pvar Choice firstchoice
cat $barlist | sed -n "/${firstchoice}/{:a;n;/x/q;p;ba}" > $barnotes
#extract relevant note durations from barlist to barnotes
shuf $barnotes >> $randombarnotes
#put durations in random order and add to randombarnotes
pvar barnotes barnotes
}
# ------------ end of fillbar ----------------

addrhythms() {
#merges the pitches and durations together to get tune
paste -d " " $random $randombarnotes > $withrhythms
pvar 'random tune' withrhythms
}
# ------------ end of addrhythms ----------------

timestart() {
#initialise a datetimevariable
date1=$(date +"%s")
echo "Lapsed time initialised to 0"
}
# ------------ end of timestart ----------------

timeduration() {
#calculate elapsed time from timestart and print
date2=$(date +"%s")
diff=$(($date2-$date1))
echo "Total $(($diff / 60)) minutes and $(($diff % 60)) seconds elapsed."
}
# ------------ end of timeduratiopn ----------------

archive(){
#archive the tonerow folder
archdir=$archivepath'tone-'$mdate  #$made date/time from html function
mkdir -p $archdir #-p in case path doesn't exist
cp /tmp/tone* $archdir
}
# ------------ end of archive ----------------

#------------ program commands start from here -------------

timestart
#check if Desktop is running
if [[ `pidof lxsession` = "" ]]
then
echo 'no desktop running on the Pi.startx & from this terminal, then rerun'
exit
else
export DISPLAY=:0
fi
echo prefbrowser is $prefbrowser #print preferred browser name
echo 'Generating source music file lilyfile.ly'
cd /tmp #work in temp directory
############ make first 12 bar tune, configured with upbeat
getbarlist
getbarnames
getallowedbars
initrandombarnotes
makechosenbarsfile
fillbar 1
fillbar 2
fillbar 3
pvar 'randomised rhythms for each of the three bars joined to first note' randombarnotes
#at this stage random rhythm list is complete
#assemble tune
getpitches
addrhythms
make_top_lily_file # create top section of lilypond source file including notes for tune 1
#repeat next section as desired#######################################
for i in {1..11}
do
getallowedbars
initrandombarnotes
makechosenbarsfile
fillbar 1
fillbar 2
fillbar 3
pvar 'randomised rhythms for each of the three bars joined to first note' randombarnotes
#at this stage random rhythm list is complete
#assemble the notes and add to the existing tune
getpitches
addrhythms
next_tune #this adds the next set of notes, preceded with two crotchet rests 4 4
done
#end of repeated section###########################################
make_bottom_lily_file #add the remainder of the source file code
cat $lilyfile
echo 'Compiling lilyfile.ly with lilypond'
runlilyandcrop #run lilypond command to generate a png output and crop bottom of image
sleep 0.5
timeduration
html-long #run code to assemble the html page
echo 'html made'
if [ "$(pidof $prefbrowser)" ]  #first kill existing browser if running
then
killall $prefbrowser
fi
echo 'Starting Web Browser'
if [ $prefbrowser = midori ]
then
#rm -f ~/.config/midori/session.xbel
$prefbrowser -e Fullscreen -a $web &
else
$prefbrowser file://$web &
fi
sleep 4
#allow time for web browser to open
echo Playing midi file
sleep 2
timidity $midifile &> /dev/null #play midi file

#wait before killing browser
sleep 2
if [ $quitbrowser = TRUE ] #then force browser to quit
then
killall $prefbrowser
echo browser killed
fi
#clean stuff up
rm /tmp/*.ily
if [ $archivesave = "TRUE" ]
then
echo 'Archiving';archive
fi
echo "Finished!"
timeduration
#------------ end of script -------------

or you can see it listed here and you can download it to your Pi by typing
wget http://r.newman.ch/rpi/sounding-off/longplay.txt and then
mv longplay.txt longplay.sh and chmod 755 longplay.sh

The program generates the first tune as in serialrpi.sh, apart from using make_top_lily_file instead of the previous make-lily_file, and omitting the call to the chords function. The relevant functions to generate a tune are then called again in a do…….done loop 11 times to generate a further 11 tunes which are appended to the 1st tune each tune preceded by two  crotchet rests to complete the bar for the upbeat, using the newtune  function. Finally the end of the lilypond source file is added by make_bottom_lily-file.
The tune is then compiled by the altered runlilyandcrop function and the program works as before, apart from removing the cleanup for .eps files as there are none generated.

As before you can set the script to create and use an archive to store each long tune that is generated, and I have written scripts to process such an archive and produce web pages either just with images, or with the addition of mp3 files, which can be played on an external browser on another computer. However, as the tunes are longer, the file creation can take some time, so more than about 5 long tunes in one archive may not be a good idea.

Links to the files required are provided below and they can be downloaded to the Pi using the wget command and changed to executable files as described previously

1 longplaymakearchive,sh  Generates an archived tune without displaying it
You can run this say 5 times in a loop to get 5 archived long tunes
2 createLONGwpimageonly.sh  makes an image only webpage of the archived tunes
3 setupmidimp3.sh is exactly the same as previously described (and is available in part 2)
NB you may have to change the archive location and webpagename for this script
4 createLONGmp3webpage.sh makes a complete webpage of the archived tunes with embedded mp3 files

Make sure to check that the archive location is set in each file and archivesave is TRUE if you want to use it, before running the scripts, and that setupmidimp3.sh and createLONGmp3webpage.sh have the same directory name for the webpage

wget http://r.newman.ch/rpi/sounding-off/longplaymakearchive.txt
wget http://r.newman.ch/rpi/sounding-off/createLONGwpimageonly.txt
wget http://r.newman.ch/rpi/sounding-off/createLONGmp3webpage.txt

Alarm on a remote computer

The final section of this article deals with how to display and run the fullwebpage remotely on a different computer, completely under the control of the Pi. I describe this in terms of a connection to my Mac which is my main computer, but with slight adjustment it can work with another Linux computer provided you have an available share. I have tried this satisfactorily with Ubuntu. I have not managed to get this working with a Windows 7 PC, even after installing an ssh server (mobaSSH) because I haven’t found a way to launch a gui application from the ssh connection which is visible to the local user.

The script I use is called serialr.sh and it is very similar to the serialrpi.sh script described in part 1.
It has the additional parameter pibrowser set by a command line argument pi or mac at runtime. If this is set to TRUE then the program is essentially identical to serialrpi.sh If it is set to FALSE, then the tune is generated exactly as in serialrpi with the addition of an embedded mp3 file, then the webpage and associated image and mp3 file are copied to a share on the Mac which has been set up, after which the script opens an ssh connection to the Mac and runs a command to open a browser and display the webpage. Then after a pause to allow the tune to play, If the option quitbrowser has been set to TRUE  it runs a script, which has also been copied to the share, to quit the browser again.

Creating a Mac share and mounting it on your Pi
For this script to work, you first have to have a share created on the Mac or other computer, and the Pi has to be linked to it.

On the mac I created a folder called pishare in my Documents folder, and then in System Preferences… selected Sharing, made sure File Sharing was ticked and in the options… Share files and folder using SMB (Windows) was also ticked and added the folder (in teh main sharing window) to the list of shares using the + Setting my username to Read & Write access.

on the Pi I created a mount point for the share, by typing mkdir ~/pishare
Then I created a credentials file by typing nano .cifscredentials
and adding the contents
username=nnnnnnn
password=ppppppppp
then wrote the file with ctrl+x  Y  ENTER
I changed the ownership and access using
chown root:root .cifscredentials
chmod 600 .cisfcredentials
This file holds the sensitive logon details for the share (use your own values for nnnnnnn and ppppppppp)

I then edited the fstab file using sudo nano /etc/fstab
and added the line

//mac-ip-address/pishare /home/pi/pishare cifs credentials=/home/pi/.cifscredentials,nounix,sec=ntlmssp,uid=pi,gid=pi,file_mode=0764,dir_mode=0764,auto 0 0

(substitute the actual ip address of your mac)
This line specifies the address and name of the shared folder on the mac, the mount point on the Pi, the full address of the credentials file and then various mount options. nounix and ntlmssp are necessary to mount a share from a Mac. Then can be ommitted if the share is on Ubuntu. The gid an pid give the account/group that will own the files on the Pi and file_mode and dir_mode give the permissions that will appear for each file and directory. Auto makes it mount on boot. Make sure there are no trailing spaces on the line, or in the list from credentials….auto

You can test if it works by typing sudo mount -a
then type mkdir ~/pishare/test, followed by ls
One slight quirk which I haven’t explained yet, is that you will see a blank line, but if you do ls again you will see the new directory test on your share, and you can also see it on the mac in the shared directory. This glitch only occurs the first time you access the share after mounting it.

Setting up automatic ssh
The mac has a built in ssh server and you can enable access from System Preferences… again on the Sharing module by clicking the Remote Login box. Again make sure that the username you wish to use is given access to this.
You can test by using ssh username@mac-ip-address substituting appropriate values for username and mac-ip-address. What is of more use is to set up automatic login. You do this by creating security keys on the Pi and installing the public key on the Mac.
On the Pi type ss-keygen -t rsa
and press ENTER for each prompt until the process is complete
then type ssh-copy-id username@mac-ip-address
which will log onto your Mac and install the key in the appropriate directory. You can inspect it there as prompted if you wish.

Subsequently you can ssh to the mac without having to give a password. This enables you to perform commands on the mac with lines like

ssh username@mac-ip-address “ls”
which will list the contents of the user’s home directory on the Mac.

Now for the script code
At long last we can look at the script code. This is shown below and can be viewed in the usual way by clicking the file name. Refreshing this page collapses it again.

#!/bin/bash
#===========================================================================
#					File serialr.sh
#	usage .serialr.sh pi|mac
#
#   Three manual options to be set below to configure program operation
#   For mac operation a further 4 context settings to be adjusted
#	This version adds random rhythms and works with Pi or Pi+Mac
#
#	Written by Robin Newman, Aug 2013, based on a script by Jonathan Kulp (debug 10Mar2014)
#===========================================================================

#three manually defined parameters
verbose=FALSE #FALSE or TRUE. TRUE allows full output of process
quitbrowser=TRUE #TRUE forces browser to quit at end of script or not if FALSE
prefbrowser=netsurf #select midori or netsurf (for pi playback)
#pibrowser is set to TRUE for local pi and FALSE to run tune on mac
#now set by argument on command line
#parse input argument and set pibrowser accordingly
if  [[ -z $1  || $1 != 'pi' && $1 != 'mac' ]]
then
echo "usage ./serialr.sh pi   or ./serialr.sh mac"
exit 1
fi
if [ $1 = 'mac' ]; then pibrowser=FALSE;else pibrowser=TRUE;fi

#------------ set up context variables for running on Mac ------------
pishare='/home/pi/pishare' #where share is mounted on the Pi
macssh="rbn@192.168.1.12" #ssh logon to the Mac
macbrowserapp="Firefox.app" #Browser to run on Mac (could be Safari.app,Google Chrome.app)
macpathtoshare='/Users/rbn/Documents/pishare' #path to share on the Mac

#you shouldn't need to configure anything after this line
macbrowser=`echo $macbrowserapp | cut -d'.' -f1` #strips the .app from macbrowserapp
quitscript=$PWD/quit.sh

#------------ set up variables for filenames ------------
pitches=/tmp/pitches.ily #pitches in 12 tone scale
random=/tmp/random.ily #pitches in scale in random order
lilyfile=/tmp/tonerow.ly #completed source music file for lilypond
midifile=/tmp/tonerow.midi #midifile created by lilypond
mp3file=/tmp/tonerow.mp3 #mp3 file created from the midi file by timidity

stem=$(readlink -f $lilyfile | sed -e 's/\..*$//') #prefix path for tone* files

withrhythms=/tmp/withrhythms.ily #used to hold assembled lilypond tune code
chordOne=/tmp/chord1.ily #lilypond code for the first chord
chordTwo=/tmp/chord2.ily #lilypond code for the second chord
chordThree=/tmp/chord3.ily #lilypond code for the third chord
#following files used in generation of random rhythms for the notes
allowed3bars=/tmp/allowed3bars.ily #3 bar patterns of 3/4 bars 11 notes in total
barnames=/tmp/barnames.ily #names of the 28 bar structures
barlist=/tmp/barlist.ily #all the possible 3/4 bar rhythms
#for 1 to 6 notes restricted to lengths 1 2 3
subbarlist=/tmp/subbarlist.ily #used to hold all the bar choices for a given number of notes in the bar
chosenlist=/tmp/chosenlist.ily # 3 bar list chosen from barlist
rbars=/tmp/rbars.ily #used to hold the chosen 3 bar lengths in random order
barnotes=/tmp/tune.ily #bar note values for each of the three bars in turn
randombarnotes=/tmp/randomtune.ily #assemble chosen note durations inc upbeat
# ------------ end of file variable setup ---------------

# ------------ start definition of functions ---------------

pvar(){
#used to print a text string, a variable name and cat the contents (if a file)
# or echo if not. Uses two supplied arguments $1 and $2
if [ $verbose = TRUE ] #allows suppression if not verbose mode
then
	echo -e '==========\n'$1' (variable name '$2')';
	if [ -f ${!2} ] #variable name passed as parameter is dereferenced to get the value
					#using the format ${!2}
		then
			cat ${!2}
		else
			echo ${!2}
		fi
	echo -e '==========\n'
fi
}
# ------------ end of pvar ---------------

getpitches (){
#makes a file of the pitches in the 12 tone scale and then puts in random order
# first make a list of all 12 chromatic pitches
# using lilypond's naming conventions
# one per line b/c it's easier to shuffle, add rhythms, etc
cat > $pitches << EOFallpitches
c'
cis'
d'
dis'
e'
f'
fis'
g'
gis'
a'
bes'
b'
EOFallpitches
# now run them through the shuf command to put them in random order
shuf $pitches > $random
} # ------------ end of getpitches ---------------

getallowedbars () {
#read in list of allowed bar patterns for 3 bars of 11 notes total
cat > $allowed3bars << EOFallowed3bars
146
155
236
245
335
344
EOFallowed3bars
} #------------ end of getallowedbars ------------

getbarlist () {
#generates a file containing the structure of the 28 possible distinct
#bar types  in the structure name n n n x
#first build as a string
b="1n1 2. x "
b+="2n1 2 4 x 2n2 4. 4. x "
b+="3n1 4.. 4 16 x 3n2 4.. 8. 8 x 3n3 4. 4 8 x 3n4 4. 8. 8. x \
3n5 4 4. 8 x 3n6 4 4 4 x "
b+="4n1 2 8 16 16 x 4n2 4.. 8. 16 16 x 4n3 4.. 8 8 16 x 4n4 4. 4 16 16 \
x 4n5 4. 8. 8 16 x 4n6 4 4 8. 16 x 4n7 4 4 8 8 x 4n8 8. 8. 8. 8. x "
b+="5n1 2 16 16 16 16 x 5n2 4.. 8 16 16 16 x 5n3 4. 8. 16 16 16 \
x 5n4 4. 8 8 16 16 x 5n5 4 4 8 16 16 x 5n6 4 8. 8 8 16 x 5n7 4 8 8 8 8 x "
b+="6n1 4. 8 16 16 16 16 x 6n2 4 4 16 16 16 16 x 6n3 4 8. 8 16 16 16 \
x 6n4 4 8 8 8 16 16 x"
echo $b |sed -e 's/\s\s*/\n/g' > $barlist
#convert string to file with one entry on each line (spaces replaced by LF)
} #------------ end of getbarlist ------------

chords(){
# stack up the notes of the row in 3 chords of (re  -e 'P;D')
# 4 notes each
#NB modified from original script which didn't work for me
cat $random | sed -n '1,4p' \
| sed -e :a -e '$!N;s/\n/ /;ta' \
| sed -e 's/.*/<&>2/' > $chordOne
cat $random | sed -n '5,8p' \
| sed -e :a -e '$!N;s/\n/ /;ta' \
| sed -e 's/.*/<&>4/' > $chordTwo
cat $random | sed -n '9,12p' \
| sed -e :a -e '$!N;s/\n/ /;ta' \
| sed -e 's/.*/<&>2./' > $chordThree
pvar 'Chosen 3 bars in a list' chosenlist
shuf $chosenlist > $rbars #put thes number in random order
pvar 'Chosen 3 bars in random order list' rbars
} # ------------ end of chords ---------------

make_lily_file (){
# determine lilypond version for later inclusion
version=$(lilypond --version | grep LilyPond | cut -d " " -f3)
# make it cat the random list of pitches
lilypitches=$(cat $withrhythms $chordOne $chordTwo $chordThree)
# assemble the lilypond source file, sticking the
# pitches in at the right place
cat > $lilyfile << EOFscore
\\score {
{
\\version "$version"
\\time 3/4
\\tempo 4 = 100
#(set-accidental-style 'dodecaphonic)
#(set-global-staff-size 24)
\\set Staff.midiInstrument = "violin"
\\partial 4
$lilypitches
\\bar "|."
}
\\layout {} \\midi {}
}
EOFscore
pvar 'The completed source lilyfile' lilyfile
} # ------------ end of chords ---------------

runlily(){
# compile the score redirecting all console output to /dev/null
lilycmd="lilypond -dno-point-and-click -ddelete-intermediate-files -dpreview"
$lilycmd $lilyfile &> /dev/null
} # ------------ end of runlily ---------------

html(){ #generate html file
web="$stem".html
image="$stem".preview.png
midi="$stem".midi
cat > $web << EOFhtml
<meta http-equiv=Content-Type content="text/html; charset=UTF-8">
<title>12-Tone Tune of the Day</title>
<h2>Random 12-Tone Tune of the Day, with random rhythms</h2>
<img src="tonerow.preview.png" alt="randomly generated 12-tone
row"
title="randomly-generated 12-tone row">

<a href="tonerow.midi">Midi file</a>
EOFhtml
if [ $pibrowser = FALSE ] #i.e. not to play on Pi add mp3 playback
then
cat >> $web << EOFhtml
<a href="tonerow.mp3">mp3 file</a>
EOFhtml
fi
cat >> $web << EOFhtml
Generated with bash and <a href="http://lilypond.org"
target="_blank">Lilypond</a>
generated on $(date)
Modified and developed by Robin Newman from an original script
by <a href="http://jonathankulp.org/archives/662">
Jonathan Kulp</a>

EOFhtml
pvar 'The html page' web
} # ------------ end of html ---------------

initrandombarnotes () {
#put 4 (crotchet duration) as first note value of randombarnotes
cat > $randombarnotes << EOFnotes
4
EOFnotes
} # ------------ end of initrandombarnotes ----------------

getbarnames() {
#generate list of barnames from barlist
cat $barlist |sed -n /n/p > $barnames #generate list of barnames from barlist
} # ------------ end of getbarnames ----------------

makechosenbarsfile() {
#generate a file containing the number of notes in each of the three tune bars
shuf $allowed3bars > $chosenlist #get allowed bars in random order
pvar 'Shuffled list' chosenlist
cat $chosenlist |sed q > $rbars #select 1st line of randomised allowed bars list
pvar 'Chosen 3 bars' rbars
#this is the number we want. Need individual digits shuffled
cat $rbars |sed -e 's/\(.\)/\1\n/g'| sed -e '$d' >$chosenlist
#write 3 digit number to a file 1 digit per line
pvar 'Chosen 3 bars in a list' chosenlist
shuf $chosenlist > $rbars #put thes number in random order
pvar 'Chosen 3 bars in random order list' rbars
}
# ------------ end of makechosenbarsfile ----------------

fillbar() {
#add random rhythm for bar with number of notes in supplied arg $1
action=`cat $rbars |sed -n "$1{p;q;}"` #select no of notes from the list
echo bar $1 has $action notes
sed -n /${action}n/p $barnames > $subbarlist #get list of possible barnames
pvar 'possible bar names' subbarlist
read -r firstchoice < <(shuf $subbarlist) #shuffle and take first one as choice
pvar choice firstchoice
cat $barlist | sed -n "/${firstchoice}/{:a;n;/x/q;p;ba}" > $barnotes
#extract relevant note durations from barlist to barnotes
shuf $barnotes >> $randombarnotes
#put durations in random order and add to randombarnotes
pvar barnotes barnotes
}
# ------------ end of fillbar ----------------

addrhythms() {
#merges the pitches and durations together to get tune
paste -d " " $random $randombarnotes > $withrhythms
pvar 'random tune' withrhythms
}
# ------------ end of addrhythms ----------------

timestart() {
#initialise a datetimevariable
date1=$(date +"%s")
echo "Lapsed time initialised to 0"
}
# ------------ end of timestart ----------------

timeduration() {
#calculate elapsed time from timestart and print
date2=$(date +"%s")
diff=$(($date2-$date1))
echo "Total $(($diff / 60)) minutes and $(($diff % 60)) seconds elapsed."
}
# ------------ end of timeduratiopn ----------------

mp3(){ #produce mp3 from midi file using timidity and lame. used on mac version
`timidity -Ow -o - $midifile 2> /dev/null | lame - $mp3file &>/dev/null`
#timidity creates.wav file which is piped to lame to give mp3 file
#warning text suppressed from timidity with 2>/dev/null and
#text op from lame with &>/dev/null
}	# ------------ end of mp3 ----------------

#------------ program commands start from here -------------

timestart
if [ $pibrowser = "TRUE" ] #displaying on pi
then
#check if Desktop is running
if [[ `pidof lxsession` = "" ]]
then
echo 'no desktop running on the Pi.startx & from this terminal, then rerun'
exit
else
export DISPLAY=:0
fi
fi
echo prefbrowser is $prefbrowser #print preferred browser name
echo 'Generating source music file lilyfile.ly'
cd /tmp #work in temp directory
getbarlist
getbarnames
getallowedbars
initrandombarnotes
makechosenbarsfile
fillbar 1
fillbar 2
fillbar 3

pvar 'randomised rhythms' randombarnotes

#at this stage random rhythm list is complete
#assemble final tune

getpitches
addrhythms
chords
make_lily_file
pvar lilyfile lilyfile
timeduration

echo 'Compiling lilyfile.ly with lilypond'
runlily
timeduration
if [ $pibrowser = FALSE ]
then
echo making mp3
mp3
timeduration
fi
#sleep 25 #sync to next minute (tune this)
html

if [ $pibrowser = TRUE ]
then
if [ "$(pidof $prefbrowser)" ]  #first kill existing browser if running
then
killall $prefbrowser
fi
echo Starting Web Browser
if [ $prefbrowser = midori ]
then
$prefbrowser -e Fullscreen -a $web & # midori -e Fullscreen -a /tmp/tonerow.html &
else
$prefbrowser file://$web & # netsurf file:///tmp/tonerow.html
fi
sleep 4
#allow time for web browser to open
echo Playing midi file
sleep 2
timidity $midifile &> /dev/null #play midi file
#wait before killing browser
sleep 2
if [ $quitbrowser = TRUE ] #then force browser to quit
then
killall $prefbrowser
echo browser killed
fi
else
#copy files to mac share
echo 'move files to mac and open browser there'
mkdir $pishare/tonerow 2>/dev/null #make tonerow directory, suppress error if already there
cp /tmp/tone* $pishare/tonerow
open firefox with html page by ssh to mac
cmd='ssh '$macssh' " open -a /Applications/'$macbrowserapp'/ '
cmd+='file://'$macpathtoshare'/tonerow/tonerow.html"'
eval $cmd
#wait for tune to play
sleep 24 #adjust if necessary
if [ $quitbrowser = TRUE ] #then force browser to quit
then
echo 'close mac browser'
cp $quitscript $pishare/tonerow #quit.sh used to quit Mac browser
#use quit.sh script in tonerow folder to close Firefox again
#this script is obtainable at http://mattdturner.com/resources/quit
cmd='ssh '$macssh' "sh '$macpathtoshare'/tonerow/quit.sh '
cmd+=$macbrowser' &>/dev/null"'
eval $cmd
fi
fi
#clean stuff up
rm /tmp/*.ily /tmp/*.eps
echo "Finished!"
timeduration
#------------ end of script -------------

or you can see it listed here and you can download it to your Pi by typing
wget http://r.newman.ch/rpi/sounding-off/serialr.txt and then
mv serialr.txt serialr.sh and chmod 755 serialr.sh

If you have been following this article series then the first part of the script should be familiar. It is very similar to serialrpi.sh and I would detail it again. However if the option to run on a mac is selected from the command line by starting the script with serialr.pi mac
then the variable pibrowser is set to FALSE This causes a change to be made to the web page generation in the html function which adds an embedded mp3 file link. When the creation of the files is complete, the value of pibrowser is used to select one of two output routes. When pibrowser is TRUE the page is output to the Pi web browser as before. When it is FALSE the relevant files in the /tmp directory are copied to a folder tonerow in the share on the Mac.
Then a command is sent via ssh to the Mac to launch the browser (I used Firefox) and load the tune and play it. In my case the command is
ssh rbn@192.168.1.12″open =a /Applications/Firefox.app/ file:///Users/rbn/Documents/pishare/tonerow/print.html”
Yours will differ according to the settings in the script.
If the variable quitbrowser is set, then after a wait for the tune to be played, the script copies another script called quit.sh to the Mac share and runs it there, again using an ssh command line. This quit script I found on the internet at http://mattdturner.com/resources/quit and it closes a Mac application gracefully rather than just using a kill command. For convenience there is also a copy on my site which you can download with wget http://r.newman.ch/rpi/sounding-off/quit.txt changing .txt to .sh and making it executable as described for previous downloads.
the ssh command line in my case was
ssh rbn@192,168,1 “sh /Users/rbn/Documents/pishare/tonerow/quit.sh Firefox.app &>/dev/null”
with the text output from the sub script being discarded
Finally the main script tidies up the files in /tmp and finishes.

A new version of the playlastpi.sh script called playlast.sh which is invoked with ./playlast.sh pi or playlast.sh mac will play the last version created stored on either the Pi or the Mac. I won’t discuss this, but it is downloadable to your Pi on a link below, and as before, you should rename it from .txt to .sh and chmod it to 755
wget http://r.newman.ch/rpi/sounding-off/playlast.txt

At last an Alarm!

To date the the article has been a bit misleading because although I have discussed the generation of lots of tunes in different ways we have not yet had one functioning as an alarm. However this is fairly easy to achieve now by making use of the cron utility. Cron is a system which can be used to launch any command at a given time, or repetitively at given time intervals. It is widely used to time backups and other housekeeping processes on a linux computer. Each user can create their own table of cron tasks using the crontab -e command which edits the cron table.
first check cron is running typing service cron status
and if not sudo service cron start

You can see a full description of using cron at
http://kvz.io/blog/2007/07/29/schedule-tasks-on-linux-using-crontab/
For example, to run serialr.sh every two minutes we could add

* * * * *  path-to-file/serialr.sh mac &> devl/null
to the end of the file. In my case the entry was * * * * * ~/12note/serialr.sh mac &>/dev/null
As this is a user crontab it is ok to give paths relative to the user’s home directory ~/ The &> /dev/null ditches all text output to standard out from the script.
To make the script run every hour it would be
0 * * * *   path-to-file/serialr.sh mac &> devl/null
and to run at 8am every day
0 8 * * *    path-to-file/serialr.sh mac &> devl/null
Note that is the starting time of the script. It takes about 40 seconds before it is seen in the web browser.
The same technique can be used for any of the scripts which have been discussed to run at specific times or time intervals.

Tidying up
Well at long last we have come to the end of this series of articles. I didn’t think it was going to be as protracted when I started out, but I hope you have found it useful. I have certainly found it good to brush up on a range of topics from mounting shares, creating instant ssh connections, learning lots of bash scripting techniques and more. The other thing I have learned is the huge amount of time it takes to write up an article like this :-)

As promised at the start, there is now a link to a zip file containing all the scripts mentioned which you can download. Just remember that most will need to be configured for your own environment  before you run them, especially the archive locations and the extranal mac settings if you use one.
wget http://r.newman.ch/rpi/sounding-off/allscripts.zip

Thank you for reading. Hope you have enjoyed it!

Leave a comment