Instantly share code, notes, and snippets.

# rbnpi/Spirograph.py

Last active December 16, 2018 22:28
Star You must be signed in to star a gist
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] #store large circle radius set :s,p[x] #store current synth osc "/draw",p[x],p[x],p[x],p[x] #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,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,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, yCoordinates) 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)