Skip to content

Instantly share code, notes, and snippets.

@rbnpi
Last active October 13, 2021 00:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbnpi/9954fa7b18253c4b74826ab65aade550 to your computer and use it in GitHub Desktop.
Save rbnpi/9954fa7b18253c4b74826ab65aade550 to your computer and use it in GitHub Desktop.
Sonic Pi 3 visualiser (Resizable version) See article at https://rbnrpi.wordpress.com/2017/10/09/a-visualiser-for-sonic-pi-resizable-version/
//Visualiser for use with Sonic Pi 3 written by Robin Newman, September 2017
// based on an original sketch https://github.com/andrele/Starburst-Music-Viz
//THIS VERSION FOR RESIZEABLE SCREEN: CAN BE GRAGGED TO SECOND MONITOR
//Changes: changed to resizable screen, updated for Processing 3, added colour, added rectangle and star shapes
//added OSC input (from Sonic Pi) to alter parameters as it runs, removed slider inputs.
//input OSC: /viz/float updates STROKE_MAX, STROKE_MIN and audioThresh
// /viz/pos updates XY random offset values (can be zero)
// /viz/col updates RGB values
// /viz/shapes sets shapes to be used from S, E and R (or combinations thereof)
// /viz/stardata sets data for star shapes
// /viz/rotval turns rotation of shapes on/off
// /viz/shift turns XY shift across screen on/off
// /viz/stop initiates sending stop all signal back to Sonic Pi port 4557
import ddf.minim.analysis.FFT;
import ddf.minim.*;
import oscP5.*; //to support OSC server
import netP5.*;
Minim minim;
AudioInput input;
FFT fftLog;
int recvPort = 5000; //can change to whatever is convenient. Match with use_osc comand in Sonic Pi
OscP5 oscP5;
NetAddress myRemoteLocation; //used to send stop command b ack to Sonic PI
// Setup params
color bgColor = color(0, 0, 0);
// Modifiable parameters
float STROKE_MAX = 10;
float STROKE_MIN = 2;
float audioThresh = .9;
float[] circles = new float[29];
float DECAY_RATE = 2;
//variables for OSC input
float [] fvalues = new float[5]; //STROKE_MAX, STROKE_MIN,audioThresh values
int [] cols = new int[3]; //r,g,b colours
int [] positions = new int[2];// random offset scales for X,Y
int [] stardata = new int[4];// data for star shape, number of points, random variation
int shiftflag = 0; //flag to control xy drift across the screen set by OSC message
int two = 0; //variable to force concentric shapes when more than one is displayed
String shapes = "E"; //shapes to be displayed, including multiples from S,E,R
int rotval =0;
int xoffset = 0,yoffset = 0;
int xdirflag = 1,ydirflag = 1;
void setup() {
size(400, 400,P2D);
frame.setResizable(true);
//noLoop();
frameRate(60);
myRemoteLocation = new NetAddress("127.0.0.1",4557); //address to send commands to Sonic Pi
minim = new Minim(this);
input = minim.getLineIn(Minim.MONO, 2048); //nb static field MONO referenced from class not instance hence Minim not minim
fftLog = new FFT( input.bufferSize(), input.sampleRate()); //setup logarithmic fast fourier transform
fftLog.logAverages( 22, 3); // see http://code.compartmental.net/minim/fft_method_logaverages.html
noFill();
ellipseMode(RADIUS); //first two coords centre,3&4 width/2 and height/2
fvalues[0]=1.0;
fvalues[1]=0.0;
fvalues[2]=0.32;
cols[0] = 255;
cols[1]=0;
cols[2]=150;
positions[0] = 50;
positions[1]=40;
stardata[0]=2;
stardata[1]=4;
stardata[2]=3;
stardata[3]=5;
/* start oscP5, listening for incoming messages at recvPort */
oscP5 = new OscP5(this, recvPort);
background(0);
}
void draw() {
background(0);
pushMatrix();
//calculate changing xy offsets: shiftflag set to 0 to siwtch this off
xoffset += 10*xdirflag*shiftflag;
yoffset += 10*ydirflag*shiftflag;
if(shiftflag==0){xoffset=0;yoffset=0;} //reset offset values to zero if shifting is off
//reverse directions of shifting when limits reached
if (xoffset >width/3){xdirflag=-1;}
if (xoffset < -width/3){xdirflag=1;}
if (yoffset > height/3){ydirflag=-1;}
if (yoffset < -height/3){ydirflag=1;}
//transform to new shift settings
translate(width/2+xoffset, height/2+yoffset); //half of screen width and height (ie centre) plus shift values
//optional rotate set by OSC call
rotate(float(rotval)*(2*PI)/360);
//get limits for stroke values and audiThreshold from OSC data received
STROKE_MIN=fvalues[0];
STROKE_MAX=fvalues[1];
audioThresh=fvalues[2];
//println("fvalues: ",STROKE_MIN,STROKE_MAX,audioThresh); //for debugging
// Push new audio samples to the FFT
fftLog.forward(input.left);
// Loop through frequencies and compute width for current shape stroke widths, and amplitude for size
for (int i = 0; i < 29; i++) {
// What is the average height in relation to the screen height?
float amplitude = fftLog.getAvg(i);
// If we hit a threshold, then set the "circle" radius to new value (originally circles, but applies to other shapes used)
if (amplitude < audioThresh) {
circles[i] = amplitude*(height/2);
} else { // Otherwise, decay slowly
circles[i] = max(0, min(height, circles[i]-DECAY_RATE));
}
pushStyle();
// Set colour and opacity for this shape circle. (opacity depneds on amplitude)
if (1>random(2)) {
stroke(cols[0], cols[1], cols[2], amplitude*255);
} else {
stroke(cols[1], cols[2], cols[0], amplitude*255);
}
strokeWeight(map(amplitude, 0, 1, STROKE_MIN, STROKE_MAX)); //weight stroke according to amplitude value
if (shapes.length()>1) { //if more than one shape being drawn, set two to 0 to draw them concentrically
two = 0;
} else {
two = 1;
}
// draw current shapes
if (shapes.contains("e")) {
// Draw an ellipse for this frequency
ellipse(random(-1, 1)*amplitude*positions[0]*two, random(-1, 1)*amplitude*positions[1]*two, 1.4*circles[i], circles[i]);
}
if (shapes.contains("r")) {
rectMode(RADIUS);
rect( random(-1, 1)*amplitude*positions[0]*two, random(-1, 1)*amplitude*positions[1]*two, 1.4*circles[i], circles[i]);
}
if (shapes.contains("s")) {
strokeWeight(3); //use fixed stroke weight when drawing stars
//star data Xcentre,Ycentre,radius1,radius2,number of points
star(random(-1, 1)*amplitude*positions[0]*two, random(-1, 1)*amplitude*positions[1]*two, circles[i]*stardata[0]*0.25, circles[i]*stardata[1]*0.25, int(stardata[2]+random(stardata[3])));
}
popStyle();
//System.out.println( i+" "+circles[i]); //for debugging
} //end of for loop
popMatrix();
}
void oscEvent(OscMessage msg) { //function to receive and parse OSC messages
System.out.println("### got a message " + msg);
System.out.println( msg);
System.out.println( msg.typetag().length());
if (msg.checkAddrPattern("/viz/float")==true) {
for (int i =0; i<msg.typetag().length(); i++) {
fvalues[i] = msg.get(i).floatValue();
System.out.print("float number " + i + ": " + msg.get(i).floatValue() + "\n");
}
}
if (msg.checkAddrPattern("/viz/pos")==true) {
for (int i =0; i<msg.typetag().length(); i++) {
positions[i] = msg.get(i).intValue();
System.out.print("pos number " + i + ": " + msg.get(i).intValue() + "\n");
}
}
if (msg.checkAddrPattern("/viz/col")==true) {
for (int i =0; i<msg.typetag().length(); i++) {
cols[i] = msg.get(i).intValue();
System.out.print("col number " + i + ": " + msg.get(i).intValue() + "\n");
}
}
if (msg.checkAddrPattern("/viz/shapes")==true) {
shapes=msg.get(0).stringValue();
//for(int i =0; i<msg.typetag().length(); i++) {
// shapes += msg.get(i).stringValue().toLowercase();
//}
System.out.print("shapes code "+ shapes + "\n");
}
if (msg.checkAddrPattern("/viz/stardata")==true) {
for (int i =0; i<msg.typetag().length(); i++) {
stardata[i] = msg.get(i).intValue();
System.out.print("stardata number " + i + ": " + msg.get(i).intValue() + "\n");
}
}
if (msg.checkAddrPattern("/viz/rotval")==true) {
rotval =msg.get(0).intValue();
System.out.print("rotval code "+ rotval + "\n");
}
if (msg.checkAddrPattern("/viz/shift")==true) {
shiftflag =msg.get(0).intValue();
System.out.print("shiftflag code "+ shiftflag + "\n");
}
if (msg.checkAddrPattern("/viz/stop")==true) {
kill(); //stop Sonic Pi from running
}
}
//function to draw a star (and polygons)
void star(float x, float y, float radius1, float radius2, int npoints) {
float angle = TWO_PI / npoints;
float halfAngle = angle/2.0;
beginShape();
for (float a = 0; a < TWO_PI; a += angle) {
float sx = x + cos(a) * radius2;
float sy = y + sin(a) * radius2;
vertex(sx, sy);
sx = x + cos(a+halfAngle) * radius1;
sy = y + sin(a+halfAngle) * radius1;
vertex(sx, sy);
}
endShape(CLOSE);
}
void kill(){ //function to send stop message to Sonic Pi on local machine
OscMessage myMessage = new OscMessage("/stop-all-jobs");
myMessage.add("RBN_GUID"); //any value here. Need guid to make Sonic PI accept command
oscP5.send(myMessage, myRemoteLocation);
}
#Program to drive Sonic Pi 3 visualiser written in "processing"
#by Robin Newman, September 2017
#see article at https://rbnrpi.wordpress.com
#This program for resizeable version of visualiser
#set up OSC address of processing sketch
use_osc '127.0.0.1',5000
#select shapes to show
osc "/viz/shapes","se" #"s" "e" "r" Star,Ellipse, Rectangle or combination
sleep 0.1
live_loop :c do
#choose starting colour for shapes
osc "/viz/col",rrand_i(0,64),rrand_i(128,255),rrand_i(0,255)
sleep 0.1
end
live_loop :f do
#set Stroke max min widths and audioThreshold
osc "/viz/float",([8.0,5.0,3.0].choose),[1.0,2.0].choose,(0.4+rand(0.3))
sleep 2
end
#set range of random positional offset (can be 0,0)
#automatically disabled when showng more than one shape
osc "/viz/pos",10,0
#control "bouncing" shapes around the screen 1 for on 0 for off
osc "/viz/shift",0
live_loop :s do
#setup star data inner/outer circle radius, number of points
#and random variation of number of points
osc "/viz/stardata",[1,2,3].choose,[1,2,4].choose,5,1
sleep 2
end
rv=0 #variable for current rotation
live_loop :r do
rv+=5*[1,1].choose # choose rotation increment
rv=rv%360
osc "/viz/rotval",rv #change rv to 0 to disable rotation
sleep 0.1
end
#Now setup the sounds to play which will trigger the visualiser
use_bpm 60
set_volume! 5
use_random_seed 999
with_fx :level do |v|
control v,amp: 0 #control the volume using fx :level
sleep 0.1
in_thread do #this loop does the volume control
control v,amp: 1,amp_slide: 10 #fade in
sleep 140
control v,amp: 0,amp_slide: 10 #fade out
sleep 10
osc "/viz/stop" #send /viz/stop OSC message to sketch
#sketch sends back a /stop_all_jobs command to port 4557
end
# This drum loop is written by Eli see https://groups.google.com/forum/#!topic/sonic-pi/u71MnHnmkVY
# used with his permission. I liked it, and it has good percussive output
# to drive a visualiser
live_loop :drums do
this_sample = [:loop_compus, :loop_tabla, :loop_safari].ring
start = [ 0.0 , 0.125 , 0.25 , 0.375 , 0.5 , 0.625 , 0.75 , 0.875 ].ring
sample this_sample.look , beat_stretch: 4, start: start.look, rate: 0.5
sleep 1
tick
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment