Sonic Pi 2 plays Bach with expression!

bach_col

In my last article I discussed the development of Purcell’s Funeral Music for Queen Mary to play on Sonic Pi 2. One feature missing from that was variation in volume in terms of a crescendo or diminuendo. To be fair it wasn’t necessary in that piece. However the piece I want to discuss now does depend on such features if it is to be played musically. Bach is a great composer to use with Sonic Pi because of the rhythmical nature of his music. In this particular piece – his Prelude in C Major – it has exactly the same rhythmic format for 32 of its 35 bars. So in order to make it interesting it depends on variations in volume as it is played, and it incorporates in particular one long crescendo and subsequent diminuendo, which need to be played by Sonic Pi if the piece is to sound at all authentic.
Here is the music:

BachPreduldeInCmajor1 BachPreduldeInCmajor2 BachPreduldeInCmajor3

In my previous article here I discussed how to include rits and trills in a piece. This time I am going to concentrate on how to allow for crescendos and diminuendos. Many of the other techniques, for example, how to store notes and durations and play them are treated in the same way as before.
If we want to allow the volume of a note to be controlled this can be done in the parameters connected to the play command. Thus

play :c5, attack: a, sustain: duration*0.9 - a, release: duration 0.1,amp: vol
sleep duration

This allows us to play a C in octave 5, with a shaped envelope lasting for a total time duration and with an amplitude or volume vol.
Previously we used lists or arrays to hold the notes to be played and their durations. In the same way we can add a third array to hold volume information for each note. That is what is done in this program. It would normally be quite a daunting task, but in this particular instance because of the repetitive nature, each bar in the right hand part has 14 elements in it: 12 notes and two rests, and each bar in the two left hand parts has either 4 or 2 elements in it, so we can fairly easily work out the change in volume per element required to crescendo over several bars. Here is the code required to generate the volume changes in the right hand part.

#dynamic settings
pp = 0.1
p = 0.15
mp = 0.22
mf = 0.3
f = 0.5

#volumes for part 1
#first set up crescendo over 5 bars from pp to f
cr = [pp]
vol = pp
npb=14#number of note elements/standard bar including rests
inc = (f-pp)/(5*npb)
(5*npb-1).times do
cr = cr + [vol]
vol=vol+inc
end
#now set up diminuendo from f to p over 2 bars
dim = [f]
vol = f
inc = (p-f)/(2*npb)
(2*npb-1).times do
dim = dim + [vol]
vol = vol + inc
end
#now assemble complete volume profile
v1 = [p]*4*npb + [mf]*npb + [mp]*npb + [mf]*npb + [mp]*7*npb + [p]*7*npb +[pp]*2*npb +cr + [f]*npb + dim +[p]*(npb+npb+1+npb+1+1) #adjust for different bar structures at end

You can see that an array cr is built up by adding to it the volume of each note, coded as it is calculated. It will contain numbers, but when writing it, you use recognisable symbols such as pp, p, mf and f to specify the loudness required. times…do loops are used to work out the interpolation when a crescendo is required. For example, the first crescendo is over 5 complete bars which contain 5*npb elements where nbp is 14 as discussed. We want to increase the volume from pp to f so we require each element to be (f-pp)/(5*npb) louder than its predecessor. A loop calculates each volume and adds it to the cr array.
Similarly we can calculate the entries for a diminuendo from f to p over 2 bars, and store the volumes in an array called dim.
Then we can assemble the complete volume profile for part 1 into an array list v1 as shown in the last line. We start with 4 bars of pianissimo. [p]*4*npb, then add on a bar of mezzo-forte [mf]*npb and so on. When we want the crescendo, we simply add in the list cr. Similar procedures are used to calculate the volumes for the second and third parts.
We also need to modify the routine used to play the notes from the tune definition used in the Purcell music. In this case I call it pl1 and it looked like this:-

#definition to play 3 arrays containing note pitch, duration and volume. Can also set global attack and synth
#sustain set to 90% duration, release to 10% duration
define :pl1 do |notearray,durationarray,volarray,attack=(dsq / 2),voice = :tri|
  with_synth voice do
    notearray.zip(durationarray,volarray).each do |notearray,durationarray,volarray| #zip traverses arrays together
      if notearray == :r #a rest
        sleep durationarray
      else
          play notearray,amp: volarray,sustain: durationarray * 0.9 - attack,release: durationarray * 0.1,attack: attack
          sleep durationarray
      end
    end
  end
end

It uses the same technique of zipping together arrays which are traversed at the same time. The difference is that there are three arrays this time: one containing notes, one durations and one volumes. The crunch line is

notearray.zip(durationarray,volarray).each do |notearray,durationarray,volarray| #zip traverses arrays together

This gives us sets of three corresponding numbers for the pitch, duration and volume of each note, which can then be played by the following play command. as before the pitch parameter (notearray) is first tested to see if it is a rest :r and if so a sleep command is invoked, but NOT a play command. Otherwise the note pitch is played with the appropriate duration and volume, with a sleep duration setting the separation before the next note.
The note and duration arrays are setup exactly as in the Purcell example, adding them in two or three bar sections which are concatenated together. As in the Purcell, there is a Rit included at the end, achieved by progressively lengthening the last few notes above their normal duration. There are no section definitions this time because the piece is only played once through, so the final tune is set up inside a reverberation effect with_fx :reverb room: 0.4 do…end and the first two parts are played in threads so that they are concurrent with the third part. To make it stand out the third part is played with the :saw synth whereas the upper two parts use the :tri synth.

You can get a great overview of the effects of the volume part by looking at the envelope of the recording of the piece playing in Sonic Pi.

BachPreludeEnvelope

The complete program is shown below, and can be downloaded here. You can download an mp3 file of Sonic Pi playing the piece here

#Bach Prelude in C Major, transcribed by Robin Newman Sept 2014
#tested on Sonic-Pi ver 2.0.1
#illustrates the use of volume settings for each note to allow for crescendo, diminuendo
#also built in rallentando at end of the piece

use_synth :tri #unless specified otherwise
s = 1.0 / 8.5                       #tempo about 64 c/minute

#note durations (not all used)
dsq = 1 * s
sq = 2 * s
q = 4 * s
qd = 6 * s
c = 8 * s
cd = 12 * s
m = 16 * s
md = 24 * s
b = 32 * s
bd = 48 * s

#dynamic settings
pp = 0.1
p = 0.15
mp = 0.22
mf = 0.3
f = 0.5

#volumes for part 1
#first set up crescendo over 5 bars from pp to f
cr = [pp]
vol = pp
npb=14#number of note elements/standard bar including rests
inc = (f-pp)/(5*npb)
(5*npb-1).times do
cr = cr + [vol]
vol=vol+inc
end
#now set up diminuendo from f to p over 2 bars
dim = [f]
vol = f
inc = (p-f)/(2*npb)
(2*npb-1).times do
dim = dim + [vol]
vol = vol + inc
end
#now assemble complete volume profile
v1 = [p]*4*npb + [mf]*npb + [mp]*npb + [mf]*npb + [mp]*7*npb + [p]*7*npb +[pp]*2*npb +cr + [f]*npb + dim +[p]*(npb+npb+1+npb+1+1) #adjust for different bar structures at end

#vols for part 2
cr = [p]
vol = p
npb=4 #4 note +rest elements /standard bar
inc = (f-pp)/(5*npb)
(5*npb-1).times do
cr = cr + [vol]
vol=vol+inc
end
dim = [f]
vol = f
inc = (p-f)/(2*npb)
(2*npb-1).times do
dim = dim + [vol]
vol = vol + inc
end
v2 = [p]*4*npb + [mf]*npb + [mp]*npb + [mf]*npb + [mp]*7*npb + [p]*7*npb +[pp]*2*npb +cr + [f]*npb + dim +[p]*(npb+npb-2+npb-2+1) #adjust for different bar structures at end

#vols for part 3
pp = 0.5*pp
p = 0.5*p
mp = 0.5*mp
mf = 0.5*mf
f = 0.5*f
cr = [pp]
vol = pp
npb=2 #2 notes per standard bar
inc = (f-pp)/(5*npb)
(5*npb-1).times do
cr = cr + [vol]
vol=vol+inc
end
dim = [f]
vol = f
inc = (p-f)/(2*npb)
(2*npb-1).times do
dim = dim + [vol]
vol = vol + inc
end
v3 = [p]*4*npb + [mf]*npb + [mp]*npb + [mf]*npb + [mp]*7*npb + [p]*7*npb +[pp]*2*npb +cr + [f]*npb + dim +[p]*(npb+1+1+1) #adjust for different bar structures at end

#definition to play 3 arrays containing note pitch, duration and volume. Can also set global attack and synth
#sustain set to 90% duration, release to 10% duration
define :pl1 do |notearray,durationarray,volarray,attack=(dsq / 2),voice = :tri|
  with_synth voice do
    notearray.zip(durationarray,volarray).each do |notearray,durationarray,volarray| #zip traverses arrays together
      if notearray == :r #a rest
        sleep durationarray
      else
          play notearray,amp: volarray,sustain: durationarray * 0.9 - attack,release: durationarray * 0.1,attack: attack
          sleep durationarray
      end
    end
  end
end

#now define the three parts and the durations for each of their notes
p1n = [:r,:g4,:c5,:e5,:g4,:c5,:e5,:r,:g4,:c5,:e5,:g4,:c5,:e5,:r,:a4,:d5,:f5,:a4,:d5,:f5,:r,:a4,:d5,:f5,:a4,:d5,:f5]
p1d = [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b3
p1n.concat [:r,:g4,:d5,:f5,:g4,:d5,:f5,:r,:g4,:d5,:f5,:g4,:d5,:f5,:r,:g4,:c5,:e5,:g4,:c5,:e5,:r,:g4,:c5,:e5,:g4,:c5,:e5]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b5
p1n.concat [:r,:a4,:e5,:a5,:a4,:e5,:a5,:r,:a4,:e5,:a5,:a4,:e5,:a5,:r,:fs4,:a4,:d5,:fs4,:a4,:d5,:r,:fs4,:a4,:d5,:fs4,:a4,:d5]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b7
p1n.concat [:r,:g4,:d5,:g5,:g4,:d5,:g5,:r,:g4,:d5,:g5,:g4,:d5,:g5,:r,:e4,:g4,:c5,:e4,:g4,:c5,:r,:e4,:g4,:c5,:e4,:g4,:c5]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b9
p1n.concat [:r,:e4,:g4,:c5,:e4,:g4,:c5,:r,:e4,:g4,:c5,:e4,:g4,:c5,:r,:d4,:fs4,:c5,:d4,:fs4,:c5,:r,:d4,:fs4,:c5,:d4,:fs4,:c5]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b11
p1n.concat [:r,:d4,:g4,:b4,:d4,:g4,:b4,:r,:d4,:g4,:b4,:d4,:g4,:b4,:r,:e4,:g4,:cs5,:e4,:g4,:cs5,:r,:e4,:g4,:cs5,:e4,:g4,:cs5]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b13
p1n.concat [:r,:d4,:a4,:d5,:d4,:a4,:d5,:r,:d4,:a4,:d5,:d4,:a4,:d5,:r,:d4,:f4,:b4,:d4,:f4,:b4,:r,:d4,:f4,:b4,:d4,:f4,:b4]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b15
p1n.concat [:r,:c4,:g4,:c5,:c4,:g4,:c5,:r,:c4,:g4,:c5,:c4,:g4,:c5,:r,:a3,:c4,:f4,:a3,:c4,:f4,:r,:a3,:c4,:f4,:a3,:c4,:f4]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b17
p1n.concat [:r,:a3,:c4,:f4,:a3,:c4,:f4,:r,:a3,:c4,:f4,:a3,:c4,:f4,:r,:g3,:b3,:f4,:g3,:b3,:f4,:r,:g3,:b3,:f4,:g3,:b3,:f4]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b19
p1n.concat [:r,:g3,:c4,:e4,:g3,:c4,:e4,:r,:g3,:c4,:e4,:g3,:c4,:e4,:r,:bb3,:c4,:e4,:bb3,:c4,:e4,:r,:bb3,:c4,:e4,:bb3,:c4,:e4]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b21
p1n.concat [:r,:a3,:c4,:e4,:a3,:c4,:e4,:r,:a3,:c4,:e4,:a3,:c4,:e4,:r,:a3,:c4,:eb4,:a3,:c4,:eb4,:r,:a3,:c4,:eb4,:a3,:c4,:eb4]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b23
p1n.concat [:r,:b3,:c4,:d4,:b3,:c4,:d4,:r,:b3,:c4,:d4,:b3,:c4,:d4,:r,:g3,:b3,:d4,:g3,:b3,:d4,:r,:g3,:b3,:d4,:g3,:b3,:d4]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b25
p1n.concat [:r,:g3,:c4,:e4,:g3,:c4,:e4,:r,:g3,:c4,:e4,:g3,:c4,:e4,:r,:g3,:c4,:f4,:g3,:c4,:f4,:r,:g3,:c4,:f4,:g3,:c4,:f4]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b27
p1n.concat [:r,:g3,:b3,:f4,:g3,:b3,:f4,:r,:g3,:b3,:f4,:g3,:b3,:f4,:r,:a3,:c4,:fs4,:a3,:c4,:fs4,:r,:a3,:c4,:fs4,:a3,:c4,:fs4]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b29
p1n.concat [:r,:g3,:c4,:g4,:g3,:c4,:g4,:r,:g3,:c4,:g4,:g3,:c4,:g4,:r,:g3,:c4,:f4,:g3,:c4,:f4,:r,:g3,:c4,:f4,:g3,:c4,:f4]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b31
p1n.concat [:r,:g3,:b3,:f4,:g3,:b3,:f4,:r,:g3,:b3,:f4,:g3,:b3,:f4,:r,:g3,:bb3,:e4,:g3,:bb3,:e4,:r,:g3,:bb3,:e4,:g3,:bb3,:e4]
p1d.concat [q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq]
#b33
p1n.concat [:r,:f3,:a3,:c4,:f4,:c4,:a3,:c4,:a3,:f3,:a3,:f3,:d3,:f3,:d3,:r,:g4,:b4,:d5,:f5,:d5,:b4,:d5,:b4,:g4,:b4,:d4,:f4,:e4,:d4]
#set up the rit in the last bar. (nb chord bar follows this)
p1d.concat [q,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,q,sq,sq,sq,sq,sq,sq,sq,sq*1.1,sq*1.2,sq*1.3,sq*1.4,sq*1.5,sq*1.6,sq*1.7   ]

#second part
p2n = [:r,:e4,:r,:e4,:r,:d4,:r,:d4,:r,:d4,:r,:d4,:r,:e4,:r,:e4,:r,:e4,:r,:e4,:r,:d4,:r,:d4,:r,:d4,:r,:d4,:r,:c4,:r,:c4]
p2d = [sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c]
#b9
p2n.concat [:r,:c4,:r,:c4,:r,:a3,:r,:a3,:r,:b3,:r,:b3,:r,:bb3,:r,:bb3,:r,:a3,:r,:a3,:r,:ab3,:r,:ab3,:r,:g3,:r,:g3,:r,:f3,:r,:f3]
p2d.concat [sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c]
#b17
p2n.concat [:r,:f3,:r,:f3,:r,:d3,:r,:d3,:r,:e3,:r,:e3,:r,:g3,:r,:g3,:r,:f3,:r,:f3,:r,:c3,:r,:c3,:r,:f3,:r,:f3,:r,:f3,:r,:f3]
p2d.concat [sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c]
#b25
p2n.concat [:r,:e3,:r,:e3,:r,:d3,:r,:d3,:r,:d3,:r,:d3,:r,:eb3,:r,:eb3,:r,:e3,:r,:e3,:r,:d3,:r,:d3,:r,:d3,:r,:d3,:r,:c3,:r,:c3]
p2d.concat [sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c,sq,qd+c]
#b33
p2n.concat  [:r,:c3,:r,:b3,:c3]
#compensate for rit in penultimate bar and add pause
p2d.concat [sq,qd+c+m,sq,qd+c+m+2.8*sq,b*1.1]

#third part
p3n = [:c4,:c4,:c4,:c4,:b3,:b3,:c4,:c4,:c4,:c4,:c4,:c4,:b3,:b3,:b3,:b3]
p3d = [m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m]
#b9
p3n.concat [:a3,:a3,:d3,:d3,:g3,:g3,:g3,:g3,:f3,:f3,:f3,:f3,:e3,:e3,:e3,:e3]
p3d.concat [m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m]
#b17
p3n.concat [:d3,:d3,:g2,:g2,:c3,:c3,:c3,:c3,:f2,:f2,:fs2,:fs2,:ab2,:ab2,:g2,:g2]
p3d.concat [m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m]
#b25
p3n.concat [:g2,:g2,:g2,:g2,:g2,:g2,:g2,:g2,:g2,:g2,:g2,:g2,:g2,:g2,:c2,:c2]
p3d.concat [m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m]
#b33
p3n.concat [:c2,:c2,:c2]
#compensate for rit in penultimate bar and add pause
p3d.concat [b,b+2.8*sq,b*1.1]

#==========start playing here ============
#now play the piece with some reverb set
with_fx :reverb, room: 0.4 do
  #first two parts in threads to play concurrently with third part
  in_thread do
    #parameters for pl1 are notearray,durationarray,volarray,attack=(dsq / 2),voice = :tri_s
    pl1(p1n,p1d,v1)
    #play last bar chord, adjusted for pause, vol set to p
    play_chord [:e4,:g4,:c5],sustain: 0.9*b*1.1,release: 0.1*b*1.1,amp: p
    sleep b * 1.1
  end
  #second part thread
  in_thread do
    pl1(p2n,p2d,v2)
  end
  #third part
  pl1(p3n,p3d,v3,0,:saw)
end

The End!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s