This collaborative experiment ended up coming together quite nicely!
The midi inputs are visualized as ellipses along a grid; their position is dependent on the note and the diameter is dependent on the velocity. If the wacom cursor passes through one of these ellipses before it has faded, it will play the note again at that velocity.
I added some last minute functionality that makes for a more expressive experience: the pressure of the wacom pen controls the stroke weight globally, clearing the canvas is enabled (which can also create a pulsing effect, or turn the line into a fleeting white dot), and holding down a key while passing through an ellipse will echo that ellipse as 1/8th notes until it fades away.
I think, once we practice, we can get something beautiful going. But for now, a clumsy collaboration/ exploration with Yotam on keyboard and me on the Wacom:
And for your additional viewing pleasure, a bunch of code:
//PRESS b while drawing within a dot to initiate repeat
//PRESS c to clear canvas
import codeanticode.protablet.*;
import promidi.*;
MidiIO midiIO;
MidiOut midiOut;
Tablet tablet;
//set these each song
int tempo = 102;
float minPitch = 21; // set these depending on the number of octaves (small keyboard 48)
float maxPitch = 102; //set these depending on the number of octaves (small keyboard 73)
float numOfPitches = maxPitch-minPitch; // this is to set how many spots the notes can appear.
PImage curs;
int numberOfColumns = int (sqrt(numOfPitches));
int numberOfRows = int (sqrt(numOfPitches));
ArrayList <Dot> Dots = new ArrayList();
void setup() {
size(850, 850);
frame.setBackground(new java.awt.Color(0));
background(0);
tablet = new Tablet(this);
midiIO = MidiIO.getInstance(this);
midiIO.printDevices();
midiIO.openInput(0, 0);
//open an midiout using the first device and the first channel
//good ones: 4, 5
//10- space bells
//12 – decay bells
//fine: 8 – vibes
//7 computer beep
//14 harmonic church bells
midiOut = midiIO.getMidiOut(12, 1);
curs = loadImage(“cursor.png”);
cursor(curs);
}
void draw() {
// background(0);
ArrayList<Dot> keepList = new ArrayList();
for (int i = 0; i<Dots.size();i++) {
Dot d = Dots.get(i);
d.update();
if (!d.done()) {
d.drawDot(); //this would draw the specific dot that has been created in the noteOn function below.
keepList.add(d);
}
}
Dots = keepList;
if (keyPressed) {
if (key == ‘c’) {
background(0);
}
}
}
void noteOn(Note note, int device, int channel) {
int vel = note.getVelocity();
if (vel != 127) {
int pit = note.getPitch();
// println(pit);
noFill();
Dot d = new Dot(pit, vel); //here I am invoking the constructor, and making the parameters specific to the actual note that is coming in by using my own variables
Dots.add(d); //this adds my new dot or “pushes it” after a note has been played
}
}
void mouseDragged() {
float tabPressure = tablet.getPressure();
strokeWeight(10 * tabPressure);
stroke(255);
line(mouseX, mouseY, pmouseX, pmouseY);
//test every circle to see if which circle has been touched by mouseX/mouseY
//backwards loop so that the note which is played is the LAST one!
for (int i = Dots.size(); i>0; i–) {
Dot d = Dots.get(i-1);
if (d.drawnOver(mouseX, mouseY)) {
d.playNote(tabPressure);
if (keyPressed) {
if (key==’b') { //32 is the key code for spacebar
d.dotClicked = true;
}
}
break;//exits the loopdadoop
}
}
}
class Dot {
/*
CLASS VARIABLES
*/
int fadeShade = 255;
float pitch = 0;
int velocity = 0;
float dotX, dotY; //these are the dot’s drawing position
float radius = 30; //if you want to decide this something based on the input, do it within the Dot constructor.
int lastPlayed = 0;
boolean dotClicked =false;
float maxVelocity = 1;
/*
CONSTRUCTOR
*/
Dot (float _pitch, int _vel) { //this is the constructor. I am setting arguments for dot: the pitch and velocity of the note
pitch = map(_pitch, minPitch, maxPitch, 0, numOfPitches); // maps the incoming pitch to the number of pitches (makes 48 into 1, for instance);
velocity = _vel;
radius = map(velocity, 0, 127, 2, width/numberOfRows);
dotX = map(pitch%numberOfColumns, 0, numberOfColumns, .05*width, width-.05*width);
dotY = map(floor(pitch/numberOfRows), 0, numberOfRows, .05*height, height -.05*height);
}
/*
CLASS METHODS
*/
//called every loop
void update() {
fadeShade-=2;//subtracts by two (as opposed to — which subtract by one);
if (dotClicked) {
playNote(maxVelocity);
}
}
void drawDot() {
noFill();
float tabPressure = tablet.getPressure();
strokeWeight(10 * tabPressure);
stroke(fadeShade);
if (dotClicked) {
fill(fadeShade);
}
ellipse(dotX, dotY, radius, radius);
}
boolean done() { //boolean replaces void because this function will return a boolean about whether or not the dot is ready to dissapear
return fadeShade < 1; //this is how you have something get returned, it’s called a return statement
}
boolean drawnOver(int mX, int mY) { //this boolean is used to say whether or not it’s true that an X and Y are over a Dot;
float xDiff = dotX-mX;
float yDiff = dotY-mY;
float dist = sqrt((xDiff*xDiff)+(yDiff*yDiff)); // this is an equasion used to find the distance between the passed in point and the center of my circle
return dist<radius/2;
}
void playNote(float maxPressure) {
maxVelocity = maxPressure;
int now = millis();
int timeDiff = now – lastPlayed;
int eighthNote = 30000/tempo;
if (timeDiff > eighthNote) {
int mappedPitch = (int) map(pitch, 0, numOfPitches, minPitch, maxPitch);
int mappedVelocity = (int) map(fadeShade * maxVelocity, 0, 255, 0, 127);
Note note = new Note(mappedPitch, mappedVelocity, eighthNote);
midiOut.sendNote(note);
lastPlayed = now;
}
}
}



