logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·30.10.26
← Back to news

Phyllotaxis: An audio-reactive LED display

Original article
jagi.studio|330 points|48 comments|by evakhoury|Sep 28, 2026

Phyllotaxis: An audio-reactive LED display

By Jagi Natarajan

"One of nature's beautiful patterns captured in dancing lights"

I have always been driven by a desire to decode the emergent patterns of the universe through the lens of programming. One such captivating phenomenon is phyllotaxis—the expanding, double-spiral arrangement frequently observed in succulents, sunflowers, and various other botanical structures.

🌀 The Mathematical Foundation

The underlying logic of this pattern is surprisingly elegant. By placing points along a radial line and rotating each subsequent point by a multiple of the golden ratio (ϕ≈1.6180339887\phi \approx 1.6180339887ϕ≈1.6180339887), a naturalistic point cloud emerges.

The Logic in Code

The following Processing snippet demonstrates how to generate these coordinates:

// Inner and outer are throw-away, 
// just for spacing the cells that are used
float [][] points = new float [ numCells + numOuter + numInner ][ 2 ] ;
float outerRadius = width / 2 ;
final int total = numCells + numInner + numOuter ;

for ( int i = 0 ; i < total ; i ++ ) {
    float f = i / float ( total );
    float a = i * 1.6180339887 ; // The Golden Ratio
    float distance = f * outerRadius ;
    float x = - cos ( a * TWO_PI ) * distance ;
    float y = sin ( a * TWO_PI ) * distance ;
    points [ i ][ 0 ] = x ;
    points [ i ][ 1 ] = y ;
}

When these points are processed via Voronoi tessellation, the result transforms from a simple cloud into a structure resembling a seed pod.

Voronoi Tessellation Result


🛠️ From Digital to Physical

While I had previously created a standard rectangular Neopixel matrix in high school, I wanted a more organic geometry for my next project. I decided to turn this mathematical model into a physical RGB LED display.

The Design Pipeline

The transition from a sketch to a 3D-printable object followed this workflow:

To create the physical housing, I used the CadQuery library in Python. The logic involved creating a total volume and then subtracting the interior of each cell to create walls.

# Create the negative space of a single cell 
# shrunk with a negative offset-2d 
cell_shape = ( cq.offset2D ( - wall_thickness / 2 , kind = 'intersection' )
               .translate (( 0 , 0 , base_height )))

# Cut it out of the total to create a shell with walls 
total_shape = total_shape.cut ( cell_shape )

# Cut the shape of the hole to fit the LED in the center of the cell 
total_shape = total_shape.translate (( centerX , centerY , 0 )))

Fabrication Details

  • Segmentation: The model was split into four quadrants to fit the 3D printer bed.
  • Refinement: I used FreeCAD to add screw holes and a thin faceplate.
  • Diffusion: I selected mulberry paper for the faceplate due to its organic fibers and excellent light diffusion properties.

💻 Firmware and Visuals

The hardware is powered by an STM32 Blackpill board, utilizing an SPI-based Neopixel driver. To make programming the visuals easier, I implemented a framework that mimics fragment shaders. By using a Look-Up Table (LUT) to store the floating-point positions of each LED as if they were on a unit circle, I could write radial effects easily.

Example: Radial Spirals

The following C++ code calculates brightness based on polar coordinates: Distance r=x2+y2\text{Distance } r = \sqrt{x^2 + y^2}Distance r=x2+y2​ and Angle θ=atan2⁡(y,x)\text{Angle } \theta = \operatorname{atan2}(y, x)Angle θ=atan2(y,x).

void radial_spirals ( LEDBuffer leds ) {
    for ( int i = 0 ; i < NUM_LEDS ; i ++ ) {
        // Get LED position from LUT
        float x = led_positions [ i ][ 0 ];
        float y = led_positions [ i ][ 1 ];

        // Polar coordinates
        float r = sqrtf ( x * x + y * y );
        float theta = atan2f ( y , x );

        // Spiral pattern: combine angle and radius
        float spiral = sinf ( theta * 2.0f + r * 5.0f - seconds * 2.0f );
        float brightness = ( spiral + 1.0f ) * 0.5f ;
        uint8_t level = ( uint8_t )( brightness * 255.0f );
        leds [ i ] = rgb ( level , 0 , 0 );
    }
}

🎵 Adding Audio Reactivity

To move beyond static patterns, I integrated real-time audio analysis.

Technical Stack

ComponentPurpose
INMP441Digital I2S Microphone (reduces analog noise)
ARM CMSISLibrary for DSP and signal processing
FFTFast Fourier Transform for frequency analysis
Auto-GainNormalizes input volume levels

By splitting the audio into multiple frequency bands, I could map specific energy levels to the pulsating sine waves radiating from the center of the display.


📦 Final Assembly and Iteration

The final aesthetic combines the 3D-printed core with a routed bamboo cutting board base, which complements the organic feel of the mulberry paper.

The Build Checklist

  • 3D print quadrants
  • Glue mulberry paper faceplate
  • Route bamboo base
  • Install I2S Microphone
  • Implement audio-reactive sketch

Initial Version: The electronics were built on a perfboard and housed in a 3D-printed box with a guitar pedal switch, but they were janky and prone to flickering noise.

Final Version: I transitioned to a professional custom PCB to mount the Blackpill and microphone. This included a logic-level shifter to ensure a stable 5V signal for the LEDs, eliminating the flicker and improving reliability.