К колонке на кухне я хотел подсветку, которая пульсирует в такт, и по классическому пути идти не захотел: микрофонный модуль в квартире с двумя детьми бесполезен — он слышит не музыку, а всю эту жизнь, и светомузыка пляшет на разговоры и чайники. Тот путь с MAX4466, огибающей и спектром уже разобран в уроке про музыкальную подсветку — отличный вариант для шумных сцен. А для тихой комнаты есть второй путь, о котором редко пишут: не слушать звук вообще, а считать его.
Идея: музыка с равномерным битом — это математика. Если трек идёт на 120 ударов в минуту, бочка обязана бить каждые 500 миллисекунд, и millis() знает об этом всё. Лента бьёт по метроному — и глаз, который видит вспышку рядом с колонкой, сшивает их в «подсветка играет в такт». Честно оговорю сразу: лента не слушает трек и не попадёт в сабвуфер живого диджея. Это фоновый пульс, как у рекламных стойок и фотозон, — и в девяти бытовых случаях этого достаточно.
Собираем три вещи: перевод BPM в длину доли, паттерн бочки и снейра по номеру доли такта, и огибающую удара — всплеск, который затухает сам. Всё на millis-кадре из урока про эффекты без delay — ни одной паузы, кнопка и ручка работают в том же цикле.
Метроном из millis: доля, такт и огибающая
Математика в три шага. Шаг первый: BPM в миллисекунды — длина доли равна 60000, делённому на темп: при 120 BPM это 500 мс, при 180 — 333. И вот первая грабля: на Uno переменная int шестнадцатибитная, максимум 32767 — число 60000 в неё не влезает и переворачивается в отрицательное. Держите его в unsigned long или считайте в два приёма. Шаг второй: номер доли такта — целочисленное деление millis() на длину доли и остаток по модулю четыре: получится счётчик 0-1-2-3, который бежит по такту вечно. Фаза внутри доли — остаток от того же деления, от 0 до длины доли.
Шаг третий — сам удар. Когда фаза внутри доли меньше сорока миллисекунд и предыдущий удар уже отработал, огибающая level ставится в максимум: 255 для бочки, 170 для снейра. Дальше каждый кадр level умножается на 85 сотых — экспоненциальный спад, за полсекунды от максимума до тьмы. Тот же механизм пик-холда работает в VU-метре из микрофонного урока, только там холд держал максимум громкости, а здесь затухание рисует хвост удара. Страховка level ниже 100 не даёт удару перезапускаться каждый кадр, пока фаза гуляет по окну сорока миллисекунд.
Раскраска колонки — снизу вверх: нижние восемь пикселей качает бочка глубоким синим, верхние восемь откликаются на снейр почти белым. Фоновый уровень 20 из 255 держит силуэт колонки между ударами — без него лента в паузах гаснет в чёрное и колонка исчезает из комнаты, а этого никто не просил.
#include <FastLED.h>#define LED_PIN 6#define NUM_LEDS 16 CRGB leds[NUM_LEDS];int level = 0; // огибающая удараint pattern = 0; // 0: четыре-на-пол, 1: оффбит, 2: бочка-снейрint prevBtn = HIGH;unsigned long lastFrame = 0; void setup() { FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS); FastLED.setBrightness(170); pinMode(2, INPUT_PULLUP);} void loop() { bool btn = digitalRead(2) == LOW; if (prevBtn == HIGH && !btn) { // фронт: листаем паттерн pattern++; if (pattern > 2) { pattern = 0; } } prevBtn = btn; int bpm = map(analogRead(A0), 0, 1023, 60, 180); unsigned long beat = 60000UL / bpm; // длина доли в мс int barPos = (millis() / beat) % 4; // доля такта: 0,1,2,3 int phase = millis() % beat; // мс с начала доли int kick = 0, snare = 0; if (pattern == 0) { kick = 1; } // бочка в каждую долю if (pattern == 1 && barPos % 2 == 1) { kick = 1; } // только 2 и 4 if (pattern == 2) { if (barPos == 0 || barPos == 2) { kick = 1; } else { snare = 1; } // 1 и 3 — снейр } if (kick && phase < 40 && level < 100) { level = 255; } if (snare && phase < 40 && level < 100) { level = 170; } level = (level * 85) / 100; // экспоненциальный хвост удара for (int i = 0; i < NUM_LEDS; i++) { int v = 20; // фоновый силуэт колонки if (i < 8) { v = v + level * (8 - i) / 8; } // низ качает бочка else { v = v + level * (i - 7) / 9; } // верх — снейр if (i < 8) { leds[i] = CHSV(170, 200, v); } // синий низ else { leds[i] = CHSV(0, 40, v); } // почти белый верх } FastLED.show();}Что здесь стоит разглядеть под лупой. Деление millis() на длину доли — целочисленное, и это хорошо: номер доли держится стабильным всю долю, никаких дробных дрожаний. Зато фаза millis() % beat начинает жизнь с произвольного числа — удар рискует прийти на середине первой доли после включения. Лечится обнулением якоря: возьмите millis() один раз в setup и работайте от разницы — приём якорного времени тот же, что в любом millis-планировщике. Вторая мелочь: деление 60000UL на bpm при ручке в крайнем положении даёт 333 миллисекунды, и окно удара в 40 мс съедает двенадцать процентов доли — при очень быстрых темпах окно стоит ужимать.
Бюджет времени кадров тут не проблема: на шестнадцати пикселях show() занимает полмиллисекунды, даже на шестидесяти — под два, а кадр у нас 30 мс. По памяти тоже спокойно: буфер три байта на пиксель, 16 пикселей — 48 байт. Зато бюджет тока — да, и об этом ниже отдельное предупреждение.
Подгон темпа под трек делается на слух и занимает минуту. Откройте плеер с тап-темпом или считайте удары секундомером: восемь ударов, время пополам — вот BPM, и ручка симулятора крутится в том же диапазоне 60-180. Промах на три-пять ударов глаз не ловит: вспышка на долю, уехавшую на пятую часть длины, всё ещё читается «в такт», рассинхрон копится медленно. Дальше десяти процентов метроном уже слышен отдельно от музыки — честный сигнал, что трек сыгран живыми, а не по сетке. Я подписываю подобранные значения маркером на корпусе: витрина переезжала, а цифры оставались.
В симуляторе сначала крутите темп на одном паттерне: при 60 BPM огибающая успевает догореть до фона между ударами, при 180 — хвосты сливаются, и колонка вечно дымится. Это не сбой, а честная арифметика: полсекунды между ударами хватает для полного спада, треть секунды — нет. Потом листайте паттерны кнопкой: четыре-на-пол — ровная качалка, оффбит — редкие двойные вспышки через долю, бочка-снейр — классика с чередованием синего и белого.
Паттерн листается по фронту кнопки — с проверкой предыдущего состояния, а не по удержанию. Если убрать проверку prevBtn, одно нажатие пролетит три-четыре паттерна за 150 миллисекунд удержания: за это время loop успевает сотню раз увидеть нажатую кнопку. Механику фронтов в подробностях мы разбирали в уроке про кнопку-листалку эффектов.
И загляните в Serial: счётчик ударов тикает вместе с лентой, и по нему виден главный выигрыш метронома — он не зависит ни от длины кадра, ни от загруженности loop: сколько бы строк ни добавилось в цикл, доля считается от millis() и не съезжает ни на миллисекунду.
| Паттерн | Куда бьёт | Когда уместен |
|---|---|---|
| Четыре-на-пол: бочка в каждую долю | Низ, синий всплеск каждые 333–1000 мс | Ровный фон: витрина, подсветка полки |
| Оффбит: бочка только на 2 и 4 | Редкие вспышки с паузой в две доли | Спокойные сцены, лоу-фай атмосфера |
| Бочка-снейр через долю | Низ синим, верх белым, чередование | Вечеринка, фотозона, кинотеатральный уголок |
Когда фейковый бит честен, а когда — нет
Метроном выигрывает там, где музыка фоновая и предсказуемая: плейлист одного жанра, витрина с расписанием, ночная подсветка бара, видеосъёмка с заранее известным треком. Подведите ручку к BPM трека — такие цифры показывают музыкальные плееры и таблицы BPM — и лента будет держать такт часами без единого провода к источнику звука. Для съёмки видео это вообще идеальный случай: анимация детерминирована, дубль после брака выглядит точно так же.
А вот на живом звуке метроном разоблачается мгновенно: диджей замедлил, пауза, брейк — лента продолжит бить в пустоту, и рассинхрон за минуту станет комичным. Там, где трек живой или меняется, нужен настоящий вход: микрофонная схема из музыкального урока ловит бас огибающей, а если звук гоняет компьютер — честнее передавать удары по проводу, как в уроке про команды по Serial: ПК знает сетку трека лучше любого датчика. Гибрид тоже никто не отменял: метроном как фон и микрофон как модуляция поверх.
Отдельная забава — уходить от равного такта. Паттерн живёт в номере доли, и ничто не мешает считать такт на три: бочка на первую долю, лёгкий акцент на третью — и метроном вальсирует. Та же механика обслуживает шесть восьмых и пунктирные ритмы: длина доли в миллисекундах не меняется, меняется раскраска номеров. Ограничение одно: всё это по-прежнему расписание, а не слух. Мой вердикт для витрин: фейковый бит честен, пока посетитель не стал слушать музыку специально; в момент, когда музыка — часть сценария, копите на микрофонный вход, а метроном оставьте фоном.
- 1. Трек идёт на 120 BPM. Как часто бьёт бочка?
- 2. Почему число 60000 нельзя просто положить в int на Uno?
- 3. Что делает строка level = level * 85 / 100 в каждом кадре?
- 4. Зачем в условии удара стоит проверка level < 100?
- 5. В каком случае фейковый бит разоблачит себя мгновенно?
- Длина доли = 60000 мс, делённые на BPM: 120 ударов — 500 мс; держите 60000 в unsigned long, int на Uno переворачивает его в минус
- Номер доли — целочисленное деление millis() на длину доли по модулю 4, фаза — остаток того же деления
- Удар — всплеск огибающей до 255 (бочка) или 170 (снейр) со спадом level * 85 / 100 каждый кадр
- Фоновый уровень 20 держит силуэт колонки между ударами; окно удара 40 мс и страховка level ниже 100 против повторов
- Метроном честен как фон и разоблачён на живом звуке: там нужен микрофон или удары по Serial
Частые вопросы по теме урока
- Трек идёт на 120 BPM. Как часто бьёт бочка?
- Минута — 60000 мс, делим на удары в минуту: 60000 / 120 = 500 мс между долями. При 180 BPM доля короче — 333 мс, при 60 — целая секунда. Каждые 500 мс — 60000 мс минуты, делённые на 120
- Почему число 60000 нельзя просто положить в int на Uno?
- На Uno int — 16 бит со знаком, потолок 32767. Шестнадцатитысячный запас сверх — и значение оборачивается в минус. Держите такие числа в unsigned long или делите расчёт на этапы. int на AVR шестнадцатибитный, максимум 32767 — число перевернётся отрицательным
- Что делает строка level = level * 85 / 100 в каждом кадре?
- Каждый кадр остаток теряет пятнадцать процентов — это экспоненциальный спад. От 255 уровень доходит до фоновых значений примерно за шестнадцать-семнадцать кадров, то есть за полсекунды. Гасит удар экспоненциально: за полсекунды от пика до тьмы
- Зачем в условии удара стоит проверка level < 100?
- Окно фазы меньше 40 мс — это один-два кадра. Без страховки второй кадр в том же окне снова поставил бы level в максимум, и хвост удара не успел бы начаться. Чтобы удар не перезапускался каждый кадр, пока фаза внутри окна
- В каком случае фейковый бит разоблачит себя мгновенно?
- Метроном не слышит звук: любой брейк или замедление оставит ленту бить в пустоту. Для живого звука нужен микрофон или явная передача ударов по Serial с машины, которая ведёт трек. На живом выступлении, где темп и паузы меняются на ходу