Skip to content

Instantly share code, notes, and snippets.

@golanlevin
Last active October 19, 2020 13:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save golanlevin/621e283b11fa68e0f6b738b82e3f128b to your computer and use it in GitHub Desktop.
Save golanlevin/621e283b11fa68e0f6b738b82e3f128b to your computer and use it in GitHub Desktop.
Palm synth
// https://imgur.com/a/ZEE2f
final color wristCol = color(255, 0, 255);
final color palmCol = color(127);
final color thumbCol = color(0, 255, 0);
final color finger1Col = color(255, 0, 0);
final color finger2Col = color(0, 0, 255);
final color finger3Col = color(0, 255, 255);
final color finger4Col = color(255, 255, 0);
color fingerColors[] = {
finger1Col, finger2Col, finger3Col, finger4Col
};
float fingerDigitRatios[] = {
0.86, 1.00, 0.90, 0.65
};
float fingerThicknessRatios[] = {
0.94, 1.00, 0.95, 0.87
};
PGraphics medianGraphics;
int medianIndexCol[];
boolean bDebugView = false;
float targetLenPerUnit = 25.0;
float fingerAspect = 4.0;
float probs[];
char codes[] = {'W', 'T', 'b', 'B', '1', '2', '3'};
float units[] = {3, 3, 1, 2, 1, 2, 3};
float wristProbability = 0.15;
float thumbProbability = 0.20;
float blnk1Probability = 0.10;
float blnk2Probability = 0.10;
float fing1Probability = 0.20;
float fing2Probability = 0.20;
float fing3Probability = 0.05;
int nPalms;
String palmStrings[];
Palm palms[];
class Palm {
int nSides;
String palmCode;
char sideTypes[];
float sideUnits[];
int totalUnits;
PVector crotchPoints[];
float crotchAngles[];
float angleDeltas[];
float diameter;
//-----------------------------------------
Palm (String aCode) {
diameter = 160;
palmCode = aCode;
nSides = palmCode.length();
sideTypes = new char[nSides];
sideUnits = new float[nSides];
for (int i=0; i<nSides; i++) {
sideTypes[i] = palmCode.charAt(i);
for (int k=0; k<codes.length; k++) {
if (sideTypes[i] == codes[k]) {
sideUnits[i] = units[k];
totalUnits += units[k];
}
}
}
createCrotchPoints();
}
//-----------------------------------------
void createCrotchPoints() {
crotchPoints = new PVector[nSides];
crotchAngles = new float[nSides];
angleDeltas = new float[nSides];
float ang = random(0, TWO_PI);
for (int i=0; i<nSides; i++) {
crotchAngles[i] = ang;
angleDeltas[i] = 0;
float px = diameter/2 * cos(ang);
float py = diameter/2 * sin(ang);
crotchPoints[i] = new PVector(px, py);
ang += TWO_PI * (float)sideUnits[i] / (float)totalUnits;
}
}
//-----------------------------------------
void updateCrotchPoints() {
// compute perimeter
float perimeter = 0;
for (int i=0; i<nSides; i++) {
angleDeltas[i] = 0;
PVector pi = crotchPoints[i];
PVector pj = crotchPoints[(i+1)%nSides];
float sideLen = dist(pi.x, pi.y, pj.x, pj.y);
perimeter += sideLen;
}
float lenPerUnit = perimeter/ (float)totalUnits;
float error = 0;
for (int i=0; i<nSides; i++) {
int j = (i+1)%nSides;
PVector pi = crotchPoints[i];
PVector pj = crotchPoints[j];
float actualSideLength = dist(pi.x, pi.y, pj.x, pj.y)/lenPerUnit;
float targetSideLength = sideUnits[i];
float lenDelta = (targetSideLength - actualSideLength);
error += abs(lenDelta);
float angi = crotchAngles[i];
float angj = crotchAngles[j];
float dang = angj - angi;
float angm = (angi + angj)/2.0;
if (j == 0) {
angm = (angm+PI)%TWO_PI;
}
if (dang < 0) {
dang += TWO_PI;
}
angleDeltas[i] -= lenDelta/100.0;
angleDeltas[j] += lenDelta/100.0;
}
for (int i=0; i<nSides; i++) {
crotchAngles[i] += angleDeltas[i];
}
for (int i=0; i<nSides; i++) {
angleDeltas[i] = 0;
float px = diameter/2 * cos(crotchAngles[i]);
float py = diameter/2 * sin(crotchAngles[i]);
crotchPoints[i].x = px;
crotchPoints[i].y = py;
}
// alter the diameter to hold the unit length constant
diameter = 0.95*diameter + 0.05*diameter*(targetLenPerUnit/lenPerUnit);
}
//-----------------------------------------
void render() {
updateCrotchPoints();
drawThumbs();
drawPalm();
drawFingers();
drawDebug();
}
//============================================================
void drawThumbs() {
for (int i=0; i<nSides; i++) {
if (sideTypes[i] == 'T') {
int j = (i+1)%nSides;
float pxi = crotchPoints[i].x;
float pyi = crotchPoints[i].y;
float pxj = crotchPoints[j].x;
float pyj = crotchPoints[j].y;
int swap = 1;
if (random(1.0) < 0.5) {
swap = -1;
float sx = pxi;
float sy = pyi;
pxi = pxj;
pyi = pyj;
pxj = sx;
pyj = sy;
}
float dx = pxj - pxi;
float dy = pyj - pyi;
float dh = dist(pxi, pyi, pxj, pyj);
float thumbThickness = targetLenPerUnit * 0.75;
float ux = pxi * (diameter*0.5 - thumbThickness*0.5)/(diameter*0.5);
float uy = pyi * (diameter*0.5 - thumbThickness*0.5)/(diameter*0.5);
float thumbBaseAng = atan2(dy, dx);
float thumbLength = dh;
float thang1 = swap * random(-HALF_PI, -PI);
float thang2 = swap * random(-PI*0.8, -PI*0.2);
float vx = ux + 0.5*thumbLength * cos(thumbBaseAng + 0.5*thang1);
float vy = uy + 0.5*thumbLength * sin(thumbBaseAng + 0.5*thang1);
float wx = vx + 0.5*thumbLength * cos(thumbBaseAng + 0.5*thang2);
float wy = vy + 0.5*thumbLength * sin(thumbBaseAng + 0.5*thang2);
noFill();
stroke(thumbCol);
strokeCap(ROUND);
curveTightness(0.0);
// line(ux,uy, vx,vy);
// line(vx,vy, wx,wy);
strokeWeight(thumbThickness);
beginShape();
curveVertex(ux, uy);
curveVertex(ux, uy);
curveVertex(vx, vy);
curveVertex(wx, wy);
curveVertex(wx, wy);
endShape();
strokeWeight(thumbThickness*0.9);
beginShape();
curveVertex(pxi+dx*0.4, pyi+dy*0.4);
curveVertex(pxi+dx*0.4, pyi+dy*0.4);
curveVertex(vx, vy);
curveVertex(wx, wy);
curveVertex(wx, wy);
endShape();
}
}
}
//============================================================
void drawFingers() {
// draw fingers
int fingerDir = 1;
int fingerCount = 0;
int fingerIndex = 0;
for (int i=0; i<nSides; i++) {
if ((sideTypes[i] >= '1') && (sideTypes[i] <= '3')) {
int j = (i+1)%nSides;
float pxi = crotchPoints[i].x;
float pyi = crotchPoints[i].y;
float pxj = crotchPoints[j].x;
float pyj = crotchPoints[j].y;
float dx = pxj - pxi;
float dy = pyj - pyi;
float dh = dist(pxi, pyi, pxj, pyj);
int nFingers = (int) sideUnits[i];
int fingerCurveDir = (int)((random(1.0) < 0.5) ? -1 : 1);
for (int n=0; n<nFingers; n++) {
float txa = map(n+0.4, 0, nFingers, pxi, pxj);
float tya = map(n+0.4, 0, nFingers, pyi, pyj);
float txb = map(n+0.5, 0, nFingers, pxi, pxj);
float tyb = map(n+0.5, 0, nFingers, pyi, pyj);
float txc = map(n+0.6, 0, nFingers, pxi, pxj);
float tyc = map(n+0.6, 0, nFingers, pyi, pyj);
int f = n;
switch(nFingers) {
case 1:
fingerIndex = (random(1.0) < 0.5) ? 0:3;
break;
case 2:
case 3:
switch (fingerIndex) {
case 3:
fingerIndex = 2;
fingerDir = -1;
break;
case 0:
fingerIndex = 1;
fingerDir = 1;
break;
case 1:
case 2:
fingerIndex += fingerDir;
}
break;
}
f = fingerIndex;
float fingerThickness = targetLenPerUnit * 0.75;
float fh = fingerThickness * fingerAspect * fingerDigitRatios[f];
float fx = (dy/dh)*fh;
float fy = (dx/dh)*fh*(-1);
float angi = crotchAngles[i];
float angj = crotchAngles[j];
if (angj < angi) {
angj += TWO_PI;
}
float t01 = map(n+0.5, 0, nFingers, 0, 1);
t01 = function_DoubleExponentialOgee (t01, 0.75);
float angf = map(t01, 0, 1, angi, angj);
fx = fh * cos(angf);
fy = fh * sin(angf);
noFill();
strokeCap(ROUND);
stroke(fingerColors[f]);
strokeWeight(fingerThickness * fingerThicknessRatios[f]);
curveTightness(0.0);
float er = 0.1*fingerCurveDir*random(0, 1);
float ex = fx/2 - er*fy;
float ey = fy/2 + er*fx;
beginShape();
curveVertex(txa, tya);
curveVertex(txa, tya);
curveVertex(txb+ex, tyb+ey);
curveVertex(txb+fx, tyb+fy);
curveVertex(txb+fx, tyb+fy);
endShape();
beginShape();
curveVertex(txb, tyb);
curveVertex(txb, tyb);
curveVertex(txb+ex, tyb+ey);
curveVertex(txb+fx, tyb+fy);
curveVertex(txb+fx, tyb+fy);
endShape();
beginShape();
curveVertex(txc, tyc);
curveVertex(txc, tyc);
curveVertex(txb+ex, tyb+ey);
curveVertex(txb+fx, tyb+fy);
curveVertex(txb+fx, tyb+fy);
endShape();
//line(tx, ty, tx+fx, ty+fy);
}
}
}
}
//============================================================
void drawPalm() {
if (bDebugView) {
fill(127, 127);
stroke(127);
} else {
noStroke();
}
// draw the Palm polygon
curveTightness(0.6666); //mouseX/(float)width); //0.25); //1.0);
strokeJoin(ROUND);
noStroke();
fill(palmCol);
beginShape();
for (int i=-1; i<nSides; i++) {
int j=constrain(i, 0, nSides-1);
float px = crotchPoints[j].x;
float py = crotchPoints[j].y;
curveVertex(px, py);
}
curveVertex(crotchPoints[0].x, crotchPoints[0].y);
curveVertex(crotchPoints[1].x, crotchPoints[1].y);
endShape(CLOSE);
strokeWeight(targetLenPerUnit * 0.15);
stroke(127);
noFill();
beginShape();
for (int i=-1; i<nSides; i++) {
int j=constrain(i, 0, nSides-1);
float px = crotchPoints[j].x;
float py = crotchPoints[j].y;
curveVertex(px, py);
}
curveVertex(crotchPoints[0].x, crotchPoints[0].y);
curveVertex(crotchPoints[1].x, crotchPoints[1].y);
endShape(CLOSE);
}
//============================================================
void drawDebug() {
if (bDebugView) {
noFill();
stroke(127);
strokeWeight(1);
ellipse(0, 0, diameter, diameter);
for (int i=0; i<nSides; i++) {
int j = (i+1)%nSides;
float pxi = crotchPoints[i].x;
float pyi = crotchPoints[i].y;
float pxj = crotchPoints[j].x;
float pyj = crotchPoints[j].y;
fill(127);
for (int n=0; n<=sideUnits[i]; n++) {
float tx = map(n, 0, sideUnits[i], pxi, pxj);
float ty = map(n, 0, sideUnits[i], pyi, pyj);
ellipse(tx, ty, 3, 3);
}
}
textAlign(CENTER);
for (int i=0; i<nSides; i++) {
float px = crotchPoints[i].x;
float py = crotchPoints[i].y;
stroke(160);
line (0, 0, px, py);
fill(255);
text(sideTypes[i], px, py);
// text(i, px, py);// ":" + sideUnits[i]
}
fill(255);
text(palmCode, 0, diameter/2 + 20);
}
}
}
//============================================================
void setup() {
size(600, 600);
medianGraphics = createGraphics(width, height);
noSmooth();
medianIndexCol = new int[49];
makeProbs();
nPalms = 4;
palmStrings = new String[nPalms];
palms = new Palm[nPalms];
for (int i=0; i<nPalms; i++) {
// Generate palmCodes, like 3T1W, TW230, 1TW0TW
int nPalmSides = (int)random(5.0, 7.5);
String palmStr = "";
char prevElementChar = ' ';
for (int j=0; j<nPalmSides; j++) {
char elementChar = ' ';
do {
float r = random(1.0);
int k = 0;
while (r > probs[k]) {
k++;
}
k--;
elementChar = codes[k];
} while ((elementChar == prevElementChar) || (abs(elementChar-prevElementChar) == 32));
palmStr += elementChar;
prevElementChar = elementChar;
}
palms[i] = new Palm(palmStr);
}
}
void draw() {
background(0);
randomSeed(hour() + minute() + second());
for (int i=0; i<nPalms; i++) {
float cx = (i%2+1)*(width/3);//+(width/4);
float cy = (i/2+1)*(width/3);//+(width/4);
pushMatrix();
translate(cx, cy);
palms[i].render();
popMatrix();
}
boolean bComputeMedianImage = false;
if (bComputeMedianImage) {
loadPixels();
//medianGraphics
medianGraphics.beginDraw();
medianGraphics.background(0);
medianGraphics.loadPixels();
int index;
int indexij;
for (int y=3; y<(height-3); y++) {
for (int x=3; x<(width-3); x++) {
int medi = 0;
for (int i=(y-3); i<=(y+3); i++) {
for (int j=(x-3); j<=(x+3); j++) {
indexij = i*width + j;
int pixelAtij = pixels[indexij];
switch(pixelAtij) {
case -16777216: // black
medianIndexCol[medi] = -1;
break;
case -65281: //wristCol:
medianIndexCol[medi] = 0;
break;
case -8421505: //palmCol:
medianIndexCol[medi] = 1;
break;
case -16711936: //thumbCol:
medianIndexCol[medi] = 2;
break;
case -65536: //finger1Col:
medianIndexCol[medi] = 3;
break;
case -16776961: //finger2Col:
medianIndexCol[medi] = 4;
break;
case -16711681: //finger3Col:
medianIndexCol[medi] = 5;
break;
case -256: //finger4Col:
medianIndexCol[medi] = 6;
break;
}
medi++;
}
}
index = y * width + x;
medianIndexCol = sort(medianIndexCol);
switch(medianIndexCol[24]) {
case -1:
medianGraphics.pixels[index] = color(0);
break;
case 0:
medianGraphics.pixels[index] = wristCol;
break;
case 1:
medianGraphics.pixels[index] = palmCol;
break;
case 2:
medianGraphics.pixels[index] = thumbCol;
break;
case 3:
medianGraphics.pixels[index] = finger1Col;
break;
case 4:
medianGraphics.pixels[index] = finger2Col;
break;
case 5:
medianGraphics.pixels[index] = finger3Col;
break;
case 6:
medianGraphics.pixels[index] = finger4Col;
break;
}
}
}
medianGraphics.updatePixels();
medianGraphics.endDraw();
image(medianGraphics, width/2, 0);
}
}
void makeProbs() {
probs = new float[8];
int c = 0;
probs[c] = 0;
c++;
probs[c] = probs[c-1] + wristProbability;
c++;
probs[c] = probs[c-1] + thumbProbability;
c++;
probs[c] = probs[c-1] + blnk1Probability;
c++;
probs[c] = probs[c-1] + blnk2Probability;
c++;
probs[c] = probs[c-1] + fing1Probability;
c++;
probs[c] = probs[c-1] + fing2Probability;
c++;
probs[c] = probs[c-1] + fing3Probability;
c++;
println("Checksum = " + probs[7]);
}
//------------------------------------------------------------------
float function_DoubleExponentialOgee (float x, float a) {
float min_param_a = 0.0 + EPSILON;
float max_param_a = 1.0 - EPSILON;
a = constrain(a, min_param_a, max_param_a);
float y = 0;
if (x<=0.5) {
y = (pow(2.0*x, 1.0-a))/2.0;
} else {
y = 1.0 - (pow(2.0*(1.0-x), 1.0-a))/2.0;
}
return y;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment