Как стать автором
Обновить

MIDI Wavy: делаем управляемую цветомузыку с помощью Arduino и MIDI

Время на прочтение 13 мин
Количество просмотров 41K
Здравствуйте. В данном руководстве описан процесс сборки управляемой через MIDI-контроллер (или миди-дорожку в любом секвенсоре) цветомузыки на Arduino. Изначально, я делал это устройство для визуализации живого выступления, предлагается 2 режима работы: живая игра и воспроизведение MIDI-дорожки.

Статья рассчитана для начинающих — все, что нужно, объясняется доступно, более продвинутые могут просто ознакомиться с данным методом реализации, и, возможно, используют его в создании своих арт-инсталляций или используют его для динамического управления светом. Знать языки программирования не обязательно.



Оглавление


  1. Наглядная модель
  2. Компоненты
    1. LED-фонари
    2. Библиотека для работы с LED
    3. Эмулятор MIDI и секвенсор
    4. Связь Arduino — MIDI-контроллер
    5. MIDI-контроллер
    6. Макетная плата
  3. Отражатели
  4. Программная часть
  5. Пример анимации ленты
  6. Выводы
  7. Демонстрационное видео
  8. Файлы

MIDI Wavy будет состоять из:
  • 4 самодельных фонарей LED,
  • 4 3-пиновых коннекторов разной длинны (50 и 30 см) для связи LED <-> микроконтроллер,
  • микроконтроллера (например, arduino),
  • макетной платы (маленькой хватит)
  • любого MIDI-контроллера
  • эмулятора (а именно — Ableton Live + loopBe1)


§ 1. Наглядная модель


image

Путь от нажатия на клавишу до момента загорания светодиодов:
MIDI-контроллерAbleton Live (с ASIO) LoopBe1 Hairless MIDI<->Serial BridgeArduino LED-фонарь

Вместо MIDI-контроллера можно использовать клавиатуру компьютера в секвенсоре.

§ 2. Компоненты


§ 2.1. LED-фонари


Фонарь делается из светодиодной ленты 5050 с чипом WS2811, по 5 диодов на фонаре. WS2811 позволяет контролировать каждый светодиод отдельно, к тому же лента с конденсаторами уже спаяна на заводе, а на выходе имеем 3 пина DIN, 5V, GND. Продается как без защиты, так и с защитным прозрачным силиконовым чехлом. Для создания презентабельного вида устройства лучше всего использовать 3-пиновые коннекторы. Ими удобно пользоваться, упрощается изъятие светодиодов из системы.
image
(например, «Удлиннитель для сервомашинки 15 см JR 3pin». длину выбирать по-вкусу)

§ 2.2. Библиотека для работы с LED


В скетч ардуино подключаем библиотеку FastLED. Рекомендуется ознакомиться с интересной документацией, там можно найти код цветов и прочую полезную информацию.



На первый раз у меня получилось что-то вроде того, что показано ниже. Спаяв все фонари, нужно каждый проверить на работу:

Показать скетч ардуино для проверки LED
#include "FastLED.h"
#define NUM_LEDS 5  // 5 диодов
#define DATA_PIN 6 // 6-пин из ардуино подключается к DIN на ленте (информационный канал)

// Закидываем светодиоды в массив
CRGB leds[NUM_LEDS];

void setup() {
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS); // Инициализация
}

void loop() {
   FastLED.showColor(CHSV(190,255,255)); // вся лента вспыхнет примерно фиолетовым
}



(это старая версия фонаря без волшебного коннектора, просто припаянные провода из набора ардуино, но смысл от этого не меняется)

§ 2.3. Эмулятор MIDI и секвенсор


Эмулятор нужен в случае, когда мы хотим соединить секвенсор (пусть Ableton Live) с нашей цветомузыкой, чтобы управлять изменением световой картины в реальном времени, либо для проигрывания записанного изменения. Подойдет любой, но я взял LoopBe1 (http://www.nerds.de/en/loopbe1.html). Устанавливаем, перезагружаемся, открываем секвенсор. Суть настройки секвенсора — сделать выход с секвенсора в виртуальный MIDI-канал, чтобы потом этот канал связать с Arduino.

Заходим в настройки Ableton Live, либо в любой-другой и настраиваем выход MIDI-OUTPUT на наш MIDI-эмулятор:

Показать настройки секвенсора
image

В секвенсоре создаем 2 MIDI-дорожки. На первой — ставим любой синтезатор (советую Drum Rack, потому что можно удобно настроить имена клавиш, что поможет легче ориентироваться во всем этом), затем делаем на нем MUTE (можно и не делать на самом деле, если хотите работать прямо на этой дорожке), а второй оставляем пустым, делаем следующую настройку:

image

Активируем кнопку I/O (пункт 2 на рисунке), задаем настройки второй дорожки (пункт 1).
  • MIDI From: 1-Drum Rack (указываем откуда «снимать сигнал» — с первой дорожки).
  • Pre FX (значение можно любое, но Pre FX используется чтобы эффекты с первой доржки шли одновременно со второй, подробнее лучше в интернете почитать, в данной задаче это не имеет значения).
  • Monitor: Auto
  • MIDI To: LoopBe Internal MIDI (выбираем название эмулятора MIDI)
  • Ch: 1. (по-умолчанию используентся 1 (первый) канал. Всего доступно 16 каналов, т.е. целых 16 слоев миди-сообщений (нот). Полезно использовать разные каналы, когда создаем более сложную систему управления LED).

Зажмем кнопку CTRL и активируем обе дорожки (на рисунке пункт 3). Готово. Обратите внимание на пункт 4 на рисунке, это как раз то, о чем я говорил, потом будет удобно с этим работать. Например через редактор нот.

На рисунке выше, такого же вида карты нот, как представлено в области №4, можно добиться следующим образом: закидываете в ячейку Drum Rack любой аудиофайл (я использовал kick.wav), нажимаете по ней правой кнопкой мыши, «Rename» (или CTRL+R).

Не обязательно:ASIO Driver

Это эмулятор звуковой карты, с помощью него можно получить очень малую задержку между нажатием клавиши и результатом обработки сигнала (для сравнения, на базовых настройках секвенсора, если вы будете переключаться между клавишами много и быстро, то появится задержка между нажатием клавиши и началом работы светодиода).

Найти можно на официальном сайте, в свободном доступе.

image

Создание MIDI-дорожки


↑ Перейдем в режим аранжировки, на дороже LED обязательно должна стоять опция None в пункте 2 на рисунке, иначе MIDI-клип нельзя будет создать. Выделяем зону и создаем новый клип.



↑ Добавляем ноты. Например, на рисунке выше показано поочередное включение светодиодов на LEFT_LONG-ленте. Как только кончается нота, так сразу выключается светодиод. Получается резкое мерцание ленты в одну сторону:





↑ Удлинняя ноты можно создать шлейф, который на глаз созерцать приятнее, чем просто поочередную смену работающего светодиода:



Меняя значение ударов в минуту, можно изменить скорость воспроизведения дорожки:



Если мы хотим сделать музыкальное сопровождение, то просто добавляйте аудиотрек с аудиофайлом каким нужно (перетаскивая его на рабочую область). Чтобы свет шел синхронно с музыкой, делаем Warp аудиоклипа. Подробнее.



Связь Arduino — MIDI-контроллер

С помощью программы hairless-midiserial (http://projectgus.github.io/hairless-midiserial/) создаем мост Serial<->MIDI (Через Serial-порт будут передаваться значения с эмулятора/контроллера):

image

Как видно, MIDI In — вход на мост, Serial port — выход с моста на ардуино. В этой настройке можно напрямую подключить любой MIDI-контроллер, например MIDI-клавиатуру, но честно говоря, у меня напрямую клавиатура не захотела работать, видимо нужно какое-то спец. приложение, разберемся с этим по-возможности как-нибудь, но управление светом через миди + секвенсор удобнее. Из минусов — программа-секвенсор может грузиться довольно медленно.

§ 2.4. MIDI-контроллер


Подойдет абсолютно любой (в зависимости от ваших задач, конечно же) контроллер, который можно купить в музыкальном магазине, либо собрать самому (в интернете есть инструкции как это сделать). А если вы пишете электронную музыку, то такая клавиатура наверняка у вас есть. Можно использовать как крутилки, так и сами клавиши. Каждой клавише (ноте) «вешаем» выполнение определенных команд, аналогично и с крутилками. В данном примере используются 3 крутилки (обозначим их Master, Color, Delay) и ноты из четырех октав.

image

В Ableton, FL Studio и т.д. можно обойтись и без MIDI-контроллера. В этих программах можно пользоваться обычной клавиатурой ПК как фортепиано.

§ 2.5. Макетная плата


Для прототипа устройства достаточно соединить всё на одной маленькой макетной плате. В дальнейшем не составит труда заменить макетную плату на печатную (припаивать все что надо — не отвалится). Корпуса для устройства есть в каких-нибудь магазинах с ардуино или радиотехникой, алюминиевые, стоят от ~300 Р в зависимости от размера.

image image

Беспаечная макетная плата SYB-170 10x17 отверстий 60 Р; Алюминиевый корпус 100 * 76* 35 мм, золотой. 300 Р.

§ 3. Отражатели


Направленность излучения светодиодов можно задавать с помощью отражателей произвольных видов. По желанию можно прикрутить любой отражатель, какой сможете. Я взял пластиковый уголок для стен — он матовый, белый и хорошо подсвечивается. Одну внутреннюю сторону (пусть будет нижняя) поклеим алюминиевым скотчем (или каким-нибудь зеркальным), а другую оставим. Направим излучение на не оклеенную сторону, получим матовое свечение отражателя, причем ограниченное (т.е. будет казаться, что светится вся или какая-то часть отражателя, в зависимости от пропорций длины отражателя к длине ленты). Не обязательно клеить скотч, но тогда коэффициент отражения будет меньше. Можно наоборот, оклеить все области отражателя.

image

Есть отражатели с различными текстурами, которые влияют на форму излучения, но их рассматривать не будем.

§ 4. Программная часть


Разберем скетч ардуино

Для начала подключим нужные библиотеки, всем переменным задаем тип byte (хранит от 0 до 255, как раз нужные нам значения, остальные использовать не будем):
Показать код, часть 1
// MIDI Wavy
// JVLNS.ORG 2014
// Подключаем библиотеку для управления светодиодами
#include "FastLED.h"

// обозначим цифровые выходы на ардуино, смотри рисунок ниже
#define LEFT_LONG 7
#define LEFT_SHORT 6
#define RIGHT_SHORT 5
#define RIGHT_LONG 4

#define NUM_STRIPS 4 // количество лент
#define NUM_LEDS_PER_STRIP 5 // количество светодиодов в 1 ленте

// создаем двухмерный массив для удобного управления
CRGB leds[NUM_STRIPS][NUM_LEDS_PER_STRIP];

// удобно самому задать массив цветов, значения можно посмотреть рисунок выше
byte colors[] = {0, 32, 64, 96, 128, 160, 192, 224, 239 };

// Значение цвета и Master (яркости) по-умолчанию
byte ColorBasic = 0;
byte Master = 255;

byte commandByte; // первый байт MIDI-сообщения, здесь будут хранится значения команды (noteOn, noteOff, Controller)
byte noteByte; // второй (номер ноты)
byte velocityByte; // третий байт — значение (для команд noteOn/Off имеет смысл velocity (https://www.ableton.com/en/manual/editing-midi-notes-and-velocities), а для Controller — это значение крутилки).
byte velocityByte1; // используем функцию map() с 0-127 на 0-255
byte noteOn = 144; // нота нажата
byte noteOff = 128; // отпустили ноту

byte faza = 0; // через эту переменную будем задавать задержку delay(faza) в некоторых командах 

Обозначение фонарей:

image

(Примечание: 0, 1, 2, 3 — номера ячеек в массиве)

Код, часть 2. Задаем функцию setup():

void setup() { 
  // запускаем Serial-порт со скорость 256000
  Serial.begin(256000);
  FastLED.addLeds<NEOPIXEL, LEFT_LONG>(leds[0], NUM_LEDS_PER_STRIP);
  FastLED.addLeds<NEOPIXEL, LEFT_SHORT>(leds[1], NUM_LEDS_PER_STRIP);
  FastLED.addLeds<NEOPIXEL, RIGHT_SHORT>(leds[2], NUM_LEDS_PER_STRIP);
  FastLED.addLeds<NEOPIXEL, RIGHT_LONG>(leds[3], NUM_LEDS_PER_STRIP);
}

Данный способ обозначения светодиодных лент нужен для одновременного управления каждой лентой независимо от другой. Подробнее на странице документации к библиотеке FastLED.

Дело в том, что 8-битные контроллеры не поддерживают многопоточность, код выполняется последовательно. Нагромождение кода замедляет его исполнение. Данный метод подключения и работы со светодиодами (рассмотрим дальше) в каком-то смысле решает эту задачу, программа тормозит меньше, а следовательно, каждое нажатие на клавишу будет учтено (по опыту скажу, что раньше делал иначе и возникало много косяков с пропусканием нажатия ноты из-за задержек или косяков вычисления). К тому же изменять свойства светодиодов намного удобнее именно этим методом.

Запускаем Serial-порт, через который микроконтроллер будет общаться с компьютером. Указываем 256000. В настройках Hairless Bridge указываем эту же скорость.

image

Создадим пару функций, которые будут включать и выключать светодиоды с определенными настройками:

Показать код, часть 3
// pointer() включает определенный светодиод в определенной ленте независимо от других светодиодов

// num_strip = номер ленты (от 0 до 3, см рис в [Обозначении фонарей])
// num_led = номер светодиода в ленте с номером num_strip 
// master = яркость светодиодов
// color = цвет
void pointer(byte num_strip, byte num_led, byte master, byte color) {
    leds[num_strip][num_led] = CHSV(color, 255, master);
    FastLED.show();
}

// blackout() выключает определенный светодиод в определенной ленте независимо от других светодиодов
void blackout(byte num_strip, byte num_led) {
    leds[num_strip][num_led] = CHSV(0, 0, 0);
    FastLED.show();
}


Определим функцию работы с MIDI-командами:

Показать код, часть 4
void checkMIDI(){
  do{ // считываем первые 3 байта команды
    if (Serial.available()){
      commandByte = Serial.read();//read first byte
      noteByte = Serial.read();//read next byte  
      velocityByte1 = Serial.read();//read final byte

      velocityByte = map(velocityByte1, 0, 127, 0, 255);
      Master = velocityByte; // связали Master со значением Velocity ноты
      
      // Назначаем нотам определенные цвета
      if(noteByte == 96) ColorBasic = colors[0];   
      if(noteByte == 97) ColorBasic = colors[1];   
      if(noteByte == 98) ColorBasic = colors[2];   
      if(noteByte == 99) ColorBasic = colors[3];   
      if(noteByte == 100) ColorBasic = colors[4];   
      if(noteByte == 101) ColorBasic = colors[5];   
      if(noteByte == 102) ColorBasic = colors[6];   
      if(noteByte == 103) ColorBasic = colors[7];   
      if(noteByte == 104) ColorBasic = colors[8];    
      
      // Крутилки
      if (commandByte == 176 && noteByte == 22)ColorBasic = velocityByte;      // Color
      if(commandByte == 176 && noteByte == 21) Master = velocityByte;      // Master
      if(commandByte == 176 && noteByte == 23) faza = velocityByte; // Velocity

    // LEFT_LONG      
    if(noteByte == 36) { //C1
      if(commandByte == noteOn) pointer(0, 0, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(0, 0);
    }
    if(noteByte == 38) {
      if(commandByte == noteOn) pointer(0, 1, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(0, 1);
    }
    if(noteByte == 40) {
      if(commandByte == noteOn) pointer(0, 2, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(0, 2);
    }
    if(noteByte == 41) {
      if(commandByte == noteOn) pointer(0, 3, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(0, 3);
    }
    if(noteByte == 43) {
      if(commandByte == noteOn) pointer(0, 4, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(0, 4);
    }
   // end left_long
  
    // LEFT_SHORT      
    if(noteByte == 45) {
      if(commandByte == noteOn) pointer(1, 0, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(1, 0);
    }
    if(noteByte == 47) {
      if(commandByte == noteOn) pointer(1, 1, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(1, 1);
    }
    if(noteByte == 48) {
      if(commandByte == noteOn) pointer(1, 2, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(1, 2);
    }
    if(noteByte == 50) {
      if(commandByte == noteOn) pointer(1, 3, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(1, 3);
    }
    if(noteByte == 52) {
      if(commandByte == noteOn) pointer(1, 4, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(1, 4);
    }
  // end left_short

    // RIGHT_SHORT     
    if(noteByte == 53) { 
      if(commandByte == noteOn) pointer(2, 0, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(2, 0);
    }
    if(noteByte == 55) {
      if(commandByte == noteOn) pointer(2, 1, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(2, 1);
    }
    if(noteByte == 57) {
      if(commandByte == noteOn) pointer(2, 2, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(2, 2);
    }
    if(noteByte == 59) {
      if(commandByte == noteOn) pointer(2, 3, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(2, 3);
    }
    if(noteByte == 60) {
      if(commandByte == noteOn) pointer(2, 4, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(2, 4);
    }
   // end right_short

    // RIGHT_LONG      
    if(noteByte == 62)
      if(commandByte == noteOn) pointer(3, 0, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(3, 0);
    }
    if(noteByte == 64) {
      if(commandByte == noteOn) pointer(3, 1, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(3, 1);
    }
    if(noteByte == 65) {
      if(commandByte == noteOn) pointer(3, 2, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(3, 2);
    }
    if(noteByte == 67) {
      if(commandByte == noteOn) pointer(3, 3, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(3, 3);
    }
    if(noteByte == 69) {
      if(commandByte == noteOn) pointer(3, 4, Master, ColorBasic);
      else if(commandByte == noteOff) blackout(3, 4);
    }
 // end right_long

    if(noteByte == 72) { // нота C4, лента LEFT_LONG горит полностью
      if(commandByte == noteOn) {
        for(byte i = 0; i < NUM_LEDS_PER_STRIP; i++) pointer(0, i, Master, ColorBasic);
      }
      else if(commandByte == noteOff) { // отпускаем ноту — светодиоды затухают
       for(byte i = 0; i < NUM_LEDS_PER_STRIP; i++) blackout(0, i);
      }
    }

    if(noteByte == 74) { // лента LEFT_SHORT горит полностью
      if(commandByte == noteOn) {
        for(byte i = 0; i < NUM_LEDS_PER_STRIP; i++) pointer(1, i, Master, ColorBasic);
      }
      else if(commandByte == noteOff) {
       for(byte i = 0; i < NUM_LEDS_PER_STRIP; i++) blackout(1, i);
      }
    }
    if(noteByte == 76) { // лента RIGHT_SHORT горит полностью
      if(commandByte == noteOn) { 
        for(byte i = 0; i < NUM_LEDS_PER_STRIP; i++) pointer(2, i, Master, ColorBasic);
      }
      else if(commandByte == noteOff) {
       for(byte i = 0; i < NUM_LEDS_PER_STRIP; i++) blackout(2, i);
      }
    }

    if(noteByte == 77) { // лента RIGHT_LONG горит полностью
      if(commandByte == noteOn) { 
        for(byte i = 0; i < NUM_LEDS_PER_STRIP; i++) pointer(3, i, Master, ColorBasic);
      }
      else if(commandByte == noteOff) {
       for(byte i = 0; i < NUM_LEDS_PER_STRIP; i++) blackout(3, i);
      }
    } 
   // СБРОС, ВЫКЛЮЧЕНИЕ ВСЕХ СВЕТОДИОДОВ
   if(noteByte == 37) {
      for(byte x = 0; x < NUM_STRIPS; x++) {
        // This inner loop will go over each led in the current strip, one at a time
        for(byte i = 0; i < NUM_LEDS_PER_STRIP; i++) {
          leds[x][i] = CRGB::Black;
          FastLED.show();
        }
      }
   }
   
   // плавающая точка
   if(noteByte == 73){
      if(commandByte == noteOn) {
        for(byte x = 0; x < NUM_STRIPS; x++) {
          for(byte i = 0; i < NUM_LEDS_PER_STRIP; i++) {
            leds[x][i] = CHSV(ColorBasic, 255, 255);
            FastLED.show();
            leds[x][i] = CRGB::Black;
            delay(faza); // значение задержки можно задавать крутилкой Delay
          }
        }
      } // end Note ON
      else if(commandByte == noteOff) {
       for(byte i = 0; i < NUM_LEDS_PER_STRIP; i++) blackout(3, i);
      }
   }// end note On // end noteByte 73

   // все светодиоды загораются одновременно и постепенно затухают через промежуток faza в миллисекундах, кратный максимальному значению i в цикле.
  if(noteByte == 75 && commandByte == noteOn) {
    faza = map(faza, 0, 200, 0, 3);
    for(byte i = 255; i > 0; i--) {
    FastLED.showColor(CHSV(ColorBasic, 255, i));
    delay(faza);
    }
  }
  
  }// end if
  }// end do
  while (Serial.available() > 2);// считываем первые 3 байта
}

Комментарии по коду: как вы заметили, при нажатии на клавишу загорается определенный светодиод, несколько определенных одновременно нажатых клавиш дадут сплошное свечение, например одной из лент, до тех пор, пока клавишу не отпустить. Это позволяет создавать плавные переходы от одного светодиода к другому, создавая шлейф.

Бывает, что светодиод зависает и не выключается, для этого назначили на ноту 37 «СБРОС, ВЫКЛЮЧЕНИЕ ВСЕХ СВЕТОДИОДОВ».

Значения в MIDI-командах можно взять отсюда (http://www.midi.org/techspecs/midimessages.php), либо непосредственно отследить нажатую клавишу в hairless-midiserial:

image

Можно (но не обязательно) добавить еще одну функцию — простое переливание всех светодиодов цветом. Тогда устройство можно будет использовать как ночник:

void Rainbow(int delay_time) {
  for(int hue = 0; hue < 255; hue++) {
    FastLED.showColor(CHSV(hue,255,255));
    delay(delay_time);
  }
}


Код, часть 5. И последний штрих, задаем loop() функцию:

void loop(){  
  // запускаем функцию работы с MIDI
  checkMIDI();
  // экспериментально полученное значение задержки в миллисекундах. если убрать это значение, программа не будет работать корректно. скорее всего из-за того, что вычисления будут производиться быстрее, чем микроконтроллер сможет переключаться между светодиодами.
  delay(50);  
}


§ 5. Пример анимаций ленты


Волна



Чем «длиннее» ноты, которые вы нажимаете/вставляете, тем более размытая получается картина из-за создающегося эффекта призрака:

Рассмотрим пример.



Дублируем ноты несколько раз, причем с каждым разом понижаем Velocity ноты на определенный уровень, допустим на 25%. В данном случае задавать хвост можно точнее:



§ 6. Выводы


§ 6.1. Ценовой вопрос


  • Один такой светодиод стоит около 17 рублей, нам нужно 20 таких => 340 Р.
  • Ардуино (выбрал один из дешевых) 400 Р.
  • Коннекторы 4 шт — не помню точно, около 100 Р вышло.
  • Макетная плата — 60 Р.
  • Итого: 908 Р (без учета проводов из набора Arduino)

Самое дорогое — светодиоды и микроконтроллер. Вообще, если вы в теме, то в плане ардуино можно использовать и более дешевые вещи.

§ 6.2. Область применения


В основном это световое оформление инсталляций, концертов, сооружений, и т.д. Ограничение по фантазии. Например, я использую MIDI Wavy для освещения своих треков. Светодиоды помогают придать особую атмосферу и драйв музыке, но смотрите — не убейте людей эпилепсией. Желательно использовать с дымом, тогда картина излучения выглядит намного мощнее и круче.

Упрощенный вариант управления без миди-контроллера я использую в подсветке своего велосипеда, но о нем позже.

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

Если вы заинтересовались, желаю удачи в начинаниях! Обязательно пришлите ссылку на свою работу.

§ 7. Демонстрационное видео




§ 8. Ссылки и файлы


Теги:
Хабы:
+8
Комментарии 6
Комментарии Комментарии 6

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн