Ski Sunday theme on Sonic Pi

10559-a-woman-snow-skiing-pv

Now that version 2.10 is released for Sonic Pi, I thought I would choose a suitable piece to exploit some of the new features within it, especially the improved methods of loading samples. In a recent post I  described how I had added access to the sample voices contained in the Sonatina Symphonic Orchestra (hereafter SSO) utilising the features in the (then) 2.10dev version. I have always enjoyed the theme music for Ski Sunday which is a piece called “Pop Looks Bach” by the late  Sam Fonteyn. It a a great highly rhythmical piece, with sections which are repeated and is very suitable for coding into Sonic Pi. I wanted to produce something close to the original recording This involved downloading the audio file and playing it in the program Audacity, where I could reduce the tempo, to hear the note structure more easily, and also apply a low pass filter to try and extract the guitar bass part. In fact it took many hours of listening again and again to various sections to work out the notes for the various parts. I do not claim that what I have produced is an exact replica, but it certainly does justice to the flavour of the piece and sounds pleasing.

I knew that because of the size limitations on the contents of a Sonic Pi buffer that the piece would require at least two buffers, as the code to download and process the samples of the SSO so that they can be utilised by Sonic Pi is quite large, and occupies a good part of one buffer. In fact initially I used three buffers, but by careful analysis of the notes, and extracting repeated sections so that the were only coded once, I was able to fit the entire playing portion of the code into one buffer, giving a total of two overall.

The first buffer contains code to load the 9 voices which are utilised from the SSO. The code is based on that in my previous article here. Also included in this first buffer are various functions, used in playing the piece, as these can be shared with and utilised by the second buffer. Once the first buffer has been run, it can be ignored, and you can run the second buffer as many times as you like independently. However also note that subsequent runs of the first buffer detect if the samples have already been loaded and thus are much quicker. Two lines at the start of the first buffer can be uncommented to clear all samples, which can be useful for testing purposes. The only adjustments required is to ensure that the path to the location of SSO on your system is correct at the start of each buffer, and also to uncomment line 4 in the second buffer to set a sched_ahead_time! of 4 if running on a Pi2 or Pi3. The programs are not suitable for use on earlier Pi models.

Musically, I have used the following parts:

A violin part which plays the main tune through using notes held in nva, nvb and nvcoda

An accompanying harmony part using notes held in hna and hnb which is played by the supersaw synth

Brass chords, played on trumpets and trombones. There are four such sections. One in the intro (bn) accompanied by timpany (nt)  bars 1-5, which is repeated as bn1 several times in the piece (bars 14-17,48-51 and 82-85). One before the middle wind section (cn2) (bars 26-28, 60-62) and one at the end of the central wind section (njabsus and njabst) bars 36-38 and 70-72 , played by trumpets sustained and staccato. Lastly the final sequence of chords (nfinals) (bars 94-100) played on Trumpets, accompanied by horns (hn) and flutes (nfcoda)

A plucked guitar bass played throughout using the :plck synth. This is coded in two sepearate threads, one for bars 1-39 and the other in two sections for bars 40-73 and 74-100

A central wind section played on clarinets and flutes (nf+nf2+nf ) bars 30-35 and 64-69

Rhythm is provided by a one bar riff played on the drum_cymbal_closed sample (live_loop :cm) together with a timpani motive (nt) occasional snare drumrools (dr) (bars 36,37,70 and 71) and a falling drum pattern (drumfall) played with a tuned :tabal_na_0 sample (bars 29,39,63 and 73)

I hope that if you are so included the above taken together with some comments in the program will let you understand how the program works, although it will take quite a bit of study to follow it fully.

The two programs skiSunday-p1.rb (the loader buffer) and skiSunday-p2.rb (the player buffer) are listed below.

#program loads samples and functions required by SkiSunday programs
#written by Robin Newman, March 2016
#require Sonatina Sympohonic Orchestra installed on Desktop
# http://sso.mattiaswestlund.net/download.html

##| sample_free_all  #can uncomment these lines to clear all samples
##| stop

use_debug false
#path to library samples folder (including trailing /)
path="~/Desktop/Sonatina Symphonic Orchestra/Samples/"  #adjust as necessary

#llist=[0,1,2,3,4,5,6,7,8] #voicenumbers used
#create array of instrument details
voices=[["1st Violins sus","1st Violins","1st-violins-sus-",1,:g3,:b6],\
        ["Clarinets","Clarinets","clarinets-sus-",2,:d3,:d6],\
        ["Flutes sus","Flutes","flutes-sus-",0,:c3,:bb5],\
        ["Trombones sus","Trombones","trombones-sus-",1,:ds2,:e5],\
        ["Trumpet","Trumpet","trumpet-",1,:e3,:f6],\
        ["Trumpets sus","Trumpets","trumpets-sus-",1,:e3,:f6],\
        ["Trumpets stc","Trumpets","trumpets-stc-rr1-",1,:e3,:f6],\
        ["Horns stc","Horns","horns-stc-rr1-",1,:e2,:e5],\
        ["Timpani f lh","Percussion","timpani-f-lh-",0,:c1,:c2]]

uncomment do #can comment if samples loaded, to allow quick redefine of functions
  define :load do |i|
    trigger=0
    live_loop :t do
      sleep 0.3
      if trigger== 1
        cue :start
        stop
      end
    end
    load_samples path+voices[i][1],voices[i][2]
    trigger=1
    sync :start
  end

  for i in (0..8) do
      load(i)
    end
    sleep 2
  end

  puts "The following voices from Sonatina Symphonic Library can be used:-"
  voices.each_with_index do |n,i|
    puts i.to_s,n[0]
  end

  puts voices.length.to_s+" voices"
  #setup global variables
  sampledir=""
  sampleprefix=""
  offsetclass=""
  low=""
  high=""
  paths=""

  #setup data for current inst
  define :setup do |inst,path|
    sampledir=voices.assoc(inst)[1]
    sampleprefix=voices.assoc(inst)[2]
    offsetclass=voices.assoc(inst)[3]
    low=voices.assoc(inst)[4]
    high=voices.assoc(inst)[5]
    #amend path for instrument sampledir
    paths=path+sampledir+"/"
  end

  sleep 0.2

  #define routine to play sample using Sonatina data
  define :pl do |np,d,inst,vol=1,s=0.9,r=0.1,tp=0,pan=0|
    setup(inst,path)
    #check if note in range of supplied samples
    #use lowest/highest sample for out of range
    change=0 #used to give rpitch for coverage outside range
    frac=0
    n=np+tp #note allowing for transposition
    if n.is_a?(Numeric) #allow frac tp or np
      frac=n-n.to_i
      n=n.to_i
    end
    if note(np)+tp<note(low) #calc adjustment for low note       change=note(np).to_i+tp-note(low)       n=note(low)     end     if note(np).to_i+tp > note(high) #calc adjustment for high note
      change = note(np).to_i+tp-note(high)
      n=note(high)
    end
    if change < -5 or change > 5 #set allowable out of range
      #if outside print messsage
      puts 'inst: '+inst+' note '+np.to_s+' with transpostion '+tp.to_s+' out of sample range'
    else #otherwise calc and play it
      #calculate base note and octave
      base=note(n)%12
      oc = note(n) #do in 2 stages because of alignment bug
      oc=oc/12 -1
      #find first part of sample note
      slookup=['c','c#','d','d#','e','f','f#','g','g#','a','a#','b']
      #lookup sample to use,and rpitch offset, according to offsetclass
      case offsetclass
      when 0
        oc += 1 if base == 11 #adjust if sample needs next octave
        snumber=[0,0,3,3,3,6,6,6,9,9,9,0]
        offset=[ 0,1,-1,0,1,-1,0,1,-1,0,1,-1]
      when 1
        snumber=[1,1,1,4,4,4,7,7,7,10,10,10]
        offset=[-1,0,1,-1,0,1,-1,0,1,-1,0,1]
      when 2
        oc -= 1 if base == 0 #adjust if sample needs previous octave
        snumber=[11,2,2,2,5,5,5,8,8,8,11,11]
        offset=[1,-1,0,1,-1,0,1,-1,0,1,-1,0]
      when 3
        snumber=[0,1,2,3,4,5,6,7,8,9,10,11] #this class has sample for every note
        offset=[0,0,0,0,0,0,0,0,0,0,0,0]
      end
      #generate sample name
      sname=sampleprefix+(slookup[snumber[base]]).to_s+oc.to_s
      #play sample with appropriate rpitch value
      sample paths,sname,rpitch: offset[base]+change+frac,sustain: s*d,release: r*d,pan: pan,amp: vol
    end
  end

  #define function to play lists of linked samples/durations using Sonatina samples
  define :plarray do |notes,durations,offsetclass,vol=1,s=0.9,r=0.1,tp=0,pan=0|
    #puts offsetclass
    notes.zip(durations).each do |n,d|
      if n.respond_to?(:each)
        n.each do |nv|
          pl(nv,d,offsetclass,vol,s,r,tp,pan) if ![nil,:r,:rest].include? nv#allow for rests
        end
      else
        pl(n,d,offsetclass,vol,s,r,tp,pan) if ![nil,:r,:rest].include? n#allow for rests
      end
      sleep d
    end
  end

  define :plnarray do |n,d,v=1,shift=0| #used to play harmony supersaw part
    n.zip(d).each do |n,d|
      play n+shift,sustain: 0.9*d,release: 0.1*d,amp: v
      sleep d
    end
  end
  define :pluckarray do |n,d,v=1,shift=0| #used to play plucked bass part
    n.zip(d).each do |n,d|
      play n+shift,release: 1.5*d,amp: v
      sleep d
    end
  end
  define :dl do |d| #used for debugging to determine durations of duration lists
    t=0
    d.each do |d|
      t +=d
    end
    return t
  end

  define :dr do |d,v| #drum roll
    sample :drum_roll,sustain: d*0.9,release: d*0.1,amp: v
    sleep d
    sample :drum_snare_hard,rate: 1.25,amp: v
  end

  define :drfall do |n,d,v| #falling tabla drum motif and drumroll
    sample :tabla_na_o,rpitch: n-:ds4,sustain:0,release: 2*d,amp: v
    sleep d
    sample :tabla_na_o,rpitch: n-:ds4,sustain:0,release: 2*d,amp: v
    sleep d
    sample :tabla_na_o,rpitch: n-:ds4,sustain:0,release: 2*d,amp: v
    sleep d
    sample :tabla_na_o,rpitch: n-5-note(:ds4),sustain:0,release: 2*d,amp: v
    sleep d
    sample :tabla_na_o,rpitch: n-5-note(:ds4),sustain:0,release: 2*d,amp: v
    sleep d
    sample :tabla_na_o,rpitch: n-5-note(:ds4),sustain:0,release: 2*d,amp: v
    sleep d
    sample :tabla_na_o,rpitch: n-12-note(:ds4),sustain:0,release: 2*d,amp: v
  end

  define :riff do |n1,n2| #these riffs used in plucked base part
    return [n1,:r,n1,n1,:r,n2,:r]
  end
  define :riff2 do |n1,n2,n3|
    return [n1,:r,n2,n3,:r,n2,:r]
  end
  define :riff3 do |n1,n2,n3|
    return [n1,:r,n1,n2,:r,n3,:r]
  end

  load_sample :tabla_na_o #used in drfall
  load_sample :drum_cymbal_closed #used in rhythm live_loop :cm
  load_sample :drum_roll
  load_sample :drum_snare_hard

 

#Ski Sunday arranged for SP by Robin Newman April 2016
#Run the sample loader program first, then this program.

#set_sched_ahead_time! 4  #uncomment for Pi2 or Pi3
path="~/Desktop/Sonatina Symphonic Orchestra/Samples/" #adjust as necessary
voices=[["1st Violins sus","1st Violins","1st-violins-sus-",1,:g3,:b6],\
        ["Clarinets","Clarinets","clarinets-sus-",2,:d3,:d6],\
        ["Flutes sus","Flutes","flutes-sus-",0,:c3,:bb5],\
        ["Trombones sus","Trombones","trombones-sus-",1,:ds2,:e5],\
        ["Trumpet","Trumpet","trumpet-",1,:e3,:f6],\
        ["Trumpets sus","Trumpets","trumpets-sus-",1,:e3,:f6],\
        ["Trumpets stc","Trumpets","trumpets-stc-rr1-",1,:e3,:f6],\
        ["Horns stc","Horns","horns-stc-rr1-",1,:e2,:e5],\
        ["Timpani f lh","Percussion","timpani-f-lh-",0,:c1,:c2]]
#######
q=0.15 #set note duration of quaver
qd=3*q/2 #dotted quaver
sq=q/2 #semiquaver
c=2*q #crotchet
cd=3*q #dotted crotchet
m=2*c #minim

sc= :drum_cymbal_closed #used for drum part
use_synth :supersaw #used for accompanying part

d=(ring q,q,q,q,c,q,q) #drum rhythm
nt=[:a1,:a1,:d2,:r,:a1,:a1,:d2,:r,:a1,:a1,:d2] #timpani part
dt=[q,q,m,5*c,q,q,m,5*c,q,q,m]
#nv.nva,nvb violin parts
nv=[:r,:a5,:g5,:a5,  :fs5,:a5,:e5,:a5,  :d5,:a5,:cs5,:a5,  :d5,:a5,:e5,:a5,  :fs5,:a4,:d5,:cs5,  :b4,:d5,:a4,:d5,  :g4,:d5,:fs4,:d5,  :g4,:d5,:a4,:d5,  :b4,:d5,:g5,:fs5,  :e5,:g5,:d5,:g5,  :cs5,:g5,:b4,:g5,]
nva=[:cs5,:g5,:d5,:g5,  :e5,:a4,:a5,:g5,  :fs5,:e5,:fs5,:g5,  :a5,:fs5,:e5,:d5,  :e5,:a5,:fs5,:a5,  :g5,:e5,:cs5,:a4]
nvb=[:cs5,:g5,:d5,:g5,  :e5,:a4,:a5,:g5,  :fs5,:e5,:fs5,:gs5, :a5,:e5,:cs5,:e5,  :d5,:cs5,:b4,:cs5, :d5,:e5,:fs5,:gs5,:a5]
dva=[m+q]+[q]*(((nv+nva).length)-1)
dvb=dva+[q]
#hna,hnb harmony part played by supersaw synth
hn=[:r,:d5,:cs5,:d5,:e5,:fs5,:d5, :g5,:fs5,:e5,:d5,:e5,:fs5,:g5,:b5, :cs6,:b5,:a5,:g5,:a5,:b5]
hn2=[:cs6,:a5,:g5,:e5,:d5,:cs5,:d5,:e5,:fs5,:a5,:b5,:g5,:a5,:a4]
hn3=[:cs6,:a5,:g5,:e5,:d5,:cs5,:d5,:b4,:cs5,:a4,:e5,:fs5,:e5,:d5,:fs5,:gs5,:fs5,:gs5,:b5,:a5]
hna=hn+hn2
hnb=hn+hn3
hda=[6*c]+[c]*20 +[q]*12+[m,m]
hdb=[6*c]+[c]*20 +[q]*8+[c]+[q]*10+[m]
#cn,bn1,bn,cn2 with dur. dn1,cd2,dn brass chords
cn=[[:fs4,:a4,:d5],[:fs4,:a4,:d5],[:g4,:a4,:d5],[:g4,:a4,:d5],[:g4,:a4,:d5],[:fs4,:a4,:d5],[:fs4,:a4,:d5],[:fs4,:a4,:d5],[:g4,:a4,:d5]]
bn1=[:r]+cn
bn=bn1 * 2
dn1=[c,q,q,c,q,q,c,q,q,c,c]
cn2=[:r,[:a5,:c6],:r,[:d5,:g5,:b5],[:cs5,:e5,:a5]]
cd2=[q,q,c,cd,q]
dn=[2*c,q,q,c,q,q,c,q,q,c,m,q,q,c,q,q,c,q,q,q]
#nf,nf2 flute part middle section with dur df,df2
nf=[:r,:d5,:f5,:g5,:f5,:ab5,:f5,:g5,:f5,:d5,:f5,:g5]
df=[c,q,q,c,q,c,q,c,q,q,q,q]
nf2=[:r,:d5,:a4,:c5,:d5,:f5,:d5,:f5,:d5]
df2=[c,q,q,c,q,c,q,c,m]
#njab sustained and stac brass chords at end of middle section with dur djab
njabsus=[:r,:r,[:g4,:b4,:d5,:g6],:r,:r,[:g4,:b4,:d5,:g6],:r,   :r,[:f4,:a4,:c5,:f5],[:g4,:b4,:d5,:g5],:r,:r]
djab=[q,m,cd,q,m,cd,q,q,c,c,q,q]
njabst=[[:a4,:e5,:a6],:r,:r,[:a4,:e5,:a6],:r,:r,[:a4,:e5,:a6], :r,:r,:r,[:gs4,:c5,:ds5,:gs5],[:a4,:cs5,:e5,:a6]]
#last play-through and coda violin part ncoda, with dur dcoda
ncoda=nv+[:cs5,:g5,:d5,:g5,  :e5,:a4,:a5,:g5, :fs5,:e5,:fs5,:g5, :a5,:fs5,:e5,:d5, :e5,:fs5,:g5,:fs5, :g5,:a5,:b5,:cs6, :d6,:d5,:fs5,:a5, :g5,:e5,:g5,:a5]
ncoda.concat [:fs5,:d5,:fs5,:a5, :g5,:e5,:g5,:a5]*3+[:d6,:cs6,:b5,:a5, :b5,:a5,:b5,:cs6, :d6,:cs6,:b5,:a5, :b5,:a5,:b5,:cs6,[:fs5,:a5,:d6]]
dcoda=[m+q] +[q]*nv.length+[q]*71+[c]
#flute part final 7 bars nfcoda with dur dfcoda
nfcoda=([:d6]*12+[:d6,:cs6,:b5,:a5])*2+[:d6,:cs6,:b5,:a5, :b5,:a5,:b5,:cs6, :d6,:cs6,:b5,:a5, :b5,:a5,:b5,:cs6,[:fs5,:a5,:d6]]
dfcoda=[q]*48+[c]
#riffs used in plucked bass part (see functions in loader prog)
riffd=[c,q,q,q,q,q,q]
bsnbase=riff(:d2,:a1)*6+riff(:g1,:d2)+riff(:g2,:b1)+riff(:cs2,:e2)+riff2(:a2,:e2,:cs2)
bsna=bsnbase+[:d2,:cs2,:b1,:b1,:e1,:e1,:a2,:a2]
bsnb=bsnbase+[:d2,:b1,:cs2,:a2,:b1,:b1,:e2,:e2]
bsnc=bsnbase+[:d2,:d2,:b1,:b1,:e2,:e2,:a2,:a2]+riff(:d3,:a2)*6+[:d2]
bsd=riffd*10+[cd,q,cd,q,cd,q,c,c]
bsn2=riff(:a1,:e2)+riff(:a2,:e2)+[:a1,:c2,:r,:b1,:a1]
bsd2=riffd*2+[q,q,c,cd,q]
bsn3= [:r,:cs1,:e1,:a1,:e1]+riff3(:g1,:b1,:d2)*2+riff3(:d2,:f2,:a2)*2+riff3(:g1,:b1,:d2)*2+[:a1,:r,:g1,:a1,:r,:g1,:a1,:r,:f1,:g1,:gs1,:a1]
bsd3= [c*2,q,q,q,q]+riffd*6+[q,m,cd,q,m,cd,q,q,c,c,q,q]
#flute chords in last few bars nfc, nfinals with dur dfinals
nfc=[[:d4,:fs4,:a4],[:d4,:fs4,:a4],[:d4,:g4,:a4]]
nfinals=([:r]+nfc)*6+[[:d4,:a4,:d5]]
dfinals=[c,q,q,m]*6+[c]
#hn horn part final few bars hn with dur hd
hn=[:r,:fs4,:fs4,:g4,:fs4]*6
hd=[c,q,q,m]+[q,q,q,q,m]*5 + [c]

################ plarray and plnarray defined in loader prog
define :sec1 do
  in_thread do
    plarray(nv+nva,dva,"1st Violins sus",2)
  end
  in_thread do
    plnarray(hna,hda,0.22,-12)
  end
  sleep 35*c
  in_thread do
    plarray(bn,dn,'Trombones sus')
  end
  in_thread do
    plarray(bn,dn,'Trumpets sus')
  end
  in_thread do
    plarray(nt,dt,"Timpani f lh")
  end
  sleep 13*c
end
define :sec2 do #bars 17-39, 51-73
  in_thread do
    plarray(nv+nvb,dvb,"1st Violins sus",2)
  end
  in_thread do
    plnarray(hnb,hdb,0.22,-12)
  end
  sleep 36*c
  in_thread do
    plarray(bn1,dn1,'Trombones sus',0.8,0.90,0.1,7) #transposed up 7
  end
  in_thread do
    plarray(bn1,dn1,'Trumpets sus',0.8,0.90,0.1,7)
  end
  sleep 8*c
  in_thread do
    plarray(cn2,cd2,'Trumpet',1.2)
  end
  in_thread do
    plarray(cn2,cd2,'Trumpets sus',1.2)
  end
  plarray(cn2,cd2,'Trombones sus',1.2,0.90,0.1,-12) #transposed down 12
  in_thread do
    drfall(:a2,sq,0.5) #drum fall (see loader prog)
  end
  sleep 4*c

  in_thread do
    plarray(nf+nf2+nf,df+df2+df,"Clarinets")
  end
  plarray(nf+nf2+nf,df+df2+df,"Flutes sus")
  in_thread do # drum roll acccompaniment next 2 bars
    2.times do
      sleep c
      dr(c,0.6) #drumroll
      sleep m
    end
  end
  in_thread do
    plarray(njabsus,djab,'Trumpets sus',1)
  end
  plarray(njabst,djab,'Trumpets stc',1,1.2,0.5)
  in_thread do
    drfall(:a2,sq,0.5) #drum fall
  end
end

###### start playing here ############
with_fx :level,amp: 3 do
  with_fx :reverb,room: 0.8 do
    live_loop :barnumbers do #print bar number info
      puts "Bar "+(tick + 1).to_s
      sleep 4*c
      if look== 99
        stop
      end
    end
    sleep 3*c #1st three beats bar 1
    live_loop :cm,delay: c do #closed cymbal riff starts bar 2
      sample sc,beat_stretch: c,amp: 0.3
      sleep d.tick
      if look==7*98 #notes in riff * bars
        stop
      end
    end
    #intro starts 4th beat bar 1
    in_thread do
      plarray(bn,dn,'Trombones sus')
    end
    in_thread do
      plarray(bn,dn,'Trumpets sus')
    end
    in_thread do
      plarray(nt,dt,"Timpani f lh")
    end
    in_thread do #start plucked bass
      with_synth :pluck do #bass pluck bars 1-39
        sleep c
        pluckarray(bsna+bsnb+bsn2+bsn3,bsd*2+bsd2+bsd3,0.3)
      end
    end
    sleep 13*c #time for intro to complete
    sec1 #bars 6-16
    sec2 #bars 17-39

    in_thread do
      with_synth :pluck do
        #bars 40-73 start bsna 4 bars in(after 28 elements):ignores intro
        pluckarray([:r]+bsna[28..-1]+bsnb+bsn2+bsn3,[c*4]+bsd[28..-1]+bsd+bsd2+bsd3,0.3)
        #bars 74-100 again ingore intro in bsna
        pluckarray([:r]+bsna[28..-1]+bsnc,[c*4]+bsd[28..-1]+bsd+riffd*6+[c],0.3)
      end
    end
    sec1 #bars 40-50
    sec2 #defined in part 1 bars 51-73
    sec1 #bars 74-84
    in_thread do
      plarray(ncoda,dcoda,"1st Violins sus",2) #bars 85-100
    end
    sleep 36*c #delay to bar 94
    in_thread do
      plarray(nfinals,dfinals,'Trumpets sus',1.2) #bars 94-100
    end
    in_thread do
      plarray(hn,hd,'Horns stc',0.7) #bars 94-100
    end
    in_thread do
      plarray(nfcoda,dfcoda,"Flutes sus") #bars 94-100
    end
  end
end

You can download the programs here

You can download the sso here  unzip it and place in a known location. Adjust program path to suit.

You can hear the piece played by Sonic Pi 10 here

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