Sonic Pi and Spirograph (using python3) See article at https://rbnrpi.wordpress.com/project-list/sonic-pi-and-spirograph/ video at https://youtu.be/uzD9HEr4Cl4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#updated to python3 by Robin Newman December 2018 | |
#function that finds greatest common divisor using Euclidian algorithm. | |
#For math description of the algorithm, visit https://en.wikipedia.org/wiki/Euclidean_algorithm | |
def euclidianGCD(a, b): | |
#check if either of the inputs is zero | |
if (a==0 or b==0): | |
if (a==0 and b!=0): | |
return b | |
elif (a!=0 and b==0): | |
return a | |
else:#if both are zero | |
print("Cannot return a common divisor when both inputs are zero. Return None.") | |
return None | |
else:#if neither input is zero | |
a_new = abs(a-b) | |
b_new = min(a, b) | |
while (a_new != b_new): | |
this_a = a_new | |
this_b = b_new | |
a_new = abs(this_a - this_b) | |
b_new = min(this_a, this_b) | |
return a_new #alternatively, might return b_new since they are the same thing with a_new |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#python3 version (same as python2 version) | |
#analog of the range() function but this one allows decimal increments | |
def frange(start, end, step=None): | |
if step!=None: | |
this = start | |
list = [] | |
list.append(this) | |
while (this <= end): | |
this = this + step | |
this = round(this, 2) | |
list.append(this) | |
return list |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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] #ssend 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Author: marktini github.com/marktini | |
#Spirograph curve drawing program | |
#Version 1.0 | |
#modified to Python3 by Robin Newman Dec 2018 | |
#additions to allow export of x,y coords using OSC message | |
#with aid of pythonosc library | |
import math | |
import turtle | |
import random | |
import time | |
from euclidian import euclidianGCD | |
from frange import frange | |
from pythonosc import osc_message_builder | |
from pythonosc import udp_client | |
class Spirograph: | |
#set radius of the spirograph toy (outer circle) | |
def __init__ (self, R): | |
self.R = R | |
self.t = turtle.Turtle() #to be able to access turtle from different methods | |
#set radius of the inner circle | |
def setSmallCircle(self, r): | |
self.r = r | |
#set distance of pen from inner circle radius; set pen color | |
def setPen(self, d, color='black', random=False): | |
self.d = d | |
self.color = color | |
self.random = random | |
#draw the spirograph given current settings | |
def draw(self): | |
#find greatest common denominator of r and R using Euclidian algorithm: | |
gcd = euclidianGCD(self.r, self.R) | |
#number of periods is the reduced numerator of the fraction r/R | |
numPeriods = self.r/gcd | |
numPetals = self.R/gcd | |
#calculate constants for graphing | |
print('Periods: ', numPeriods) | |
print('Petals: ', numPetals) | |
k = float(self.r)/self.R | |
l = float(self.d)/self.r | |
print('k=',self.r, '/',self.R, '=',k,' l=',self.d,'/',self.r,'=',l) | |
#use the custom-made frange function to make a list of angles of given increment | |
angleIncrement = 0.01 #the smaller angleIncrement, the more data points | |
ptsPeriod = math.ceil(2*math.pi/angleIncrement) | |
print("Points per Period: ", ptsPeriod) | |
#frange function is an alternative to range(). The last argument specifies a decimal step | |
angles = frange(0, 2*math.pi*numPeriods, angleIncrement) | |
xCoordinates = [] | |
yCoordinates = [] | |
#calculate all the (x,y) points corresponding to parameters in the "angles" list | |
for theta in angles: | |
thisX = self.R*((1-k)*math.cos(theta) + l*k*math.cos((1-k)/(k)*(theta))) | |
thisY = self.R*((1-k)*math.sin(theta) + l*k*math.sin((1-k)/(k)*(theta))) | |
xCoordinates.append(thisX) | |
yCoordinates.append(thisY) | |
print('Num data points: ', len(xCoordinates)) | |
t = self.t #for brevity in future references to the turtle | |
screen= t.getscreen() #same as above | |
screen.bgcolor("black") | |
#name the canvas window | |
title = "Spirograph with R= " + str(self.R) + ", r = "+str(self.r) + ", and d = " +str(self.d) | |
screen.title(title) | |
#for the first point, just move the pen without leaving trace | |
t.up() | |
t.goto(xCoordinates[0], yCoordinates[0]) | |
t.down() | |
#speed up the drawing! update every 20 points. Change these parameters to vary speed | |
screen.tracer(20) | |
t.speed(6) | |
randColors = False #if True, change up colors randomly with each period | |
t.color(self.color) | |
sender=udp_client.SimpleUDPClient("127.0.0.1",4559) | |
pointsCount = 0 | |
for each in range(len(xCoordinates)): | |
t.goto(xCoordinates[each], yCoordinates[each]) | |
pointsCount = pointsCount + 1 | |
#additonal section to export x.y coords using OSC message | |
if (pointsCount % (numPetals*2) == 0): | |
sender.send_message('/xcoord',xCoordinates[each]) | |
if (pointsCount % (numPetals*4) ==0): | |
sender.send_message('/ycoord',yCoordinates[each]) | |
#end of additional section | |
if (randColors): | |
if (pointsCount % ptsPeriod == 0): | |
red = random.random() | |
green = random.random() | |
blue = random.random() | |
t.color(red, green, blue) | |
t.hideturtle() | |
print("Done drawing this curve") | |
time.sleep(2) | |
sender.send_message("/finished","done") | |
#clear the drawing surface after "sec" seconds. Limit time to 2 min just in case | |
def clear(self, sec): | |
if sec > 2*60:#reset time in case it's too long | |
sec = 10 | |
time.sleep(sec) | |
self.t.getscreen().reset() #now reset the screen |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#spiroOSCserver.py by Robin Newman, December 2018. (use python3) | |
from pythonosc import osc_server | |
from pythonosc import dispatcher | |
import argparse | |
import time | |
import os | |
from Spirograph import Spirograph | |
cwd = os.getcwd() #current directory | |
def oscTest(unused_addr,args,data): | |
print("data received",data) | |
def drawIt(unused_addr,args,r,sr,d,col): | |
s="/usr/local/bin/python3 "+cwd+"/spiroRun.py -r "+r+" -sr "+sr+" -d "+d+" -col '"+col+"'" | |
os.system(s) | |
if __name__ == "__main__": | |
try: | |
parser = argparse.ArgumentParser() | |
parser.add_argument("-ip", | |
default = '127.0.0.1', help="The ip of this computer") | |
parser.add_argument("-sp", | |
default = '127.0.0.1', help="The ip of the computer running Sonic Pi") | |
#This is the port on which the server listens. Usually 8000 is OK | |
#but you can specify a different one | |
parser.add_argument("--port", | |
type=int, default=8000, help="The port to listen on") | |
args=parser.parse_args() | |
spip=args.sp | |
#######dispatchers which handle incoming osc calls. They pass the data received on to | |
####### the routine following the OSC address. eg "/setAll" calls oscSetAll with data cname and id | |
####### All routines can send optional message back to Sonic Pi if id param is > -1 (default),sender | |
####### steps, wait_ms and id all have default values if omitted | |
dispatcher = dispatcher.Dispatcher() | |
dispatcher.map("/test",oscTest,"data") #for testing purposes only | |
dispatcher.map("/draw",drawIt,"r","sr","d","col") | |
#now setup sender to return OSC messages to Sonic Pi | |
print("Sonic Pi on ip",spip) | |
#sender set up for specified IP | |
#Now set up and run the OSC server | |
server = osc_server.ThreadingOSCUDPServer( | |
(args.ip, args.port), dispatcher) | |
print("Serving on {}".format(server.server_address)) | |
#run the server "forever" (till stopped by pressing ctrl-C) | |
server.serve_forever() | |
except KeyboardInterrupt: | |
print("exiting program") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#spiroRun.py by Robin Newman, December 2018 | |
from Spirograph import Spirograph | |
import argparse | |
import time | |
parser = argparse.ArgumentParser() | |
parser.add_argument("-r") | |
parser.add_argument("-sr") | |
parser.add_argument("-d") | |
parser.add_argument("-col") | |
args=parser.parse_args() | |
r=int(args.r) | |
sr=int(args.sr) | |
d=int(args.d) | |
col=args.col | |
s = Spirograph(r) | |
s.setSmallCircle(sr) | |
s.setPen(d, col) | |
s.draw() | |
time.sleep(2) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment