Phyllotaxis: Een audio-reactief LED-display
Ik ben gefascineerd door het begrijpen van patronen die in de natuur voorkomen door middel van code; het communiceren met de inherente emergente patronen die in het universum bestaan.
Phyllotaxis is zo'n patroon: de uitdijende dubbele spiraalvorm die verschijnt in het centrum van zonnebloemen, vetplanten en andere planten. De code hiervoor is verrassend eenvoudig:
// Inner and outer zijn tijdelijke variabelen,
// enkel bedoeld voor de spatiëring van de gebruikte cellen
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;
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;
}
Door een aantal lineaire punten op een radiale lijn van een cirkel te nemen en elk punt te roteren met een toenemende veelvoud van de gulden snede, ontstaat er een puntenwolk die al sterk op een zonnebloem lijkt. Wanneer hier een Voronoi-tesselatie op wordt toegepast, ziet het resultaat er interessant uit, vergelijkbaar met een zaaddoos.
Van digitaal naar fysiek
Na een tijd te hebben geëxperimenteerd, kwam ik op het idee om hier een fysiek object van te maken en in elke cel een RGB-adresseerbare LED te plaatsen. Een soort cellulaire LED-matrix. Ik had op de middelbare school al eens een rechthoekige neopixel-matrix gebouwd, maar ik wachtte op een interessantere vorm voor mijn volgende project.
Ik exporteerde de gegevens van de celranden uit mijn Processing-sketch en importeerde deze in een Python-sketch met de CadQuery-bibliotheek. Vervolgens schreef ik code om fysieke geometrie uit de data te bouwen:
# Create the negative space of a single cell
# shrunk with a negative offset-2d
cell_shape = (cq.WorkPlane("XY")
.polyline(vertices)
.close()
.offset2D(-wall_thickness/2, kind='intersection')
.extrude(total_height)
.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.cut(
led_hole.rotate((0, 0, 0), (0, 0, 1), math.degrees(angle))
.translate((centerX, centerY, 0)))
Dit fragment bouwt eerst het totale volume van alle samengevoegde Voronoi-cellen, trekt daar het verkleinde volume van elke individuele cel vanaf en boort vervolgens een gat ter grootte van een LED in het midden van elke cel. Door dit voor alle cellen te doen, ontstond de uiteindelijke vorm. Ik splitste de geometrie op dat moment in vier ongeveer gelijke kwadranten, zodat ze elk op het bed van mijn 3D-printer pasten.
Ik exporteerde de STEP-bestanden en deed wat nabewerking in FreeCAD, waarbij ik schroefgaten toevoegde en een dunne versie maakte die als 'frontpaneel' diende met papier eronder. Ik sneed papier op maat, plakte dit aan de onderkant van het frontpaneel en gebruikte de schroefgaten om het aan de celwanden te bevestigen.
Ik heb de vormen 3D-geprint en translucent papier gevonden dat het licht goed diffundeert en er organisch en speels uitziet. Ik ben erg aandoenlijk van de look van dit moerbeiboompapier (mulberry paper) met zijn prachtige organische vezels.
Het solderen van 89 LED's was monnikenwerk, maar ik raakte in een 'flow-state' waardoor het ontspannend werd. Ik koppelde het geheel aan een reserve STM32 Blackpill-board en schreef een kleine neopixel-driver met behulp van SPI. Zelfs met basis testpatronen zag het er al cool uit.
Ik bouwde een eenvoudig sketch-framework waarmee ik Processing-stijl 'shader'-code voor de LED's kon schrijven. Door een look-up table (LUT) te gebruiken met de floating-point positie voor elke LED (alsof deze zich op een eenheidscirkel bevond), kon ik eenvoudig code schrijven die aanvoelt als een fragment shader:
void radial_spirals(LEDBuffer leds) {
for (int i = 0; i < NUM_LEDS; i++) { // iterate every cell
// 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); // Angle in radians [-π, π]
// 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);
}
}
Ik ontdekte al snel een radiaal patroon dat ik het mooist vond: een pulserende sinusgolf die de helderheid vanuit het centrum naar buiten moduleert. Dit werd later de primaire functie van de audio-reactieve sketch.
Audio toevoegen
Ik vroeg me af of het display dynamisch kon reageren op geluiden in de kamer, in plaats van alleen vooraf gedefinieerde patronen te tonen. Het moest levendig en dynamisch aanvoelen.
Het STM32-board dat de matrix aanstuurde, ondersteunde floating point en ARM DSP-instructies, waardoor ik de Fourier-transformatie kon uitvoeren en audio-analyse kon doen. Ik voegde een digitale I2S-microfoon, de INMP441, toe aan mijn breadboard-circuit.
Omdat deze een digitaal signaal uitvoert, is de bedrading eenvoudig en is er geen risico op analoge ruis. De ARM CMSIS-bibliotheek was zeer nuttig voor het verwerken van het audiosignaal. Met automatische gain-controle en multiband-splitting om energie op verschillende frequenties te analyseren, had ik een goede basis om code te schrijven die op dynamische wijze op audio reageerde.
Na veel tuning en een chaotische sketch met veel globale timers en slordige states, ontstond het audio-reactieve programma dat tot op de dag van vandaag op het board draait. Het project kwam echt tot leven bij het luisteren naar Neon Pattern Drum van Jon Hopkins.
Verfijning
Er waren nog enkele details over. Het display had een stevigere ondergrond nodig om op gemonteerd te worden. Ik vond een bamboe snijplank en freesde deze in een cirkel; de natuurlijke nerf van het bamboe hout vult het organische papier goed aan.
Ik had de controller MCU en microfoon op een perfboard geassembleerd en in een 3D-geprinte doos met een gitaarpedaalschakelaar geplaatst. De elektronica waren echter fragiel en gevoelig voor ruis, waardoor de LED's flikkerden wanneer de doos bewoog. Ik besloot te leren hoe ik PCB's moet maken en stapte over op een echte printplaat voor het Blackpill-board, de microfoon en een knop, inclusief een geschikte plek voor een logic-level shifter om een goed 5V-signaal uit de microcontroller te krijgen. Dit werkte goed; het ontwerpen van PCB's was makkelijker dan verwacht voor dit niveau van complexiteit.
De tweede versie
Ik liet het project zien tijdens een nieuwjaarsprofeest voor 2026 en mensen waren enthousiast; sommige vrienden wilden er zelfs een. Geïnspireerd hiervan besloot ik een tweede versie te bouwen die makkelijker te assembleren en steviger zou zijn. Nu ik PCB-ontwerp onder de knie had, wilde ik overstappen van een 3D-geprinte plaat naar PCB's als achterplaat. Ik paste de vorm iets aan en voegde een 5-voudige symmetrie toe, aangezien PCB's in batches van vijf worden besteld. In plaats van vier unieke 3D-geprinte delen, bestelde ik vijf identieke PCB's die ik in een ring kon assembleren.
Het bleek echter een nachtmerrie om neopixel LED's met een soldeerbout te solderen, omdat de pads zich onder de LED's bevinden; ze zijn bedoeld om gesoldeerd te worden met een methode die de PCB verhit in plaats van een bout. Het project kwam hierdoor een tijdje stil te liggen.
In de zomer van 2026 nam ik de onderdelen mee naar een programmeer-retreat bij het Recurse Center. Een vriend die ik daar ontmoette was veel handiger met een hetelucht-soldeerstation dan ik. Samen voltooiden we het solderen, waarna ik nieuwe celwanden ontwierp en 3D-printe die op de PCB's geklikt en aan elkaar geschroefd konden worden.
Na het voltooien van deze versie besloot ik het als installatie achter te laten bij het Recurse Center. Ik stapte over op een ESP32, zodat het apparaat op het wifi-netwerk kon, en herschreef de firmware in Rust. De controller serveert een lokale website waar bezoekers van het centrum sketches en games kunnen uploaden via een kleine WebAssembly-backend en een minimale API.
Wat volgt er nu?
Ik ben nog steeds niet volledig tevreden met dit project. Omdat er zoveel interesse in is getoond, zou ik er graag meer van willen maken, om ze cadeau te doen aan vrienden of eventueel breder te verkopen. Maar het proces is nog niet optimaal; het solderen was beide keren om verschillende redenen tijdrovend. Het bevestigen van het papier is lastig, en het papier zelf is fragiel en moeilijk te repareren.
Ik heb een aantal ideeën voor verbeteringen:
- Experimenteren met andere typen LED's die makkelijker handmatig te solderen zijn.
- Gebruikmaken van professionele PCB-assemblage.
- Testen met een laag helder acryl om het papier te beschermen.
- De papieren frontpanelen bevestigen met magneten.
Voor nu verdient het project een pauze, maar wanneer de tijd rijp is, volgt versie 3!
Groetjes,