Fastled beat triggered 'traffic' pattern help please

Greetings,

I am melting my brain trying to figure out a pattern I want and could use some help please.

The Idea is to use a " beat trigger" to fire off a randomly coloured dot in a random direction along an LED strip at a random speed. in my mind it will be like cars driving in both directions along a road. in this example a beat is triggered every two seconds but I have a mic input in reality.

I am using FASTLED and a to get a tail behind the dot I figure beatsin8 is the best way to do it and I've figured out how to change the direction but my example can only make two dots symmetrical which is way off what i want.

The final nice to have part would be having the traffic start and finish at random locations so there is no obvious ends to the strip because I am planning to use a ring of 300 WS2812B leds.

Any help would be really appreciated thanks!

psudo code
if beat detected
select random colour
select random direction left or right
select random speed
select random start and end
fire off vehicle

This is the rubbish I have now but I cant get my head around how to start with beatsin8 or 16

#include <FastLED.h>

//FASTLED 
#define LEDstripPin 4                          
#define NUM_LEDS 60
#define COLOR_ORDER GRB
#define CHIPSET WS2812B

CRGB leds[NUM_LEDS];

bool BeatTrigger = false;

CRGBPalette16 currentPalette = Rainbow_gp;

void setup() 
{
  FastLED.addLeds<WS2812B,LEDstripPin , GRB>(leds, NUM_LEDS);

  fill_solid(leds, NUM_LEDS, CRGB( 0, 0, 0));
  FastLED.show();
  FastLED.setBrightness(255);
}


void loop() 
{
  BeatTrigger = true;
  Traffic();
  //delay(20); 
}

void Traffic()
{
  //int left =0; 
  //int speed = 0;
  //int colour = 0;
  //if (BeatTrigger == true ){
    //left = random(0,1);
    //speed = random(1,20);
    //colour = random(255);
    //BeatTrigger == false; 
  
  
  //if(left == 0){
  ////launch a vehicle right 
  //
  //}
  //if(left == 1){
  ////launch a vehicle left  
  //}
  //}

fadeToBlackBy( leds, NUM_LEDS, 5); //change 10 to smaller or larger number to adjust the length of the tail.
uint8_t u= beat8(50,0); //BPM will allow you to adjust the speed the dot is moving.
uint8_t pos=map(u,0,255,0,NUM_LEDS-1);
leds[(NUM_LEDS-1)-pos] = CHSV( 50, 255, 192); 
leds[pos] = CHSV( 255, 255, 192); 
FastLED.show();
}

I would declare the "cars" as separate objects, each car can have it's own color & speed.
A run ("drive") method cares about the proper movemement. Basically it's just a small timer which increases the position to the next pixel.
Any may be, a car can only go forward if the next pixel is empty, otherwise it will wait to avoid crashes.

Some time ago I did something similar:
a Small Town simulation with 4 blocks of houses and some traffic intersections and traffic lights. Here is a short video
https://www.youtube.com/watch?v=j-oSjrKha9Q. Please apologize the bad quality, but you might get an impression,...

edit:
here is a short description how that was done:
https://werner.rothschopf.net/microcontroller/202105_arduino_traffic_city_en.htm

Could you change that link to a link to a youtube video ?

@Koepel does it work now for you?

Yes, thank you.

Thanks for the help. Your Neo City is really awesome @noiasca great work!
I have no experience with objects so this might be a mission. ill take a look at what you've done and see if I can make something work.

p.s. I wanted faster cars to blend and overtake slower cars. I'm using a 1D led strip so i think that should look good.

this is a very much simplified sketch with could give you a first impression:

/*******************************************************************************

   simplified version of cars driving along a strip
   https://forum.arduino.cc/t/fastled-beat-triggered-traffic-pattern-help-please/861691
   
   by noiasca

   2021-05-15 V0: just some cars

 *******************************************************************************/

#include <Adafruit_NeoPixel.h>
constexpr uint16_t ledCount = 256;     // How many NeoPixels are attached to the Arduino? (max is 256 for this sketch!)
constexpr uint8_t ledPin = 12;        // Which pin on the Arduino is connected to the NeoPixels?
Adafruit_NeoPixel strip(ledCount, ledPin, NEO_GRB + NEO_KHZ800); // Declare our NeoPixel strip object

class VehicleSimple                                    // old/basic vehicle with a very simple pathfinding
{
  public:
    uint32_t color;                              // the color of this car
    bool debug = false;                          // debug messages for this car    Vehicle(uint32_t color): color(color) {}     // constructor
    uint16_t newPosition = 4711;                  // number should be out of the range of the real grid.
    uint16_t currentPosition = 0;                 // startposition is anywhere in the middle of the screen, might interfere with some traffic lights, but I don't care...
    uint32_t previousMillis = 0;                 // timekeeping for intervals
    uint16_t wait = 500;                         // timekeeping standard/speed

    VehicleSimple(uint32_t color): color(color) {}     // constructor

    void setPosition(byte newPosition) {
      currentPosition = newPosition;
    }

    void setSpeed (uint16_t newSpeed) {
      wait = newSpeed;
    }

    void toggleDebug()
    {
      debug = !debug;
    }

    void tick()                                  // call this method in the loop (run)
    {
      if (millis() - previousMillis > wait)      // time to move
      {
        if (debug) Serial.print (color, HEX);
        
        if (currentPosition < ledCount) 
          newPosition = currentPosition + 1;
        else
          newPosition = 0;

        // move
        if (newPosition != currentPosition) strip.setPixelColor(currentPosition, 0);   // delete if necessary
        strip.setPixelColor(newPosition, color);                                       // set new field
        strip.show();                                                                  // update strip to match
        currentPosition = newPosition;
        previousMillis = millis();
      }
    }
};

VehicleSimple car[]
{ // a group of newer cars - to make some traffic in the city
  strip.Color(0,   0,  64),
  strip.Color(64,    0,  64),
  strip.Color(64,   16,  64),
  strip.Color(64,   32,  64),
  strip.Color(128,   0, 128),
  strip.Color(128,  16, 128),
  //strip.Color(128,  32, 128),
  //strip.Color(200,  16, 255),
  //strip.Color(200,  32, 255),
  //strip.Color(255,  0,  255),
  //strip.Color(255,  16, 255),
  //strip.Color(255,  32, 255)
};

void setup() {
  strip.begin();             // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();              // Turn OFF all pixels ASAP
  strip.setBrightness(8);    // Set BRIGHTNESS of NeoPixel (max = 255)
  Serial.begin(115200);
  Serial.println(F("\nNeopixel Traffic Simplified"));
  randomSeed(analogRead(0));

  for (auto &i : car)
    i.setSpeed((random(15, 30) * 10)); // give each car in this group an indiviudal speed

  car[0].setSpeed(100);  // a very fast car
}

void loop() {
  for (auto &i : car)       // there are lot of cars in this group/array of objects
    i.tick();
}

Oh wow thanks! im having a read of classes now. but will try to see what your code does.

first thing ill swap to fastled because i am at least a little more familiar with that and ive heard its more efficient.

thanks @noiasca
I still don't understand classes at all but your code works and I have cars driving along my strip.

I don't understand what causes the vehicles to begin their journey and if i can add or remove them periodically with a "beat trigger"

the movement is done with the tick method.

the loop "ticks" every car ("gives every car a timeslot")

The car's method "tick" uses a timer
millis()-previousMillis > wait
to determine, if it is time to move forward.
Thats based on "BlinkWithoutDelay" - nonblocking code.
So each car knows on its own, if it has to move or not.

When you have understood this concept, we can go further and introduce an external trigger to start cars.

The project in a Wokwi simulation: https://wokwi.com/arduino/projects/298652422326190605.
In the middle-upper of the screen is the start button. Click it :star_struck:

it nearly looks like my hardware :wink:

yeah i am blown away with the simulation.... I only learnt about Wokwi last week and love it.

so each element in the cars array is a car of a different colour.

so this is where i want to add an element each time a beat is detected?

how can i have cars going in both directions?

p.s. sorry for the dumb questions. I am way out of my depth with understanding
classes. but willing to research and learn.

Those cars will keep on running. I suppose that you want your cars to fade away ? Or do they just drop on the floor at the end of the ledstrip ?

The class VehicleSimple is the blueprint. It is a description of the things in the class.
A VehicleSimple myCar; creates a object in memory, according to the blueprint.
However, the contructor of the class takes the color as parameter.
So it will be: VehicleSimple myCar( CRGB(0, 0, 64));
The code shows how an array of cars is created.

A beat could create a new object which stops and gets deleted after a while. That is making use of C++, but it is not easy, because you need to store the object somewhere.
You could also have a fixed number of objects (in a array) and turn them on and off in the code. For example with a extra variable inside the class.

class VehicleSimple
{
  public:
    bool active = false;
    ...

yes this is certainly complicated based on my current noob skills.

in a perfect world it would work like this;

my beat trigger would create a new car, cars would have a random colour and random length between 1-4 pixels to simulate different sized vehicles like bikes, cars, trucks, etc. and a tail fading away as it zoomed along the road so it was a bit more fluid than a single dots moving.

Cars would start randomly on the left or right side of the strip and drive a random distance before fading away. My strip is actually a loop so I wouldn't mid if a car drove a few loops before fading away too.

Unfortunately this is beyond me for now and ill have to do a lot more study to understand classes.

Question for 100 points:

  • if the movement forward is calculated by
newPosition = currentPosition + 1;

how would you calculate a backward movement?

Question for additional 20 points:

  • what else could be needed if you decrease a number and how will you solve that codewise?

edit:
my last version with a short intro sequence.
Other cars can be "added" with a
c
in the serial monitor.
Random forward or backward.
car disapears at the end of the strip
cars have a tail / length.

I decided against a real new/delete of objects, I just "hide" cars using a isActive flag.

/*******************************************************************************

   simplified version of cars driving along a strip
   https://forum.arduino.cc/t/fastled-beat-triggered-traffic-pattern-help-please/861691

   start sequence
   cars can be added with the serial monitor: 
   c enter

   by noiasca

   2021-05-15 V1: just some cars

 *******************************************************************************/

#include <Adafruit_NeoPixel.h>
constexpr uint16_t ledCount = 256;    // How many NeoPixels are attached to the Arduino? (max is 256 for this sketch!)
constexpr uint8_t ledPin = 12;        // Which pin on the Arduino is connected to the NeoPixels?
Adafruit_NeoPixel strip(ledCount, ledPin, NEO_GRB + NEO_KHZ800); // Declare our NeoPixel strip object

constexpr uint8_t maxLength = 5;

class VehicleSimple                              // basic vehicle
{
  public:
    uint32_t color;                              // the color of this car
    bool debug = false;                          // debug messages for this car
    int16_t newPosition = 0;                     // number should be out of the range of the real grid.
    int16_t currentPosition = 0;                 // startposition is anywhere in the middle of the screen, might interfere with some traffic lights, but I don't care...
    uint32_t previousMillis = 0;                 // timekeeping for intervals
    uint16_t wait = 500;                         // timekeeping standard/speed
    uint8_t len = 5;
    int8_t direction = 1;                        // 1 forward, -1 backward
    bool isActive = true;                        // is car active
    bool deactivateOnEnd = true;                 // set to inactive if finished

    VehicleSimple(uint32_t color): color(color) {}     // constructor

    void setPosition(byte newPosition) {
      if (newPosition >= (int)(ledCount) - 1) newPosition = ledCount - 1;
      currentPosition = newPosition;
    }

    void setSpeed (uint16_t newSpeed) {
      wait = newSpeed;
    }

    void setDirection (int8_t newDirection) {
      if (newDirection == -1)
        direction = -1;
      else
        direction = 1;
    }

    void toggleDebug()
    {
      debug = !debug;
    }

    void deactivate()
    {
      isActive = false;
    }

    void activate()
    {
      isActive = true;
      currentPosition = 0;
      direction = 1;
    }

    bool getStatus()
    {
      return isActive;
    }

    void tick()                                  // call this method in the loop (run)
    {
      if (isActive && millis() - previousMillis > wait)      // time to move
      {
        if (debug) {
          Serial.print (color, HEX);
          Serial.print(" c");
          Serial.print(currentPosition);
          Serial.print(" ");
        }

        if (direction == 1)                                 // forward
        {
          if (currentPosition < (int)(ledCount + len))
            newPosition = currentPosition + 1 * direction;  // calculate next position
          else
          {
            newPosition = -1;  // Park outside visible area
            if (deactivateOnEnd) deactivate();
          }
        }
        else                                                // backward
        {
          if (currentPosition > (0 - len))
            newPosition = currentPosition - 1;
          else
          {
            newPosition = ledCount;
            if (deactivateOnEnd) deactivate();
          }
        }

        // move
        for (int8_t i = 0; i < len; i++)
        {
          int16_t actualPixel = newPosition - i * direction;
          if (actualPixel >= 0 && actualPixel < (int)ledCount)
          {
            strip.setPixelColor(actualPixel, color);                                       // set new field
            if (debug) {
              Serial.print(" s");
              Serial.print (actualPixel);
            }
          }
        }
        if (direction == 1 && currentPosition >= 0 && currentPosition >= len)
        {
          strip.setPixelColor(currentPosition - len, 0);              // delete if necessary
          if (debug) {
            Serial.print(" r");
            Serial.print (currentPosition);
          }
        }
        if (direction == -1 && currentPosition < (int)ledCount)
        {
          strip.setPixelColor(currentPosition + len, 0);              // delete if necessary
          if (debug) {
            Serial.print(" r");
            Serial.print (currentPosition + len);
          }
        }
        strip.show();                                                                  // update strip to match
        currentPosition = newPosition;
        previousMillis = millis();
        if (debug) Serial.println();
      }
    }
};

VehicleSimple car[]
{ // a group of newer cars - to make some traffic in the city
  strip.Color(0,   0,  64),
  strip.Color(64,    0,  64),
  strip.Color(64,   16,  64),
  strip.Color(64,   32,  64),
  strip.Color(128,   0, 128),
  strip.Color(128,  16, 128),
  //strip.Color(128,  32, 128),
  //strip.Color(200,  16, 255),
  //strip.Color(200,  32, 255),
  //strip.Color(255,  0,  255),
  //strip.Color(255,  16, 255),
  //strip.Color(255,  32, 255)
};

size_t noOfCars = sizeof(car) / sizeof(car[0]); // evaluate the number of defined cars

// handle serial input
void serialTick()
{
  if (Serial.available())
  {
    char c = Serial.read();
    switch (c)
    {
      case 'c' : addNewCar(); break;           // "trigger" event to add a new car
      case '0' : car[0].toggleDebug(); break;
      case '1' : car[1].toggleDebug(); break;
    }
  }
}


void addNewCar()               //adds a new car to the display - if available
{
  int result = getFreeCar();
  if (result >= 0)  // we got a valid car
  {
    car[result].activate();
    if (millis() & 0b1) // just a cheap random to reverse some cars
    {
      car[result].setPosition(ledCount - 1);
      car[result].setDirection(-1);
    }
    Serial.print(F("activated car ")); Serial.println(result);
  }
  else
  {
    Serial.println(F("sorry, no car available"));
  }
}

int getFreeCar()           // search an index of a free car; returns -1 if no free car was found
{
  for (size_t i = 0; i < noOfCars; i++)
  {
    if (car[i].getStatus() == 0)
    {
      return i;
    }
  }
  return -1; // no free car found to start
}

void setup() {
  strip.begin();             // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();              // Turn OFF all pixels ASAP
  strip.setBrightness(8);    // Set BRIGHTNESS of NeoPixel (max = 255)
  Serial.begin(115200);
  Serial.println(F("\nNeopixel Traffic Simplified"));
  randomSeed(analogRead(0));

  for (auto &i : car)
    i.setSpeed((random(15, 30) * 10)); // give each car in this group an indiviudal speed

  car[0].setSpeed(100);  // a very fast car

  car[1].setPosition(ledCount - 1);
  car[1].setDirection(-1);
  //car[1].toggleDebug();
  car[2].deactivate();
  car[3].deactivate();
  car[4].deactivate();
}

void loop() {
  for (auto &i : car)       // there are lot of cars in this group/array of objects
    i.tick();

  serialTick();             // read serial commands
}

Video:
https://youtu.be/IrRlr0bI5yo

1 Like

Ledstrip as a loop in Wokwi: https://wokwi.com/arduino/projects/298652422326190605.
I had to increase the brightness, because the loop is dimmer in Wokwi.

@noiasca thank you for showing how to do this :+1: It is educational and entertaining at the same time. I translated your first example to FastLED, but I have not updated the Wokwi simulation with your new code yet :cry:

1 Like

sorry, I'm still working with my zigzag grid and didn't want to install just another library.
Now I have to play around with Wokwi...^^

1 Like