/// @file NoisePlusPalette.ino /// @brief Demonstrates how to mix noise generation with color palettes on a 2D LED matrix /// @example NoisePlusPalette.ino /// /// OVERVIEW: /// This sketch demonstrates combining Perlin noise with color palettes to create /// dynamic, flowing color patterns on an LED matrix. The noise function creates /// natural-looking patterns that change over time, while the color palettes /// determine which colors are used to visualize the noise values. #include // Main FastLED library for controlling LEDs #if !SKETCH_HAS_LOTS_OF_MEMORY // Don't compile this for AVR microcontrollers (like Arduino Uno) because they typically // don't have enough memory to handle this complex animation. // Instead, we provide empty setup/loop functions so the sketch will compile but do nothing. void setup() {} void loop() {} #else // For all other platforms with more memory (ESP32, Teensy, etc.) // LED hardware configuration #define LED_PIN 3 // Data pin connected to the LED strip #define BRIGHTNESS 96 // Default brightness level (0-255) #define LED_TYPE WS2811 // Type of LED strip being used #define COLOR_ORDER GRB // Color order of the LEDs (varies by strip type) // Matrix dimensions - defines the size of our virtual LED grid const uint8_t kMatrixWidth = 16; // Number of columns in the matrix const uint8_t kMatrixHeight = 16; // Number of rows in the matrix // LED strip layout configuration const bool kMatrixSerpentineLayout = true; // If true, every other row runs backwards // This is common in matrix setups to allow // for easier wiring // HOW THIS EXAMPLE WORKS: // // This example combines two features of FastLED to produce a remarkable range of // effects from a relatively small amount of code. This example combines FastLED's // color palette lookup functions with FastLED's Perlin noise generator, and // the combination is extremely powerful. // // You might want to look at the "ColorPalette" and "Noise" examples separately // if this example code seems daunting. // // // The basic setup here is that for each frame, we generate a new array of // 'noise' data, and then map it onto the LED matrix through a color palette. // // Periodically, the color palette is changed, and new noise-generation parameters // are chosen at the same time. In this example, specific noise-generation // values have been selected to match the given color palettes; some are faster, // or slower, or larger, or smaller than others, but there's no reason these // parameters can't be freely mixed-and-matched. // // In addition, this example includes some fast automatic 'data smoothing' at // lower noise speeds to help produce smoother animations in those cases. // // The FastLED built-in color palettes (Forest, Clouds, Lava, Ocean, Party) are // used, as well as some 'hand-defined' ones, and some procedurally generated // palettes. // Calculate the total number of LEDs in our matrix #define NUM_LEDS (kMatrixWidth * kMatrixHeight) // Find the larger dimension (width or height) for our noise array size #define MAX_DIMENSION ((kMatrixWidth>kMatrixHeight) ? kMatrixWidth : kMatrixHeight) // Array to hold all LED color values - one CRGB struct per LED CRGB leds[kMatrixWidth * kMatrixHeight]; // The 16-bit version of our coordinates for the noise function // Using 16 bits gives us more resolution and smoother animations static uint16_t x; // x-coordinate in the noise space static uint16_t y; // y-coordinate in the noise space static uint16_t z; // z-coordinate (time dimension) in the noise space // ANIMATION PARAMETERS: // We're using the x/y dimensions to map to the x/y pixels on the matrix. We'll // use the z-axis for "time". speed determines how fast time moves forward. Try // 1 for a very slow moving effect, or 60 for something that ends up looking like // water. uint16_t speed = 20; // Speed is set dynamically once we've started up // Higher values = faster animation // Scale determines how far apart the pixels in our noise matrix are. Try // changing these values around to see how it affects the motion of the display. The // higher the value of scale, the more "zoomed out" the noise will be. A value // of 1 will be so zoomed in, you'll mostly see solid colors. uint16_t scale = 30; // Scale is set dynamically once we've started up // Higher values = more "zoomed out" pattern // This is the array that we keep our computed noise values in // Each position stores an 8-bit (0-255) noise value uint8_t noise[MAX_DIMENSION][MAX_DIMENSION]; // The current color palette we're using to map noise values to colors CRGBPalette16 currentPalette( PartyColors_p ); // Start with party colors // If colorLoop is set to 1, we'll cycle through the colors in the palette // This creates an additional animation effect on top of the noise movement uint8_t colorLoop = 1; // 0 = no color cycling, 1 = cycle colors // Forward declare our functions so that we have maximum compatibility // with other build tools outside of ArduinoIDE. The *.ino files are // special in that Arduino will generate function prototypes for you. // For non-Arduino environments, we need these declarations. void SetupRandomPalette(); // Creates a random color palette void SetupPurpleAndGreenPalette(); // Creates a purple and green striped palette void SetupBlackAndWhiteStripedPalette(); // Creates a black and white striped palette void ChangePaletteAndSettingsPeriodically(); // Changes palettes and settings over time void mapNoiseToLEDsUsingPalette(); // Maps noise data to LED colors using the palette uint16_t XY( uint8_t x, uint8_t y); // Converts x,y coordinates to LED array index void setup() { delay(3000); // 3 second delay for recovery and to give time for the serial monitor to open // Initialize the LED strip: // - LED_TYPE specifies the chipset (WS2811, WS2812B, etc.) // - LED_PIN is the data pin number // - COLOR_ORDER specifies the RGB color ordering for your strip FastLED.addLeds(leds,NUM_LEDS); // NOTE - This does NOT have a ScreenMap (because it's a legacy sketch) // so it won't look that good on the web-compiler. But adding it is ONE LINE! // Set the overall brightness level (0-255) FastLED.setBrightness(BRIGHTNESS); // Initialize our noise coordinates to random values // This ensures the pattern starts from a different position each time x = random16(); // Random x starting position y = random16(); // Random y starting position z = random16(); // Random time starting position } // Fill the x/y array of 8-bit noise values using the inoise8 function. void fillnoise8() { // If we're running at a low "speed", some 8-bit artifacts become visible // from frame-to-frame. In order to reduce this, we can do some fast data-smoothing. // The amount of data smoothing we're doing depends on "speed". uint8_t dataSmoothing = 0; if( speed < 50) { // At lower speeds, apply more smoothing // This formula creates more smoothing at lower speeds: // speed=10 → smoothing=160, speed=30 → smoothing=80 dataSmoothing = 200 - (speed * 4); } // Loop through each pixel in our noise array for(int i = 0; i < MAX_DIMENSION; i++) { // Calculate the offset for this pixel in the x dimension int ioffset = scale * i; for(int j = 0; j < MAX_DIMENSION; j++) { // Calculate the offset for this pixel in the y dimension int joffset = scale * j; // Generate the noise value for this pixel using 3D Perlin noise // The noise function takes x, y, and z (time) coordinates uint8_t data = inoise8(x + ioffset, y + joffset, z); // The range of the inoise8 function is roughly 16-238. // These two operations expand those values out to roughly 0..255 // You can comment them out if you want the raw noise data. data = qsub8(data, 16); // Subtract 16 (with underflow protection) data = qadd8(data, scale8(data, 39)); // Add a scaled version of the data to itself // Apply data smoothing if enabled if( dataSmoothing ) { uint8_t olddata = noise[i][j]; // Get the previous frame's value // Blend between old and new data based on smoothing amount // Higher dataSmoothing = more of the old value is kept uint8_t newdata = scale8(olddata, dataSmoothing) + scale8(data, 256 - dataSmoothing); data = newdata; } // Store the final noise value in our array noise[i][j] = data; } } // Increment z to move through the noise space over time z += speed; // Apply slow drift to X and Y, just for visual variation // This creates a gentle shifting of the entire pattern x += speed / 8; // X drifts at 1/8 the speed of z y -= speed / 16; // Y drifts at 1/16 the speed of z in the opposite direction } // Map the noise data to LED colors using the current color palette void mapNoiseToLEDsUsingPalette() { // Static variable that slowly increases to cycle through colors when colorLoop is enabled static uint8_t ihue=0; // Loop through each pixel in our LED matrix for(int i = 0; i < kMatrixWidth; i++) { for(int j = 0; j < kMatrixHeight; j++) { // We use the value at the (i,j) coordinate in the noise // array for our brightness, and the flipped value from (j,i) // for our pixel's index into the color palette. // This creates interesting patterns with two different noise mappings. uint8_t index = noise[j][i]; // Color index from the flipped coordinate uint8_t bri = noise[i][j]; // Brightness from the normal coordinate // If color cycling is enabled, add a slowly-changing base value to the index // This makes the colors shift/rotate through the palette over time if( colorLoop) { index += ihue; // Add the slowly increasing hue offset } // Brighten up the colors, as the color palette itself often contains the // light/dark dynamic range desired if( bri > 127 ) { // If brightness is in the upper half, make it full brightness bri = 255; } else { // Otherwise, scale it to the full range (0-127 becomes 0-254) bri = dim8_raw( bri * 2); } // Get the final color by looking up the palette color at our index // and applying the brightness value CRGB color = ColorFromPalette( currentPalette, index, bri); // Set the LED color in our array, using the XY mapping function // to convert from x,y coordinates to the 1D array index leds[XY(i,j)] = color; } } // Increment the hue value for the next frame (for color cycling) ihue+=1; } void loop() { // The main program loop that runs continuously // Periodically choose a new palette, speed, and scale // This creates variety in the animation over time ChangePaletteAndSettingsPeriodically(); // Generate new noise data for this frame fillnoise8(); // Convert the noise data to colors in the LED array // using the current palette mapNoiseToLEDsUsingPalette(); // Send the color data to the actual LEDs FastLED.show(); // No delay is needed here as the calculations already take some time // Adding a delay would slow down the animation // delay(10); } // PALETTE MANAGEMENT: // // There are several different palettes of colors demonstrated here. // // FastLED provides several 'preset' palettes: RainbowColors_p, RainbowStripeColors_p, // OceanColors_p, CloudColors_p, LavaColors_p, ForestColors_p, and PartyColors_p. // // Additionally, you can manually define your own color palettes, or you can write // code that creates color palettes on the fly. // This controls how long each palette is displayed before changing // 1 = 5 sec per palette // 2 = 10 sec per palette // etc. #define HOLD_PALETTES_X_TIMES_AS_LONG 1 // Multiplier for palette duration // Periodically change the palette, speed, and scale settings void ChangePaletteAndSettingsPeriodically() { // Calculate which "second hand" we're on (0-59) based on elapsed time // We divide by HOLD_PALETTES_X_TIMES_AS_LONG to slow down the changes uint8_t secondHand = ((millis() / 1000) / HOLD_PALETTES_X_TIMES_AS_LONG) % 60; static uint8_t lastSecond = 99; // Track the last second to detect changes // Only update when the second hand changes if( lastSecond != secondHand) { lastSecond = secondHand; // Every 5 seconds, change to a different palette and settings // Each palette has specific speed and scale settings that work well with it if( secondHand == 0) { currentPalette = RainbowColors_p; speed = 20; scale = 30; colorLoop = 1; } if( secondHand == 5) { SetupPurpleAndGreenPalette(); speed = 10; scale = 50; colorLoop = 1; } if( secondHand == 10) { SetupBlackAndWhiteStripedPalette(); speed = 20; scale = 30; colorLoop = 1; } if( secondHand == 15) { currentPalette = ForestColors_p; speed = 8; scale =120; colorLoop = 0; } if( secondHand == 20) { currentPalette = CloudColors_p; speed = 4; scale = 30; colorLoop = 0; } if( secondHand == 25) { currentPalette = LavaColors_p; speed = 8; scale = 50; colorLoop = 0; } if( secondHand == 30) { currentPalette = OceanColors_p; speed = 20; scale = 90; colorLoop = 0; } if( secondHand == 35) { currentPalette = PartyColors_p; speed = 20; scale = 30; colorLoop = 1; } if( secondHand == 40) { SetupRandomPalette(); speed = 20; scale = 20; colorLoop = 1; } if( secondHand == 45) { SetupRandomPalette(); speed = 50; scale = 50; colorLoop = 1; } if( secondHand == 50) { SetupRandomPalette(); speed = 90; scale = 90; colorLoop = 1; } if( secondHand == 55) { currentPalette = RainbowStripeColors_p; speed = 30; scale = 20; colorLoop = 1; } } } // This function generates a random palette that's a gradient // between four different colors. The first is a dim hue, the second is // a bright hue, the third is a bright pastel, and the last is // another bright hue. This gives some visual bright/dark variation // which is more interesting than just a gradient of different hues. void SetupRandomPalette() { // Create a new palette with 4 random colors that blend together currentPalette = CRGBPalette16( CHSV( random8(), 255, 32), // Random dim hue (low value) CHSV( random8(), 255, 255), // Random bright hue (full saturation & value) CHSV( random8(), 128, 255), // Random pastel (medium saturation, full value) CHSV( random8(), 255, 255)); // Another random bright hue // The CRGBPalette16 constructor automatically creates a 16-color gradient // between these four colors, evenly distributed } // This function sets up a palette of black and white stripes, // using code. Since the palette is effectively an array of // sixteen CRGB colors, the various fill_* functions can be used // to set them up. void SetupBlackAndWhiteStripedPalette() { // 'black out' all 16 palette entries... fill_solid( currentPalette, 16, CRGB::Black); // and set every fourth one to white to create stripes // Positions 0, 4, 8, and 12 in the 16-color palette currentPalette[0] = CRGB::White; currentPalette[4] = CRGB::White; currentPalette[8] = CRGB::White; currentPalette[12] = CRGB::White; // The palette interpolation will create smooth transitions between these colors } // This function sets up a palette of purple and green stripes. void SetupPurpleAndGreenPalette() { // Define our colors using HSV color space for consistency CRGB purple = CHSV( HUE_PURPLE, 255, 255); // Bright purple CRGB green = CHSV( HUE_GREEN, 255, 255); // Bright green CRGB black = CRGB::Black; // Black // Create a 16-color palette with a specific pattern: // green-green-black-black-purple-purple-black-black, repeated twice // This creates alternating green and purple stripes with black in between currentPalette = CRGBPalette16( green, green, black, black, // First 4 colors purple, purple, black, black, // Next 4 colors green, green, black, black, // Repeat the pattern purple, purple, black, black ); // Last 4 colors } // // Mark's xy coordinate mapping code. See the XYMatrix for more information on it. // // This function converts x,y coordinates to a single array index // It handles both regular and serpentine matrix layouts uint16_t XY( uint8_t x, uint8_t y) { uint16_t i; // For a regular/sequential layout, it's just y * width + x if( kMatrixSerpentineLayout == false) { i = (y * kMatrixWidth) + x; } // For a serpentine layout (zigzag), odd rows run backwards if( kMatrixSerpentineLayout == true) { if( y & 0x01) { // Check if y is odd (bitwise AND with 1) // Odd rows run backwards uint8_t reverseX = (kMatrixWidth - 1) - x; i = (y * kMatrixWidth) + reverseX; } else { // Even rows run forwards i = (y * kMatrixWidth) + x; } } return i; } #endif // End of the non-AVR code section