Skip to content Skip 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();
  }
}

Видео


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

Похожие примеры
Подписаться
Уведомить о
53 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии