Last active
August 29, 2015 14:13
-
-
Save embedded-creations/5cd47d83cb0e04f4574d to your computer and use it in GitHub Desktop.
NoiseSmearing Refactored - 20150117
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include<SmartMatrix_32x32.h> | |
#include<FastLED.h> | |
// NoiseSmearing by Stefan Petrick: https://gist.github.com/StefanPetrick/9ee2f677dbff64e3ba7a | |
// Timed playlist code is from Mark Kriegsman's TimedPlaylist demo here: https://gist.github.com/kriegsman/841c8cd66ed40c6ecaae | |
// Some refactoring by Louis Beaudoin | |
#if FASTLED_VERSION < 3001000 | |
#error "Requires FastLED 3.1 or later; check github for latest code." | |
#endif | |
#define kMatrixWidth 32 | |
#define kMatrixHeight 32 | |
byte CentreX = (kMatrixWidth / 2) - 1; | |
byte CentreY = (kMatrixHeight / 2) - 1; | |
#define NUM_LEDS (kMatrixWidth * kMatrixHeight) | |
CRGB leds[kMatrixWidth * kMatrixHeight]; | |
CRGB leds2[kMatrixWidth * kMatrixHeight]; | |
// The coordinates for 3 16-bit noise spaces. | |
#define NUM_LAYERS 1 | |
uint32_t x[NUM_LAYERS]; | |
uint32_t y[NUM_LAYERS]; | |
uint32_t z[NUM_LAYERS]; | |
uint32_t scale_x[NUM_LAYERS]; | |
uint32_t scale_y[NUM_LAYERS]; | |
uint8_t noise[NUM_LAYERS][kMatrixWidth][kMatrixHeight]; | |
uint8_t noisesmoothing; | |
// List of patterns to cycle through. Each is defined as a separate function below. | |
typedef void(*SimplePattern)(); | |
typedef SimplePattern SimplePatternList []; | |
typedef struct { SimplePattern mPattern; uint16_t mTime; } PatternAndTime; | |
typedef PatternAndTime PatternAndTimeList []; | |
// These times are in seconds, but could be changed to milliseconds if desired; | |
// there's some discussion further below. | |
const PatternAndTimeList gPlaylist = { | |
{ MultipleStream, 5 }, | |
{ MultipleStream2, 10 }, // nice and slow, three dots | |
{ MultipleStream3, 10 }, // fire across the midddle: 8 dots in a line across the middle, slow noise smear | |
{ MultipleStream4, 5 }, // a single dot in the middle that rapidly changes color, with very fast noise smearing | |
{ MultipleStream5, 5 }, // fire across the bottom: 8 dots in a line across the bottom, slow noise smear | |
{ MultipleStream8, 10 }, | |
}; | |
// If you want the playlist to loop forever, set this to true. | |
// If you want the playlist to play once, and then stay on the final pattern | |
// until the playlist is reset, set this to false. | |
bool gLoopPlaylist = true; | |
void setup() { | |
Serial.begin(115200); | |
LEDS.addLeds<SMART_MATRIX>(leds,NUM_LEDS); | |
FastLED.setDither(0); | |
pSmartMatrix->setColorCorrection(cc48); | |
BasicVariablesSetup(); | |
RestartPlaylist(); | |
} | |
uint8_t gCurrentTrackNumber = 0; // Index number of which pattern is current | |
bool gRestartPlaylistFlag = false; | |
void loop() { | |
// Call the current pattern function once, updating the 'leds' array | |
gPlaylist[gCurrentTrackNumber].mPattern(); | |
//MultipleStream(); | |
//MultipleStream2(); | |
//MultipleStream3(); | |
//MultipleStream4(); | |
//MultipleStream5(); | |
//MultipleStream8(); | |
FastLED.show(); | |
// Here's where we do two things: switch patterns, and also set the | |
// 'virtual timer' for how long until the NEXT pattern switch. | |
// | |
// Instead of EVERY_N_SECONDS(10) { nextPattern(); }, we use a special | |
// variation that allows us to get at the pattern timer object itself, | |
// and change the timer period every time we change the pattern. | |
// | |
// You could also do this with EVERY_N_MILLISECONDS_I and have the | |
// times be expressed in milliseconds instead of seconds. | |
{ | |
EVERY_N_SECONDS_I(patternTimer, gPlaylist[gCurrentTrackNumber].mTime) { | |
nextPattern(); | |
patternTimer.setPeriod(gPlaylist[gCurrentTrackNumber].mTime); | |
} | |
// Here's where we handle restarting the playlist if the 'reset' flag | |
// has been set. There are a few steps: | |
if (gRestartPlaylistFlag) { | |
// Set the 'current pattern number' back to zero | |
gCurrentTrackNumber = 0; | |
// Set the playback duration for this patter to it's correct time | |
patternTimer.setPeriod(gPlaylist[gCurrentTrackNumber].mTime); | |
// Reset the pattern timer so that we start marking time from right now | |
patternTimer.reset(); | |
// Finally, clear the gRestartPlaylistFlag flag | |
gRestartPlaylistFlag = false; | |
} | |
} | |
} | |
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) | |
void nextPattern() | |
{ | |
// add one to the current pattern number | |
gCurrentTrackNumber = gCurrentTrackNumber + 1; | |
// If we've come to the end of the playlist, we can either | |
// automatically restart it at the beginning, or just stay at the end. | |
if (gCurrentTrackNumber == ARRAY_SIZE(gPlaylist)) { | |
if (gLoopPlaylist == true) { | |
// restart at beginning | |
gCurrentTrackNumber = 0; | |
} | |
else { | |
// stay on the last track | |
gCurrentTrackNumber--; | |
} | |
} | |
} | |
void RestartPlaylist() | |
{ | |
gRestartPlaylistFlag = true; | |
} | |
void BasicVariablesSetup() { | |
noisesmoothing = 200; | |
for(int i = 0; i < NUM_LAYERS; i++) { | |
x[i] = random16(); | |
y[i] = random16(); | |
z[i] = random16(); | |
scale_x[i] = 6000; | |
scale_y[i] = 6000; | |
} | |
} | |
uint16_t XY( uint8_t x, uint8_t y) { | |
uint16_t i; | |
i = (y * kMatrixWidth) + x; | |
return i; | |
} | |
void DimAll(byte value) | |
{ | |
for(int i = 0; i < NUM_LEDS; i++) { | |
leds[i].nscale8(value); | |
} | |
} | |
void FillNoise(byte layer) { | |
for(uint8_t i = 0; i < kMatrixWidth; i++) { | |
uint32_t ioffset = scale_x[layer] * (i-CentreX); | |
for(uint8_t j = 0; j < kMatrixHeight; j++) { | |
uint32_t joffset = scale_y[layer] * (j-CentreY); | |
byte data = inoise16(x[layer] + ioffset, y[layer] + joffset, z[layer]) >> 8; | |
uint8_t olddata = noise[layer][i][j]; | |
uint8_t newdata = scale8( olddata, noisesmoothing ) + scale8( data, 256 - noisesmoothing ); | |
data = newdata; | |
noise[layer][i][j] = data; | |
} | |
} | |
} | |
#ifdef CONNECT_THE_DOTS | |
byte point1_x = 0; | |
byte point1_y = 0; | |
byte point2_x = 0; | |
byte point2_y = 0; | |
int delta1_x = 0; | |
int delta1_y = 0; | |
int delta2_x = 0; | |
int delta2_y = 0; | |
#endif | |
void ShiftRow(byte row, int delta) { | |
if(delta < 0) | |
delta += kMatrixWidth; | |
for(int x = 0; x < kMatrixWidth-delta; x++) { | |
leds2[XY(x,row)] = leds[XY(x+delta,row)]; | |
} | |
for(int x = kMatrixWidth-delta; x < kMatrixWidth; x++) { | |
leds2[XY(x,row)] = leds[XY(x+delta-kMatrixWidth,row)]; | |
} | |
} | |
void ShiftColumn(byte column, int delta) { | |
if(delta < 0) | |
delta += kMatrixHeight; | |
for(int y = 0; y < kMatrixHeight-delta; y++) { | |
leds2[XY(column,y)] = leds[XY(column,y+delta)]; | |
} | |
for(int y = kMatrixHeight-delta; y < kMatrixHeight; y++) { | |
leds2[XY(column,y)] = leds[XY(column,y+delta-kMatrixHeight)]; | |
} | |
} | |
void ShiftRowFractional(byte row, byte fractionalDelta) { | |
CRGB PixelA; | |
CRGB PixelB; | |
for(uint8_t x = 0; x < kMatrixWidth; x++) { | |
PixelA = leds2[XY((x+1) % kMatrixWidth, row)]; | |
PixelB = leds2[XY(x, row)]; | |
// blend together | |
PixelA %= 255 - fractionalDelta; | |
PixelB %= fractionalDelta; | |
leds[XY((x+1) % kMatrixWidth, row)] = PixelA + PixelB; | |
} | |
} | |
void ShiftColumnFractional(byte column, byte fractionalDelta) { | |
CRGB PixelA; | |
CRGB PixelB; | |
for(uint8_t y = 0; y < kMatrixHeight; y++) { | |
PixelA = leds2[XY(column, (y+1) % kMatrixHeight)]; | |
PixelB = leds2[XY(column, y)]; | |
PixelA %= 255 - fractionalDelta; | |
PixelB %= fractionalDelta; | |
leds[XY(column, (y+1) % kMatrixHeight)] = PixelA + PixelB; | |
} | |
} | |
// radius is maximum number of pixels to shift rows +/- from the center based on the noise value for that row | |
// offset is additional number of pixels to shift rows in the positive direction (left) | |
void MoveFractionalNoiseX(byte radius, int offset = 0) { | |
for(int y = 0; y < kMatrixHeight; y++) { | |
int noiseX = noise[0][0][y]; | |
/* move bitmap to the right edge of the radius, or to the left of the radius | |
minus one pixel (saving room for the fractional move later). | |
movement off center is based on the high byte of noiseX */ | |
int delta = offset + (radius) - ((radius*2 * noiseX)/256); | |
#ifdef CONNECT_THE_DOTS | |
if(y == point1_y) | |
delta1_x = -delta; | |
if(y == point2_y) | |
delta2_x = -delta; | |
#endif | |
if(delta < 0) | |
delta += kMatrixWidth; | |
ShiftRow(y, delta); | |
} | |
// blur pixels, shifting them between 0 and 1 pixels to the left then copy back to leds buffer | |
for(uint8_t y = 0; y < kMatrixHeight; y++) { | |
byte fractions = noise[0][0][y]*radius*2 % 256; | |
ShiftRowFractional(y, fractions); | |
} | |
} | |
// radius is maximum number of pixels to shift columns +/- from the center based on the noise value for that column | |
// offset is additional number of pixels to shift columns in the positive direction (up) | |
void MoveFractionalNoiseY(byte radius = 8, int offset = 0) { | |
for(int x = 0; x < kMatrixWidth; x++) { | |
int noiseY = noise[0][x][0]; | |
int delta = offset + (radius) - ((radius*2 * noiseY)/256); | |
#ifdef CONNECT_THE_DOTS | |
if(x == point1_x) | |
delta1_y = -delta; | |
if(x == point2_x) | |
delta2_y = -delta; | |
#endif | |
ShiftColumn(x, delta); | |
} | |
for(uint8_t x = 0; x < kMatrixWidth; x++) { | |
byte fractions = noise[0][x][0]*radius*2 % 256; | |
ShiftColumnFractional(x, fractions); | |
} | |
} | |
void NoiseSmearWithRadius(byte radius, int offset = 0) { | |
MoveFractionalNoiseX(radius, offset); | |
MoveFractionalNoiseY(radius, offset); | |
} | |
// this pattern draws two points to the screen based on sin/cos if a counter | |
// (comment out NoiseSmearWithRadius to see pattern of pixels) | |
// these pixels are smeared by a large radius, giving a lot of movement | |
// the image is dimmed before each drawing to not saturate the screen with color | |
// the smear has an offset so the pixels usually have a trail leading toward the upper left | |
void MultipleStream() { | |
static unsigned long counter = 0; | |
#if 1 | |
// this counter lets put delays between each frame and still get the same animation | |
counter++; | |
#else | |
// this counter updates in real time and can't be slowed down for debugging | |
counter = millis()/10; | |
#endif | |
byte x1 = 4+sin8( counter * 2) / 10; | |
byte y1 = 4+cos8( counter * 2) / 10; | |
byte x2 = 8+sin8( counter*2) / 16; | |
byte y2 = 8+cos8( (counter*2)/3) / 16; | |
#ifdef CONNECT_THE_DOTS | |
point1_x = x1; | |
point1_y = y1; | |
// draw line from previous position of point to current posistion | |
// TODO: draw line that wraps around edges | |
pSmartMatrix->drawLine(point1_x, point1_y, point1_x + delta1_x, point1_y + delta1_y, {(0xff * 230) / 256, 0xff * 230 / 256, 0x00}); | |
point2_x = x2; | |
point2_y = y2; | |
pSmartMatrix->drawLine(point2_x, point2_y, point2_x + delta2_x, point2_y + delta2_y, {(0xff * 230) / 256, 0x00, 0x00}); | |
// dim the image more as lines add more light than just pixels | |
DimAll(230); | |
#else | |
DimAll(249); | |
#endif | |
leds[XY( x1, y1)] = 0xFFFF00; | |
leds[XY( x2, y2)] = 0xFF0000; | |
// Noise | |
x[0] += 1000; | |
y[0] += 1000; | |
scale_x[0] = 4000; | |
scale_y[0] = 4000; | |
FillNoise(0); | |
// this pattern smears with an offset added so the pixels usually have a trail going to the upper left | |
NoiseSmearWithRadius(8,1); | |
} | |
void MultipleStream2() { | |
DimAll(230); | |
byte xx = 4+sin8( millis() / 9) / 10; | |
byte yy = 4+cos8( millis() / 10) / 10; | |
leds[XY( xx, yy)] += 0x0000FF; | |
xx = 8+sin8( millis() / 10) / 16; | |
yy = 8+cos8( millis() / 7) / 16; | |
leds[XY( xx, yy)] += 0xFF0000; | |
leds[XY( 15,15)] += 0xFFFF00; | |
x[0] += 1000; | |
y[0] += 1000; | |
z[0] += 1000; | |
scale_x[0] = 4000; | |
scale_y[0] = 4000; | |
FillNoise(0); | |
NoiseSmearWithRadius(2); | |
} | |
void MultipleStream3() { | |
//CLS(); | |
DimAll(235); | |
for(uint8_t i = 3; i < 32; i=i+4) { | |
leds[XY( i,15)] += CHSV(i*2, 255, 255); | |
} | |
// Noise | |
x[0] += 1000; | |
y[0] += 1000; | |
z[0] += 1000; | |
scale_x[0] = 4000; | |
scale_y[0] = 4000; | |
FillNoise(0); | |
NoiseSmearWithRadius(2); | |
} | |
void MultipleStream4() { | |
//CLS(); | |
DimAll(235); | |
leds[XY( 15, 15)] += CHSV(millis(), 255, 255); | |
// Noise | |
x[0] += 1000; | |
y[0] += 1000; | |
scale_x[0] = 4000; | |
scale_y[0] = 4000; | |
FillNoise(0); | |
NoiseSmearWithRadius(2); | |
} | |
void MultipleStream5() { | |
//CLS(); | |
DimAll(235); | |
for(uint8_t i = 3; i < 32; i=i+4) { | |
leds[XY( i,31)] += CHSV(i*2, 255, 255); | |
} | |
// Noise | |
x[0] += 1000; | |
y[0] += 1000; | |
z[0] += 1000; | |
scale_x[0] = 4000; | |
scale_y[0] = 4000; | |
FillNoise(0); | |
// apply an offset to 1 to keep streams moving toward the top of the scren | |
MoveFractionalNoiseX(2); | |
MoveFractionalNoiseY(2, 1); | |
} | |
void MultipleStream8() { | |
//CLS(); | |
// dim existing image slightly so trail will be left of existing light after smearing | |
// 128 is just dots, 192 is small trail, 230 is large trail, 255 is light filling screen | |
DimAll(230); | |
// draw grid of rainbow dots on top of the dimmed image | |
for(uint8_t y = 1; y < 32; y=y+6) { | |
for(uint8_t x = 1; x < 32; x=x+6) { | |
leds[XY( x, y)] += CHSV((x*y)/4, 255, 255); | |
} | |
} | |
// Noise | |
x[0] += 1000; | |
y[0] += 1000; | |
z[0] += 1000; | |
scale_x[0] = 4000; | |
scale_y[0] = 4000; | |
FillNoise(0); | |
// move image (including newly drawn dot) within +/-2 pixels of original position | |
NoiseSmearWithRadius(2); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment