Maker Pro
Maker Pro

Spectrum analyser Equalizer project how to go about it?

Maglatron

Jul 12, 2023
1,529
Joined
Jul 12, 2023
Messages
1,529
just tried it because it said that was what wan't downloading I'm going to go through the steps again, back in 5

managed to install 2.0.9 and it's fine

which esp do you think I should get because esp32 nano is not listed

so this is the error that I have
 

Attachments

  • errors.txt
    4.8 KB · Views: 3

Maglatron

Jul 12, 2023
1,529
Joined
Jul 12, 2023
Messages
1,529
can you help me getting this working please?
 

Attachments

  • esp32_spectrum_analyzer_32x16.ino
    19.9 KB · Views: 2

Maglatron

Jul 12, 2023
1,529
Joined
Jul 12, 2023
Messages
1,529
so I've found more code, this is the code used in the video can I adapt it to work with my 20 row 24 column common cathodes connected across the rows, and the common anodes connected on the columns
 

Attachments

  • ESP32_FFT_VU.ino
    12.1 KB · Views: 1

Maglatron

Jul 12, 2023
1,529
Joined
Jul 12, 2023
Messages
1,529
is there another way to wire up the matrix other than common cahodes on thee rows and common anodes on the columns?
 

Maglatron

Jul 12, 2023
1,529
Joined
Jul 12, 2023
Messages
1,529
I am planning on having a 24 x 20 LED matrix that have common cathodes along the rows, and common anodes up the columns each column has to refresh 25 times a second, multiply by 24 (because of the columns) that equals 600 pulses a second, the time that each column is on is 1 second/600 pulses that equals 1.67milliseconds. so each column is on for 1.67milliseconds as it progresses through the frequencies it steps to the next column in 1.67milliseconds.
41.5Hz, 53.8Hz, 69.8Hz, 90.5Hz, 117Hz, 151Hz, 196Hz, 253Hz, 328Hz, 424Hz, 549Hz, 710Hz, 917Hz, 1186Hz, 1532Hz, 1980Hz, 2559Hz, 3306Hz, 4265Hz, 5489Hz, 7050Hz, 9032Hz, 11631Hz, 14955Hz
I want the amplitude of the frequencies correspond to decibels, (3 decibels per LED's upwards) and I want the amplitudes to also correspond to the columns. And so for instance, when the column is at the 7th column (ie 196Hz) I want the amplitude for that frequency corresponds to the right column that is on at the time, so when say column 7 is on it might have an 18 decibel, so 6 LED's would be lit, then when it clicks over to column 8 that might have decibel of 6, (two LED's illuminated) and when it clicks over to column 9, that might have 22 decibels (it would light up seven LED's) how do I keep the amplitudes in sync with the columns? I have 1.67 milliseconds to process the sound frequency and to display it on the matrix how can I achieve this? any idea's welcomed and I will answer all questions to the best of my ability. I believe I have to utilise 74HC595 shift register to accomodate the 24 channels and I think I can use the rows to directly go through transistors to the the pins of the esp32 arduino.
I am not certain and am open to idea's I hope someone can understand the question. Can I maybe have a different method of doing this? or help me work with the method that I have come up with, I can't shift on the type of LED's that I'm using because I'm going for a certain "look" the leds are of the bar graph type with 10 flat LED's in each bar.
 
Last edited:

Maglatron

Jul 12, 2023
1,529
Joined
Jul 12, 2023
Messages
1,529
I am planning on having a 24 x 20 LED matrix that have common cathodes along the rows, and common anodes up the columns each column has to refresh 25 times a second, multiply by 24 (because of the columns) that equals 600 pulses a second, the time that each column is on is 1 second/600 pulses that equals 1.67milliseconds. so each column is on for 1.67milliseconds as it progresses through the frequencies it steps to the next column in 1.67milliseconds.
41.5Hz, 53.8Hz, 69.8Hz, 90.5Hz, 117Hz, 151Hz, 196Hz, 253Hz, 328Hz, 424Hz, 549Hz, 710Hz, 917Hz, 1186Hz, 1532Hz, 1980Hz, 2559Hz, 3306Hz, 4265Hz, 5489Hz, 7050Hz, 9032Hz, 11631Hz, 14955Hz
I want the amplitude of the frequencies correspond to decibels, (3 decibels per LED's upwards) and I want the amplitudes to also correspond to the columns. And so for instance, when the column is at the 7th column (ie 196Hz) I want the amplitude for that frequency corresponds to the right column that is on at the time, so when say column 7 is on it might have an 18 decibel, so 6 LED's would be lit, then when it clicks over to column 8 that might have decibel of 6, (two LED's illuminated) and when it clicks over to column 9, that might have 22 decibels (it would light up seven LED's) how do I keep the amplitudes in sync with the columns? I have 1.67 milliseconds to process the sound frequency and to display it on the matrix how can I achieve this? any idea's welcomed and I will answer all questions to the best of my ability. I believe I have to utilise 74HC595 shift register to accomodate the 24 channels and I think I can use the rows to directly go through transistors to the the pins of the esp32 arduino.
I am not certain and am open to idea's I hope someone can understand the question. Can I maybe have a different method of doing this? or help me work with the method that I have come up with, I can't shift on the type of LED's that I'm using because I'm going for a certain "look" the leds are of the bar graph type with 10 flat LED's in each bar.

Does anybody have any new ideas of how to achive this? thank you
 

Maglatron

Jul 12, 2023
1,529
Joined
Jul 12, 2023
Messages
1,529
Note you will have a huge breadboard, tons of wiring, if you do it this way
for 30 channels if you use linear filters. A FFT approach can be done in one
part, including LED drive, all filters (the FFT freq bins that occurs as FFT
output). Just a thought.
from dananak
I don't understand how to use FFT for arduino any tips
 

Maglatron

Jul 12, 2023
1,529
Joined
Jul 12, 2023
Messages
1,529
so I have this code and it compiled fine for the esp32 dev board and I'm not using WS2812B LED strip can you help me modify the code for my needs and explain please!? do I still use the LM3914 for the columns? is the first LED in matrix the bottom left?



C:
// (Heavily) adapted from https://github.com/G6EJD/ESP32-8266-Audio-Spectrum-Display/blob/master/ESP32_Spectrum_Display_02.ino
// Adjusted to allow brightness changes on press+hold, Auto-cycle for 3 button presses within 2 seconds
// Edited to add Neomatrix support for easier compatibility with different layouts.

#include <FastLED_NeoMatrix.h>
#include <arduinoFFT.h>
#include <EasyButton.h>

#define SAMPLES         1024          // Must be a power of 2
#define SAMPLING_FREQ   40000         // Hz, must be 40000 or less due to ADC conversion time. Determines maximum frequency that can be analysed by the FFT Fmax=sampleF/2.
#define AMPLITUDE       1000          // Depending on your audio source level, you may need to alter this value. Can be used as a 'sensitivity' control.
#define AUDIO_IN_PIN    35            // Signal in on this pin
#define LED_PIN         5             // LED strip data
#define BTN_PIN         4             // Connect a push button to this pin to change patterns
#define LONG_PRESS_MS   200           // Number of ms to count as a long press
#define COLOR_ORDER     GRB           // If colours look wrong, play with this
#define CHIPSET         WS2812B       // LED strip type
#define MAX_MILLIAMPS   2000          // Careful with the amount of power here if running off USB port
const int BRIGHTNESS_SETTINGS[3] = {5, 70, 200};  // 3 Integer array for 3 brightness settings (based on pressing+holding BTN_PIN)
#define LED_VOLTS       5             // Usually 5 or 12
#define NUM_BANDS       16            // To change this, you will need to change the bunch of if statements describing the mapping from bins to bands
#define NOISE           500           // Used as a crude noise filter, values below this are ignored
const uint8_t kMatrixWidth = 16;                          // Matrix width
const uint8_t kMatrixHeight = 16;                         // Matrix height
#define NUM_LEDS       (kMatrixWidth * kMatrixHeight)     // Total number of LEDs
#define BAR_WIDTH      (kMatrixWidth  / (NUM_BANDS - 1))  // If width >= 8 light 1 LED width per bar, >= 16 light 2 LEDs width bar etc
#define TOP            (kMatrixHeight - 0)                // Don't allow the bars to go offscreen
#define SERPENTINE     true                               // Set to false if you're LEDS are connected end to end, true if serpentine

// Sampling and FFT stuff
unsigned int sampling_period_us;
byte peak[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};              // The length of these arrays must be >= NUM_BANDS
int oldBarHeights[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int bandValues[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
double vReal[SAMPLES];
double vImag[SAMPLES];
unsigned long newTime;
arduinoFFT FFT = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQ);

// Button stuff
int buttonPushCounter = 0;
bool autoChangePatterns = false;
EasyButton modeBtn(BTN_PIN);

// FastLED stuff
CRGB leds[NUM_LEDS];
DEFINE_GRADIENT_PALETTE( purple_gp ) {
  0,   0, 212, 255,   //blue
255, 179,   0, 255 }; //purple
DEFINE_GRADIENT_PALETTE( outrun_gp ) {
  0, 141,   0, 100,   //purple
127, 255, 192,   0,   //yellow
255,   0,   5, 255 };  //blue
DEFINE_GRADIENT_PALETTE( greenblue_gp ) {
  0,   0, 255,  60,   //green
 64,   0, 236, 255,   //cyan
128,   0,   5, 255,   //blue
192,   0, 236, 255,   //cyan
255,   0, 255,  60 }; //green
DEFINE_GRADIENT_PALETTE( redyellow_gp ) {
  0,   200, 200,  200,   //white
 64,   255, 218,    0,   //yellow
128,   231,   0,    0,   //red
192,   255, 218,    0,   //yellow
255,   200, 200,  200 }; //white
CRGBPalette16 purplePal = purple_gp;
CRGBPalette16 outrunPal = outrun_gp;
CRGBPalette16 greenbluePal = greenblue_gp;
CRGBPalette16 heatPal = redyellow_gp;
uint8_t colorTimer = 0;

// FastLED_NeoMaxtrix - see https://github.com/marcmerlin/FastLED_NeoMatrix for Tiled Matrixes, Zig-Zag and so forth
FastLED_NeoMatrix *matrix = new FastLED_NeoMatrix(leds, kMatrixWidth, kMatrixHeight,
  NEO_MATRIX_TOP        + NEO_MATRIX_LEFT +
  NEO_MATRIX_ROWS       + NEO_MATRIX_ZIGZAG +
  NEO_TILE_TOP + NEO_TILE_LEFT + NEO_TILE_ROWS);

void setup() {
  Serial.begin(115200);
  FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalSMD5050);
  FastLED.setMaxPowerInVoltsAndMilliamps(LED_VOLTS, MAX_MILLIAMPS);
  FastLED.setBrightness(BRIGHTNESS_SETTINGS[1]);
  FastLED.clear();

  modeBtn.begin();
  modeBtn.onPressed(changeMode);
  modeBtn.onPressedFor(LONG_PRESS_MS, brightnessButton);
  modeBtn.onSequence(3, 2000, startAutoMode);
  modeBtn.onSequence(5, 2000, brightnessOff);
  sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQ));
}

void changeMode() {
  Serial.println("Button pressed");
  if (FastLED.getBrightness() == 0) FastLED.setBrightness(BRIGHTNESS_SETTINGS[0]);  //Re-enable if lights are "off"
  autoChangePatterns = false;
  buttonPushCounter = (buttonPushCounter + 1) % 6;
}

void startAutoMode() {
  autoChangePatterns = true;
}

void brightnessButton() {
  if (FastLED.getBrightness() == BRIGHTNESS_SETTINGS[2])  FastLED.setBrightness(BRIGHTNESS_SETTINGS[0]);
  else if (FastLED.getBrightness() == BRIGHTNESS_SETTINGS[0]) FastLED.setBrightness(BRIGHTNESS_SETTINGS[1]);
  else if (FastLED.getBrightness() == BRIGHTNESS_SETTINGS[1]) FastLED.setBrightness(BRIGHTNESS_SETTINGS[2]);
  else if (FastLED.getBrightness() == 0) FastLED.setBrightness(BRIGHTNESS_SETTINGS[0]); //Re-enable if lights are "off"
}

void brightnessOff(){
  FastLED.setBrightness(0);  //Lights out
}

void loop() {

  // Don't clear screen if waterfall pattern, be sure to change this is you change the patterns / order
  if (buttonPushCounter != 5) FastLED.clear();

  modeBtn.read();

  // Reset bandValues[]
  for (int i = 0; i<NUM_BANDS; i++){
    bandValues = 0;
  }

  // Sample the audio pin
  for (int i = 0; i < SAMPLES; i++) {
    newTime = micros();
    vReal = analogRead(AUDIO_IN_PIN); // A conversion takes about 9.7uS on an ESP32
    vImag = 0;
    while ((micros() - newTime) < sampling_period_us) { /* chill */ }
  }

  // Compute FFT
  FFT.DCRemoval();
  FFT.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.Compute(FFT_FORWARD);
  FFT.ComplexToMagnitude();

  // Analyse FFT results
  for (int i = 2; i < (SAMPLES/2); i++){       // Don't use sample 0 and only first SAMPLES/2 are usable. Each array element represents a frequency bin and its value the amplitude.
    if (vReal > NOISE) {                    // Add a crude noise filter

    /*8 bands, 12kHz top band
      if (i<=3 )           bandValues[0]  += (int)vReal;
      if (i>3   && i<=6  ) bandValues[1]  += (int)vReal;
      if (i>6   && i<=13 ) bandValues[2]  += (int)vReal;
      if (i>13  && i<=27 ) bandValues[3]  += (int)vReal;
      if (i>27  && i<=55 ) bandValues[4]  += (int)vReal;
      if (i>55  && i<=112) bandValues[5]  += (int)vReal;
      if (i>112 && i<=229) bandValues[6]  += (int)vReal;
      if (i>229          ) bandValues[7]  += (int)vReal;*/

    //16 bands, 12kHz top band
      if (i<=2 )           bandValues[0]  += (int)vReal;
      if (i>2   && i<=3  ) bandValues[1]  += (int)vReal;
      if (i>3   && i<=5  ) bandValues[2]  += (int)vReal;
      if (i>5   && i<=7  ) bandValues[3]  += (int)vReal;
      if (i>7   && i<=9  ) bandValues[4]  += (int)vReal;
      if (i>9   && i<=13 ) bandValues[5]  += (int)vReal;
      if (i>13  && i<=18 ) bandValues[6]  += (int)vReal;
      if (i>18  && i<=25 ) bandValues[7]  += (int)vReal;
      if (i>25  && i<=36 ) bandValues[8]  += (int)vReal;
      if (i>36  && i<=50 ) bandValues[9]  += (int)vReal;
      if (i>50  && i<=69 ) bandValues[10] += (int)vReal;
      if (i>69  && i<=97 ) bandValues[11] += (int)vReal;
      if (i>97  && i<=135) bandValues[12] += (int)vReal;
      if (i>135 && i<=189) bandValues[13] += (int)vReal;
      if (i>189 && i<=264) bandValues[14] += (int)vReal;
      if (i>264          ) bandValues[15] += (int)vReal;
    }
  }

  // Process the FFT data into bar heights
  for (byte band = 0; band < NUM_BANDS; band++) {

    // Scale the bars for the display
    int barHeight = bandValues[band] / AMPLITUDE;
    if (barHeight > TOP) barHeight = TOP;

    // Small amount of averaging between frames
    barHeight = ((oldBarHeights[band] * 1) + barHeight) / 2;

    // Move peak up
    if (barHeight > peak[band]) {
      peak[band] = min(TOP, barHeight);
    }

    // Draw bars
    switch (buttonPushCounter) {
      case 0:
        rainbowBars(band, barHeight);
        break;
      case 1:
        // No bars on this one
        break;
      case 2:
        purpleBars(band, barHeight);
        break;
      case 3:
        centerBars(band, barHeight);
        break;
      case 4:
        changingBars(band, barHeight);
        break;
      case 5:
        waterfall(band);
        break;
    }

    // Draw peaks
    switch (buttonPushCounter) {
      case 0:
        whitePeak(band);
        break;
      case 1:
        outrunPeak(band);
        break;
      case 2:
        whitePeak(band);
        break;
      case 3:
        // No peaks
        break;
      case 4:
        // No peaks
        break;
      case 5:
        // No peaks
        break;
    }

    // Save oldBarHeights for averaging later
    oldBarHeights[band] = barHeight;
  }

  // Decay peak
  EVERY_N_MILLISECONDS(60) {
    for (byte band = 0; band < NUM_BANDS; band++)
      if (peak[band] > 0) peak[band] -= 1;
    colorTimer++;
  }

  // Used in some of the patterns
  EVERY_N_MILLISECONDS(10) {
    colorTimer++;
  }

  EVERY_N_SECONDS(10) {
    if (autoChangePatterns) buttonPushCounter = (buttonPushCounter + 1) % 6;
  }

  FastLED.show();
}

// PATTERNS BELOW //

void rainbowBars(int band, int barHeight) {
  int xStart = BAR_WIDTH * band;
  for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
    for (int y = TOP; y >= TOP - barHeight; y--) {
      matrix->drawPixel(x, y, CHSV((x / BAR_WIDTH) * (255 / NUM_BANDS), 255, 255));
    }
  }
}

void purpleBars(int band, int barHeight) {
  int xStart = BAR_WIDTH * band;
  for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
    for (int y = TOP; y >= TOP - barHeight; y--) {
      matrix->drawPixel(x, y, ColorFromPalette(purplePal, y * (255 / (barHeight + 1))));
    }
  }
}

void changingBars(int band, int barHeight) {
  int xStart = BAR_WIDTH * band;
  for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
    for (int y = TOP; y >= TOP - barHeight; y--) {
      matrix->drawPixel(x, y, CHSV(y * (255 / kMatrixHeight) + colorTimer, 255, 255));
    }
  }
}

void centerBars(int band, int barHeight) {
  int xStart = BAR_WIDTH * band;
  for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
    if (barHeight % 2 == 0) barHeight--;
    int yStart = ((kMatrixHeight - barHeight) / 2 );
    for (int y = yStart; y <= (yStart + barHeight); y++) {
      int colorIndex = constrain((y - yStart) * (255 / barHeight), 0, 255);
      matrix->drawPixel(x, y, ColorFromPalette(heatPal, colorIndex));
    }
  }
}

void whitePeak(int band) {
  int xStart = BAR_WIDTH * band;
  int peakHeight = TOP - peak[band] - 1;
  for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
    matrix->drawPixel(x, peakHeight, CHSV(0,0,255));
  }
}

void outrunPeak(int band) {
  int xStart = BAR_WIDTH * band;
  int peakHeight = TOP - peak[band] - 1;
  for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
    matrix->drawPixel(x, peakHeight, ColorFromPalette(outrunPal, peakHeight * (255 / kMatrixHeight)));
  }
}

void waterfall(int band) {
  int xStart = BAR_WIDTH * band;
  double highestBandValue = 60000;        // Set this to calibrate your waterfall

  // Draw bottom line
  for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
    matrix->drawPixel(x, 0, CHSV(constrain(map(bandValues[band],0,highestBandValue,160,0),0,160), 255, 255));
  }

  // Move screen up starting at 2nd row from top
  if (band == NUM_BANDS - 1){
    for (int y = kMatrixHeight - 2; y >= 0; y--) {
      for (int x = 0; x < kMatrixWidth; x++) {
        int pixelIndexY = matrix->XY(x, y + 1);
        int pixelIndex = matrix->XY(x, y);
        leds[pixelIndexY] = leds[pixelIndex];
      }
    }
  }
}
[Mod edit: put the code into a code section for better readability
 

Attachments

  • matrix led180.png
    matrix led180.png
    698.5 KB · Views: 1
  • bar graph led.jpg
    bar graph led.jpg
    77.9 KB · Views: 1
Last edited by a moderator:

Maglatron

Jul 12, 2023
1,529
Joined
Jul 12, 2023
Messages
1,529
so I think the code is fine, I should use the LM3914 and the pwm pins of the esp32 dev board (there are 16) each pin to represent a column and band width and set the max signal of the LM3914to 5V so that means that when the signal is at 5v, all the LED's in that column would lit. so I have this code and it compiled fine for the esp32 dev board and I'm not using WS2812B LED strip, I am using bar graph type LED's, can you help me modify the code for my needs and explain please!? with 16 columns and 20 rows (instead of 30) thats 320 LED's! but I need the bar graph type because I want a certain "look", thank you! also I can change the amplitude to make the columbs go up to the desired 20 LED's this is the actual size of the LED matrix that I'll be using
 

Attachments

  • 16x20led.jpg
    16x20led.jpg
    189.1 KB · Views: 0
  • bar graph data.pdf
    102.6 KB · Views: 0

Harald Kapp

Moderator
Moderator
Nov 17, 2011
13,766
Joined
Nov 17, 2011
Messages
13,766
I should use the LM3914 and the pwm pins of the esp32 dev board
A quirky way to go about it:
  1. First you digitize an analog signal.
  2. Second you output the digital values as a (quasi-)analog signal using pwm.
  3. Third you digitize the analog signal using the LM3914 to digitize the analog signal for display.

Personally, my approach would be to use e.g. shift registers:
  1. Output the digital data from the microcontroller into the shift registers. You ned 1 clock pin and as many data pins as there are columns in your display (alternatively you could reduce the number of pins by multiplexing them).
  2. Drive the LEDs from the shift register's outputs (with the appropriate series resistors, of course).

But naturally it is entirely up to you how to do this.
 

Maglatron

Jul 12, 2023
1,529
Joined
Jul 12, 2023
Messages
1,529
A quirky way to go about it:
  1. First you digitize an analog signal.
  2. Second you output the digital values as a (quasi-)analog signal using pwm.
  3. Third you digitize the analog signal using the LM3914 to digitize the analog signal for display.

Personally, my approach would be to use e.g. shift registers:
  1. Output the digital data from the microcontroller into the shift registers. You ned 1 clock pin and as many data pins as there are columns in your display (alternatively you could reduce the number of pins by multiplexing them).
  2. Drive the LEDs from the shift register's outputs (with the appropriate series resistors, of course).

But naturally it is entirely up to you how to do this.
ok so if I use shift registers do I still use the LM3914's, do you know of any youtube tutorials that can help? and any shift registers, I'm a little confused tbh can you give a more in depth instruction please? I'm not too bothered about how I achieve this for my project just that it works!
 
Last edited:

danadak

Feb 19, 2021
784
Joined
Feb 19, 2021
Messages
784
If your processor has enough I/O drive directly from processor. An example :

1699529178281.png

You basically do one column at a time in code, enable the column drive transistor
and output the the data to turn on the desired led(s) in that column. In the case of
using FFT that data is formatted from the strength of the FFT harmonic being displayed.
Like how a 7 segment display digits are done on processors. So you need # GPIO =
#columns + # leds per column.


Regards, Dana.
 

Maglatron

Jul 12, 2023
1,529
Joined
Jul 12, 2023
Messages
1,529
That's fantastic, thank you, but I don't understand fully, so why do you put digital pins into the transistors and why do you use the analog pins for rows?
 

Harald Kapp

Moderator
Moderator
Nov 17, 2011
13,766
Joined
Nov 17, 2011
Messages
13,766
why do you put digital pins into the transistors
The transistors provide the necessary current for the LEDs within one column.
why do you use the analog pins for rows
The "analog pins" can be used for analog signals, but these pins are in fact GPIOs 8General Purpose IOs) and double tehrefore as digital pins. In this application they are used as digital pins.
Look up "multiplexing" for more detailed information on this technique.
 

Maglatron

Jul 12, 2023
1,529
Joined
Jul 12, 2023
Messages
1,529
So say you wanted to light the LED's circled to light how would that be?
 

Attachments

  • example led matrix modified.png
    example led matrix modified.png
    554.9 KB · Views: 2

Maglatron

Jul 12, 2023
1,529
Joined
Jul 12, 2023
Messages
1,529
so we light up each row individually and one after the other, but we go thru the rows so fast that our eyes do not notice, and just see a static complete picture
 
Top