Skip to contentSkip to main navigation Skip to footer

Бегущие под музыку огни

Задача


  • Сделать реакцию ленты на звук как в вирусном видео (см. внизу страницы)
21.11.2021 – исправил схему, добавил комментариев в код. Добавил огненную палитру

Базовые уроки


Подключение


  • Лента подключается к внешнему питанию и пину D2
  • В данной схеме Arduino питается от внешнего источника, подключение к USB необязательно
  • Микрофон к питанию и аналоговому пину (А0)
  • Пин Gain микрофона задаёт усиление звука (значения также написаны на модуле):
    • Никуда не подключен (float) – 60dB (максимум)
    • GND – 50dB
    • VCC – 40dB

Библиотеки


Программа


Примечание: можно настроить нижний порог шумов, добавив строчку sound.setTrsh(величина) в блок setup(). По умолчанию порог задан 40, его можно увеличить, чтобы снизить чувствительность системы к шумам.

Версия с FastLED
// бегущие частицы 1D, версия с FastLED

#define STRIP_PIN 2     // пин ленты
#define SOUND_PIN A0    // пин звука

#define COLOR_STEP 151  // шаг цвета, интересные 151, 129
#define LEDS_AM 300     // количество светодиодов
#define P_SPEED 2       // скорость движения

#include <FastLED.h>
CRGB leds[LEDS_AM];

#include "VolAnalyzer.h"
VolAnalyzer sound(SOUND_PIN);

byte curColor = 0;      // текущий цвет

void setup() {
  FastLED.addLeds<WS2812, STRIP_PIN, GRB>(leds, LEDS_AM);
  FastLED.setBrightness(255);

  // настройки анализатора звука
  sound.setVolK(15);        // снизим фильтрацию громкости (макс. 31)
  sound.setVolMax(255);     // выход громкости 0-255
  sound.setPulseMax(200);   // сигнал пульса
  sound.setPulseMin(150);   // перезагрузка пульса
}

void loop() {
  if (sound.tick()) {   // если анализ звука завершён (~10мс)
    // перематываем массив светодиодов на P_SPEED вправо
    for (int k = 0; k < P_SPEED; k++) {
      for (int i = LEDS_AM - 1; i > 0; i--) leds[i] = leds[i - 1];
    }
    
    // резкий звук - меняем цвет
    if (sound.pulse()) curColor += COLOR_STEP;
    
    // берём текущий цвет с яркостью по громкости (0-255)
    CRGB color = CHSV(curColor, 255, sound.getVol());
    
    // красим P_SPEED первых светодиодов
    for (int i = 0; i < P_SPEED; i++) leds[i] = color;
    
    // выводим
    FastLED.show();
  }
}
FastLED + огненная палитра

Вместо яркости-от-громкости будем варьировать все три параметра в пространстве HSV: цвет, насыщенность и яркость. Чем выше громкость – тем больше цвет (например от красного к жёлтому), чем выше громкость – тем меньше насыщенность (от цвета к белому), и наконец чем выше громкость, тем ярче. В FastLED это можно организовать так:

color = CHSV(START_HUE + vol / 5, 255 - vol / 2, vol);

где START_HUE задаёт стартовый цвет (0.. 255), мы его смещаем на громкость / 5. Насыщенность берём как 255 - громкость / 2, и яркость по громкости как раньше. Полный код примера:

// бегущие частицы 1D, версия с FastLED, огненная палитра

#define STRIP_PIN 2     // пин ленты
#define SOUND_PIN A0    // пин звука

#define LEDS_AM 300     // количество светодиодов
#define P_SPEED 2       // скорость движения
#define START_HUE 0     // цвет огня (0.. 255). 0 красный, 150 синий, 200 розовый

#include <FastLED.h>
CRGB leds[LEDS_AM];

#include "VolAnalyzer.h"
VolAnalyzer sound(SOUND_PIN);

byte curColor = 0;      // текущий цвет

void setup() {
  FastLED.addLeds<WS2812, STRIP_PIN, GRB>(leds, LEDS_AM);
  FastLED.setBrightness(255);

  // настройки анализатора звука
  sound.setVolK(15);        // снизим фильтрацию громкости (макс. 31)
  sound.setVolMax(255);     // выход громкости 0-255
  sound.setPulseMax(200);   // сигнал пульса
  sound.setPulseMin(150);   // перезагрузка пульса
}

void loop() {
  if (sound.tick()) {   // если анализ звука завершён (~10мс)
    // перематываем массив светодиодов на P_SPEED вправо
    for (int k = 0; k < P_SPEED; k++) {
      for (int i = LEDS_AM - 1; i > 0; i--) leds[i] = leds[i - 1];
    }

    // цвет по огненной палитре
    int vol = sound.getVol();
    CRGB color = CHSV(START_HUE + vol / 5, 255 - vol / 2, vol);
    
    // красим P_SPEED первых светодиодов
    for (int i = 0; i < P_SPEED; i++) leds[i] = color;
    
    // выводим
    FastLED.show();
  }
}
Версия с MicroLED
// бегущие частицы 1D, версия с MicroLED

#define STRIP_PIN 2     // пин ленты
#define SOUND_PIN A0    // пин звука

#define COLOR_STEP 151  // шаг цвета, интересные 151, 129
#define LEDS_AM 300     // количество светодиодов
#define P_SPEED 2       // скорость движения

#define COLOR_DEBTH 2
#include <microLED.h>
microLED < LEDS_AM, STRIP_PIN, -1, LED_WS2812, ORDER_GRB, CLI_AVER > strip;

#include "VolAnalyzer.h"
VolAnalyzer sound(SOUND_PIN);

byte curColor = 0;      // текущий цвет

void setup() {
  strip.setBrightness(255);

  // настройки анализатора звука
  sound.setVolK(15);        // снизим фильтрацию громкости (макс. 31)
  sound.setVolMax(255);     // выход громкости 0-255
  sound.setPulseMax(200);   // сигнал пульса
  sound.setPulseMin(150);   // перезагрузка пульса
}

void loop() {
  if (sound.tick()) {   // если анализ звука завершён (~10мс)
    // перематываем массив светодиодов на P_SPEED вправо
    for (int k = 0; k < P_SPEED; k++) {
      for (int i = LEDS_AM - 1; i > 0; i--) {
        strip.leds[i] = strip.leds[i - 1];
      }
    }
    
    // резкий звук - меняем цвет
    if (sound.pulse()) curColor += COLOR_STEP;
    
    // берём текущий цвет с яркостью по громкости (0-255)
    mData color = mWheel8(curColor, sound.getVol());
    
    // красим P_SPEED первых светодиодов
    for (int i = 0; i < P_SPEED; i++) strip.set(i, color);
    
    // выводим
    strip.show();
  }
}
MicroLED + асинхронный АЦП
// бегущие частицы 1D, версия с MicroLED и асинхронным АЦП

#define STRIP_PIN 2     // пин ленты
#define SOUND_PIN 0     // аналоговый пин звука

#define COLOR_STEP 151  // шаг цвета, интересные 151, 129
#define LEDS_AM 300     // количество светодиодов
#define P_SPEED 2       // скорость движения

#define COLOR_DEBTH 2
#include <microLED.h>
microLED < LEDS_AM, STRIP_PIN, -1, LED_WS2812, ORDER_GRB, CLI_AVER > strip;

#include "VolAnalyzer.h"
VolAnalyzer sound;

byte curColor = 0;      // текущий цвет
volatile int mins = 1023, maxs = 0;

void setup() {
  strip.setBrightness(255);

  // настройки анализатора звука
  sound.setVolK(15);        // снизим фильтрацию громкости (макс. 31)
  sound.setVolMax(255);     // выход громкости 0-255
  sound.setPulseMax(200);   // сигнал пульса
  sound.setPulseMin(150);   // перезагрузка пульса
  sound.setDt(0);           // период 0 (ручной режим tick)
  sound.setWindow(2);       // окно на 2 измерения, мы передаём их вручную

  // настройки АЦП
  ADMUX = DEFAULT << 6 | SOUND_PIN;     // reference + пин
  ADCSRA = 1 << ADEN | 1 << ADATE | 1 << ADIE | 0b111; // запускаем асинх. режим на мин. скорости
  ADCSRA |= 1 << ADSC;      // поехали
}

// прерывание АЦП по завершению измерения
ISR(ADC_vect) {
  // ищем минимум и максимум
  if (mins > ADC) mins = ADC;
  if (maxs < ADC) maxs = ADC;
}

void loop() {
  // забираем значения
  noInterrupts();       // отключаем прерывания
  // читаем минимумы и максимумы
  int cmin = mins;
  int cmax = maxs;
  // сбрасываем для анализа в прерывании
  mins = 1023;
  maxs = 0;
  interrupts();         // включаем прерывания обратно
  
  // передаём в тик минимум и максимум
  sound.tick(cmin);
  sound.tick(cmax);

  // перематываем массив светодиодов на P_SPEED вправо
  for (int k = 0; k < P_SPEED; k++) {
    for (int i = LEDS_AM - 1; i > 0; i--) {
      strip.leds[i] = strip.leds[i - 1];
    }
  }

  // резкий звук - меняем цвет
  if (sound.pulse()) curColor += COLOR_STEP;
  
  // берём текущий цвет с яркостью по громкости (0-255)
  mData color = mWheel8(curColor, sound.getVol());
  
  // красим P_SPEED первых светодиодов
  for (int i = 0; i < P_SPEED; i++) strip.set(i, color);
  
  // выводим
  strip.show();
}
Потоковая версия

Более сложный вариант, позволяет выводить на ленту неограниченной длины. Позиции частиц хранятся в массиве, расчёт и отрисовка “хвостов” происходит при каждом выводе. Реакция только на “резкие звуки”.

// бегущие частицы 1D, потоковая версия

#define STRIP_PIN 2     // пин ленты
#define SOUND_PIN A0    // пин звука

#define COLOR_STEP 10   // шаг цвета, интересные 151, 129
#define PART_SP 4       // скорость движения частиц
#define LEDS_AM 500     // количество светодиодов
#define PART_AM 50      // макс. количество частиц
#define FADE_VAL 100    // скорость затухания

/*
  // микролед
  #include <microLED.h>
  microLED < 0, STRIP_PIN, -1, LED_WS2812, ORDER_GRB, CLI_AVER > strip;
*/

// тинилед (чуть быстрее работает)
#define TLED_PORT PORTD
#define TLED_DDR DDRD
#include "tinyLED.h"
tinyLED<STRIP_PIN> strip;

#include "VolAnalyzer.h"
VolAnalyzer sound(SOUND_PIN);

int partsPos[PART_AM];  // позиции частиц
byte partsCol[PART_AM]; // цвет частиц
byte curColor = 0;      // текущий цвет
int partsEx = 0;        // количество активных частиц
int tailSize = 0;       // размер хвоста-градиента

void setup() {
  //strip.setBrightness(255);  // для тинилед не нужно, яркость и так максимум

  // настройки анализатор звука
  sound.setPulseMin(40);  // громкость перезагрузки пульса
  sound.setPulseMax(60);  // громкость активации пульса
  sound.setAmpliK(20);    // плавность амплитуды
  sound.setVolK(10);      // плавность громкости

  // определяем длину хвоста при текущих настройках
  tailSize = 1;
  byte color = 255;
  while (color > 0) {   // пока яркость выше 0
    color = fade8(color, FADE_VAL); // гасим
    tailSize++;         // хвост растёт
  }
}

void loop() {
  if (sound.tick()) {   // если анализ звука завершён (~10мс)

    // двигаем активные частицы
    for (int i = 0; i < partsEx; i++) partsPos[i] += PART_SP;

    // самая дальняя частица вышла за ленту
    if (partsPos[0] >= LEDS_AM) {

      // перематываем массив на ячейку влево
      for (int i = 0; i < PART_AM - 1; i++) {
        partsPos[i] = partsPos[i + 1];
        partsCol[i] = partsCol[i + 1];
      }
      partsEx--;    // уменьшаем количество активных частиц
    }

    if (sound.pulse()) {              // зарегистрирован скачок
      if (partsEx + 1 <= PART_AM) {   // есть место в буфере
        partsEx++;                    // добавляем частицу
        partsPos[partsEx - 1] = 0;    // её позиция
        partsCol[partsEx - 1] = curColor; // и цвет
        curColor += COLOR_STEP;       // мотаем цвет
      }
    }

    // начинаем потоковую отправку
    strip.begin();
    mData color = 0;  // тут храним цвет
    int lastPart = partsEx - 1; // младшая частица

    // проходимся по всем светодиодам
    for (int i = 0; i < LEDS_AM; i++) {

      // хвост текущей активной частицы попадает на текущий светодиод
      if (lastPart >= 0 && i > partsPos[lastPart] - tailSize && i <= partsPos[lastPart]) {
        // личный цвет частицы
        color = mWheel8(partsCol[lastPart]);

        // гасим в разницу в позиции раз
        for (int k = 0; k < partsPos[lastPart] - i; k++) color = getFade(color, FADE_VAL);

        // позиция частицы совпала со светодиодом - переходим к следующей
        if (i == partsPos[lastPart]) lastPart--;
      } else color = 0;   // иначе чёрный

      // включаем светодиод
      if (i < LEDS_AM) strip.send(color);
    }
    strip.end();
  }
}

Видео


Полезный пример?

Похожие примеры
52 Комментария
  • Было бы круто в этом проекте добавить функцию изменения цвета от частоты звука, т.е. например чтобы басс более темным синим цветом воспроизводился, а вокал более нежным теплым цветом. Или еще круче, чтобы можно было самому настраивать выборку по цветам с шагом допустим в 10Гц от 5 до 100 и увеличивая шаг с ростом частоты, потому что разницу в 1000 и 1010 не услышать практически, а вот на низах эта разница ощутима)
    В общем есть в какую сторону улучшить проект))

    • Тут все упирается в скорость обработки. Не потянет дуинка нано.

      Разве что внешние фильтры по частотам сделать.

      • Всё отлично тянет, подключил по схеме, зашил ардуину и всё отлично работает)

  • Мне кажется что оптимальнее было бы использовать функцию на подобии memmove для сдвига массива в пямяти, либо вообще на прямую менять указатель на первый элемент массива, хоть тут нужно подумать не будет ли проблем с дырами в памяти, поэтому лучше что-то на подобии memmove

  • Добавил внешнее опорное напряжение через переменный резистор, а то засвечивало ленту и скорость движения уменьшил до 1 для 60 светодиодов, нормально получилось.
    А как сделать из центра в разные стороны ?

  • Крутая штука, моим мелких должна понравится! Только разбираться ещё, чайник я!)

  • Не понял, как такую длинную ленту (4-5 метров) Гайвер питает с одного конца без проблем с просадками?
    В статье про адресную (https://alexgyver.ru/ws2812_guide) ленту читал, что нужно дублировать питание примерно каждый метр.
    Или для качественных лент такой проблемы нет? Например если купить в Gigant4?

  • Alex, добрый день. повторил проект, выглядит ну очень красиво, будет очередным режимом для встроенной в стену ленты, но из-за непонимания алгоритма фильтрации шумов и громкости столкнулся с проблемой: если на протяжении нескольких секунд не издавать никаких звуков, по ленте начинает бесперывно пробегать огоньки в качестве шума, что бы они пропали приходится издать какой то громкий звук. каким образом можно установить нижний порог? повозился с библиотекой VolAnalyzer, потыкал переменные, но так ничего и не удалось

    • Проблема решена xD. как и предполагалось, всё легко и просто…
      Нужно всего лишь поднять значение функции setTrsh() и все шумы пропадают) Спасибо за такой проект!!!

      • привет, покаж образец как ты прописал в Скетче (sound.setTrsh) у меня такая же проблема бегают огоньки))))

  • Подскажите а можно ли запитать и ленту и ардуинку одним питанием как в уроке “Игрушка для кота”?

  • Немного поигрался с настройками в этой строчке ( CRGB color = CHSV(START_HUE + vol / -2, 255 – vol / 20, vol) с минусом прикольно и интересный эффект у меня получился а также увеличил чувствительность микрофона до 80 и так как мне нравиться цвет зеленый то нашел его в этой строчке #define START_HUE 80 (-+10)

  • У меня версии с микролед работают всего на нескольких светодиодах в начале, правда звук брал просто с колонки… А первые два варианта работают отлично, после вывода добавляю небольшой delay

  • Вот респект, все работает как часики то что надо под потолком протянул ленту
    режим эквалайзера

  • а можно как ко скоректировать скрипт что бы именно змейки бежали заноцветные без нахлестов, у гайвера 60 на метр у меня 30 на метр и змейка как то не особо смотрится на 6 метрах у него видно что на 60 диодов она пробегает по половине метра. надеюсь смысл понятен, адаптация на 30 диодов на метр надо

  • Привет, хочу прикрепить в спортзале к боксерской груше, и соответственно вместо микрофона использовать датчик вибрации. Как думаете, как для человека без опыта программирования задача посильная?

  • подскажите, как дописать код, что бы в паузе более X сек включался режим автоматического какого-то бегущего эффекта. а то без музыки грустно )

  • или вообще сделать беззвуковой вариант разного рода световых эффектов для улицы.

  • А без микрофонного модуля никто не пробовал?А то он явно не успеет приехать до праздников.С обычным диамиком от мобилки явно нехватает амплитуды.Работает только при очень большой громкости.Надо предуселитель мудрить.

  • Для использования поточной версии скетча: как выглядит подключение нескольких светодиодных лент, например, 3 по 5 м?

  • Собрал. При заливке скетча на последних версиях Arduino IDE программа ругалась на библиотеки красным цветом (матом) и не грузилась. Только на версии 1.8.5 вроде загрузилась и ругалась белым цветом. Ленту разместил на стене в виде елки. Очень интересный эффект получается- огоньки хаотично плавают в контуре елки, круто. Не знаю как здесь можно прикрепить видео. Спасибо, Александр.

  • может кто помочь с пошаговым обьяснением как шить ардуино под эти проэкты? А то всё купил а прошить не могу 🙁

  • Вот и у меня проблема, эти проекты не хочет шить. После проверки пишет ошибку и всё. Другие пробовал шьёт без проблем.
    Кто подскажет где косяк?
    С уважением Эдик.

  • Arduino: 1.8.19 (Windows Store 1.8.57.0) (Windows 10), Board: “Arduino Uno”

    sketch_jan05a:13:10: fatal error: VolAnalyzer.h: No such file or directory

    Benutzt: C:\Users\flaum\Documents\Arduino\libraries\FastLED

    #include “VolAnalyzer.h”

    Nicht benutzt: C:\Users\flaum\Documents\Arduino\libraries\FastLED-master

    ^~~~~~~~~~~~~~~

    compilation terminated.

    exit status 1

    VolAnalyzer.h: No such file or directory

  • Сдвиг массива стоит сделать линейным:
    for (int i = LEDS_AM – 1; i >= P_SPEED; –i) {
    leds[i] = leds[i – P_SPEED];
    }

    В основном для образовательных целей, для избегания квадратичных алгоритмов там, где они не нужны.
    Экономия на практичных значениях P_SPEED и LEDS_AM копеечная: сотни наносекунд.

    Стандартная функция memmove
    memmove(&leds[P_SPEED], &leds[0], sizeof(CRGB) * (LEDS_AM – P_SPEED));
    по факту работает примерно вдвое медленнее приведенного цикла.

  • Подключил… Закачал скетч на MicroLed…
    У меня WS2815 на 12В, так, что подключал 5В на микрофон отдельно от макетной платки…
    Но при подключении лента просто переливается и на микрофон не реагирует…
    Но когда подключил GND от микрофона не к “-” 5В, а к GND на Arduino nano, то микрофон не реагирует, а когда рукой трогаешь провод Gain – то дорожка бежит.
    Может не так подключаю, подскажите кто может…
    PS: “+” 12В подключаю к VIN на Arduino nano

  • Алекс подскажи пжл, что б на 4 ленты одновременно работало, как надо сделать? А то 2 без проблем, подключаю ещё 2 и горят только первые пару диодов на всех лентах и на этом всё 🙁

  • Собрал проект. Прошилась только MicroLED. Подключил так называемые пиксельные диоды ( те же адресные только в колпачках). Из 150 в работе немного больше 100 штук и последние просто горят. Как заставить все работать?

    • Вряд ли ты получишь ответ… В любом случае на самый крайний случай можешь заказать себе SP107E контроллер там около 200 световых программ настраиваемых с телефона. Я сам собрал оба проекта с видео Алекса на Arduino и уже через неделю они приелись 🙁 И я заказал себе новый контроллер и ни капли не пожалел, т.к. Алекс уже собирал блутуз управляемые гирлянды не раз и писал под них разные свето-программы, но увы не в этом проекте, хотя, как по мне он самый жизнеспособный и занимает заслуженное место в видео подборке топ проектов автора.

  • Подскажите, пожалуйста, что надо сделать, чтобы подключить 12В ленту ? Ардуина 12В проглотит, а микрофон ?

  • Приветствую! Собрал из gyverkit pro по схеме, загрузил версию прошивки с FastLED, все прекрасно работало! Но после того как загрузил прошивку MicroLED + асинхронный АЦП перестало работать корректно. Теперь даже загружаю первую прошивку “с FastLED” стало работать наоборот т.е. постоянно идут полосы в цвете а при звуке(хлопке и т.д.) диоды затухают, в чем может быть проблема? Я вставил другую ардуинку из набора и прошил первой прошивкой, работала она корректно секунд 20 и после так же изменила реакцию на звуковой сигнал…

Оставить комментарий

Ваш адрес email не будет опубликован.