Following the last post in which I detailed how to set up the Sonatina Symphonic Orchestra for use with Sonic Pi, this post contains details of a pice that I have just coded with Sonic Pi which utilises the SSO library to produce the required instrument voices. The pice was written by the composer Victor Kalinnikov in 1986 and was originally scored for 2 Violins, Viola and Cello, but here I am utilising an Oboe for Violin 1 and samples for violins (plural) violas and Basses for the other three parts,as I think the texture of the sounds produced by Sonic Pi suit this combination better. As described in the last post, the voices are set up to be played by the plarray function. However I have made several changes. First, to save space, only the instrument voices required are loaded, rather than the larger number used previously. Secondly, both the viola and bass parts need to be able to play chords. Logically one would alter the pl function which played a single note to accommodate this, but as this function is quite complex it is practically easier to make the alteration in the plarray function. This is done by testing each entry in the notes list and seeing it will respond to the .each Ruby method, using the test if n.respond_to?(:each)
If this is true, then it means that n (which is a member of the list of notes being fed to the plarray) is itself a list, and so each note in this list is sent to the pl function without any sleep commands in between, thus causing them to be played at the same time.
The second alteration I made to the pl and plarray routines was to expose the envelope sustain and decay fractions used when the sample is played, which can be used to alter the overall sound. Previously I had sustain: 0.9*d, release: d which suited the Frere Jaques example, but now I have sustain: s*d,release: r*d and s and r are included in the parameter list for each function, with default values set at 0.9 and 0.1 respectively. In the event I used the default values for this piece.
The third change made was because of the length limitations currently in force in Sonic Pi. There is a maximum buffer length which can be sent to the server, and if this is exceeded no sound results. On-going work in taking place to over come this, but at present it is possible to split programs over more than one buffer and link the parts using Sonic Pi’s cue and sync commands. Functions defined in one buffer are available for other buffers in Sonic Pi. So if the functions are defined in the first buffer used, then you only have to duplicate definitions of any variables required in subsequent parts for this to work. In fact, once the first section has been run once, the other two can be used independently to play each of the two movements are required, by commenting out the sync commands at the beginning of each of them. To play them linked together thee parts should be run in reverse order, 3,2 then 1.
There is more that can be done to the prgram, for example to add expression in the way of changes in loudness p,f,ff etc and also to add a couple of rits in the score. I may do this ;ater, but it doesn’t sound bad as it is at present.
If you are interested in seeing the music, it can be downloaded from http://imslp.org/wiki/2_Miniatures_for_String_Quartet_(Kalinnikov,_Viktor)
The Deux Miniatures can be heard on soundcloud.com here
The amended pl and plarray functions are shown below, and the three parts of the program can be downloaded here.
#define routine to play sample 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 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