Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Christian Esparza
Published © GPL3+

Inclusive and Accessible STEM

Multisensorial mathematics.

IntermediateWork in progress2 hours699
Inclusive and Accessible STEM

Things used in this project

Hardware components

Flick Large
Pi Supply Flick Large
×1
ELEGOO UNO R3 Board ATmega328P ATMEGA16U2 with USB Cable
ELEGOO UNO R3 Board ATmega328P ATMEGA16U2 with USB Cable
×1
Grove - Vibration Motor
Seeed Studio Grove - Vibration Motor
×1

Software apps and online services

Arduino IDE
Arduino IDE
Processing
Supercollid

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

Skywriter cap

Skywriter cap (sliced)

Skywriter base

Skywriter base (sliced)

Schematics

Flick Large-Arduino

Connect the pins GND, SDA and SCL to the same pinholes in the Arduino/Elego UNO. The VCC should be connected to the 3.3V pinhole and the rest of the pins can be connected to any of the digital pinholes, in the diagram here they are connected as follow TS-D10, RESET-D11, LED1-D8 and LED2-D9.

Code

Flick 3D tracking

Arduino
This piece of code enables 3d tracking with the Flick/Skywriter and prints the Cartesian coordinates to the serial port.
#include <Wire.h>
#include <skywriter.h>
void setup() {
  Serial.begin(9600);                                             //Initialise serial communication at 9600 bds
  while(!Serial){};//We wait for the data serial port to start
  Serial.println("Hello world!");//The port is ready!

  Skywriter.begin(10,11);                                         //Initialise the Flick with the pins TS=D10 and RESET=D11
  
  Skywriter.onXYZ(handle_xyz);//This method records the position on the Flick and the function handle_xyz manipulates the data.
  
  //We can initialise the LED pins as follows
  pinMode (9, OUTPUT); //Red LED
  pinMode (8, OUTPUT); //Green LED

  //We can turn on the green LED to know when the Flick is ready
  digitalWrite (8, HIGH);

}

void loop() {
    Skywriter.poll();//Check if the status of the Flick has changed
}

void handle_xyz(unsigned int x, unsigned int y, unsigned int z){
  char buf[17];//An array of 17 characters 5 for each coordinate and two delimiters
  sprintf(buf, "%05u:%05u:%05u",x,y,z);//Record the position on the Flick
  Serial.println(buf);//Print the position to the serial port, one line at a time
}

Flick airwheel

Arduino
This piece of code enables the airwheel gesture in the flick and uses it to increase or decrease the value of a variable that is used to modify the frequency of a sinusoidal wave in Supercollider.
#include <Wire.h>
#include <skywriter.h>

int freq=440;//Frequency control variable

void setup() {
  Serial.begin(9600);//Initialise serial communication at 9600 bds
  while(!Serial){};//We wait for the data serial port to start
  Serial.println("Hello world!");//The port is ready!

  Skywriter.begin(10,11);//Initialise the Flick with the pins TS=D10 and RESET=D11
  Skywriter.onAirwheel(handleAirwheel);//This method records an airwheel event and returns a positive/negative value if clockwise/counterclockwise rotation

  //We can initialise the LED pins as follows
  pinMode (9, OUTPUT);//Red LED
  pinMode (8, OUTPUT);//Green LED

  digitalWrite (8, HIGH);//Turn on green LED
  
}

void loop() {
  Skywriter.poll();//Check if the status of the Flick has changed
  Serial.print(freq);//Print frequency control variable to the serial port
  Serial.print('a');//Print delimiter character to the serial port
  delay(1);//Small delay to avoid crashing the server in Supercollider
}

void handleAirwheel (int delta){
  if(delta>0){//Increase the frequency if clockwise rotation
    if(freq<1000){//Upper cutoff
      freq=freq+1;
      }
  }else if(delta<0){//Decrease the frequency if counterclockwise rotation
    if(freq>1){//Lower cutoff
      freq=freq-1;
      }
  }
}

Supercollider synth control via serial port

Plain text
This piece of code shows how to enable serial port communication and how to use the data printed to the serial port to modify the frequency of a sine wave
SerialPort.devices;                                           //Check the available ports
~port = SerialPort.new("/dev/ttyACM0",9600);

(
~charArray = [];                                              //An array to store the characters printed to the serial port
~getValues = Routine.new({                                    //The code inside the routine loops indefinitely
	var ascii;                                                //Supercollider read the characters in the serial port as ascii, so we need to convert them to numbers
	{
		ascii = ~port.read.asAscii;                           //We read the characters one by one and convert them to digits
		if(ascii.isDecDigit, {
			~charArray = ~charArray.add(ascii)
		});
		if(ascii == $a, {                                     //We stop reading the characters when Supercollider finds the delimiter 'a'
			~val = ~charArray.collect(_.digit).convertDigits; //We collect and combine the digits into a number
			~charArray = [];                                  //We empty the array
		})
	}.loop;
}).play
)

(
SynthDef.new(\sineWave, {                                     //Name of the Synth
	arg freq = 440;                                           //Frequency
	var sig;                                                  //Output
	sig = SinOsc.ar(freq,0,1);                                //Sine oscillator with frequency freq, phase 0 and amplitude 1
	Out.ar(0,sig);                                            //send output signal to the left speaker
}).add;
)

~synth = Synth(\sineWave, [\freq, 440]);                      //Play the synth
(
~control = Routine.new({                                      //Control routine
	{
		~synth.set(\freq, ~val.linexp(0,1000,80,2000));       //Change the frequency of the sine wave using the Flick, we use a exponential map as humans perceive frequency logarithmically
		0.01.wait;
	}.loop;
}).play;
)

~control.stop;
~synth.free;
~port.stop;

Gravity controller

Processing
We simulate a particle that falls under gravity and use the flick swiping gesture to control the direction of the gravity.
//Bouncing ball
//This sketch shows serial communication with Arduino
//We show a particle being accelerated in the +/-x or +/-y direction. The direction is changed by swiping on a 3d tracker and sending the value via serial port

//load libraries
import processing.serial.*;
Serial myPort;

float x=width/2, y=height/2; //initial position
float vX=0, vY=0; //initial velocity 
float gX=0, gY=0.1; // acceleration
int radius=15; //size of the particle 
int direction=0;

void setup(){
  size(400,400);
  background(255);
  myPort = new Serial(this, "/dev/ttyACM0",9600); //read data from serial port ttyACM0 same as in the Arduino project at 9600 Bd
  myPort.bufferUntil('\n'); //wait for the port to be ready
}

void serialEvent (Serial myPort){
  direction=int(float(myPort.readStringUntil('\n'))); //read data as an integer one line at a time
  gravity();//change acceleration
}

void draw(){
  stroke(255);
  fill(255,75);
  rect(0,0,width,height);
  stroke(0);
  fill(0);
  ellipse(x,y,2*radius,2*radius);
  move();
}

void move(){//move the particle
  vX+=gX; vY+=gY; //increase velocity
  x+=vX; y+=vY; //increase position
  if(x>width-radius){ //bounce at the borders
    x=width-radius; vX*=-0.75;
  }else if(x<radius){
    x=radius; vX*=-0.75;
  }else if(y>height-radius){
    y=height-radius; vY*=-0.75;
  }else if(y<radius){
    y=radius; vY*=-0.75;
  }
}

void gravity(){ //modify acceleration according to the swipe in the 3Dtracker
  if(direction==2){
    gX=0.1; gY=0;
  }else if(direction==3){
    gX=-0.1; gY=0;
  }else if(direction==4){
    gX=0; gY=-0.1;
  }else if(direction==5){
    gX=0; gY=0.1;
  }
}

Flick swipe gesture

Arduino
This piece of code shows how to handle the Flick swipe gesture in Arduino
#include <Wire.h>
#include <skywriter.h>

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);                       //Initialise serial communication at 9600 bds
  while(!Serial){};                         //We wait for the data serial port to start
  //Serial.println("Hello world!");           //The port is ready!

  Skywriter.begin(10,11);                   //Initialise the Flick with the pins TS=D10 and RESET=D11
  Skywriter.onGesture(handleGesture);       //This method records a gesture event up/down/right/left

  //We can initialise the LED pins as follows
  pinMode (9, OUTPUT);                      //Red LED
  pinMode (8, OUTPUT);                      //Green LED

  digitalWrite (8, HIGH);                   //Turn on green LED
  
}

void loop() {
  // put your main code here, to run repeatedly:
  Skywriter.poll();                         //Check if the status of the Flick has changed
}

void handleGesture(unsigned char type){
  Serial.println(type);                 //Prints 2 left-right, 3 right-left, 4 bottom-top, 5 top-bottom swipe
}

Simple oscilloscope

Processing
This code demostrates OSC communication between Processing and Supercollider
//Simple Oscilloscope
//This sketch demonstrates OSC communication between Processing and Supercollider
//This sketch plots a travelling wave representing a sine oscillator in Supercollider. The frequency is generated at random in Supercollider and received as an OSC message.
//The amplitude is controlled in Processing using the up and down keys and this increases or decreases the volume in supercollider via a OSC message  
//load libraries
import netP5.*;
import oscP5.*;

//declare objects osc and supercollider address
OscP5 osc;
NetAddress supercollider;

long iT=0; //Time index
float t; //time
float omega=TWO_PI/800; //angular frequency control variable
float amplitude=100; //amplitude control variable


void sineWave(float omega, float t){ //This function draws a sine wave travelling in the -x direction as a series of dots 
  for(float x=0; x<=width; x+=0.1){
    fill(255);
    stroke(255);
    ellipse(x,height/2-amplitude*sin(omega*(x+t)),1,1); //ellipse(posX,posY,radX,radY)
  }
}
    
void setup(){
  
  size(800,400);
  
  osc = new OscP5(this,12000); //construct object osc, this refernces the Processing sketch and 12000 the port at which it talks, it can be any number but there are some reserved for other purposes, a 5 digit number is usually safe to use
  supercollider = new NetAddress("127.0.0.1", 57120); //construct object supercollider 127.0.0.1 is the local IP Address and 57120 the port. You can get this info by evaluating the
}

void oscEvent(OscMessage theOscMessage){
  omega=TWO_PI*float(theOscMessage.get(0).intValue())/(440*width); //convert the frequency value sent as a string from supercollider into an integer and then calculate the angular frequency such that one period of a 440Hz wave fits in the screen 
}
  
void draw(){
  background(0);
  stroke(125);
  line(0,height/2,width,height/2);
  sineWave(omega,t);//plot sine wave
  iT+=1;//increase time and move the wave
  t=iT*10;
}

void keyPressed(){ //Registers when a key is pressed and stores it value on the variable key
  if(keyCode==UP){ //UP,DOWN,RIGHT and LEFT are coded keys
    if(amplitude<200){ //Increase amplitude
      amplitude+=10;
    }
  }else if(keyCode==DOWN){ //Decrease amplitude
    if(amplitude>0){
      amplitude-=10;
    }
  }
  OscMessage msg = new OscMessage("/amplitude"); //Construct OscMessage object with name /amplitude
  msg.add(map(amplitude,0,200,0,1)); //map the amplitude to the [0,1] range and add it to the OSC message
  osc.send(msg,supercollider); //send the message
}



/********Supercollider Code*********/
/*
//Simple Oscilloscope
//This example demonstrates communication between Processing and Supercollider using OSC messages. We generate a sine oscillator and then change its frequency at random.
//We send the frequency as an OSC message to Processing and use it to plot a travelling wave with the same frequency. The amplitude is controlled in Processing.

~processing = NetAddr.new("127.0.0.1",12000); //Construct Net Address object with local IP at port 12000, this has to match the port number used in Processing

(
SynthDef.new(\sineWave, { //Name of the Synth
	arg freq = 440, amp = 1; //Frequency
	var sig; //Output
	sig = SinOsc.ar(freq)*amp; //Sine oscillator with frequency freq, phase 0 and amplitude amp
	Out.ar(0,sig); //send output signal to the left speaker
}).add;
)

~synth = Synth(\sineWave, [\freq, 440, \amp, 0.5]);

(//control routine

~fr = rrand(220,3520); //change the frequency of the oscillator at random
~synth.set(\freq, ~fr);

~processing.sendMsg(
	'/frequency', ~fr  //send the value of the frequency as an OSC message
);

~fr;

)

( //This routine reads the message /amplitude from processing and uses it to control the volume of the sine wave
OSCdef('volume',{
	arg msg; //This variable stores the message
	~synth.set(\amp,msg[1]); //component [0] contains the OSC address, [1], [2],... contain the values added to the message
	},"/amplitude"); //the OSC message we are listening to
)


~synth.free; //stop the oscillator
*/

Credits

Christian Esparza

Christian Esparza

1 project • 2 followers
Thanks to Eli Fieldsteel and Abe Pazos.

Comments