Бегущие под музыку огни
Содержание
ToggleЗадача
- Сделать реакцию ленты на звук как в вирусном видео (см. внизу страницы)
Базовые уроки
Подключение
- Лента подключается к внешнему питанию и пину D2
- В данной схеме Arduino питается от внешнего источника, подключение к USB необязательно
- Микрофон к питанию и аналоговому пину (А0)
- Пин Gain микрофона задаёт усиление звука (значения также написаны на модуле):
- Никуда не подключен (float) — 60dB (максимум)
- GND — 50dB
- VCC — 40dB
Библиотеки
Программа
Примечание: можно настроить нижний порог шумов, добавив строчку sound.setTrsh(величина) в блок setup(). По умолчанию порог задан 40, его можно увеличить, чтобы снизить чувствительность системы к шумам.
// бегущие частицы 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();
}
}
Вместо яркости-от-громкости будем варьировать все три параметра в пространстве 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();
}
}
// бегущие частицы 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();
}
}
// бегущие частицы 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();
}
}
