diff --git a/examples/Processing/sequencer/data/glass.jpeg b/examples/Processing/sequencer/data/glass.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d045dc95c89f296acc673799896cb771a9528f60 Binary files /dev/null and b/examples/Processing/sequencer/data/glass.jpeg differ diff --git a/examples/Processing/sequencer/sequencer.pde b/examples/Processing/sequencer/sequencer.pde index 38071ab88578a426a321f0324328939a339ec532..c7abc152370d54ab5ed58f7f332ac323f9c217cf 100644 --- a/examples/Processing/sequencer/sequencer.pde +++ b/examples/Processing/sequencer/sequencer.pde @@ -4,22 +4,22 @@ float BPM = 120; int[][] pattern = { + {0, 0, 0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, 0, 0, 0, 0}, + {0, 1, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0, 0, 0, 1}, + {1, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 1, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 1}, + {0, 0, 0, 0, 1, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 1, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0} + {0, 0, 0, 0, 1, 0, 0, 1} }; OPC opc; @@ -39,7 +39,8 @@ float patternDuration = pattern.length / rowsPerSecond; int ledX = 400; int ledY = 360/2; int ledSpacing = 15; -int ledBottom = int(ledY + 3.5 * ledSpacing); +int ledWidth = ledSpacing * 23; +int ledHeight = ledSpacing * 7; // Images PImage imgGreenDot; @@ -47,7 +48,11 @@ PImage imgOrangeDot; PImage imgPinkDot; PImage imgPurpleDot; PImage imgCheckers; +PImage imgGlass; +PImage[] dots; +// Timekeeping +long startTime, pauseTime; void setup() { @@ -58,6 +63,10 @@ void setup() imgPinkDot = loadImage("pinkDot.png"); imgPurpleDot = loadImage("purpleDot.png"); imgCheckers = loadImage("checkers.png"); + imgGlass = loadImage("glass.jpeg"); + + // Keep our multicolored dots in an array for easy access later + dots = new PImage[] { imgOrangeDot, imgPurpleDot, imgPinkDot, imgGreenDot }; // Connect to the local instance of fcserver. You can change this line to connect to another computer's fcserver opc = new OPC(this, "127.0.0.1", 7890); @@ -82,17 +91,54 @@ void setup() // If the Fadecandy board's status LED is bothersome, it's easy to turn off or repurpose. opc.setStatusLed(false); + + // Init timekeeping, start the pattern from the beginning + startPattern(); } void draw() { background(0); - float now = millis() * 1e-3; + long m = millis(); + if (pauseTime != 0) { + // Advance startTime forward while paused, so we don't make any progress + long delta = m - pauseTime; + startTime += delta; + pauseTime += delta; + } + + float now = (m - startTime) * 1e-3; drawEffects(now); drawGrid(now); + drawInstructions(); +} + +void clearPattern() +{ + for (int row = 0; row < pattern.length; row++) { + for (int col = 0; col < pattern[0].length; col++) { + pattern[row][col] = 0; + } + } } +void startPattern() +{ + startTime = millis(); + pauseTime = 0; +} + +void pausePattern() +{ + if (pauseTime == 0) { + // Pause by stopping the clock and remembering when to unpause at + pauseTime = millis(); + } else { + pauseTime = 0; + } +} + void mousePressed() { int gx = (mouseX - gridX) / gridSquareSpacing; @@ -100,7 +146,15 @@ void mousePressed() if (gx >= 0 && gx < pattern[0].length && gy >= 0 && gy < pattern.length) { pattern[gy][gx] ^= 1; } -} +} + +void keyPressed() +{ + if (keyCode == DELETE) clearPattern(); + if (keyCode == BACKSPACE) clearPattern(); + if (keyCode == UP) startPattern(); + if (key == ' ') pausePattern(); +} void drawGrid(float now) { @@ -122,6 +176,18 @@ void drawGrid(float now) } } +void drawInstructions() +{ + int size = 12; + int x = gridX + gridSquareSpacing * pattern[0].length + 5; + int y = gridY + size; + + fill(255); + textSize(size); + + text("<- Click squares to create an effect pattern\n[Delete] to clear, [Space] to pause, [Up] to restart pattern.\n", x, y); +} + void drawEffects(float now) { // To keep this simple and flexible, we'll calculate every possible effect that @@ -136,29 +202,49 @@ void drawEffects(float now) for (int row = 0; row < pattern.length; row++) { for (int col = 0; col < pattern[0].length; col++) { if (pattern[row][col] != 0) { - float firingTime = patternStartTime + patternDuration * whichPattern + rowDuration * row; - drawSingleEffect(col, now - firingTime); + float patternTime = patternStartTime + patternDuration * whichPattern; + float firingTime = patternTime + rowDuration * row; + drawSingleEffect(col, firingTime, now); } } } } } -void drawSingleEffect(int column, float time) +void drawSingleEffect(int column, float firingTime, float now) { + /* + * Map sequencer columns to effects. Edit this to add your own effects! + * + * Parameters: + * column -- Number of the column in the sequencer that we're drawing an effect for. + * firingTime -- Time at which the effect in question should fire. May be in the + * past or the future. This number also uniquely identifies a particular + * instance of an effect. + * now -- The current time, in seconds. + */ + + float timeDelta = now - firingTime; + switch (column) { // First four tracks: Colored dots rising from below - case 0: drawDotEffect(column, time, imgOrangeDot); break; - case 1: drawDotEffect(column, time, imgPinkDot); break; - case 2: drawDotEffect(column, time, imgPurpleDot); break; - case 3: drawDotEffect(column, time, imgGreenDot); break; - - // Spinner overlay - case 6: drawSpinnerEffect(time, imgCheckers); break; + case 0: + case 1: + case 2: + case 3: + drawDotEffect(column, timeDelta, dots[column]); + break; + + // Stripes moving from left to right. Each stripe particle is unique based on firingTime. + case 4: drawStripeEffect(firingTime, timeDelta); break; + + // Image spinner overlays + case 5: drawSpinnerEffect(timeDelta, imgCheckers); break; + case 6: drawSpinnerEffect(timeDelta, imgGlass); break; // Full-frame flash effect - case 7: drawFlashEffect(time); break; + case 7: drawFlashEffect(timeDelta); break; } } @@ -172,7 +258,7 @@ void drawDotEffect(int column, float time, PImage im) float shrinkSpeed = motionSpeed * 1.2; float size = 200 - max(0, time * shrinkSpeed); float centerX = ledX + (column - 1.5) * 75.0; - float topY = ledBottom - time * motionSpeed; + float topY = ledY + ledHeight/2 - time * motionSpeed; int brightness = int(255 - max(0, fadeSpeed * time)); // Adjust the 'top' position so the dot seems to appear on-time @@ -187,20 +273,22 @@ void drawDotEffect(int column, float time, PImage im) void drawSpinnerEffect(float time, PImage im) { - float t = time / (rowDuration * 8.0); - if (t < 0 || t > 1) return; + float t = time / (rowDuration * 1.5); + if (t < -1 || t > 1) return; - float angle = t * 2.5; + float angle = time * 5.0; float size = 400; - int alpha = int(100 * sin(t * PI)); + int alpha = int(128 * (1.0 + cos(t * PI))); - pushMatrix(); - translate(ledX, ledY); - rotate(angle); - blendMode(ADD); - tint(alpha); - image(im, -size/2, -size/2, size, size); - popMatrix(); + if (alpha > 0) { + pushMatrix(); + translate(ledX, ledY); + rotate(angle); + blendMode(ADD); + tint(alpha); + image(im, -size/2, -size/2, size, size); + popMatrix(); + } } void drawFlashEffect(float time) @@ -214,3 +302,21 @@ void drawFlashEffect(float time) rect(0, 0, width, height); } +void drawStripeEffect(float identity, float time) +{ + // Pick a pseudorandom dot and Y position + randomSeed(int(identity * 1e3)); + PImage im = dots[int(random(dots.length))]; + float y = ledY - ledHeight/2 + random(ledHeight); + + // Animation + float motionSpeed = rowsPerSecond * 400.0; + float x = ledX - ledWidth/2 + time * motionSpeed; + float sizeX = 300; + float sizeY = 30; + + blendMode(ADD); + tint(255); + image(im, x - sizeX/2, y - sizeY/2, sizeX, sizeY); +} +