Skip to content

Instantly share code, notes, and snippets.

@rbnpi
Last active August 27, 2022 18:47
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbnpi/abbb0a7880f355c12d0e to your computer and use it in GitHub Desktop.
Save rbnpi/abbb0a7880f355c12d0e to your computer and use it in GitHub Desktop.
Glass Armonica for Sonic Pi plays Mozart UPDATED to use rpitch: (introduced after this was first written) to calculate sample rate. MUCH simpler
#OUT OF DATE VERSION. USE THE SECOND FILE INSTEAD
#a Glass Armonica "voice" for Sonic Pi by Robin Newman November 2014
#This simulates the Glass Armonica oinvented by Benjamin Franklin and for which Mozart
#composed the piece played here
#better definition of glass armonica voice, using rates calculated rather than looked up
use_debug false
rm = 2**(1.0/12) #rate multiplier between adjacent semitones (twelth root of 2)
inst = :ambi_glass_rub
s=1.0/8 #speed multiplier give 1 crotchet/sec or 60 bpm
shift=0 #adjust for iffernt pitch if you want
define :ntosym do |n| #this returns the equivalent note symbol to an input integer e.g. 59 => :b4
#nb no error checking on integer range included
#only returns notes as n or n sharps.But will sound ok for flats
@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
nt = []#setup note names :f2 to :c8
for i in (41..108) do
nt << ntosym(i)
end
#puts nt
#puts nt.length
#puts nt[37]
rt=[]#set up rate multipliers :fs5 = nt[37] has rate 1
rt[37]=1
36.downto(0) do |i| #calculate rate for all lower notes
rt[i]=rt[i+1] / rm
#puts rt[i]
end
38.upto(108) do |i| #calculate rate for all higher notes
rt[i]=rt[i-1]* rm
#puts rt[i]
end
nhash=[]#build a hash of notenames and rates
0.upto(67) do |i|
nhash << [nt[i],rt[i]]
end
#now add in aliases for flats and e and b sharp
flat=[: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=[: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 [:es2,:es3,:es4,:es5,:es6,:es7,:bs2,:bs3,:bs4,:bs5,:bs6,:bs7]
sharp.concat [:f2,:f3,:f4,:f5,:f6,:f7,:c3,:c4,:c5,:c6,:c7,:c8]
#initialise array for extra values
extra=[]
flat.zip(sharp).each do |f,s|
# adds element with flat name and rate factor looked up from associated sharp entry
extra.concat [[f,(nhash.assoc(s)[1])]]
end
nhash = nhash + extra #add extras in
#set up function to play a given sample note.
define :pl do |n,d=0.2,pan=0,v=0.8|
sample inst,rate: (nhash.assoc(n)[1]),attack: d*0.1,sustain: d*0.85,release: d*0.1,amp: v,pan: pan,start: 0.04
#use one of line above OR below
#sample inst,rate: (nhash.assoc(n)[1]),attack: d*0,sustain: d*0.9,release: d*0.1,amp: v,pan: pan,start: 0.00
end
#define note relative durations. Factor s sets the speed
dsq = 1 * s
sq = 2 * s
sqd = 3 * s
q = 4 * s
qt = 2.0/3*q
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
#function plays an array of note and duration values (nt and dur), with parameters for transposition, volume and pan
define :plarray do |nt,dur,sh=0,vol=0.4,pan=0|
nt.zip(dur).each do |n,d|
if n != :r then
pl(tr(n,sh),d,pan,vol)
end
sleep d
end
end
#===================
#set up arrays of notes and durations for the 5 parts in section 1
n1 = [:g5,:f5,:f5,:e5,:r,:f5,:e5,:f5,:g5,:a5,:g5,:f5,:e5,:e5,:d5,:e5,:f5,:fs5,:g5,:f5,:f5,:e5]
d1 = [md,c,m,c,c,q,q,q,q,q,q,q,q,m,q,q,q,q,md,c,m,m]
#b7
n1.concat [:f5,:g5,:f5,:e5,:f5,:g5,:a5,:f5,:e5,:d5,:d5,:c5,:r]
d1.concat [sq,sq,sq,sq,sq,sq,sq,sq,c,c,m,c,c]
n2 = [:e5,:d5,:d5,:c5,:r,:d5,:cs5,:d5,:e5,:f5,:e5,:d5,:c5,:c5,:b4,:r,:e5,:d5,:d5,:c5,:cs5,:r,:c5,:b4,:b4,:r]
d2 = [md,c,m,c,c,q,q,q,q,q,q,q,q,m,c,c,md,c,m,c,c,m,c,c,m,m]
n3 = [:c4,:e4,:g4,:gs4,:a4,:g4,:f4,:e4,:d4,:e4,:f4,:fs4,:g4,:r,:c4,:e4,:g4,:gs4,:a4,:bb4,:a4,:g4,:g4,:r]
d3 = [c,c,c,c,md,c,cd,q,q,q,q,q,md,c,c,c,c,c,md,c,m,m,md,c]
n4 = [:r,:bb4,:f4,:r,:f4,:e4,:r]
d4 = [5*b+md,c,m,m,m,c,c]
n5 = [:r,:c4,:r]
d5 = [7*b,md,c]
#store a list of arrays and durations to be played
lar = [n1,d1,n2,d2,n3,d3,n4,d4,n5,d5]
#set up note and duration arrays for the second section
#b9
n1b = [:g5,:fs5,:g5,:fs5,:g5,:a5,:g5,:fs5,:c6,:b5,:bb5,:c6,:bb5,:a5,:g5,:fs5,:g5,:a5,:bb5,:b5,:c6,:cs6,:d6]
d1b = [m+q,sq,sq,sq,sq,sq,sq,c,m,c,cd,sq,sq,c,c,q,q,q,q,q,q,q,q]
#b13
n1b.concat [:d6,:e6,:d6,:c6,:b5,:b5,:a5,:r,:g5,:g5,:a5,:g5,:fs5,:e5,:e5,:d5,:r,:c5,:b4,:g5,:e5,:a5,:g5,:fs5]
d1b.concat [q,sq,sq,q,q,q,q,q,q,q,sq,sq,q,q,q,q,q,q,q,q,q,c,c,q]
#b16
n1b.concat [:a5,:g5,:gs5,:a5,:b5,:cs6,:d6,:g5,:a5,:b5,:c6,:fs5,:f5,:cs5,:d5,:e5,:f5,:fs5,:g5,:f5]
d1b.concat [m,c,c,m+q,q,q,q,m+q,q,q,q,b,cd,q,q,q,q,q,md,c]
#b22
n1b.concat [:f5,:e5,:f5,:g5,:f5,:e5,:r,:f5,:e5,:f5,:g5,:a5,:g5,:f5,:e5,:e5,:d5,:e5,:f5,:e5,:d5,:e5,:f5,:fs5]
d1b.concat [c,sq,sq,sq,sq,c,c,q,q,q,q,q,q,q,q,c,sq,sq,sq,sq,q,q,q,q]
#b25
n1b.concat [:g5,:b5,:a5,:c6,:b5,:d6,:c6,:g5,:f5,:f5,:e5,:f5,:g5,:f5,:e5,:f5,:g5,:a5,:f5,:e5,:f5,:fs5,:g5,:g5,:f5,:e5,:d5,:d5,:e5,:d5,:c5,:r]
d1b.concat [q,sq,sq,sq,sq,sq,sq,c,c,m,m,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,cd,sq,sq,c,c]
n2b = [:r,:d5,:c5,:fs5,:r,:eb5,:d5,:c5,:d5,:r,:e5,:d5,:d5,:c5,:r,:d5,:cs5,:d5,:e5,:f5,:e5,:d5,:c5,:c5,:b4,:r]
d2b = [6*b+m,c,c,m,2*b+m,m,c,c,cd,m+q,md,c,m,c,c,q,q,q,q,q,q,q,q,m,c,c]
#b25
n2b.concat [:e5,:r,:e5,:d5,:d5,:cs5,:d5,:r,:b4,:r]
d2b.concat [c,c,c,c,m,m,m,m,m,m]
n3b=[:e5,:d5,:c5,:d5,:e5,:d5,:r,:g5,:fs5,:e5,:d5,:c5,:b4,:a4,:fs4,:g4,:c4,:b4,:a4,:c5,:b4,:r,:g5,:fs5,:r,:f5,:e5,:r,:c5,:b4,:a4]
d3b=[m,m,m,m,b,c,md,cd,q,c,c,cd,q,c,c,c,c,c,c,m,c,m,c,c,m,c,c,c,m,c,c]
#b20
n3b.concat [:b4,:r,:c4,:e4,:g4,:gs4,:a4,:g4,:f4,:g4,:f4,:e4,:d4,:e4,:f4,:fs4,:g4,:r,:c4,:e4,:g4,:gs4,:a4,:bb4,:a4,:g4,:a4,:c5,:b4,:g4,:r]
d3b.concat [cd,q+m,c,c,c,c,md,c,q,q,q,q,q,q,q,q,md,c,c,c,c,c,c,m,q,q,m,c,c,md,c]
n4b=[:c5,:b4,:a4,:g4,:cs5,:r,:e5,:d5,:c5,:b4,:a4,:g4,:fs4,:ds4,:e4,:r,:d4,:g4,:r,:cs5,:d5,:r,:b4,:c5,:r]
d4b=[m,m,m,m,b,b,cd,q,c,c,cd,q,c,c,c,c,m,md,m,c,c,m,c,c,c]
#b19
n4b.concat [:g4,:r,:f4,:g4,:f4,:e4,:r]
d4b.concat [b+cd,q+m+6*b,m,m,m,c,c]
n5b=[:r,:c4,:r]
d5b=[19*b,md,c]
#add these parts to the lar list
lar.concat [n1b,d1b,n2b,d2b,n3b,d3b,n4b,d4b,n5b,d5b]
#sec is defined to play 5 pairs of note/duration arrays. p is the offset in lar list
#sh sets transposition
#parts played together using threads
define :sec do |p,sh=0,amp=0.4|
in_thread do
plarray(lar[8+p],lar[9+p],sh,amp,0.6)
end
in_thread do
plarray(lar[6+p],lar[7+p],sh,amp,0.6)
end
in_thread do
plarray(lar[4+p],lar[5+p],sh,amp,-0.6)
end
in_thread do
plarray(lar[2+p],lar[3+p],sh,amp,-0.6)
end
plarray(lar[0+p],lar[1+p],sh,amp,-0.6)
end
#now play first and second sections
#shift set at start adjusts transpose. 0 and 10 are offsets in lar and last parameter is vol
with_fx :reverb, room: 0.6 do
with_fx :lpf, cutoff: 85 do
sec(0,shift,0.4)
sec(0,shift,0.3)
sec(10,shift,0.3)
sec(10,shift,0.4)
end
end
#a Glass Armonica "voice" for Sonic Pi by Robin Newman November 2014
# revised to use rpitch 2018; corrected 2022 for missing tr function
#This simulates the Glass Armonica invented by Benjamin Franklin and for which Mozart
#composed the piece played here
#This version uses rpitch: parameter added to Sonic Pi after the original was written in 2014
##| sample :ambi_glass_rub
##| play :fs5
##| puts note(:fs5)
##| stop
use_debug false
shift=0 #chnage to say 7 to transpose up a fifth
inst = :ambi_glass_rub
bpm = 90
s=1.0/8 *60.0/bpm #smultipliern to calculate duration variables for notes
#set up function to play a given sample note.
define :pl do |n,d=0.2,pan=0,v=0.8|
sample inst,rpitch: note(n)-78,attack: d*0.1,sustain: d*0.85,release: d*0.1,amp: v,pan: pan,start: 0.04
end
#define note relative durations. Factor s sets the speed
dsq = 1 * s
sq = 2 * s
sqd = 3 * s
q = 4 * s
qt = 2.0/3*q
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
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 note(nv)+shift
end
end
#function plays an array of note and duration values (nt and dur), with parameters for transposition, volume and pan
define :plarray do |nt,dur,sh=0,vol=0.4,pan=0|
nt.zip(dur).each do |n,d|
if n != :r then
pl(tr(n,sh),d,pan,vol)
end
sleep d
end
end
#===================
#set up arrays of notes and durations for the 5 parts in section 1
n1 = [:g5,:f5,:f5,:e5,:r,:f5,:e5,:f5,:g5,:a5,:g5,:f5,:e5,:e5,:d5,:e5,:f5,:fs5,:g5,:f5,:f5,:e5]
d1 = [md,c,m,c,c,q,q,q,q,q,q,q,q,m,q,q,q,q,md,c,m,m]
#b7
n1.concat [:f5,:g5,:f5,:e5,:f5,:g5,:a5,:f5,:e5,:d5,:d5,:c5,:r]
d1.concat [sq,sq,sq,sq,sq,sq,sq,sq,c,c,m,c,c]
n2 = [:e5,:d5,:d5,:c5,:r,:d5,:cs5,:d5,:e5,:f5,:e5,:d5,:c5,:c5,:b4,:r,:e5,:d5,:d5,:c5,:cs5,:r,:c5,:b4,:b4,:r]
d2 = [md,c,m,c,c,q,q,q,q,q,q,q,q,m,c,c,md,c,m,c,c,m,c,c,m,m]
n3 = [:c4,:e4,:g4,:gs4,:a4,:g4,:f4,:e4,:d4,:e4,:f4,:fs4,:g4,:r,:c4,:e4,:g4,:gs4,:a4,:bb4,:a4,:g4,:g4,:r]
d3 = [c,c,c,c,md,c,cd,q,q,q,q,q,md,c,c,c,c,c,md,c,m,m,md,c]
n4 = [:r,:bb4,:f4,:r,:f4,:e4,:r]
d4 = [5*b+md,c,m,m,m,c,c]
n5 = [:r,:c4,:r]
d5 = [7*b,md,c]
#store a list of arrays and durations to be played
lar = [n1,d1,n2,d2,n3,d3,n4,d4,n5,d5]
#set up note and duration arrays for the second section
#b9
n1b = [:g5,:fs5,:g5,:fs5,:g5,:a5,:g5,:fs5,:c6,:b5,:bb5,:c6,:bb5,:a5,:g5,:fs5,:g5,:a5,:bb5,:b5,:c6,:cs6,:d6]
d1b = [m+q,sq,sq,sq,sq,sq,sq,c,m,c,cd,sq,sq,c,c,q,q,q,q,q,q,q,q]
#b13
n1b.concat [:d6,:e6,:d6,:c6,:b5,:b5,:a5,:r,:g5,:g5,:a5,:g5,:fs5,:e5,:e5,:d5,:r,:c5,:b4,:g5,:e5,:a5,:g5,:fs5]
d1b.concat [q,sq,sq,q,q,q,q,q,q,q,sq,sq,q,q,q,q,q,q,q,q,q,c,c,q]
#b16
n1b.concat [:a5,:g5,:gs5,:a5,:b5,:cs6,:d6,:g5,:a5,:b5,:c6,:fs5,:f5,:cs5,:d5,:e5,:f5,:fs5,:g5,:f5]
d1b.concat [m,c,c,m+q,q,q,q,m+q,q,q,q,b,cd,q,q,q,q,q,md,c]
#b22
n1b.concat [:f5,:e5,:f5,:g5,:f5,:e5,:r,:f5,:e5,:f5,:g5,:a5,:g5,:f5,:e5,:e5,:d5,:e5,:f5,:e5,:d5,:e5,:f5,:fs5]
d1b.concat [c,sq,sq,sq,sq,c,c,q,q,q,q,q,q,q,q,c,sq,sq,sq,sq,q,q,q,q]
#b25
n1b.concat [:g5,:b5,:a5,:c6,:b5,:d6,:c6,:g5,:f5,:f5,:e5,:f5,:g5,:f5,:e5,:f5,:g5,:a5,:f5,:e5,:f5,:fs5,:g5,:g5,:f5,:e5,:d5,:d5,:e5,:d5,:c5,:r]
d1b.concat [q,sq,sq,sq,sq,sq,sq,c,c,m,m,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,sq,cd,sq,sq,c,c]
n2b = [:r,:d5,:c5,:fs5,:r,:eb5,:d5,:c5,:d5,:r,:e5,:d5,:d5,:c5,:r,:d5,:cs5,:d5,:e5,:f5,:e5,:d5,:c5,:c5,:b4,:r]
d2b = [6*b+m,c,c,m,2*b+m,m,c,c,cd,m+q,md,c,m,c,c,q,q,q,q,q,q,q,q,m,c,c]
#b25
n2b.concat [:e5,:r,:e5,:d5,:d5,:cs5,:d5,:r,:b4,:r]
d2b.concat [c,c,c,c,m,m,m,m,m,m]
n3b=[:e5,:d5,:c5,:d5,:e5,:d5,:r,:g5,:fs5,:e5,:d5,:c5,:b4,:a4,:fs4,:g4,:c4,:b4,:a4,:c5,:b4,:r,:g5,:fs5,:r,:f5,:e5,:r,:c5,:b4,:a4]
d3b=[m,m,m,m,b,c,md,cd,q,c,c,cd,q,c,c,c,c,c,c,m,c,m,c,c,m,c,c,c,m,c,c]
#b20
n3b.concat [:b4,:r,:c4,:e4,:g4,:gs4,:a4,:g4,:f4,:g4,:f4,:e4,:d4,:e4,:f4,:fs4,:g4,:r,:c4,:e4,:g4,:gs4,:a4,:bb4,:a4,:g4,:a4,:c5,:b4,:g4,:r]
d3b.concat [cd,q+m,c,c,c,c,md,c,q,q,q,q,q,q,q,q,md,c,c,c,c,c,c,m,q,q,m,c,c,md,c]
n4b=[:c5,:b4,:a4,:g4,:cs5,:r,:e5,:d5,:c5,:b4,:a4,:g4,:fs4,:ds4,:e4,:r,:d4,:g4,:r,:cs5,:d5,:r,:b4,:c5,:r]
d4b=[m,m,m,m,b,b,cd,q,c,c,cd,q,c,c,c,c,m,md,m,c,c,m,c,c,c]
#b19
n4b.concat [:g4,:r,:f4,:g4,:f4,:e4,:r]
d4b.concat [b+cd,q+m+6*b,m,m,m,c,c]
n5b=[:r,:c4,:r]
d5b=[19*b,md,c]
#add these parts to the lar list
lar.concat [n1b,d1b,n2b,d2b,n3b,d3b,n4b,d4b,n5b,d5b]
#sec is defined to play 5 pairs of note/duration arrays. p is the offset in lar list
#sh sets transposition
#parts played together using threads
define :sec do |p,sh=0,amp=0.4|
in_thread do
plarray(lar[8+p],lar[9+p],sh,amp,0.6)
end
in_thread do
plarray(lar[6+p],lar[7+p],sh,amp,0.6)
end
in_thread do
plarray(lar[4+p],lar[5+p],sh,amp,-0.6)
end
in_thread do
plarray(lar[2+p],lar[3+p],sh,amp,-0.6)
end
plarray(lar[0+p],lar[1+p],sh,amp,-0.6)
end
#now play first and second sections
#shift set at start adjusts transpose. 0 and 10 are offsets in lar and last parameter is vol
with_fx :reverb, room: 0.6 do
with_fx :lpf, cutoff: 85 do
#shift is defined line line 11
sec(0,shift,0.4)
sec(0,shift,0.3)
sec(10,shift,0.3)
sec(10,shift,0.4)
end
end
@CUBICinfinity
Copy link

For GlassArmonicaPlaysMozart.rb, I have a problem with the tr method not being defined. (Line 40)

@rbnpi
Copy link
Author

rbnpi commented Aug 27, 2022

Oops. I've added it. I probably would have done this program rather differently if written today. It is quite old, but pleasing to listen to. I've added a setting for shift (usually zero) so that you can see how it works . EG set it to 7 to transpose up a fifth. Have fun!

@CUBICinfinity
Copy link

Cool, thanks! I'm getting to know Sonic Pi and found this through an in-thread post about rpitch on samples.

@CUBICinfinity
Copy link

Line 129 needs to be commented out, removed, or replaced with a check for if shift is already defined.

@rbnpi
Copy link
Author

rbnpi commented Aug 27, 2022

Ah I hadn't seen the previous definition. However it will replace the fist one during execution. I've it and put the comment on the earlier line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment