Sonic Pi 2.2 plays Bach Gigue in G Minor: Three separate versions!

With the advent of Sonic Pi 2.2 I thought it was time that I coded up some more Bach, and I decided to have a go at the Gigue in G Minor from English Suite number 3. I wanted to make it sound as authentic as possible, and decided to use the GrandPiano sample voices that I discussed here some month ago. I tidied up the code for this a little bit, and also decided to incorporate the technique to add a dynamic “volume” track as discussed in another earlier post here. The final feature I made use of was to split the program into two halves, one to generate the sample base voice and the other to generate the arrays holding the various parts and to play them. This could have used my conductor program, but I made it a little easier by directly linking them with a cue and sync command pair.

The music I used is shown below:

giguep1

giguep2

giguep3

The music is coded in three parts for each section: The right hand part, the left hand part and two small sections where a third part is heard. Initially I coded it up and played it using the :pulse synth whilst I dealt with the inevitable errors which arise in coding the music. I also then worked on the fourth “dynamics” part to adjust the volume of each section as it plays, including the use of crescendos and diminuendos which are achieved by using the sliding parameter for :amp with the fx level control.
I got the sample voice working in a separate program, and then connected the two together, by sending a cue form the end of the first part (which generated the sample voice) to a sync command at the start of the second part which played the tune. The way Sonic Pi works, functions defined in part 1 are accessible in part 2. If the function needs to amend variable which are used externally to it, then these need to be first defined in the main body of the program, to give them global scope in the program. You have to define a function used in this way in the program in which it is to be used. Examples are the lset and parts functions. Variables which are needed in both programs need to be defined separately in each program. e.g. ps which is used by the function sname. The program lets you know by generating an error if it can’t find a variable, so you can debug fairly easily. Using this technique, larger programs can be made to run on Sonic Pi (particularly on the Mac) thus overcoming the limitation on maximum program size.

The three programs are listed below. In order to avoid breaching size limits I have not added as many comments as I usually would do.

First, the synth based program using :pulse synth to play

#Bach Gigue in G minor from English Suite no 3 Coded by Robion Newman December 2014
#Synth version
set_sched_ahead_time! 3
use_debug false
s=0 #to set global scope
define :setbpm do |n| #set bpm equivalent
  s = (1.0 / 8) *(60.0/n.to_f)
end
use_synth :pulse
setbpm(180)
dsq=sq=q=qd=c=cd=cdd=m=md=0 #to setglobal scope

define :lset do
  dsq = 1 * s #demi-semi-quaver
  sq = 2 * s #semi-quaver
  q = 4 * s #quaver
  qd = 6 * s #quaver dotted
  c = 8 * s #crotchet
  cd = 12 * s #crotchet dotted
  m = 16 * s #minim
  md = 24 * s #minim dotted
end
lset
p=0.15 #dynamics
mp=0.2
mf=0.4
f=0.8
ff=1.6

define :plarray do |narray,darray,vol=0.4|
  narray.zip(darray).each do |n,d|
    play n,amp: vol,attack: dsq/10, sustain: 0.9*d-dsq/10,release: 0.1*d #play note
    sleep d #gap till next note
  end
end

define :ct do |ptr,lev,slid=0| #function to adjust level control
  control ptr,amp: lev,amp_slide: slid
end

ritq=ritqb=d1=d2=d3=d1b=d2b=d3b=n1=n2=n3=n1b=n2b=n3b=[] #define global scope
define :parts do
ritq=[q*1.02,q*1.04,q*1.06,q*1.08,q*1.1,q*1.12]
ritqb=[q*1.02,q*1.04,q*1.06,q*1.08,q*1.1,q*1.2,q*1.4,q*1.6,q*1.8]
n1=[:d5,:eb5,:d5,:c5,:bb4,:a4,:g4,:fs4,:a4,:d4,:c4,:bb3,:a3,:bb3,:d4,:g4,:a3,:g4,:fs4,:g4,:bb4,:a4,:bb4,:d5,:c5]
d1=[q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q]
#3
n1.concat [:d5,:e5,:f5,:e5,:f5,:g5,:a5,:g5,:f5,:g5,:f5,:e5,:f5,:g5,:a5,:d5,:c5,:bb4,:c5,:d5,:bb4,:c5]
d1.concat [cd+q,q,q,cd+q,q,q,q,q,q,q,q,q,q,q,q,cd+q,q,q,cd+c,q,c,q]
#6
n1.concat [:a4,:bb4,:c5,:d5,:bb4,:c5,:d5,:e5,:fs5,:g5,:a5,:g5,:f5,:e5,:f5,:eb5,:d5,:c5,:d5,:c5,:bb4,:a4,:bb4,:r,:a4,:r,:a4,:g4,:a4,:r,:e4,:g4]
d1.concat [c,q,c,q,c,q,cd+c,q,cd,cd,cd+cd,cd+q,q,q,cd+cd,cd+q,q,q,cd+cd,cd+q,q,q,cd,q,c,q,q,q,cd,q,q,q]
#12
n1.concat [:f4,:g4,:a4,:g4,:a4,:bb4,:a4,:d5,:f5,:g4,:g5,:e5,:f5,:a5,:g5,:a5,:a4,:cs5,:b4,:c5,:e5,:d5,:e5,:e4,:g4,:f4]
d1.concat [cd+q,q,q,cd+q,q,q,q,q,q,q,q,q,q,q,q,cd,q,q,q,q,q,q,cd,q,q,q]
#15
n1.concat [:g4,:bb4,:a4,:bb4,:cs4,:e4,:d4,:e4,:g4,:f4,:g4,:bb4,:a4,:bb4,:a4,:g4,:cs5,:bb4,:a4,:d5,:a4,:g4,:e5,:a4,:g4,:f4,:g4,:e4,:f4,:e4,:d4,:g4,:e4,:d4]
d1.concat [q,q,q,cd]+[q]*30
#18
n1.concat [:a4,:e4,:d4,:bb4,:e4,:d4,:cs4,:d4,:e4,:a3,:r,:a5,:bb5,:a5,:g5,:f5,:e5,:d5,:cs5,:e5,:a4,:g4,:f4,:e4,:f4,:a4,:d5,:e4,:d5,:cs5,:d5,:a4,:fs4,:d4]
d1.concat [q]*27+ritq+[c*1.12]
#end part 1
n2=[:r,:g4,:bb4,:a4,:g4,:f4,:e4,:d4,:cs4,:e4,:a3,:g3,:f3,:e3,:f3,:a3,:d4,:e3,:d4,:cs4,:d4,:e4,:f4,:e4,:fs4,:g4,:a4,:g4,:fs4,:g4,:fs4,:e4]
d2=[q*24]+[q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,cd+q,q,q,cd+q,q,q,q,q,q,q,q,q]
#6
n2.concat [:fs4,:e4,:d4,:e4,:g4,:fs4,:g4,:a4,:bb4,:a4,:bb4,:c5,:d5,:c5,:bb4,:c5,:bb4,:a4,:bb4,:a4,:g4,:f4,:eb4,:d4,:f4,:e4,:f4,:e4,:d4,:cs4,:b3,:cs4]
d2.concat [q,q,q,q,q,q,cd+q,q,q,cd+q,q,q,q,q,q,q,q,q,md,md,md,md,md,cd+q,q,q,c,q,cd+q,q,q,cd]
#12
n2.concat [:d4,:e4,:f4,:e4,:f4,:g4,:a4,:g4,:r]
d2.concat [cd+q,q,q,cd+q,q,q,cd,cd,cd*2+72*q+9.42*q+c*1.2] #rit at end
#end part 1
n3=[:r,:d4,:eb4,:d4,:c4,:bb3,:a3,:g3,:fs3,:a3,:d3,:c3,:bb2,:a2,:bb2,:d3,:g3,:a2,:g3,:fs3,:g3,:bb3,:a3,:bb3,:d4,:c4,:d4,:a3,:d3]
d3=[q+60*q+5*q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,cd+q,q,q]
#9

n3.concat [:eb3,:g3,:f3,:g3,:bb3,:a3,:bb3,:f3,:bb2,:c3,:eb3,:d3,:eb3,:g3,:f3,:g3,:cs3,:d3,:c3,:bb2,:c3,:bb2,:a2,:r,:a3]
d3.concat [q,q,q,q,q,q,cd+q,q,q,q,q,q,q,q,q,cd+c,q,c,q,sq,sq,c,cd,c,q]
#12
n3.concat [:bb3,:a3,:g3,:f3,:e3,:d3,:cs3,:e3,:a2,:g2,:g2,:e2,:f2,:a2,:d3,:e2,:d3,:cs3,:d3,:f3,:e3,:f3,:a3,:g3,:a3,:a2,:cs3,:b2,:cs3,:e3,:d3]
d3.concat [q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,cd+q,q,q,q,q,q,cd,q,q,q,q,q,q]
#15

n3.concat [:e3,:g3,:f3,:g3,:bb3,:a3,:bb3,:g3,:f3,:e3,:f3,:g3,:a2,:r,:b2,:r,:cs3,:a2,:b2,:cs3,:d3,:r,:e3,:r]
d3.concat [cd+q,q,q,cd+q,q,q,q,q,q,q,q,q,c,q,c,q,c,q,c,q,c,q,c,q]
#18
n3.concat [:f3,:r,:g3,:f3,:e3,:f3,:cs4,:d4,:g3,:cs4,:d4,:gs3,:cs4,:d4,:a3,:cs4,:d4,:e4,:d4,:cs4,:d4,:f3,:g3,:a3,:g3,:a3,:d3,:fs3,:a3,:d4]
d3.concat [c,q,cd+q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q]+ritq+[c*1.12]
#end part1
n1b=[:a3,:fs3,:g3,:a3,:bb3,:c4,:d4,:eb4,:c4,:g4,:a4,:bb4,:c5,:bb4,:g4,:d4,:c5,:e4,:fs4,:g4,:e4,:f4,:e4,:cs4,:d4,:a4,:g4,:f4,:g4,:f4,:e4]
d1b=[q]*25+[cd+q,q,q,cd+q,q,q]
#24
n1b.concat [:d4,:e4,:f4,:e4,:a4,:g4,:f4,:eb4,:d4,:bb4,:a4,:g4,:a4,:g4,:fs4,:g4,:eb5,:f4,:d5,:r,:g4,:fs4,:g4,:a4,:bb4,:c5,:d5,:eb5,:c5,:g5,:a5,:bb5,:c6,:bb5,:g5,:d5,:c6,:e5,:fs5,:g5,:d5,:eb5,:f5,:c5,:d5]
d1b.concat [q,q,q,q,q,q,q,q,q,cd+q,q,q,q,q,q,c,q,c,q,11*q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q]
#29
n1b.concat [:eb5,:f5,:e5,:f5,:e5,:d5,:e5,:f5,:c5,:d5,:eb5,:bb4,:c5,:d5,:a4,:bb4,:c5,:g4,:a4,:bb4,:g5,:ab4,:f5,:g4,:eb5,:f4,:d5,:eb4,:f4,:g4,:d4,:eb4,:c4]
d1b.concat [cd,sq,sq,sq,sq,sq,sq,q,q,q,q,q,q,q,q,q,q,q,q,c,q,c,q,c,q,c,q,q,q,q,q,q,q]
#32
n1b.concat [:b3,:c4,:d4,:eb4,:f4,:g4,:ab4,:f4,:c5,:d5,:eb5,:f5,:g5,:eb5,:c5,:f5,:a4,:b4,:eb5,:c5,:a4,:d5,:fs4,:g4,:c5,:a4,:fs4,:bb4,:d4,:eb4,:a4,:f4,:d4,:g4,:bb3,:c4]
d1b.concat [q]*36
#35
n1b.concat [:d4,:e4,:fs4,:g4,:a4,:bb4,:c5,:d5,:eb5,:d5,:bb5,:eb5,:d5,:c5,:bb4,:g5,:c5,:bb4,:ab4,:g4,:eb5,:ab4,:g4,:f4,:eb4,:c5,:eb4,:d4,:c5,:bb4,:c4,:bb4,:a4,:d4,:c5,:bb4,:e4,:d5,:c5,:fs4,:eb5,:d5]
d1b.concat [cd+q,q,q,q,q,q,q,q,q,c,q,q,q,q,c,q,q,q,q,c,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q]
#39

n1b.concat [:g4,:c5,:bb4,:a4,:bb4,:g4,:fs4,:eb4,:d4,:g4,:d4,:c4,:a4,:d4,:c4,:bb3,:c4,:a3,:bb3,:a3,:g3,:c4,:a3,:g3,:d4,:a3,:g3,:eb4,:a3,:g3,:fs3,:g3,:a3,:bb3,:c4,:d4]
d1b.concat [q]*3*12
#42
n1b.concat [:eb4,:c4,:g4,:a4,:bb4,:c5,:d5,:bb4,:g4,:c5,:e4,:fs4,:g4,:d5,:g5,:f5,:eb5,:d5,:c5,:bb4,:a4,:g4,:fs4,:g4,:eb5,:d5,:bb4,:c5,:bb4,:a4,:g4,:g4,:d4,:bb3,:g3]
d1b.concat [q]*12+[c]+[q]*10+ritqb[0..2]+[dsq*1.08,dsq*1.08,sq*1.08]+ritqb[4..-1]+[c*1.2]
#end part 2
n2b=[:r,:eb4,:d4,:c4,:d4,:c4,:bb3,:c4,:a4,:bb3,:c4,:d4,:c4,:bb3,:c4,:bb3,:a3,:g3,:a3,:bb3,:c4,:bb3,:c4,:b3,:c4,:b3,:a3,:b3,:r]
d2b=[q*61,q,q,q,q,q,q,c,q,c,q,cd+q,q,q,cd+q,q,q,cd,q,q,q,cd,sq,sq,sq,sq,sq,sq,q*12*15+11.3*q+c*1.2]
#end part 2
n3b = [:r,:d2,:cs2,:d2,:e2,:f2,:g2,:a2,:bb2,:g2,:d3,:e3,:f3,:g3,:f3,:d3,:a2,:g3,:b2,:cs3,:d3,:c3,:bb2,:c3,:d3,:eb3,:d3,:c3,:d3,:c3,:b2]
d3b = [q*24,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,cd+q,q,q,cd+c,q,q,q,q,q,q,q]
#26
n3b.concat [:c3,:a3,:bb2,:g3,:a3,:g3,:fs3,:g3,:f3,:eb3,:d3,:e3,:fs3,:g3,:a3,:bb3,:fs3,:g3,:eb3,:c3,:d3,:g3,:c4,:g3,:a3,:bb3,:f3,:g3,:a3,:f3,:g3,:a3]
d3b.concat [c,q,c,q,q,q,q,q,q,q,q,q,q,cd+q,q,q,cd,c,q,c,q,md,q,q,q,q,q,q,c,q,c,q]
#30
n3b.concat [:bb3,:fs3,:g3,:a3,:e3,:f3,:g3,:d3,:eb3,:f3,:c3,:d3,:eb3,:b2,:c3,:d3,:a2,:b2,:c3,:d3,:eb3,:f3,:g3,:ab3,:g3,:a3,:b3,:c3,:d3,:eb3,:f3,:g3,:ab3,:b2,:c3,:d3]
d3b.concat [q]*12*3
#33
n3b.concat [:eb3,:ab3,:d3,:g3,:c3,:r,:bb3,:a3,:r,:g3,:f3,:r,:eb3,:fs2,:g2,:a2,:bb2,:c3,:d3,:eb3,:c3,:g3,:a3,:bb3,:c3]
d3b.concat [c,q,c,q,c,cd,q,cd,c,q,cd,c,q,q,q,q,q,q,q,q,q,q,q,q,q]
#36
n3b.concat [:bb3,:g3,:d3,:c4,:e3,:fs3,:g3,:d3,:bb2,:ab3,:c3,:d3,:eb3,:bb2,:g2,:f3,:a2,:b2,:c3,:r,:g2,:r,:e2,:r,:fs2,:r,:g2,:r,:a2,:r]
d3b.concat [q]*18+[c,q,c,q,c,q,c,q,c,q,c,q]
#39
n3b.concat [:bb2,:r,:c3,:r,:d3,:r,:e3,:r,:fs3,:d2,:e2,:fs2,:g2,:r,:a2,:r,:bb2,:r,:c3,:r,:d3,:e3,:fs3,:g2,:a2,:bb2]
d3b.concat [c,q,c,q,c,q,c,q,c,q,c,q,c,q,c,q,c,q,c,q,q,q,q,q,q,q]
#42
n3b.concat [:c3,:d3,:eb3,:fs2,:g2,:a2,:bb2,:eb3,:a2,:d4,:eb4,:d4,:c4,:bb3,:a3,:g3,:fs3,:a3,:d3,:c3,:bb2,:a2,:bb2,:g3,:c3,:d3,:c3,:d3,:g2,:bb2,:d3,:g3]
d3b.concat [q,q,q,q,q,q,c,q,c,q]+[q]*12+ritqb+[c*1.2]
end
define :part1 do
  with_fx :level do |x| #control level
    in_thread do #dynamics thread
      ct(x,mf,0)
      sleep 13*q
      ct(x,mp,6*q)
      sleep 6*q
      ct(x,mf,6*q)
      sleep 30*q #5
      ct(x,p,12*q)
      sleep 12*q
      ct(x,f,6*q)
      sleep 3*12*q+3*q #4th 9
      ct(x,p,15*q) #end at 11
      sleep 15*q+2*q
      ct(x,f,22*q) #end at 13
      sleep 34*q #till 14
      ct(x,ff,6*q)
      sleep 6*q
      ct(x,f,3*q)
      sleep 3*q
      ct(x,ff,6*q)
      sleep 6*q
      ct(x,f,3*q)
      sleep 3*q
      ct(x,ff,18*q)
      sleep 18*q
      ct(x,f,6*q)
      sleep 6*q #end mid 17
      ct(x,ff,9*q)
      sleep 9*q
      ct(x,f,8*q)
      sleep 8*q
      ct(x,ff,0) #last q of 18
      sleep 25*q
    end
    in_thread do
      plarray(n1,d1)
    end
    in_thread do
      plarray(n2,d2)
    end
    plarray(n3,d3)
  end
end
define :part2 do
  with_fx :level do |x|
    in_thread do #dynamics thread
      ct(x,mf,0)
      sleep 25*q #end at start 23
      ct(x,f,47*q) #end last q of 26
      sleep 82*q #till last beat 22
      ct(x,mf,21*q) #till 24 3rd beat
      sleep 29*q #until 3rd q 25
      ct(x,f,10) #till start 26
      sleep 16*q
      ct(x,p,18*q) #to last q 27
      sleep 21*q
      ct(x,f,9*q) #to start 29
      sleep 18*q
      ct(x,mf,9*q)
      sleep 15*q
      ct(x,f,12*q) #end 31
      sleep 12*q
      ct(x,mf,6)
      sleep 9*q
      ct(x,f,3*q)
      sleep 3*q
      ct(x,p,6*q)
      sleep 10*q
      ct(x,f,25*q)
      sleep 32*q
      ct(x,mf,17*q)
    end
    in_thread do
      plarray(n1b,d1b)
    end
    in_thread do
      plarray(n2b,d2b)
    end
    plarray(n3b,d3b)
  end
end
parts #setup parts arraya
part1
part1
part2
setbpm(170)
lset #reset length variables
parts #reset parts arrays
part2

Now the first part of the sample based program

#Bach Minuet in G Minor from English Suite 3 (part 1). Coded by Robin Newman December 2014
#sample based version in two parts synced together. Start part 2 then part 1
set_sched_ahead_time! 3

use_sample_pack '/Users/rbn/Desktop/samples/GrandPiano/' #select and adjust as necessary
#use_sample_pack '/home/pi/samples/GrandPiano'
#select loud (f) or soft (p) piano samples
ps="piano_f_"
#ps="piano_p_"
rt=2**(1.0/12)  #12th root of 2
irt=1.0/rt #inverse of above

define :sname do |sn,n| #generates sample name for selected pack
  return (sn+n).intern
end
#array holding sample info for each note: [note, sample name, rate to play]
sam = [[:c1,sname(ps,"c1"),1],[:cs1,sname(ps,"c1"),rt]]
sam.concat [[:d1,sname(ps,"ds1"),irt],[:ds1,sname(ps,"ds1"),1],[:e1,sname(ps,"ds1"),rt]]
sam.concat [[:f1,sname(ps,"fs1"),irt],[:fs1,sname(ps,"fs1"),1],[:g1,sname(ps,"fs1"),rt]]
sam.concat [[:gs1,sname(ps,"a1"),irt],[:a1,sname(ps,"a1"),1],[:as1,sname(ps,"a1"),rt]]
sam.concat [[:b1,sname(ps,"c2"),irt],[:c2,sname(ps,"c2"),1],[:cs2,sname(ps,"c2"),rt]]
sam.concat [[:d2,sname(ps,"ds2"),irt],[:ds2,sname(ps,"ds2"),1],[:e2,sname(ps,"ds2"),rt]]
sam.concat [[:f2,sname(ps,"fs2"),irt],[:fs2,sname(ps,"fs2"),1],[:g2,sname(ps,"fs2"),rt]]
sam.concat [[:gs2,sname(ps,"a2"),irt],[:a2,sname(ps,"a2"),1],[:as2,sname(ps,"a2"),rt]]
sam.concat [[:b2,sname(ps,"c3"),irt],[:c3,sname(ps,"c3"),1],[:cs3,sname(ps,"c3"),rt]]
sam.concat [[:d3,sname(ps,"ds3"),irt],[:ds3,sname(ps,"ds3"),1],[:e3,sname(ps,"ds3"),rt]]
sam.concat [[:f3,sname(ps,"fs3"),irt],[:fs3,sname(ps,"fs3"),1],[:g3,sname(ps,"fs3"),rt]]
sam.concat [[:gs3,sname(ps,"a3"),irt],[:a3,sname(ps,"a3"),1],[:as3,sname(ps,"a3"),rt]]
sam.concat [[:b3,sname(ps,"c4"),irt],[:c4,sname(ps,"c4"),1],[:cs4,sname(ps,"c4"),rt]]
sam.concat [[:d4,sname(ps,"ds4"),irt],[:ds4,sname(ps,"ds4"),1],[:e4,sname(ps,"ds4"),rt]]
sam.concat [[:f4,sname(ps,"fs4"),irt],[:fs4,sname(ps,"fs4"),1],[:g4,sname(ps,"fs4"),rt]]
sam.concat [[:gs4,sname(ps,"a4"),irt],[:a4,sname(ps,"a4"),1],[:as4,sname(ps,"a4"),rt]]
sam.concat [[:b4,sname(ps,"c5"),irt],[:c5,sname(ps,"c5"),1],[:cs5,sname(ps,"c5"),rt]]
sam.concat [[:d5,sname(ps,"ds5"),irt],[:ds5,sname(ps,"ds5"),1],[:e5,sname(ps,"ds5"),rt]]
sam.concat [[:f5,sname(ps,"fs5"),irt],[:fs5,sname(ps,"fs5"),1],[:g5,sname(ps,"fs5"),rt]]
sam.concat [[:gs5,sname(ps,"a5"),irt],[:a5,sname(ps,"a5"),1],[:as5,sname(ps,"a5"),rt]]
sam.concat [[:b5,sname(ps,"c6"),irt],[:c6,sname(ps,"c6"),1],[:cs6,sname(ps,"c6"),rt]]
sam.concat [[:d6,sname(ps,"ds6"),irt],[:ds6,sname(ps,"ds6"),1],[:e6,sname(ps,"ds6"),rt]]
sam.concat [[:f6,sname(ps,"fs6"),irt],[:fs6,sname(ps,"fs6"),1],[:g6,sname(ps,"fs6"),rt]]
sam.concat [[:gs6,sname(ps,"a6"),irt],[:a6,sname(ps,"a6"),1],[:as6,sname(ps,"a6"),rt]]
sam.concat [[:b6,sname(ps,"c7"),irt],[:c7,sname(ps,"c7"),1],[:cs7,sname(ps,"c7"),rt]]
sam.concat [[:d7,sname(ps,"ds7"),irt],[:ds7,sname(ps,"ds7"),1],[:e7,sname(ps,"ds7"),rt]]
sam.concat [[:f7,sname(ps,"fs7"),irt],[:fs7,sname(ps,"fs7"),1],[:g7,sname(ps,"fs7"),rt]]
sam.concat [[:gs7,sname(ps,"a7"),irt],[:a7,sname(ps,"a7"),1],[:as7,sname(ps,"a7"),rt]]
sam.concat [[:b7,sname(ps,"c8"),irt],[:c8,sname(ps,"c8"),1]]
#puts sam

#note aliases to allow flats
flat=[:db1,:eb1,:fb1,:gb1,:ab1,:bb1,:cb2,:db2,:eb2,:fb2,:gb2,:ab2,:bb2,:cb3,:db3,:eb3,:fb3,:gb3,:ab3,:bb3,:cb3,:db4,:eb4,:fb4,:gb4,:ab4,:bb4,:cb4,:db5,:eb5,:fb5,:gb5,:ab5,:bb5,:cb5,:db6,:eb6,:fb6,:gb6,:ab6,:bb6,:cb7,:db7,:eb7,:fb7,:gb7,:ab7,:bb7,:cb8]
sharp=[:cs1,:ds1,:e1,:fs1,:gs1,:as1,:b1,:cs2,:ds2,:e2,:fs2,:gs2,:as2,:b2,:cs3,:ds3,:e3,:fs3,:gs3,:as3,:b3,:cs4,:ds4,:e4,:fs4,:gs4,:as4,:b4,:cs5,:ds5,:e5,:fs5,:gs5,:as5,:b5,:cs6,:ds6,:e6,:fs6,:gs6,:as6,:b6,:cs7,:ds7,:e7,:fs7,:gs7,:as7,:b7]

#add es and bs with aliases
flat.concat [:es1,:es2,:es3,:es4,:es5,:es6,:es7,:bs1,:bs2,:bs3,:bs4,:bs5,:bs6,:bs7]
sharp.concat [:f1,:f2,:f3,:f4,:f5,:f6,:f7,:c2,:c3,:c4,:c5,:c6,:c7,:c8]
extra=[]
flat.zip(sharp).each do |f,s|
  extra.concat [[f,(sam.assoc(s)[1]),(sam.assoc(s)[2])]]
end
sam = sam + extra #add in flat definitions

#definition to play a sample "note" specify note, duration,pan,volume,release type as parameters
define :pl do |n,d=0.2,pan=0,v=0.8,nodamp=0|
  if nodamp == 0
    rt=d #gives reasonable result: experiment with value
  else
    rt = sample_duration(sam.assoc(n)[1]) #release is sample duration time
  end
  sample (sam.assoc(n)[1]),rate: (sam.assoc(n)[2]),attack: 0,sustain: d*0.95,release: rt,amp: v,pan: pan
end

define :ntosym do |n| #this returns the equivalent note symbol to an input integer e.g. 59 => :b4
  @note=n % 12
  @octave = n / 12 - 1
  #puts @octave #for debugging
  #puts @note
  lookup_notes = {
    0  => :c,
    1  => :cs,
    2  => :d,
    3  => :ds,
    4  => :e,
    5  => :f,
    6  => :fs,
    7  => :g,
    8  => :gs,
    9  => :a,
    10 => :as,
  11 => :b}
  return (lookup_notes[@note].to_s + @octave.to_s).to_sym #return the required note symbol
end

define :tr do |nv,shift| #this enables transposition of the note. Shift is number of semitones to move
  if shift ==0 then
    return nv
  else
    return ntosym(note(nv)+shift)
  end
end

define :plarray do |nt,dur,shift=0,vol=0.6,pan=0| #This plays associated arrays of notes and durations, transposing if set, and handling rests
  nt.zip(dur).each do |n,d|
    if n != :r then
      #puts n
      pl(tr(n,shift),d,pan)
    end
    sleep d
  end
end

cue :part2

Finally the second part of the sample based program

#BachGigueInGMinor (part 2) coded by Robin Newman, December 2014
#sample based version. (two parts synced together: staret this part first, then part 1)
sync :part2
use_sample_pack '/Users/rbn/Desktop/samples/GrandPiano/' #select and adjust as necessary
#use_sample_pack '/home/pi/samples/GrandPiano'

#select loud (f) or soft (p) piano samples
#ps="piano_f_"
ps="piano_p_"

s=0
define :setbpm do |n| #set bpm equivalent
  s = (1.0 / 8) *(60.0/n.to_f)
end
setbpm(180)
dsq=sq=q=qd=c=cd=cdd=m=md=0

define :lset do
  dsq = 1 * s #demi-semi-quaver
  sq = 2 * s #semi-quaver
  q = 4 * s #quaver
  qd = 6 * s #quaver dotted
  c = 8 * s #crotchet
  cd = 12 * s #crotchet dotted
  m = 16 * s #minim
  md = 24 * s #minim dotted
end
lset
p=0.15
mp=0.2
mf=0.4
f=0.8
ff=1.6
define :ct do |ptr,lev,slid=0|
  control ptr,amp: lev,amp_slide: slid
end

ritq=ritqb=d1=d2=d3=d1b=d2b=d3b=n1=n2=n3=n1b=n2b=n3b=[] #define global scope
define :parts do
ritq=[q*1.02,q*1.04,q*1.06,q*1.08,q*1.1,q*1.12]
ritqb=[q*1.02,q*1.04,q*1.06,q*1.08,q*1.1,q*1.2,q*1.4,q*1.6,q*1.8]
n1=[:d5,:eb5,:d5,:c5,:bb4,:a4,:g4,:fs4,:a4,:d4,:c4,:bb3,:a3,:bb3,:d4,:g4,:a3,:g4,:fs4,:g4,:bb4,:a4,:bb4,:d5,:c5]
d1=[q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q]
#3
n1.concat [:d5,:e5,:f5,:e5,:f5,:g5,:a5,:g5,:f5,:g5,:f5,:e5,:f5,:g5,:a5,:d5,:c5,:bb4,:c5,:d5,:bb4,:c5]
d1.concat [cd+q,q,q,cd+q,q,q,q,q,q,q,q,q,q,q,q,cd+q,q,q,cd+c,q,c,q]
#6
n1.concat [:a4,:bb4,:c5,:d5,:bb4,:c5,:d5,:e5,:fs5,:g5,:a5,:g5,:f5,:e5,:f5,:eb5,:d5,:c5,:d5,:c5,:bb4,:a4,:bb4,:r,:a4,:r,:a4,:g4,:a4,:r,:e4,:g4]
d1.concat [c,q,c,q,c,q,cd+c,q,cd,cd,cd+cd,cd+q,q,q,cd+cd,cd+q,q,q,cd+cd,cd+q,q,q,cd,q,c,q,q,q,cd,q,q,q]
#12
n1.concat [:f4,:g4,:a4,:g4,:a4,:bb4,:a4,:d5,:f5,:g4,:g5,:e5,:f5,:a5,:g5,:a5,:a4,:cs5,:b4,:c5,:e5,:d5,:e5,:e4,:g4,:f4]
d1.concat [cd+q,q,q,cd+q,q,q,q,q,q,q,q,q,q,q,q,cd,q,q,q,q,q,q,cd,q,q,q]
#15
n1.concat [:g4,:bb4,:a4,:bb4,:cs4,:e4,:d4,:e4,:g4,:f4,:g4,:bb4,:a4,:bb4,:a4,:g4,:cs5,:bb4,:a4,:d5,:a4,:g4,:e5,:a4,:g4,:f4,:g4,:e4,:f4,:e4,:d4,:g4,:e4,:d4]
d1.concat [q,q,q,cd]+[q]*30
#18
n1.concat [:a4,:e4,:d4,:bb4,:e4,:d4,:cs4,:d4,:e4,:a3,:r,:a5,:bb5,:a5,:g5,:f5,:e5,:d5,:cs5,:e5,:a4,:g4,:f4,:e4,:f4,:a4,:d5,:e4,:d5,:cs5,:d5,:a4,:fs4,:d4]
d1.concat [q]*27+ritq+[c*1.12]
#end part 1
n2=[:r,:g4,:bb4,:a4,:g4,:f4,:e4,:d4,:cs4,:e4,:a3,:g3,:f3,:e3,:f3,:a3,:d4,:e3,:d4,:cs4,:d4,:e4,:f4,:e4,:fs4,:g4,:a4,:g4,:fs4,:g4,:fs4,:e4]
d2=[q*24]+[q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,cd+q,q,q,cd+q,q,q,q,q,q,q,q,q]
#6
n2.concat [:fs4,:e4,:d4,:e4,:g4,:fs4,:g4,:a4,:bb4,:a4,:bb4,:c5,:d5,:c5,:bb4,:c5,:bb4,:a4,:bb4,:a4,:g4,:f4,:eb4,:d4,:f4,:e4,:f4,:e4,:d4,:cs4,:b3,:cs4]
d2.concat [q,q,q,q,q,q,cd+q,q,q,cd+q,q,q,q,q,q,q,q,q,md,md,md,md,md,cd+q,q,q,c,q,cd+q,q,q,cd]
#12
n2.concat [:d4,:e4,:f4,:e4,:f4,:g4,:a4,:g4,:r]
d2.concat [cd+q,q,q,cd+q,q,q,cd,cd,cd*2+72*q+9.42*q+c*1.2] #rit at end
#end part 1
n3=[:r,:d4,:eb4,:d4,:c4,:bb3,:a3,:g3,:fs3,:a3,:d3,:c3,:bb2,:a2,:bb2,:d3,:g3,:a2,:g3,:fs3,:g3,:bb3,:a3,:bb3,:d4,:c4,:d4,:a3,:d3]
d3=[q+60*q+5*q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,cd+q,q,q]
#9

n3.concat [:eb3,:g3,:f3,:g3,:bb3,:a3,:bb3,:f3,:bb2,:c3,:eb3,:d3,:eb3,:g3,:f3,:g3,:cs3,:d3,:c3,:bb2,:c3,:bb2,:a2,:r,:a3]
d3.concat [q,q,q,q,q,q,cd+q,q,q,q,q,q,q,q,q,cd+c,q,c,q,sq,sq,c,cd,c,q]
#12
n3.concat [:bb3,:a3,:g3,:f3,:e3,:d3,:cs3,:e3,:a2,:g2,:g2,:e2,:f2,:a2,:d3,:e2,:d3,:cs3,:d3,:f3,:e3,:f3,:a3,:g3,:a3,:a2,:cs3,:b2,:cs3,:e3,:d3]
d3.concat [q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,cd+q,q,q,q,q,q,cd,q,q,q,q,q,q]
#15

n3.concat [:e3,:g3,:f3,:g3,:bb3,:a3,:bb3,:g3,:f3,:e3,:f3,:g3,:a2,:r,:b2,:r,:cs3,:a2,:b2,:cs3,:d3,:r,:e3,:r]
d3.concat [cd+q,q,q,cd+q,q,q,q,q,q,q,q,q,c,q,c,q,c,q,c,q,c,q,c,q]
#18
n3.concat [:f3,:r,:g3,:f3,:e3,:f3,:cs4,:d4,:g3,:cs4,:d4,:gs3,:cs4,:d4,:a3,:cs4,:d4,:e4,:d4,:cs4,:d4,:f3,:g3,:a3,:g3,:a3,:d3,:fs3,:a3,:d4]
d3.concat [c,q,cd+q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q]+ritq+[c*1.12]
#end part1
n1b=[:a3,:fs3,:g3,:a3,:bb3,:c4,:d4,:eb4,:c4,:g4,:a4,:bb4,:c5,:bb4,:g4,:d4,:c5,:e4,:fs4,:g4,:e4,:f4,:e4,:cs4,:d4,:a4,:g4,:f4,:g4,:f4,:e4]
d1b=[q]*25+[cd+q,q,q,cd+q,q,q]
#24
n1b.concat [:d4,:e4,:f4,:e4,:a4,:g4,:f4,:eb4,:d4,:bb4,:a4,:g4,:a4,:g4,:fs4,:g4,:eb5,:f4,:d5,:r,:g4,:fs4,:g4,:a4,:bb4,:c5,:d5,:eb5,:c5,:g5,:a5,:bb5,:c6,:bb5,:g5,:d5,:c6,:e5,:fs5,:g5,:d5,:eb5,:f5,:c5,:d5]
d1b.concat [q,q,q,q,q,q,q,q,q,cd+q,q,q,q,q,q,c,q,c,q,11*q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q]
#29
n1b.concat [:eb5,:f5,:e5,:f5,:e5,:d5,:e5,:f5,:c5,:d5,:eb5,:bb4,:c5,:d5,:a4,:bb4,:c5,:g4,:a4,:bb4,:g5,:ab4,:f5,:g4,:eb5,:f4,:d5,:eb4,:f4,:g4,:d4,:eb4,:c4]
d1b.concat [cd,sq,sq,sq,sq,sq,sq,q,q,q,q,q,q,q,q,q,q,q,q,c,q,c,q,c,q,c,q,q,q,q,q,q,q]
#32
n1b.concat [:b3,:c4,:d4,:eb4,:f4,:g4,:ab4,:f4,:c5,:d5,:eb5,:f5,:g5,:eb5,:c5,:f5,:a4,:b4,:eb5,:c5,:a4,:d5,:fs4,:g4,:c5,:a4,:fs4,:bb4,:d4,:eb4,:a4,:f4,:d4,:g4,:bb3,:c4]
d1b.concat [q]*36
#35
n1b.concat [:d4,:e4,:fs4,:g4,:a4,:bb4,:c5,:d5,:eb5,:d5,:bb5,:eb5,:d5,:c5,:bb4,:g5,:c5,:bb4,:ab4,:g4,:eb5,:ab4,:g4,:f4,:eb4,:c5,:eb4,:d4,:c5,:bb4,:c4,:bb4,:a4,:d4,:c5,:bb4,:e4,:d5,:c5,:fs4,:eb5,:d5]
d1b.concat [cd+q,q,q,q,q,q,q,q,q,c,q,q,q,q,c,q,q,q,q,c,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q]
#39

n1b.concat [:g4,:c5,:bb4,:a4,:bb4,:g4,:fs4,:eb4,:d4,:g4,:d4,:c4,:a4,:d4,:c4,:bb3,:c4,:a3,:bb3,:a3,:g3,:c4,:a3,:g3,:d4,:a3,:g3,:eb4,:a3,:g3,:fs3,:g3,:a3,:bb3,:c4,:d4]
d1b.concat [q]*3*12
#42
n1b.concat [:eb4,:c4,:g4,:a4,:bb4,:c5,:d5,:bb4,:g4,:c5,:e4,:fs4,:g4,:d5,:g5,:f5,:eb5,:d5,:c5,:bb4,:a4,:g4,:fs4,:g4,:eb5,:d5,:bb4,:c5,:bb4,:a4,:g4,:g4,:d4,:bb3,:g3]
d1b.concat [q]*12+[c]+[q]*10+ritqb[0..2]+[dsq*1.08,dsq*1.08,sq*1.08]+ritqb[4..-1]+[c*1.2]
#end part 2
n2b=[:r,:eb4,:d4,:c4,:d4,:c4,:bb3,:c4,:a4,:bb3,:c4,:d4,:c4,:bb3,:c4,:bb3,:a3,:g3,:a3,:bb3,:c4,:bb3,:c4,:b3,:c4,:b3,:a3,:b3,:r]
d2b=[q*61,q,q,q,q,q,q,c,q,c,q,cd+q,q,q,cd+q,q,q,cd,q,q,q,cd,sq,sq,sq,sq,sq,sq,q*12*15+11.3*q+c*1.2]
#end part 2
n3b = [:r,:d2,:cs2,:d2,:e2,:f2,:g2,:a2,:bb2,:g2,:d3,:e3,:f3,:g3,:f3,:d3,:a2,:g3,:b2,:cs3,:d3,:c3,:bb2,:c3,:d3,:eb3,:d3,:c3,:d3,:c3,:b2]
d3b = [q*24,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,q,cd+q,q,q,cd+c,q,q,q,q,q,q,q]
#26
n3b.concat [:c3,:a3,:bb2,:g3,:a3,:g3,:fs3,:g3,:f3,:eb3,:d3,:e3,:fs3,:g3,:a3,:bb3,:fs3,:g3,:eb3,:c3,:d3,:g3,:c4,:g3,:a3,:bb3,:f3,:g3,:a3,:f3,:g3,:a3]
d3b.concat [c,q,c,q,q,q,q,q,q,q,q,q,q,cd+q,q,q,cd,c,q,c,q,md,q,q,q,q,q,q,c,q,c,q]
#30
n3b.concat [:bb3,:fs3,:g3,:a3,:e3,:f3,:g3,:d3,:eb3,:f3,:c3,:d3,:eb3,:b2,:c3,:d3,:a2,:b2,:c3,:d3,:eb3,:f3,:g3,:ab3,:g3,:a3,:b3,:c3,:d3,:eb3,:f3,:g3,:ab3,:b2,:c3,:d3]
d3b.concat [q]*12*3
#33
n3b.concat [:eb3,:ab3,:d3,:g3,:c3,:r,:bb3,:a3,:r,:g3,:f3,:r,:eb3,:fs2,:g2,:a2,:bb2,:c3,:d3,:eb3,:c3,:g3,:a3,:bb3,:c3]
d3b.concat [c,q,c,q,c,cd,q,cd,c,q,cd,c,q,q,q,q,q,q,q,q,q,q,q,q,q]
#36
n3b.concat [:bb3,:g3,:d3,:c4,:e3,:fs3,:g3,:d3,:bb2,:ab3,:c3,:d3,:eb3,:bb2,:g2,:f3,:a2,:b2,:c3,:r,:g2,:r,:e2,:r,:fs2,:r,:g2,:r,:a2,:r]
d3b.concat [q]*18+[c,q,c,q,c,q,c,q,c,q,c,q]
#39
n3b.concat [:bb2,:r,:c3,:r,:d3,:r,:e3,:r,:fs3,:d2,:e2,:fs2,:g2,:r,:a2,:r,:bb2,:r,:c3,:r,:d3,:e3,:fs3,:g2,:a2,:bb2]
d3b.concat [c,q,c,q,c,q,c,q,c,q,c,q,c,q,c,q,c,q,c,q,q,q,q,q,q,q]
#42
n3b.concat [:c3,:d3,:eb3,:fs2,:g2,:a2,:bb2,:eb3,:a2,:d4,:eb4,:d4,:c4,:bb3,:a3,:g3,:fs3,:a3,:d3,:c3,:bb2,:a2,:bb2,:g3,:c3,:d3,:c3,:d3,:g2,:bb2,:d3,:g3]
d3b.concat [q,q,q,q,q,q,c,q,c,q]+[q]*12+ritqb+[c*1.2]
end
define :part1 do
  with_fx :level do |x|
    in_thread do
      ct(x,mf,0)
      sleep 13*q
      ct(x,mp,6*q)
      sleep 6*q
      ct(x,mf,6*q)
      sleep 30*q #5
      ct(x,p,12*q)
      sleep 12*q
      ct(x,f,6*q)
      sleep 3*12*q+3*q #4th 9
      ct(x,p,15*q) #end at 11
      sleep 15*q+2*q
      ct(x,f,22*q) #end at 13
      sleep 34*q #till 14
      ct(x,ff,6*q)
      sleep 6*q
      ct(x,f,3*q)
      sleep 3*q
      ct(x,ff,6*q)
      sleep 6*q
      ct(x,f,3*q)
      sleep 3*q
      ct(x,ff,18*q)
      sleep 18*q
      ct(x,f,6*q)
      sleep 6*q #end mid 17
      ct(x,ff,9*q)
      sleep 9*q
      ct(x,f,8*q)
      sleep 8*q
      ct(x,ff,0) #last q of 18
      sleep 25*q
    end
    in_thread do
      plarray(n1,d1)
    end
    in_thread do
      plarray(n2,d2)
    end
    plarray(n3,d3)
  end
end
define :part2 do
  with_fx :level do |x|
    in_thread do
      ct(x,mf,0)
      sleep 25*q #end at start 23
      ct(x,f,47*q) #end last q of 26
      sleep 82*q #till last beat 22
      ct(x,mf,21*q) #till 24 3rd beat
      sleep 29*q #until 3rd q 25
      ct(x,f,10) #till start 26
      sleep 16*q
      ct(x,p,18*q) #to last q 27
      sleep 21*q
      ct(x,f,9*q) #to start 29
      sleep 18*q
      ct(x,mf,9*q)
      sleep 15*q
      ct(x,f,12*q) #end 31
      sleep 12*q
      ct(x,mf,6)
      sleep 9*q
      ct(x,f,3*q)
      sleep 3*q
      ct(x,p,6*q)
      sleep 10*q
      ct(x,f,25*q)
      sleep 32*q
      ct(x,mf,17*q)
    end
    in_thread do
      plarray(n1b,d1b)
    end
    in_thread do
      plarray(n2b,d2b)
    end
    plarray(n3b,d3b)
  end
end
parts
part1
part1
part2
setbpm(170)
lset
parts
part2

As you will see, the sample based and synth based programs are very similar.

In order to play the sample based program you will need the samples for the GrandPiano. In fact there are TWO sets of piano samples, and they are both contained in the download. You can select which set to use by simply altering one variable in the first part of the sample program, selecting either ps=”piano_f_” for a load percussive piano or ps=”piano_p” for a softer played set of samples.

Downloads

The GrandPiano Samples. These should be unzipped and stored in a samples folder on your RaspberryPi or on your Mac or PC, and the path in both parts of the sample programs altered accordingly.

The synth program
The sample program part 1
The sample program part 2

You can hear three recordings on SoundCloud here (played one after the other)

Sonic-Pi Conductor: play all your workspaces!

A few posts ago I mentioned that I had found that you can link workspaces together using the cue and sync system in Sonic Pi. I used this to play several related pieces which were part of a Bach Brandenburg movement.

I have now had a chance to look at the mechanism in greater detail and to develop a program which I have called Sonic Pi Conductor which makes it very easy to carry out the linking process. I have made a video which is on my YouTube channel which shows it in operation, so I won’t repeat the details in this post.

The video is here

You can inspect and download the program, which contains full instructions in comments at the start from this link here

Sonic Pi 2.2 is published, and two new programs for it

Sam Aaron has released version 2.2 of Sonic Pi. This brings further improvements. The timing of fx synths is perfected, so that now they can be used to make rhythmical changes correctly, eg using the fx :slicer.

A new RingArray data structure is added, so that you can generate lists where the index accessign elements wraps around. New functions ring, knit, bools and range are associeted with generating these structures. One of the example programs I have put below makes extensive use of these.
There are improvements to the Graphical Interface: The help window which can be detached and increased in size, automatically re-docks if the floating window is closed. However the floating windows can be toggled on and off by the Help button, which is very useful, obviating the need to resize it all the time.
Prefences are now remembered accross sessions, such as text size, current workspace window, Studio and Debug options. On the Raspberry Pi the Audio output option and previous volume are forced on boot.

A new bass drum sample bd_tek is added, as is a new synth :square

One or two bugs are fixed.
=================================================

I have two new programs which make use of features added in 2.2

First SlidingChordsandFrerejaques.rb which utilises the ability to define functions to generate named args for synths, and secondly ringfunctions.rb which generates some Boogie utilising the new ring functions.

I put these in Gist files over the last few days, and then can be accessed by clicking the links above. Comments in the program listings should enable you to see how they work.

New versions of Sonic Pi can be downloaded here (The Windows version for 2.2 is not yet available). The Raspberry Pi version for 2.2 can be manually down-loaded at present, but will appear in the Raspbian distribution shortly. The Mac version 2.2 can also be downloaded.

Two new techniques for Sonic Pi

Over the last couple of days, I have worked on two new techniques for use with Sonic Pi 2.1.1

Adding dynamic shaping: p,f,ff and crescendos and diminuendos
First, I have for a long time wanted a better way to add dynamic shaping to the pieces I have coded for Sonic Pi. There is of course an amp: option for all play based commands which lets you set the amplitude or volume of the synth playing the sound. However, this is quite tedious to use if you have to apply an amp setting to every note that you play. Also, there are difficulties if you want to program a crescendo or diminuendo, as you have to calculate the values required using the starting and ending levels and the number of notes over which the increase or decrease is to take place.
The solution to this dilemna is to make use of the with_fx level: command. This allows you to apply a wrapper around a section of notes to be played, and to set a multiplier level which is applied to all the notes within the wrapper. So if the individual note amp: is set to say 0.8 and the level: amp: is set to 0.5, then the overall effect is to play the note at amp: 0.8 * 0.5 = 0.4
The other thing about the fx_level: command is that it can also accept a sliding parameter which allows you to set the time over which the level is to change. This allows you to implement crescendos and diminuendos very easily.

So how does this work in practice? It is perhaps best to look at a working example.
Click the link below to see it.

#In Dulci Jubilo transcribed for Sonic Pi 2.1.1 by Robin Newman December 2014
#this piece features the use of a separate dynamics track adjusting the amplitude of the parts
#This utilises the use of the fx_level effect, and also uses the amp_slide: parameter to achieve
#crescendos and diminuendos
#
set_sched_ahead_time! 2
use_debug false
use_synth :tri

s=0 #set s here with dummy value so that its scope is global

define :setbpm do |n| #set bpm equivalent
  s = (1.0 / 8) *(60.0/n.to_f)
end

setbpm(200)

dsq = 1 * s #note length definitions (not all used)
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

p = 0.1 #set dynamic levels here
mp = 0.2
mf = 0.3
f = 0.5
ff = 0.7

ps=m #adjust elongation for pauses

define :pl do |notes,durations,vol=1| #default volume 1, multiplied by level setting
  notes.zip(durations).each do |n,d|
    play n,attack: dsq*0.2,sustain: 0.9*d-dsq*0.2,release: 0.1*d,amp: vol
    sleep d
  end
end

#define the four pairs of note and duration parts
n1=[:f4,:f4,:f4,:a4,:bb4,:c5,:d5,:c5,:c5,:f4,:f4,:a4,:bb4,:c5,:d5,:c5]
d1=[c,m,c,m,c,m,c,m,c,m,c,m,c,m,c,md+ps]
#b9
n1.concat [:c5,:d5,:c5,:bb4,:a4,:f4,:f4,:g4,:g4,:a4,:g4,:f4,:g4,:a4,:a4,:c5,:d5,:c5,:bb4,:a4]
d1.concat [m,c,m,c,md,m,c,m,c,m,c,m,c,m,c,m,c,m,c,md]
#b20
n1.concat [:f4,:f4,:g4,:g4,:a4,:g4,:f4,:g4,:a4,:d4,:d4,:e4,:e4,:f4,:c5,:a4,:bb4,:g4,:g4,:f4]
d1.concat [m,c,m,c,m,c,m,c,md,m,c,m,c,md,md,m,c,m,c,m+ps]
#end
n2=[:c4,:d4,:c4,:f4,:e4,:d4,:c4,:f4,:e4,:f4,:d4,:c4,:f4,:e4,:d4,:c4,:f4,:e4]
d2=[c,m,c,cd,q,c,m,c,m,c,m,c,cd,q,c,m,c,md+ps]
#b9
n2.concat [:f4,:f4,:e4,:g4,:c4,:f4,:f4,:f4,:f4,:f4,:e4,:f4,:f4,:f4,:f4,:e4,:g4,:c4]
d2.concat [m,c,m,c,md,m,c,m,c,m,c,md+m,c,m,c,m,c,md]
#b20
n2.concat [:f4,:f4,:f4,:f4,:f4,:e4,:f4,:d4,:d4,:e4,:e4,:d4,:e4,:f4,:f4,:f4,:e4,:f4]
d2.concat [m,c,m,c,m,c,md*2,m,c,m,c,md,md,m,c,m,c,m+ps]
#end
n3=[:a3,:bb3,:a3,:c4,:d4,:a3,:bb3,:g3,:a3,:bb3,:a3,:c4,:d4,:a3,:bb3,:g3]
d3=[c,m,c,m,c,m,c,m,c,m,c,m,c,m,c,md+ps]
#b9
n3.concat [:c4,:bb3,:g3,:e3,:f3,:a3,:a3,:d4,:d4,:c4,:d4,:bb3,:a3,:bb3,:c4,:d4,:c4,:bb3,:g3,:e3,:f3]
d3.concat [m,c,m,c,md,m,c,m,c,cd,q,c,m,c,m,c,m,c,m,c,md]
#b20
n3.concat [:a3,:a3,:d4,:d4,:c4,:d4,:bb3,:a3,:bb3,:c4,:a3,:a3,:g3,:g3,:a3,:bb3,:g3,:f3,:bb3,:d4,:c4,:a3]
d3.concat [m,c,m,c,cd,q,c,m,c,md,m,c,m,c,m,c,md,m,c,m,c,m+ps]
#end
n4=[:f3,:f3,:f3,:f3,:f3,:f3,:f3,:f3,:f3,:f3,:f3,:f3,:c3]
d4=[c,m,c,m,c,md+m,c,m,c,m,c,md,md+ps]
#b9
n4.concat [:a2,:bb2,:c3,:c3,:f3,:d3,:d3,:bb2,:bb2,:c3,:c3,:f3,:d3,:a2,:bb2,:c3,:c3,:f3]
d4.concat [m,c,m,c,md,m,c,m,c,m,c,md+m,c,m,c,m,c,md]
#b20
n4.concat [:d3,:d3,:bb2,:bb2,:c3,:c3,:f3,:f3,:f3,:e3,:e3,:d3,:c3,:f3,:d3,:bb2,:c3,:f2]
d4.concat [m,c,m,c,m,c,md*2,m,c,m,c,md,md,m,c,m,c,m+ps]
#end

#These check that the note and duration pairs all match in length: uncomment for debugging
comment do
  puts n1.length
  puts d1.length
  puts n2.length
  puts d2.length
  puts n3.length
  puts d3.length
  puts n4.length
  puts d4.length
end

define :ct do |ptr,lev,slid=0| #this reduces the typing required for the control commands
  control ptr,amp: lev,amp_slide: slid
end
with_fx :reverb,room: 0.4,mix: 0.5 do
  with_fx :level do |x| #use fx level to give dynamic volume setting
    1.upto(4) do |i| #four verses. Index i used to change dynamic ending for the last verse
      puts "verse "+i.to_s
      sleep m #gives slight gap between verses
      in_thread do #volume control thread
        ct(x,p,0) #set the first dynamic level
        sleep c+md*2 #these sleep commands space out the dynamic changes to the correct points
        ct(x,mp,md+m) #b3
        sleep md+m
        ct(x,p,0) #b4 (3rd beat)
        sleep c+md*2
        ct(x,mf,md) #b7
        sleep md*2+ps
        ct(x,p,0) #b9
        sleep md*6
        ct(x,mf,md*3) #b15
        sleep md*3
        ct(x,p,md*2) #b18
        sleep md*5
        ct(x,f,md*2) #b23
        sleep md*4
        ct(x,ff,md*2) #b27
        if i<4
        then
          sleep md*2
          ct(x,mp,md*2) #b29
          sleep md*3+ps
        else
          sleep md*5+ps
        end
      end #end of volume level control thread

      #all 4 parts play together in seperate threads
      in_thread do
        pl(n1,d1)
      end
      in_thread do
        pl(n2,d2)
      end
      in_thread do
        pl(n3,d3)
      end
      pl(n4,d4)
    end #of 4 times loop
  end #end with fx_level
end #end with fx_reverb

This program plays In Dulci Jubilo in four parts, for which it uses a technique I have utilised many times in my programs, namely to put the notes and note durations of each part into a pair of arrays e.g. n1 and d1, and then to use a defined function pl to traverse these arrays zipped together and to extract in turn the corresponding note and duration values and to use the standard play command to play them, sleeping thereafter for the duration of the note before returning to process the next pair. The notes are entered symbolically, and the durations in terms of defined variables such as q,c,and m for quaver, crotchet and minim. As usual, I put the individual parts into threads so that they can be played together.
The new addition is to wrap the whole playing process inside a loop

with fx_level: do |x|
..........
end

and also to put inside that loop an extra thread containing commands of the form

control x,amp: nn, amp_slide: nnn

Such commands are used to alter the value of the amp: level to be applied.
To save typing I actually use a defined function

define :ct do |ptr,lev,slid=0| #this reduces the typing required for the control commands
  control ptr,amp: lev,amp_slide: slid
end

to do the settings, so the commands take the form

ct(x,mf,m)

Which would apply the control x to set the amp: to mf (mezzo-forte given a defined value at the start of the program), and moving or sliding to that level from the previously defined one over a time m (the time duration of a minim, again defined previously in the program)
If the change is instantaneous, the third parameter can be set at 0, or indeed missed out, when 0 will be the default parameter value supplied from the definition of the ct function.
If you study the listing, you will see the volume control thread, consisting of multiple ct(…) statements, separated by sleep statements, so that the dynamic changes that the ct commands supply take place at the right time as the music plays. To aid the checking of this, I put in as comments bar numbers at relevant places, e.g. #b18
In the example, the tune is repeated four times as their are four verses in the carol. The last time, an if statement is used to change the dynamic settings supplied by the ct commands so that the final diminuendo is omitted, and the piece finishes ff. Finally the entire playing section also has a with_fx :reverb wrapper so that some reverb is applied to make it sound a bit more interesting.

The program example can be heard and accessed on soundcloud.com here
This also contains a link to a gist containing the code here

Linking Workspaces together using cue: and sync:

Large scale example audio recording of Sonic Pi playing seven workspaces automatically end to end has been added here

I am very excited about the second technique which I discovered this morning. I have for a long time wondered if it would be possible to link together teh code in differnent workspaces and to get them to play continuously one after another, without having to manually switch and then press run, with an inevitable gap whilst this was done. I thought I would try and see if teh cue: and sync: commands introduced in version 2 might be helpful, and I was amazed to find that it was possible to send a cue from one workspace and have it give a sync in another one. This enables  you to play a series of programs together without a gap, and is also very useful if you hit (as I have) the limitation on the maximum code size that can be contained in one workspace because of the limitations in the present method whereby sound “messages” are sent from the workspace to the synthesizer scsynth producing the final sound. I was able to take one of my earliest pieces, a rendition of a movement from Bach’s 2nd Brandenburg Concerto which I had had previously to split into to halves and play manually one after the other with a slight gap, and was now able to link together so that they played automatically one after the other.

Again an example is probably the best way to explain how to use this technique.
You don’t have to use adjacent workspaces, and indeed the second workspace can be physically at a lower number that the first one if you want. However in this case I will use workspaces one and two.

Place the following short program in workspace 1

#WS1
#The function below reads two arrays zipped together and uses the play
#command to play them one by one, sleeping for the note's duration
#after each one is played
define :pl do |notes,durations|
  notes.zip(durations).each do |n,d|
    play n, sustain: 0.9*d, release: 0.1*d
    sleep d
  end
end

q=0.1 #defined the duration of a quaver

n1=scale(:c4,:major,num_octaves: 2) #n1 contains teh notes for 2 octaves of C4 major
d1= [q]*16 #all 16 notes have quaver duration 0.1 seconds

pl(n1,d1) #play the notess using the defined pl function
cue :two #cue the program in workspace 2

and the next one in workspace two

#WS2
#don't need to define pl Still active from WS1
sync :two #when the cue is received the program progresses from here

q=0.1 #DO need to define q again
n1=scale(:g4,:major,num_octaves: 2).reverse #2 octaves descending this time
d1= [q*2]*16 #each note lasts 2 quavers, so plays more slowly

pl(n1,d1) #play the descending scale (uses the definition from program 1)

Now select workspace 2 and press run. The program will start but will not progress as it is waiting for a sync signal.
Switch to workspace 1 and press run again. The program in workspace one runs, playing an ascending scale. However, as soon as it finishes it sends a cue named :two which is picked up by the waiting program in workspace 2 by the sync :two command, and immediately this program runs, playing a descending scale at a slower pace.
So you still have to switch to the second workspace and start the program running, but when you run the program in workspace one it automatically links to the waiting program in the second workspace and runs that.
There are two further points to note. If you have one or more functions defined in the first workspace, then when the program is run, these are stored in memory, and the remain available to the second program. So if this uses the same function, you do not need to redefine it. The same does NOT appear to be true for variables defined in the first program. Their scope is limited to that program, so if they are required in the second program, then they have to be redefined there. You CAN pass variables if they are defined as global by starting the name of the variable with a $ sing. Thus Sq would work and be available in the second program but q would not.

Improved version of The Sonic Pi Percussion Generator

I have done some further work on The Sonic Pi Percussion Generator. The original version was a bit tedious when coding new percussion sets, and in modifying it, I also discovered a couple of errors in the original program. This new version simplifies coding of new percussion sets. The play patterns are inserted into an array grid, putting h,m or s (high, medium or soft) whnere you want a particular percussion instrument to sound, and a 0 if you want it to be silent. The grid contains 10 rows, each of which can play a differnt specified percussion sample. You can have the same instrument in more than one row if you wish. If you only want to use two or three instruments you merely leave the remaining tracks all containing 32 zeros. As supplied you MUST have 10 instruments specified, and 10 rows in the grid.

As supplied the program contains 6 preset patterns, and you can choose between them by changing a single variable  num in the live_loop which runs the program. The relevant line is highlighted with a commented <<<<<=========
You can add extra sections to the case statement which chooses the pattern to play, by copying and pasting an existing pattern section, changing the case number and amending the pattern and instruments as required.

eg copy the code for the first pattern below:

when 1 #define program one
 p=0.15 #pulse time for each element
 #grid when instrument sounds, and volume it plays h,m or s 0 is silent
 # beats: 1 2 3 4 1 2 3 4
 tr[[0,1]]= "h0000000h0000s00h00000h000000000"
 tr[[1,1]]= "00000000000000000000000000000000"
 tr[[2,1]]= "0000h0000000h0000000h0000000h000"
 tr[[3,1]]= "0000s0000000s0000000s0000000s000"
 tr[[4,1]]= "000000000000000000000000000000h0"
 tr[[5,1]]= "h0s0h0s0h0s0h0s0h0s0h0s0h0s0h000"
 tr[[6,1]]= "00000000000000000000000000000sss"
 tr[[7,1]]= "m0000000000000000000000000000000"
 tr[[8,1]]= "00000000000000000000000000000000"
 tr[[9,1]]= "00000000000000000000000000000000"

 #define sample names to play for each track
 tr[[0,0]]= :drum_bass_hard
 tr[[1,0]]= :drum_bass_soft
 tr[[2,0]]= :drum_snare_soft
 tr[[3,0]]= :drum_cymbal_open
 tr[[4,0]]= :drum_cymbal_closed
 tr[[5,0]]= :drum_cymbal_pedal
 tr[[6,0]]= :drum_tom_hi_hard
 tr[[7,0]] =:drum_tom_lo_hard
 tr[[8,0]] =:drum_heavy_kick
 tr[[9,0]] =:drum_heavy_kick

and paste it just before the end statement preceding the define :perc statement as shown below

#<<<<=============PASTE CODE IN HERE
 end 

 define :perc do |track,sn| #play a given track with sample sn
 track.to_s.split("").each do |pl| #extract each of the 32 values
 if pl!="0" #ignore if pl=0
 #puts sn #uncomment for testing
 #puts pl #uncomment for testing
 sample sn,amp: lev["hms".index(pl)] #lev array contains volume. "hms".index(pl) determines element to use
 end
 sleep p #sleep pulse value
 end
 end

Then amend the copied line
when 1
to read
when 7

You can then amend the pattern defined in the copied code, setting up your own new rhythms, and also amend the instruments samples specified in the copied section to give the ones you want to use. Just remember, you MUST specify an instrument for all 10 lines, even if you don’t use some of them. You can leave the grid line for unwanted instruments set to 32 zeros. To make an instrument play in one of the 32 grid positions, simply place an h,m or s in that position, depending on whether you want the instrument to play with a high, medium or soft volume. Wherever a 0 exists, the instrument will not play.

For refernce, the entire program is copied below

#Percussion track generator by Robin Newman, 4th December 2014 VERSION 2
#Inspired by an article http://www.soundonsound.com/sos/feb98/articles/rythm.html
#This program generates a percussion programme defined over two bars quantised to 32 pulses per bar
#up to 10 instruments can be incorporated
#any instrument can sound on any pulse
#one of three volumes can be chosen for each instrument for each pulse
#a variable p sets the pulse tempo for each programme
#a variable n sets the number of bars the track will play for in 2 bar increments
#In this example six drum programmes are defined num = 1 to num = 6. num=0 plays silence
#These can be chosen in a live_loop by changing the variable num
use_debug false
tr=Hash.new #define the hash array to contain all the information required
n=2 #number of 2 bar repeats to play
noplay="00000000000000000000000000000000" #no sound in the 2 bar cycle

slist=[:drum_bass_hard,:drum_bass_soft ,:drum_snare_hard,:drum_snare_soft,:drum_cymbal_open,:drum_cymbal_closed,:drum_cymbal_pedal,:drum_tom_lo_soft,:drum_heavy_kick]
load_samples slist #preload samples


define :sp do |s| #used to test a particular sample: amend and uncomment lines after definition to use
 sample s
 sleep sample_duration s
end
#sp(:drum_cymbal_hard)
#sleep 1000

lev=[1,0.6,0.3] #volume levels for h,m and s

live_loop :playdrums do
 num = 0 #select programme 0 (silence) or 1 to 3 #<<<<<<<===== SET PROGRAM NUM TO PLAY HERE
 puts num #print program number at the start

 case num #use case statement to choose programme to play
 when 0 #play silence
 p=0.02 #pulse time for each element
 tr[[0,1]]=tr[[1,1]]=tr[[2,1]]=tr[[3,1]]=tr[[4,1]]=tr[[5,1]]=tr[[6,1]]=tr[[7,1]]=tr[[8,1]]=tr[[9,1]]=noplay
 tr[[0,0]]=tr[[1,0]]=tr[[2,0]]=tr[[3,0]]=tr[[4,0]]=tr[[5,0]]=tr[[6,0]]=tr[[7,0]]=tr[[8,0]]=tr[[9,0]]=:ambi_glass_rub

 when 1 #define program one
 p=0.15 #pulse time for each element
 #grid when instrument sounds, and volume it plays h,m or s 0 is silent
 # beats: 1 2 3 4 1 2 3 4
 tr[[0,1]]= "h0000000h0000s00h00000h000000000"
 tr[[1,1]]= "00000000000000000000000000000000"
 tr[[2,1]]= "0000h0000000h0000000h0000000h000"
 tr[[3,1]]= "0000s0000000s0000000s0000000s000"
 tr[[4,1]]= "000000000000000000000000000000h0"
 tr[[5,1]]= "h0s0h0s0h0s0h0s0h0s0h0s0h0s0h000"
 tr[[6,1]]= "00000000000000000000000000000sss"
 tr[[7,1]]= "m0000000000000000000000000000000"
 tr[[8,1]]= "00000000000000000000000000000000"
 tr[[9,1]]= "00000000000000000000000000000000"

 #define sample names to play for each track
 tr[[0,0]]= :drum_bass_hard
 tr[[1,0]]= :drum_bass_soft
 tr[[2,0]]= :drum_snare_soft
 tr[[3,0]]= :drum_cymbal_open
 tr[[4,0]]= :drum_cymbal_closed
 tr[[5,0]]= :drum_cymbal_pedal
 tr[[6,0]]= :drum_tom_hi_hard
 tr[[7,0]] =:drum_tom_lo_hard
 tr[[8,0]] =:drum_heavy_kick
 tr[[9,0]] =:drum_heavy_kick

 when 2 #define program two
 p=0.12 #pulse time for each element
 #grid when instrument sounds, and volume it plays h,m or s 0 is silent
 # beats: 1 2 3 4 1 2 3 4
 tr[[0,1]]= "h000000000h00000h0h000h000000000"
 tr[[1,1]]= "0h0000hh000h000h000000000h0h0000"
 tr[[2,1]]= "0000s0000s00s0s00000s00s00s0s0s0"
 tr[[3,1]]= "00000000000000000000000000000000"
 tr[[4,1]]= "h0h0hh00h000h00h0000h00h00h0h0h0"
 tr[[5,1]]= "00000000000000000000000000000000"
 tr[[6,1]]= "00000000000000000000000000000000"
 tr[[7,1]]= "00000000000000000000000000000000"
 tr[[8,1]]= "00000000000000000000000000000000"
 tr[[9,1]]= "00000000000000000000000000000000"

 #define sample names to play for each track
 tr[[0,0]]= :drum_bass_hard
 tr[[1,0]]= :drum_bass_soft
 tr[[2,0]]= :drum_snare_hard
 tr[[3,0]]= :drum_cymbal_open
 tr[[4,0]]= :drum_cymbal_pedal
 tr[[5,0]]= :drum_tom_hi_hard
 tr[[6,0]]= :drum_cymbal_closed
 tr[[7,0]] =:drum_heavy_kick
 tr[[8,0]] =:drum_heavy_kick
 tr[[9,0]] =:drum_heavy_kick

 when 3 #define program three
 p=0.16 #pulse time for each element

 #grid when instrument sounds, and volume it plays h,m or s 0 is silent
 # beats: 1 2 3 4 1 2 3 4
 tr[[0,1]]= "h00m0000h00h00smh0m0000sh0h00000"
 tr[[1,1]]= "0000000000000m00000000s000000m00"
 tr[[2,1]]= "0000m0000m0sh0000000m0000s00m000"
 tr[[3,1]]= "00m0000000000000m000000000000000"
 tr[[4,1]]= "00000000000000000000000000000000"
 tr[[5,1]]= "mh00m0m0m0s0sms000msm0m0s0mss0sm"
 tr[[6,1]]= "00000000000000000000000000000000"
 tr[[7,1]]= "00000000000000000000000000000000"
 tr[[8,1]]= "0000h0000000000000000h0000000000"
 tr[[9,1]]= "00000000000000000000000000000000"

 #define sample names to play for each track
 tr[[0,0]]= :drum_bass_hard
 tr[[1,0]]= :drum_snare_hard
 tr[[2,0]]= :drum_snare_soft
 tr[[3,0]]= :drum_cymbal_open
 tr[[4,0]]= :drum_cymbal_closed
 tr[[5,0]]= :drum_cymbal_pedal
 tr[[6,0]]= :drum_tom_hi_hard
 tr[[7,0]] =:drum_tom_lo_soft
 tr[[8,0]] =:drum_heavy_kick
 tr[[9,0]] =:drum_heavy_kick

 when 4 #define program three
 p=0.14 #pulse time for each element

 #grid when instrument sounds, and volume it plays h,m or s 0 is silent
 # beats: 1 2 3 4 1 2 3 4
 tr[[0,1]]= "h0000000h0s000s0h0000000h00000m0"
 tr[[1,1]]= "0000h0000000h0000000h0000000h000"
 tr[[2,1]]= "00000000000000000000000000000000"
 tr[[3,1]]= "h000h00s0h00h000h000h000m0sm0h00"
 tr[[4,1]]= "00000000000000000000000000000000"
 tr[[5,1]]= "00000000000000000000000000000000"
 tr[[6,1]]= "h0hm0hm0hmhmhmhhhm0hm0hm0hh0hmhm"
 tr[[7,1]]= "00000000000000000000000000000000"
 tr[[8,1]]= "00000000000000000000000000000000"
 tr[[9,1]]= "00m000s000m000s000m000s000m000s0"

 #define sample names to play for each track
 tr[[0,0]]= :drum_bass_hard
 tr[[1,0]]= :drum_snare_hard
 tr[[2,0]]= :drum_snare_soft
 tr[[3,0]]= :drum_cymbal_open
 tr[[4,0]]= :drum_tom_hi_hard
 tr[[5,0]]= :drum_cymbal_pedal
 tr[[6,0]]= :drum_cymbal_closed
 tr[[7,0]] =:drum_tom_lo_soft
 tr[[8,0]] =:drum_heavy_kick
 tr[[9,0]] =:elec_chime

 when 5 #define program three
 p=0.16 #pulse time for each element

 #grid when instrument sounds, and volume it plays h,m or s 0 is silent
 # beats: 1 2 3 4 1 2 3 4
 tr[[0,1]]= "hs0000m000hm000mh0h000h00m0s0000"
 tr[[1,1]]= "000000000h00h0s00000h0000000hmmm"
 tr[[2,1]]= "00000000000000000000000000000000"
 tr[[3,1]]= "00000000000000000000000000000000"
 tr[[4,1]]= "00000000000000000000000000000000"
 tr[[5,1]]= "h0h0hh00h000h00h0000h0s0h0s0h0s0"
 tr[[6,1]]= "00000000000000000000000000000000"
 tr[[7,1]]= "00000000000000000000000000000000"
 tr[[8,1]]= "00000000000000000000000000000000"
 tr[[9,1]]= "mshsmshsmshsmshsmshsmshshsmshsms"

 #define sample names to play for each track
 tr[[0,0]]= :drum_bass_hard
 tr[[1,0]]= :drum_snare_hard
 tr[[2,0]]= :drum_snare_soft
 tr[[3,0]]= :drum_cymbal_open
 tr[[4,0]]= :drum_cymbal_closed
 tr[[5,0]]= :drum_cymbal_pedal
 tr[[6,0]]= :drum_tom_lo_hard
 tr[[7,0]] =:drum_tom_lo_soft
 tr[[8,0]] =:drum_heavy_kick
 tr[[9,0]] =:elec_twip
 when 6 #define program three
 p=0.12 #pulse time for each element

 #grid when instrument sounds, and volume it plays h,m or s 0 is silent
 # beats: 1 2 3 4 1 2 3 4
 tr[[0,1]]= "h0000000m0000000s000m000h0000000"
 tr[[1,1]]= "0h0000000m0000000s000m000h000000"
 tr[[2,1]]= "00h0000000m0000000s000m000h00000"
 tr[[3,1]]= "000h0000000m0000000s000m000h0000"
 tr[[4,1]]= "0000h0000000m000000000000000h000"
 tr[[5,1]]= "00000h0000000m000000000000000h00"
 tr[[6,1]]= "000000000000000h0000000000000000"
 tr[[7,1]]= "0000000h00000000000000000000000h"
 tr[[8,1]]= "00000000000000000000000000000000"
 tr[[9,1]]= "00000000000000000000000000000000"

 #define sample names to play for each track
 tr[[0,0]]= :drum_tom_mid_soft
 tr[[1,0]]= :drum_tom_mid_hard
 tr[[2,0]]= :drum_tom_lo_soft
 tr[[3,0]]= :drum_tom_lo_hard
 tr[[4,0]]= :drum_tom_hi_soft
 tr[[5,0]]= :drum_tom_hi_hard
 tr[[6,0]]= :drum_splash_soft
 tr[[7,0]] =:drum_splash_hard
 tr[[8,0]] =:drum_heavy_kick
 tr[[9,0]] =:elec_twip

 end 

 define :perc do |track,sn| #play a given track with sample sn
 track.to_s.split("").each do |pl| #extract each of the 32 values
 if pl!="0" #ignore if pl=0
 #puts sn #uncomment for testing
 #puts pl #uncomment for testing
 sample sn,amp: lev["hms".index(pl)] #lev array contains volume. "hms".index(pl) determines element to use
 end
 sleep p #sleep pulse value
 end
 end

 n.times do #repeat for number of 2 bars required
 0.upto(9) do |n| #set up required number of threads to play all the tracks.
 in_thread do
 perc(tr[[n,1]],tr[[n,0]])
 end
 end
 sleep p*32 #sleep for 2 bar duration
 end
end

The sound track from the program is on soundcloud.com  here

The code can be accessed from my Gist here

Further work on Bass Sounds sample voices for Sonic Pi

I have done some further work on the sample based Bass Sounds voicves for Sonic Pi today. I added a function to play chords using the voices, which now give the aiblit to do the following.

play a sample based note using:
pl(samplename,note,duration,pan,volume)
play a list of notes and a corresponding list of durations using:
plarray(samplename,noteslist,durationlist,shift,volume,pan)   where shift is transpose in semitones

play a chord using:
plchord(samplename,noteslist,duration,shift,pan,volume)

transpose a note using tr(note,shift) can be used with pl if required

All of the functions can accept notes as numeric or symbolic entries

I have also included two functions inv1 and inv2 to produce the first and second inversions of a triad chord

The new code sets up some chords and also plays Frere Jaques as a four part round, using different voices. The example runs three times at different volume and shift (transpose) settings.

You can hear the results on soundcloud here

you can find the code in a gist here

Harmonised scales, a Percussion Generator and Bass Sounds sample voices for Sonic Pi

Oops! A couple of links were wrong. Now corrected.

Following on from the Covent Pi Jam, I have had a creative few days developing three further pieces of code for Sonic Pi 2.1 At Sam Aaron’s suggestion I now put these on sound.cloud.com and publish the code as a gist. This is quick and easy to do and I think makes everything very accessible. I won’t repeat the code or the sound tracks here, but I will say a little about each one, and give you the links to find them.

First Harmonised scales. This came about when I experimented with defining functions to produce the first and second inversions of given chords. Musically  a C major triad for octave 4 consists of the notes :c4, :e4 and :g4
To produce the first inversion, you remove the bottom note :c4 and add it to the top of the chord making :e4, :g4 and :c5
To get the second inversion you repeat the process removing teh new bottom note :e4 and adding it an octave higher at the top of the chord to give :g4,:c5 and :e5
If you harmonise a scale of c you play the chords c major, d minor, e minor, f major,
g major, a minor, b diminished and back to c major in the next octave.
I played with this and came up with code to play a rising octave in the base with these chords and their two inversions played with each note, first up an octave then back down again. The whole thing was repeated with semitone increments 12 times, rather like the singing exercises done when warming up.

The link to sound cloud for HarmonisedScales is here and it contains a link to the gist code on that site.

The second example was to produce a percussion generator. I was looking for an efficient way to build up a rhythm track, and looked on the internet to find an article which described the use of a grid for this purpose.
I coded a similar technique into sonic pi, using two grids, one to signify when a particular instrument should be played, the other to specify one of three volumes for the instrument at each point. These were sorted in a hash array, together with the instrument sample names. I set up three different combinations, and then played these using a live_loop so that the track chosen could be switched to another one each time it finished.

The link to sound cloud for PercussionGenerator is here and it contains a link to the gist code on that site.

The third example uses an idea I used previously to generate the glass armonica sample based voice using the :ambi_glass_rubbed sample built in to Sonic Pi. This time I chose a range of sample from the Bass sounds family again built in to Sonic Pi. All of these bar one were sampled on the same note :c2, so by calculating the rates to play the samples to give other notes over a three octave range I could build up code which could use any of these voices to produce “notes” at the required pitch. I used similar code to the glass armonica project to define function to play a single sample note and to play arrays of notes and durations using the specified sample voice. One of the samples was discarded as the sound was too short to be used in a meaningful manner. With the others I wrote demo code to play a variety of scales using the different voices, and finished with 100 notes which played notes of random pitch, duration, sample voice and pan position.

The link to sound cloud for Bass Sounds Sample Voices is here and it contains a link to the gist code on that site.