Skip to content

Instantly share code, notes, and snippets.

@embedded-creations
Last active August 29, 2015 14:13
Show Gist options
  • Save embedded-creations/5cd47d83cb0e04f4574d to your computer and use it in GitHub Desktop.
Save embedded-creations/5cd47d83cb0e04f4574d to your computer and use it in GitHub Desktop.
NoiseSmearing Refactored - 20150117
#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