Developing music for Sonic Pi 2

In this article I will go through the process I use when choosing and transcribing some music for Sonic Pi 2. There are two distinct ways of using Sonic Pi. One is as an instrument to play an existing piece of music which is transcribed from the original score into code that Sonic Pi can understand and render as a musical performance when the Run button is pressed – that is what I am describing here – and the second is to use it as a creative instrument to produce live sounds which can be used as part of a live performance. This is done by using the technique know as live coding, whereby a defined function is started running in a perpetual loop which is isolated and left running, and whilst it is running the code of the defined function is altered, and the sound output can thus be adjusted live in the performance, without stopping the program running. This employs different techniques, although ultimately the two uses are complementary, and they both rely on being familiar with, and using the commands which are used to drive Sonic Pi.

So how do I go about choosing a piece of music to transcribe and use in Sonic Pi? The first thing to realise is that a program like Sonic Pi, is limited (especially on a Raspberry Pi) to the number of sounds which it can simultaneously sustain. So you don’t want to try and transcribe a piece scored for a full symphony orchestra! However, having said that, you can with care cope with a reasonable number of parts. I have regularly used 5 at a time, and sometimes 8. On the Mac version at the moment there is an issue in that the workspace is limited to just over 9000 characters. After that the program will not play the code. Sam Aaron is aware of the problem, and knows how to solve it, but it is not yet done in the current version 2.0.1 Obviously if you want to tackle a long piece with many parts then this may be a issue at present. I have got round it on occasion by cutting comments to a minimum (bad programming practice) or by splitting the tune into two parts in different work-spaces and manually playing them one after the other.

Music that works well then has a limited number of parts, but also will probably have good rhythmic content. The music of Bach, Scott Joplin, Downland and Purcell fit into this quite well. Romantic music that relies on great variations in expression by means of tempo variations or volume variations is much more difficult to code. I have developed techniques to deal with “rits” and “accel” markings in music, and also crescendos and diminuendos but they are quite tricky to program. On the other hand, music with repeats where you can play the repeated section at a different volume is quite easy to implement.

The other consideration is that you want to choose music where it is easy (and preferrably free) to obtain the music score to be transcribed. Also it is less fraught if the music is out of copyright. I have found a good source of classical music is the IMSLP Petrucci Music Library and also the ChoralWiki library

By way of example, one piece which I have transcribed is from the Funeral Music for Queen Mary by Henry Purcell. This consists of both choral and instrumental works. I chose the March and Canzona which I downloaded from the Petrucci Library here This ticked the boxes on many counts. It is very old and there are no copyright issues. It is highly rhythmical and the answering phrases of brass instruments lend themselves to the use of the pan command in Sonic Pi to place the instruments on either side of the listener. There are 4 parts and a percussion part of a single drum can be added to the first March section. The music employs three sections, two of which are repeated, which mean that it is not too long to code, and interest can be added by playing the repeats at different volume settings. There is scope to employ simple ornamental trills and also to develop a “rit” at the end of the piece. It is also a great dramatic music to listen to!

The music looks like this:

1stLineFuneralMusic 2ndLineFuneralMusic3rdLineFuneralMusic4thLineFumeralMusic 2ndlastlineFuneralMusic.jpg lastlineFuneralMusic

Having decided on a piece, what is involved in transcribing it for Sonic Pi? First let us look at some basics. You want an efficient way of writing the notes and their durations into Sonic Pi. One of the major advantages of version 2, is that it will recognise symbolic representations of note pitches. such as :c4, :fs5, :gb3 which stand for C in octave 4, F sharp in octave 4 and G flat in octave 3. The range of note durations in most music will range from a demi-semi-quaver through a semi-quaver, quaver, crotchet…up to a breve, including things such as a dotted crotchet (a crotchet plus a quaver) and a double dotted crotchet (crotchet+plus quaver+plus semi-quaver).
If we give a demi-semi-quaver a notional value of 1 unit, then a semi-quaver is 2 units, a quaver 4, a crotchet 8, a minim 16 and a breve 32 units. To get the actiual duration of each of these notes, we merly need to multiple these unit values by a scaling factor. Thus if we want 60 crotchets/min or 1 per second we would need to multiply the duration of a crotchet (8 units) by (1.0/8). If we wanted twice the tempo we would multiply it by (1.0/16) and so on.
I start most of my programs with a section similar to this:

s = 1.0 / 8#s is speed multiplier
#note use of 1.0 to make the variable floating point and not integer
dsq = 1 * s #demi-semi-quaver
sq = 2 * s #semi-quaver
sqd = 3 * s #semi-quaver dotted
q = 4 * s #quaver
qd = 6 * s #quaver dotteda
qdd = 7 * s #quaver double dotted
c = 8 * s #crotchet
cd = 12 * s #crotchet dotted
cdd = 14 * s #crotchet double dotted
m = 16 * s #minim
md = 24 * s #minim dotted
mdd = 28 * s #minim double dotted
b = 32 * s #breve
bd = 48 * s #breve dotted

This sets up variables for the note lengths we are likely to use (probably not all of them will be used in any one piece, but it is easier to put them all there)

So what about putting in the actual notes and their durations?
I store these in lists or arrays, breaking the list into 4 or 5 bars at a time, and then adding or concatenating them together to make up an entire section. I use a naming convention putting notes into an array n1,n2,n3 etc where the suffix denotes the part, and maybe adding a suffix letter a,b,c to denote the section. Note durations are entered into a separate list denoted by d1, d2, d3 etc So typical entries might be:

n4b = [:r,:g3,:g3,:g3,:g3,:ab3,:g3,:ab3,:f3,:g3,:f3,:g3,:eb3,:f3,:eb3,:f3,:bb2]
d4b = [4*b,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c]

The sharp eyed will notice that there is an :r entry at the beginning of the n4b array. This is because we also have to cater for rests when playing music. I denote a rest with the symbol :r which is picked up by the function I define to play the music.
As already mentioned you add notes to the array using the concatenate operator. The next two lines of code in this example are:

n4b.concat [:eb3,:eb3,:eb4,:d4,:c4,:c4,:r,:c4,:c4,:r,:f3,:f3,:r,:eb3,:eb3,:r,:ab3,:f3,:ab3,:g3,:g3,:c4,:f3,:g3,:g3,:c3]
d4b.concat [c,c,c,c,c,c,m,c,c,m,c,c,m,c,c,c,c,c,c,c,c,c*1.1,c*1.2,c*1.3,c*1.4,b*1.2]

This adds the entries on to the end of the previous two arrays making them longer. One other feature in passing: you notice the *1.1, *1.2,*1.3 at the end of the d4b array.This is the mechanism used to allow for a ritardando. You progressively make each note last for longer than it should, therefore effectively slowing up the music.

So how do we play these arrays of note pitches and durations?

define :tune do|pitch,duration,shift=0,amp= 0.3,ratio = 0.9|
  pitch.zip(duration).each do |p,d|
    if p == :r
      sleep d
    else
      with_transpose shift do
        play p,sustain: ratio*d,release: (1-ratio)*d,attack: 0,amp: amp
        sleep d
      end
    end
  end
end

We define a function called tune, using the syntax define :tune do……end
This has 5 parameters fed into it. Two are the arrays for pitch and duration, the remaining ones dealing with a transposition or shift if required, and the volume (amp) of the note. The final parameter, ratio, adjusts the relationship between the sustain and release times of the note, with a default value of 0.9 (90% sustain, 10% release time). In this piece it is appropriate to have a very sharp 0 attack time on each note. The way the function works is that the two arrays are zipped together and then traversed reading each pair of values (one pitch (p) and one duration (d) in turn. If the pitch is :r then a conditional command causes the program to sleep for the duration d. If it is not :r then a play command plays the note p with an attack 0, a sustain value of 0.9*d and a release time of 0.1*d. It then waits for a time d before returning to get the next note/duration pair. Also wrapped round the play command is a with_transpose xxx do….end construct. This allows the note to be transposed up or down if required, although I don’t think the option is needed in this piece. It is however sometimes useful as trumpet parts are often written at a different pitch to which they sound, and the transpose parameter enables this to be corrected.

The tune function enables a single part to be played using a command like

tune(n1c,d1c,0,v1,0.95)

which plays the note array n1c and its corresponding duration array d1c without any shift, at volume v1 and with the sustain release ratio at 0.95 i.e. with a very sustained and almost continuous joined up note line.
In order to play parts together we employ Sonic Pi threads. Commands are placed inside an
in_thread do….end construct. When this sequence is reached, Sonic Pi starts the commands in between the do and end playing, but immediately continues to the next command in the main program, leaving the thread to continue on its merry way. So by placing tune functions for each part inside a thread, all the parts can be started together, allowing them to play at the same time, producing polyphonic music which is the object of the exercise.
To further tidy up the program, we can define a section, which contains threads for all the parts playing together for one section of the music, say sec1. If we want to play this twice then we can subsequently just put

sec1
sec1

into the program. In fact I also include a volume parameter in this definition in the program, so that I can say sec1(0.4) then sec1(0.2) to repeat the section at a softer volume.

So far we have just discussed the notes that are played. But in the Funeral March we also want to incorporate a tenor drum. This is best done utilising a pre-recorded audio sample. I duly found a sample recording which I edited and saved in a wav file called d1.wav You can place this is a known absolute location (I used ‘/home/pi/samples’ on my Raspberry Pi and ‘/users/rbn/Desktop/samples’ on my Mac) and you then specify the location and load it in the program using the commands

use_sample_pack '/home/pi/samples'
load_sample :d1

I defined three functions to utilise the drum sound

define :bass4 do |x|
  i=1
  4.times do
    sample :d1,amp:1+ i,start: 0.01,finish: 1
    sleep x/4
    i=i+1
  end
end

define :bass1 do |x,amp = 4|
  sample :d1,amp: amp,start: 0.01,finish: 1
  sleep x
end

define :rbass do
  bass1(cd,4)
  bass1(q,2)
  bass1(c,3)
  bass1(c,4)
end

The first one bass4 plays four drum beats, increasing the volume of each one. I actually set the amp very high here, which will cause compression but for a drum beat it doesn’t matter. I specify the total time for the 4 beats as an input parameter. I also chop off the start of the sample which had some silence. The second function bass1 plays one drum beat at volume 4, giving it a duration equal to the parameter x
The third function rbass uses bass1 to produce a drum rhythm of four notes, of varying volume.

Variables which are FIRST defined in a function are local or private to that function. If they are defined in the main program, then their values are also accessible to functions defined in the program. we defined the durations of notes in variables at the beginning of the program. However we need to be able to redefine these at different stages of the program as the tempo varies between different sections. We can’t just change the variable s, we need to redefine the values of all the different note values. So I put all the note duration variable into a defined function, so that it can be called again at different stages in the program. Because, they are defined first in the main part of the program, using this function will allow us to redefine their values globally as required.

define :setup do |s| #allows timings to be redefined
  dsq = 1 * s
  sq = 2 * s
  sqd = 3 * s
  q = 4 * s
  qd = 6 * s
  qdd = 7 * s
  c = 8 * s
  cd = 12 * s
  cdd = 14 * s
  m = 16 * s
  md = 24 * s
  mdd = 28 * s
  b = 32 * s
  bd = 48 * s
end

a command like setup(1.0/16) will let us double the speed values for arrays which are subsequently defined.
The score contains four trills in the top part, and I wanted to play these accurately. One solution is to write out the trills note by note, but this can be quite tedious, and makes the note arrays difficult to check and debug. To further complicate matters, two of the trills take place during rallentandos, so you not only have to generate the individual notes but also allow their lengths to increase as the trill progresses. I developed one or two functions to aid the process.

define :trn do |n,num,offset=2| #produces trill sequence using n  and tone (or semitone) above: n notes total
  n = note_info(n).midi_note
  return [n+offset,n]*(num / 2) #trill set to start on upper note. Can be swapped over
end

define :trd do |d,num| #produces trill note durations. d is total duration, num number of notes
  return [d/num]*num
end

The first two functions are for straight-forward trills. trn generates the notes and has three parameters. The first n is the lower note of the trill (which by default will start on the upper note). num is the total number of notes to be played in the trill, and offset (by default 2) is the offset to the upper note of the trill. 2 gives a tone and 1 a semitone. To use this function you add it into the appropriate place in the note array. e.g.

n1 = [:g4,:ab4]+trn(:ab4,14)+[:g4,:ab4,........

The function trd generates the durations for the trill notes. It has two parameters. d is the total duration of the trill and num is the number of notes in the trill. The corresponding example of use to trn is

d1 = [b,m]+trd(c+qd,14)+[dsq,dsq,...

Both these functions use the return command which means that they return (in this case a list) rather than just carrying out a series of commands.
A slightly different function is used to generate the durations when you want a rit to occur in the trill. This function will usually have to be tailored to the precise situation, rather than being completely general. For the trill at the end of section 1 I used the code below

define :trsetup do #timings for trill in section 1
  y=dsq
  yd=[dsq]
  total=dsq #for debugging
  inc=(2*c-12*dsq) #increase in time per note calculated
  inc=inc/66 #on these two lines
  11.times do
    y=y + inc
    total=total+y
    yd = yd + [y]
  end
  #puts total
  return yd
end

In this case the trill was using demi-semi-quavers (dsq) and had 12 notes in it, but during the course of the trill the accompanying parts slowed up so total time taken was two crotchets. If we let the trill notes get longer in a linear fashion, and call the increment in length per note inc, then dsq + (dsq+inc) + (dsq+2*inc)…..(dsq+11*inc) = 2*c
so summing the sequence (dsq + (dsq+11*inc))/2 * 12 = 2*c
or 12*dsq+66*inc = 2*c so inc=(2*c-12*ds)/66 as shown in the definition. The function puts 12 values of duration into an array yd, and then returns that as its value.
if you try puts total after using the function you get  0.9999999999999998 which is near enough 1 second, or two crotchets at the chosen tempo. For the trill at the end of section three, a similar function calculates the durations. In this case the trill has to last for 3.6 crotchets the first time the section is played and 4.2 crotchets the second time.There are 24 notes in the trill which has nominal dsq (demi-semi-quaver) notes. So its duration is
(dsq +(dsq+inc*23))/2*24 where inc is the increase in duration of successive notes. So
24*dsq + 276*inc = 4.2*c which is the same as 3*c +276*dsq = 4.2*c, or 276*inc = 1.2*c and inc=1.2*c/276 the first time through the section and it has double the value the second time through.

define :ysetup do |t| #change t to 2 for smaller rit
  y=dsq
  yd=[dsq]
  total=dsq
  inc = c*1.2/t/276 #increase in time per note
  23.times do
    y=y + inc
    total=total+y
    yd = yd + [y]
  end
  #puts total
  return yd
end

In order to accommodate the two different rits I use a case statement to choose between two sets of variables which contain the delays for successive notes in the rit section. The code for this is shown below. depending on the value of n one of two rit setups are chosen.

#define empty arrays for sec3 here to make them global
tr=dr=n1c=n2c=n3c=n4c=d1c=d2c=d3c=d4c=[]

define :setuppart3 do |n| #change n 1 or 2 for 1st time 2nd time
  case n
  when 1
    p2=1.5
    p3=1.3
    p4=1.7
    p5=1.2
    p6=1.4
    p7=1.6
    p8=1.8
  when 2
    p2=1.2
    p3=1.15
    p4=1.35
    p5=1.1
    p6=1.2
    p7=1.3
    p8=1.4
  end
  #last two bars for part n1c with trill and rall
  tr = trn(:d5,24,1)+[:c5,:c5]
  dr = ysetup(n) + [c*p8,b*p2]

  n1c = [:g5,:g5,:g5,:g5,:ab5,:g5,:ab5,:f5,:g5,:f5,:g5,:eb5,:f5,:eb5,:f5,:g5,:eb5,:ab5,:ab5,:g5,:g5,:r,:f5,:f5,:r,:eb5,:eb5,:r]
  d1c = [c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,m,c,c,c,c,m,c,c,m,c,c,m]
  n1c.concat [:d5,:c5,:bb4]+trn(:a4,12,1)+[:g4,:g4,:g4,:g5,:g5,:d5,:d5,:eb5,:eb5,:b4,:b4,:c5,:c5,:d5,:d5,:d5,:d5,:d5,:d5,:d5,:d5] #add last 2 bars with trill here
  d1c.concat [c,q,q]+trd(cd,12)+[q,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c] #add  duration last 2 bars with trill
  n2c = [:r,:c5,:c5,:c5,:d5,:eb5,:d5,:eb5,:c5,:d5,:c5,:d5,:b4,:c5,:c5,:c5,:c5,:r,:c5,:c5,:r,:bb4,:bb4,:r,:ab4,:ab4]
  d2c = [b,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,m,c,c,m,c,c,m,c,c]
  n2c.concat [:r,:g4,:g4,:fs4,:g4,:g4,:eb5,:eb5,:b4,:b4,:c5,:c5,:d5,:d5,:eb5,:eb5,:b4,:b4,:c5,:c5,:c5,:c5,:c5,:c5,:c5,:b4,:c5]
  d2c.concat [c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,m*p3,m*p4,b*p2]
  n3c = [:r,:g4,:g4,:g4,:g4,:ab4,:g4,:ab4,:f4,:g4,:f4,:g4,:eb4,:f4,:eb4,:f4,:d4,:eb4,:d4,:eb4,:c4]
  d3c = [b*3,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c]
  n3c.concat [:d4,:eb4,:d4,:d4,:b3,:b3,:r,:r,:g4,:g4,:d4,:d4,:c4,:eb4,:g4,:g4,:f4,:f4,:g4,:g4,:ab4,:ab4,:g4,:g4,:g4,:g4,:e4]
  d3c.concat [c,c,c,c,c,c,m,m,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c*p5,c*p6,c*p7,c*p8,b*p2]
  n4c = [:r,:c4,:c4,:c4,:d4,:eb4,:d4,:eb4,:c4,:d4,:c4,:d4,:bb3,:c4,:bb3,:c4,:ab3]
  d4c = [b*4,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c]
  n4c.concat [:bb3,:c4,:d4,:d4,:g3,:g3,:r,:g3,:g3,:r,:c3,:g3,:g3,:c4,:c4,:g3,:g3,:ab3,:ab3,:e3,:e3,:f3,:f3,:g3,:g3,:g3,:g3,:c3]
  d4c.concat [c,c,c,c,c,c,m,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c*p5,c*p6,c*p7,c*p8,b*p2]
end

As a final embellishment I play the whole piece inside a reverberation effect loop. So the final “playing” part of the program consists of

#===================play funeral march then cadenza ==================
with_fx :reverb, home: 0.8 do
  setup(1.0/8)#set timings
  sec1(0.6)
  setup(1.0/16)
  sleep b
  sec2(0.4)
  sleep q
  sec2(0.2)
  sleep q
  setuppart3(2)#short rit set up the arrays
  sec3(0.4)
  setuppart3(1)#long rit set up the arrays
  sec3(0.8)
end

Before we look at the final program, there is one other refinement. I allocate the various parts across the stereo spectrum by utilising the pan command built into Sonic Pi. You can see this in the listing of the sec2 definition. Notice the with_merged_synth_defaults pan: commands for each part. You will notice the effect particularly in the canzona as the two trumpets answer each other, one from the left and the other from the right. The x parameter feeds in the volume for the section as discussed previously.

define :sec2 do |x|
  in_thread do
    with_merged_synth_defaults pan: -0.7 do
      tune(n1b,d1b,sh,x,0.95)
    end
  end
  in_thread do
    with_merged_synth_defaults pan: 0.7 do
      tune(n2b,d2b,sh,x,0.95)
    end
  end
  in_thread do
    with_merged_synth_defaults pan: -0.4 do
      tune(n3b,d3b,sh,x,0.95)
    end
  end
  with_merged_synth_defaults pan: 0.4 do
    tune(n4b,d4b,sh,x,0.95)
  end
end

The final complete program is listed below. You can download a zip file containing the program, the samples folder containing d1.wav and a README.txt file here
Y
ou can also listen to the piece played by Sonic Pi here

#The Queen's Funeral Music by Henry Purcell transcribed by Robin Newman for Sonic Pi 2. Sept 2014
#REQUIRES SAMPLE :d1 TO BE INSTALLED ON YOUR SYSTEM
#combines synth for instruments and a sample for the drum
#also features trills and rits
set_sched_ahead_time! 4 #on RPi 4, Mac 0.25
#on an RPi turn off Print output in prefs
#select the appropriate line below for your system, and adjust where you have saved the sample d1
#use_sample_pack '/users/rbn/Desktop/samples' #comment out when using Raspberry Pi
use_sample_pack '/home/pi/samples' #comment out when using a Mac
load_sample :d1

use_synth :saw
s = 1.0 / 8#s is speed multiplier to give correct tempo
#note use of 1.0 to make the variable floating point and not integer

sh=0 #transpose shift. Can alter if you want

#The following are variables for note durations values defined in setup function
dsq=sq=sqd=q=qd=qdd=c=cd=cdd=m=md=mdd=b=bd=1*s #declare note duration variables here to make them global

define :setup do |s| #actual note timings defined here
  dsq = 1 * s #demi-semi-quaver
  sq = 2 * s #semi-quaver
  sqd = 3 * s #semi-quaver dotted
  q = 4 * s #quaver
  qd = 6 * s #quaver dotted
  qdd = 7 * s #quaver double dotted
  c = 8 * s #crotchet
  cd = 12 * s #crotchet dotted
  cdd = 14 * s #crotchet double dotted
  m = 16 * s #minim
  md = 24 * s #minim dotted
  mdd = 28 * s #minim double dotted
  b = 32 * s #breve
  bd = 48 * s #breve dotted
end

define :bass4 do |x|
  i=1
  4.times do
    sample :d1,amp:1+ i,start: 0.01,finish: 1
    sleep x/4
    i=i+1
  end
end

define :bass1 do |x,amp = 4|
  sample :d1,amp: amp,start: 0.01,finish: 1
  sleep x
end

define :rbass do
  bass1(cd,4)
  bass1(q,2)
  bass1(c,3)
  bass1(c,4)
end

define :tune do|pitch,duration,shift=0,amp= 0.3,ratio = 0.9|
  pitch.zip(duration).each do |p,d|
    if p == :r
      sleep d
    else
      with_transpose shift do
        play p,sustain: ratio*d,release: (1-ratio)*d,attack: 0,amp: amp
        sleep d
      end
    end
  end
end

define :trn do |n,num,offset=2| #produces trill sequence using n  and tone (or semitone) above: n notes total
  n = note_info(n).midi_note
  return [n+offset,n]*(num / 2) #trill set to start on upper note. Can be swapped over
end
define :trd do |d,num| #produces trill note durations. d is total duration, num number of notes
  return [d/num]*num
end

setup(1.0/8)#check values of durations correct to set up the arrays
n1 = [:g4,:ab4]+trn(:ab4,14)+[:g4,:ab4,:g4,:r,:g4,:c5,:c5,:b4,:r,:d5,:c5,:a4,:bb4,:r,:bb4,:ab4,:f4,:g4,:r,:c5,:c5,:b4,:c5]
d1 = [b,m]+trd(c+qd,14)+[dsq,dsq,b,b,b,m,m,b,b,b,m,m,b,b,b,m,m,b,b,b,m,m,b*1.2] #add pause on last note
n2 = [:e4,:f4,:f4,:e4,:r,:eb4,:eb4,:f4,:g4,:r,:bb4,:a4,:fs4,:g4,:r,:g4,:f4,:d4,:eb4,:r,:g4,:ab4,:g4,:e4]
d2 = [b,m,m,b,b,b,m,m,b,b,b,m,m,b,b,b,m,m,b,b,b,m,m,b*1.2]
n3 = [:c4,:c4,:c4,:c4,:r,:c4,:c4,:c4,:d4,:r,:g4,:eb4,:d4,:d4,:r,:eb4,:c4,:bb3,:bb3,:r,:eb4,:d4,:d4,:c4]
d3 = [b,m,m,b,b,b,m,m,b,b,b,m,m,b,b,b,m,m,b,b,b,m,m,b*1.2]
n4 = [:c3,:f3,:f3,:c3,:r,:c3,:ab3,:ab3,:g3,:r,:g3,:c4,:d4,:g3,:r,:eb3,:ab3,:bb3,:eb3,:r,:c3,:f3,:g3,:c3]
d4 = [b,m,m,b,b,b,m,m,b,b,b,m,m,b,b,b,m,m,b,b,b,m,m,b*1.2]

define :sec1 do |x|
  #drum part solo bar
  bass4(b)

  in_thread do #now start synced parts together
    i=1
    5.times do #drum part
      rbass
      bass1(m)
      bass1(m)
      bass4(b)
      if i<5
        rbass
      end
      i=i+1
    end
  end
  sleep 0.02 #sync bodge to get timing right with drums
  in_thread do #start synth parts after sync timing
    tune(n1,d1,sh,x,0.95)
  end
  in_thread do
    tune(n2,d2,sh,x,0.95)
  end
  in_thread do
    tune(n3,d3,sh,x,0.95)
  end
  tune(n4,d4,sh,x,0.95 )
end

setup(1.0/16) #set timings for canzon (a bit faster)
define :trsetup do #timings for trill in section 1
  y=dsq
  yd=[dsq]
  total=dsq #for debugging
  inc=(2*c-12*dsq) #increase in time per note calculated
  inc=inc/66 #on these two lines
  11.times do
    y=y + inc
    total=total+y
    yd = yd + [y]
  end
  #puts total
  return yd
end

n1b = [:r,:c5,:c5,:c5,:d5,:eb5,:d5,:eb5,:b4,:c5,:d5,:eb5,:f5,:b4,:b4,:g5,:g5,:r,:f5,:f5,:r,:eb5,:eb5,:r,:d5,:d5]
d1b = [b,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,m,c,c,m,c,c,m,c,c]
n1b.concat [:r,:b4,:c5,:d5,:eb5,:d5,:eb5,:f5,:g5,:g5,:g5,:g5,:ab5,:g5,:ab5,:f5,:g5,:f5,:g5,:eb5,:f5,:eb5,:f5,:g5,:eb5,:d5]+trn(:d5,12,1)+[:c5,:c5]
d1b.concat [c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c*1.1,c*1.2]+trsetup+[q*1.4,b*1.2]
n2b = [:g4,:g4,:g4,:g4,:ab4,:g4,:ab4,:f4,:g4,:f4,:g4,:d4,:eb4,:f4,:g4,:ab4,:g4,:g4,:r,:c5,:c5,:r,:bb4,:bb4,:r,:ab4,:ab4,:r]
d2b = [c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,m,c,c,m,c,c,m,c,c,m]
n2b.concat [:g4,:g4,:r,:c5,:c5,:c5,:d5,:eb5,:d5,:eb5,:c5,:f5,:eb5,:f5,:d5,:eb5,:d5,:eb5,:c5,:d5,:c5,:d5,:d5,:g4,:a4,:g4,:g4,:e4]
d2b.concat [c,c,m,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c*1.1,c*1.2,c*1.3,c*1.4,b*1.2]
n3b = [:r,:c4,:c4,:c4,:d4,:eb4,:d4,:eb4,:b3,:c4,:eb4,:f4,:c4,:eb4,:d4,:eb4,:c4,:d4,:c4,:d4,:f4]
d3b = [b*3,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c]
n3b.concat [:eb4,:d4,:eb4,:f4,:g4,:g4,:r,:eb4,:eb4,:r,:d4,:d4,:r,:c4,:c4,:c4,:c4,:b3,:b3,:c4,:c4,:b3,:b3,:c4]
d3b.concat [c,c,c,c,c,c,b,c,c,m,c,c,m,c,c,c,c,c,c,c*1.1,c*1.2,c*1.3,c*1.4,b*1.2]
n4b = [:r,:g3,:g3,:g3,:g3,:ab3,:g3,:ab3,:f3,:g3,:f3,:g3,:eb3,:f3,:eb3,:f3,:bb2]
d4b = [4*b,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c]
n4b.concat [:eb3,:eb3,:eb4,:d4,:c4,:c4,:r,:c4,:c4,:r,:f3,:f3,:r,:eb3,:eb3,:r,:ab3,:f3,:ab3,:g3,:g3,:c4,:f3,:g3,:g3,:c3]
d4b.concat [c,c,c,c,c,c,m,c,c,m,c,c,m,c,c,c,c,c,c,c,c,c*1.1,c*1.2,c*1.3,c*1.4,b*1.2]
define :sec2 do |x|
  in_thread do
    with_merged_synth_defaults pan: -0.7 do
      tune(n1b,d1b,sh,x,0.95)
    end
  end
  in_thread do
    with_merged_synth_defaults pan: 0.7 do
      tune(n2b,d2b,sh,x,0.95)
    end
  end
  in_thread do
    with_merged_synth_defaults pan: -0.4 do
      tune(n3b,d3b,sh,x,0.95)
    end
  end
  with_merged_synth_defaults pan: 0.4 do
    tune(n4b,d4b,sh,x,0.95)
  end
end

#set up trill durations with rall used in part n1c
define :ysetup do |t| #change t to 2 for smaller rit
  y=dsq
  yd=[dsq]
  total=dsq
  inc = c*1.2/t/276 #increase in time per note
  23.times do
    y=y + inc
    total=total+y
    yd = yd + [y]
  end
  puts total
  return yd
end

#define arrays for sec3 here to make them global
tr=dr=n1c=n2c=n3c=n4c=d1c=d2c=d3c=d4c=[]

define :setuppart3 do |n| #change n 1 or 2 for 1st time 2nd time
  case n
  when 1
    p2=1.5
    p3=1.3
    p4=1.7
    p5=1.2
    p6=1.4
    p7=1.6
    p8=1.8
  when 2
    p2=1.2
    p3=1.15
    p4=1.35
    p5=1.1
    p6=1.2
    p7=1.3
    p8=1.4
  end
  #last two bars for part n1c with trill and rall
  tr = trn(:d5,24,1)+[:c5,:c5]
  dr = ysetup(n) + [c*p8,b*p2]

  n1c = [:g5,:g5,:g5,:g5,:ab5,:g5,:ab5,:f5,:g5,:f5,:g5,:eb5,:f5,:eb5,:f5,:g5,:eb5,:ab5,:ab5,:g5,:g5,:r,:f5,:f5,:r,:eb5,:eb5,:r]
  d1c = [c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,m,c,c,c,c,m,c,c,m,c,c,m]
  n1c.concat [:d5,:c5,:bb4]+trn(:a4,12,1)+[:g4,:g4,:g4,:g5,:g5,:d5,:d5,:eb5,:eb5,:b4,:b4,:c5,:c5,:d5,:d5,:d5,:d5,:d5,:d5,:d5,:d5] #add last 2 bars with trill here
  d1c.concat [c,q,q]+trd(cd,12)+[q,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c] #add  duration last 2 bars with trill
  n2c = [:r,:c5,:c5,:c5,:d5,:eb5,:d5,:eb5,:c5,:d5,:c5,:d5,:b4,:c5,:c5,:c5,:c5,:r,:c5,:c5,:r,:bb4,:bb4,:r,:ab4,:ab4]
  d2c = [b,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,m,c,c,m,c,c,m,c,c]
  n2c.concat [:r,:g4,:g4,:fs4,:g4,:g4,:eb5,:eb5,:b4,:b4,:c5,:c5,:d5,:d5,:eb5,:eb5,:b4,:b4,:c5,:c5,:c5,:c5,:c5,:c5,:c5,:b4,:c5]
  d2c.concat [c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,m*p3,m*p4,b*p2]
  n3c = [:r,:g4,:g4,:g4,:g4,:ab4,:g4,:ab4,:f4,:g4,:f4,:g4,:eb4,:f4,:eb4,:f4,:d4,:eb4,:d4,:eb4,:c4]
  d3c = [b*3,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c]
  n3c.concat [:d4,:eb4,:d4,:d4,:b3,:b3,:r,:r,:g4,:g4,:d4,:d4,:c4,:eb4,:g4,:g4,:f4,:f4,:g4,:g4,:ab4,:ab4,:g4,:g4,:g4,:g4,:e4]
  d3c.concat [c,c,c,c,c,c,m,m,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c*p5,c*p6,c*p7,c*p8,b*p2]
  n4c = [:r,:c4,:c4,:c4,:d4,:eb4,:d4,:eb4,:c4,:d4,:c4,:d4,:bb3,:c4,:bb3,:c4,:ab3]
  d4c = [b*4,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c]
  n4c.concat [:bb3,:c4,:d4,:d4,:g3,:g3,:r,:g3,:g3,:r,:c3,:g3,:g3,:c4,:c4,:g3,:g3,:ab3,:ab3,:e3,:e3,:f3,:f3,:g3,:g3,:g3,:g3,:c3]
  d4c.concat [c,c,c,c,c,c,m,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c*p5,c*p6,c*p7,c*p8,b*p2]
end

define :sec3 do |x|
  in_thread do
    with_merged_synth_defaults pan: -0.7 do
      tune(n1c,d1c,sh,x,0.95)
      tune(tr,dr,sh,x,0.95)
    end
  end
  in_thread do
    with_merged_synth_defaults pan: 0.7 do
      tune(n2c,d2c,sh,x,0.95)
    end
  end
  in_thread do
    with_merged_synth_defaults pan: -0.4 do
      tune(n3c,d3c,sh,x,0.95)
    end
  end
  with_merged_synth_defaults pan: 0.4 do
    tune(n4c,d4c,sh,x,0.95)
  end
end

#===================play funeral march then cadenza ==================

with_fx :reverb, home: 0.8 do
  setup(1.0/8)#set timings
  sec1(0.6)
  setup(1.0/16)
  sleep b
  sec2(0.4)
  sleep q
  sec2(0.2)
  sleep q
  setuppart3(2)#short rit set up the arrays
  sec3(0.4)
  setuppart3(1)#long rit set up the arrays
  sec3(0.8)
end

 

2 thoughts on “Developing music for Sonic Pi 2

  1. Awesome writeup – thanks. Just a couple of comments:

    * Would it be nice if I taught Sonic Pi to treat `:r` as a rest? You can already play `nil` which will just inhibit any playing – so if `:r` resolved to `nil` that should work…
    * Where did you find the sample `:r1`? If you can find a similar CC0 licences sample (ideally from freesound.org) I can include it in the next version of Sonic Pi.

    • Well it would save a couple of lines of code if it treated :r as nil, but it is not to much extra code to put in the conditional to test if the note is :r and to ignore the play command if it is. I think it reads better than having nils there.
      I created the sample with the help of the free audiology editor processing a snippet of drums. One of my to-do jobs is to record some drums in the school where I used to teach and then process some samples from them. I have recorded my own voice singing the word hello at various pitches, and then written some Sonic Pi stuff utilising these, producing chords and a scale of hellos. I’ll get around to publishing it some time.

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