VERSION 2 IS NOW AVAILABLE. SEE THIS POST
I always like an opportunity to integrate Sonic Pi with a graphical drawing process, and recently I have been looking at Spirograph designs. I found a python module written 3 years ago by marktini on github which will generate some of these curves using turtle graphics. The module was written in Python2.7 and my first task was to convert it to Python3 as I wanted to use the python-osc library (which requires Python 3) to allow two way OSC communication with Sonic Pi.
There are basically 4 parameters required for each drawing. The radius r of a large circle, the radius sr of a small circle, the distance from the centre of this small circle when the “pen” is positioned, and the drawing colour of the pen. I wanted to hold these values in a set of list in Sonic Pi, so that it could initiate each drawing and specify the characteristics.
In the other direction, the python script has to produce a large number of data points (as x,y coordinates) which are used to control the turtle. These can be sent back to Sonic Pi using OSC calls, and used to control the generation of sounds. However there are far too many of them and so I selected subsets depending upon the number of “petal” loops produced in each drawing, and used separate streams of x and y values to control two live_loops in Sonic Pi each of which produced separate sounds.
Because of the way in which the turtle graphics screen is drawn I did not find it possible to incorporate an OSC server into the drawing package. Instead I resorted to the strategy of using a total of three python scripts. The first one, called Spirograph.py was my python3 version of the original script by marktini ( with its two required files euclidian.py and frange.py which were also converted to python3). I used an intermediate script called spiroRun.py which accepted four arguments and then initiated calls to the spirograph script. The third script called spirographOSCserver.py set up an OSC server to respond to messages sent from Sonic Pi. These contained the four data items required for each drawing. When received, they are assembled into a command string , and then an os.system command is issued to start the spiroRun script.
s="/usr/local/bin/python3 "+cwd+"/spirorun.py -r "+r+" -sr "+sr+" -d "+d+" -col '"+col+"'" os.system(s)
The values for r, sr, d and col are obtained from the OSC message sent from Sonic Pi They are picked up by the spiroRun script when it is started using the os.system call.
At the Sonic Pi end I used the following script.
#spiro.rb #Spirograph controlled by Sonic Pi written by Robin Newman, December 2018 use_debug false use_osc_logging false use_cue_logging false use_osc "localhost",8000 define :scx do |n,r| #get note to play based on circle radius r and x coordinate n return scale(:c4,:major,num_octaves: 2)[(n.abs/r.to_f*15).to_int] end define :scy do |n,r| #get selection index based on circle radius r and y coordinate n return (n.abs/r.to_f*4).to_i end set :v,1 #initial volume index value #p contains data list for 7 drawings p=[["250","105","175","purple","saw"],["300","187","203","yellow","tri"], ["300","103","201","blue","sine"],["322","63","87","purple","tri"], ["309","351","300","forest green","saw"],["272","107","109","cyan","pulse"], ["180","67","100","red","piano"]] #main control thread to start each drawing, then wait for drawing to finish in_thread do p.length.times do |x| set :r,p[x][0] #store large circle radius set :s,p[x][4] #store current synth osc "/draw",p[x][0],p[x][1],p[x][2],p[x][3] #send data for next drawing b = sync "/osc*/finished" #wait for drawing to finish end end #Playing section. Responds to OSC messages from spirograph.py with_fx :reverb do live_loop :plx do use_real_time n = sync "/osc*/xcoord" #use osc* so works with SP 3.1 and 3.2dev use_transpose [-17,-12,0,7,12,19][get(:v)] synth get(:s),note: scx(n[0],get(:r)),attack: 0.05,release: 0.2,pan: [-0.8,0.8].choose,amp: [0.3,0.5,0.7,0.8,1][get(:v)] end live_loop :ply do use_real_time n = sync "/osc*/ycoord" #trigger sample when y coord received i = scy(n[0],get(:r)) set :v,i #adjust volume for plx loop sample sample_names([:drum,:elec].choose ).choose,amp: 2,pan: [-1,0,1].choose end end
Notes: This system has only been tested on my MacBook. You need a fairly hefty computer to handle the graphics. You will have to install python-osc using pip3 install python-osc
Once the system is working, you can experiment with different for the parameters and produce your own designs
You can download the code from here
You can watch a video of the program here