ФОРУМ КУПИТЬ

Последние статьи

ВСЕ СТАТЬИ

Highway to Hell или цветомузыка по Ethernet

22/12/2013 03:07:27

No stop signs, speed limit
Nobody's gonna slow me down
Like a wheel, gonna spin it
Nobody's gonna mess me round...

Приближались новогодние праздники и я подумал, что пора бы уже поднять автоматизацию гирлянд и прочей праздничной иллюминации на новый уровень. В прошлой статье "Елочная гирлянда с управлением по 1-wire" я моргал светодиодной гирляндой посредством технологии 1-wire. Все хорошо, но как-то скучновато. Душа требовала чего-то более возвышенного. А что если... а что если моргать лампочками под музыку?

У нас же есть такая замечательная штука, как "Мегадевайс" (он же Жора, он же Гога... то есть MegaD-328). Устройство, которое управляется по сети Ethernet. А почему бы не сделать так, чтобы сервер или просто любой домашний компьютер, проигрывая музыку через медиа-систему (мультирум), отправлял в такт мелодии команды MegaD-328 на включение и выключение определенных ламп? Действительно! Но, хватит ли скорости Ethernet и производительности МК для обработки столь большого количества команд? Ведь каждая команда - это целый TCP-пакет. Микроконтроллеру нужно установить соединение, принять сообщение, распарсить его... А попробуем!

Для решений этой задачи я воспользовался языком программирования python. Во-первых, потому что в нем есть ряд уже готовых к использованию библиотек для работы со звуком, а во-вторых, ниже описанный код должен работать не только под Linux (Unix), но и под Windows и Mac OS.

Итак, поехали.
Задача первая. Делаем из MegaD-328 волюметер (VU-meter или поросту измеритель уровня звука)
Но сперва поймем как с помощью python'а организовать отправку команд Мегадевайсу. А очень все просто

import httplib
conn = httplib.HTTPConnection("192.168.0.14")
conn.request("GET", "/sec?cmd=7:2")
conn.close()

Работает!
Теперь мы подключим библиотеки alsaaudio и audioop
По умолчанию в Debian Linux библиотека alsaaudio не установлена, но решается вопрос просто.

apt-get install python-alsaaudio

Далее пишем простой скрипт, который анализирует громкость музыки и приводит значение к диапазону от 0 до 6 (количество выходных портов стандартного комплекта). К сожалению готовых средств для работы с mp3 в python нет, зато есть возможность работать с wav. А поскольку wav из mp3 делается за 5 секунд (ну хотя бы командой lame --decode), но для начала сойдет.

#!/usr/bin/env python
import alsaaudio as aa
import audioop
from time import sleep
import struct
import numpy as np
import wave
import httplib

# Set up audio
wavfile = wave.open('music/03.wav','r')
sample_rate = wavfile.getframerate()
no_channels = wavfile.getnchannels()
chunk = 2048
output = aa.PCM(aa.PCM_PLAYBACK, aa.PCM_NORMAL)
output.setchannels(no_channels)
output.setrate(sample_rate)
output.setformat(aa.PCM_FORMAT_S16_LE)
output.setperiodsize(chunk)

cmd_off_old = "";
print "Processing....."

data = wavfile.readframes(chunk)
while data!='':
    output.write(data)
    max_vol_factor =4000
    max_vol = audioop.max(data,2)/max_vol_factor
    cmd = ""
    cmd_off = ""
    last_val = 0
    for i in range (0,max_vol):
        port = 7 + i
        cmd = cmd + str(port) + ":1;"

    last_val = max_vol

    for j in range (last_val, 8):
        port = 7 + j
        cmd_off = cmd_off + str(port) + ":0;"

    print cmd

    if ( cmd != "" ):
        conn = httplib.HTTPConnection("192.168.0.14")
        conn.request("GET", "/sec?cmd=" + cmd)
        conn.close()

    data = wavfile.readframes(chunk)

    if ( cmd_off_old == cmd_off and last_val == 0 ):
        cmd_off = ""

    if ( cmd_off != "" ):
        conn = httplib.HTTPConnection("192.168.0.14")
        conn.request("GET", "/sec?cmd=" + cmd_off)
        conn.close()
        cmd_off_old = cmd_off

    cmd_old = cmd

И вот, что я получил в результате.
ПРИМЕЧАНИЕ! В ролях участвовал бесшумный симисторный исполнительный модуль MegaD-7I7O-S на который каким-то таинственным, необъяснимым и загадочным образом попала наклейка от релейного модуля. Но я специально провел подобный опыт и с релейным модулем, который работал абсолютно аналогично с той лишь разницей, что в такт морганию светодиодов щелкали еще и релюшки.

Ну как? Результат данного эксперимента показал, что MegaD-328 без труда справляется с задачей визуализации музыки без какой-либо видимой рассинхронизации. Теперь достаточно к портам подключить лампы накаливания Ватт по 150 вряд, да зарядить что-нибудь погромче на всю Ивановскую. Знатный получится волюметр!

Но постойте, отплясывать гопака под волюметр нынче не в моде. Я прошел пешком пол Европы и нигде не видел, чтобы танцевали под VU-meter. Сейчас у них в моде форменное безобразие - называется FFT (Fast Fourier transform). А по нашему, значит, "Быстрое преобразование Фурье". С применением этого алгоритма можно разложить музыку на отдельные частоты, а из MegaD-328 сделать что-то вроде спектрометра. К сожалению по математике у меня всегда была твердая тройка, поэтому я не стал изобретать велосипед, а взял уже готовую реализацию FFT для данной задачи, написанную для python'а. И вот такой получился скрипт

#!/usr/bin/env python

# 8 band Audio equaliser from wav file
 
import alsaaudio as aa
from struct import unpack
import numpy as np
import wave
import httplib

matrix    = [0,0,0,0,0,0,0,0]
power     = []
weighting = [2,2,8,8,16,32,64,64] # Change these according to taste

# Set up audio
wavfile = wave.open('music/06.wav','r')
sample_rate = wavfile.getframerate()
no_channels = wavfile.getnchannels()
chunk       = 4096 # Use a multiple of 8
output = aa.PCM(aa.PCM_PLAYBACK, aa.PCM_NORMAL)
output.setchannels(no_channels)
output.setrate(sample_rate)
output.setformat(aa.PCM_FORMAT_S16_LE)
output.setperiodsize(chunk)

# Return power array index corresponding to a particular frequency
def piff(val):
    return int(2*chunk*val/sample_rate)
  
def calculate_levels(data, chunk,sample_rate):
    global matrix
    # Convert raw data (ASCII string) to numpy array
    data = unpack("%dh"%(len(data)/2),data)
    data = np.array(data, dtype='h')
    # Apply FFT - real data
    fourier=np.fft.rfft(data)
    # Remove last element in array to make it the same size as chunk
    fourier=np.delete(fourier,len(fourier)-1)
    # Find average 'amplitude' for specific frequency ranges in Hz
    power = np.abs(fourier)  
    matrix[0]= int(np.mean(power[piff(0)    :piff(156):1]))
    matrix[1]= int(np.mean(power[piff(156)  :piff(313):1]))
    matrix[2]= int(np.mean(power[piff(313)  :piff(625):1]))
    matrix[3]= int(np.mean(power[piff(625)  :piff(1250):1]))
    matrix[4]= int(np.mean(power[piff(1250) :piff(2500):1]))
    matrix[5]= int(np.mean(power[piff(2500) :piff(5000):1]))
    matrix[6]= int(np.mean(power[piff(5000) :piff(10000):1]))
    matrix[7]= int(np.mean(power[piff(10000):piff(20000):1]))
    # Tidy up column values for the LED matrix
    matrix=np.divide(np.multiply(matrix,weighting),1000000)
    # Set floor at 0 and ceiling at 8 for LED matrix
    matrix=matrix.clip(0,8)
    return matrix

cmd_off_old = "";

# Process audio file  
print "Processing....."
data = wavfile.readframes(chunk)
while data!='':
    output.write(data)  
    matrix=calculate_levels(data, chunk,sample_rate)
    cmd = "";
    cmd_off = "";

    for i in range (0,7):
        port = 7 + i
        if ( matrix[i] > 3 ):
            cmd = cmd + str(port) + ":1;"
        else:
            cmd_off = cmd_off + str(port) + ":0;"
       
    if ( cmd_off_old == cmd_off and cmd == "" ):
        cmd_off = "";

    print cmd

    if ( cmd != "" ):
        conn = httplib.HTTPConnection("192.168.0.14")
        conn.request("GET", "/sec?cmd=" + cmd)
        conn.close()
   
    data = wavfile.readframes(chunk)

    if ( cmd_off != "" ):
        conn = httplib.HTTPConnection("192.168.0.14")
        conn.request("GET", "/sec?cmd=" + cmd_off)
        conn.close()
        cmd_off_old = cmd_off

Вот теперь можно смело подключать к выходам разноцветные лампочки и плясать "у ту степь", джампстайл или даже стрип-дэнс. Результат в студию.

Ну а как же Новый Год и елочные гирлянды? Ведь визуализация в виде спектрометра для такой задачи подойдет слабо. Ха! Когда у нас есть FFT, можно реализовать любые алгоритмы, насколько только хватит фантазии. На скорую руку я немного изменил уже существующий скрипт таким образом, чтобы лампы переключались случайным образом в такт музыке. Под "тактом" я тут понимаю не уровень громкости музыки, а всплеск по любой из частот выше определенного порога.

#!/usr/bin/env python

# 8 band Audio equaliser from wav file
 
import alsaaudio as aa
from struct import unpack
import numpy as np
import wave
import httplib
import random

matrix    = [0,0,0,0,0,0,0,0]
power     = []
weighting = [2,2,8,8,16,32,64,64] # Change these according to taste

# Set up audio
wavfile = wave.open('music/01.wav','r')
sample_rate = wavfile.getframerate()
no_channels = wavfile.getnchannels()
chunk       = 4096 # Use a multiple of 8
output = aa.PCM(aa.PCM_PLAYBACK, aa.PCM_NORMAL)
output.setchannels(no_channels)
output.setrate(sample_rate)
output.setformat(aa.PCM_FORMAT_S16_LE)
output.setperiodsize(chunk)

# Return power array index corresponding to a particular frequency
def piff(val):
    return int(2*chunk*val/sample_rate)
  
def calculate_levels(data, chunk,sample_rate):
    global matrix
    # Convert raw data (ASCII string) to numpy array
    data = unpack("%dh"%(len(data)/2),data)
    data = np.array(data, dtype='h')
    # Apply FFT - real data
    fourier=np.fft.rfft(data)
    # Remove last element in array to make it the same size as chunk
    fourier=np.delete(fourier,len(fourier)-1)
    # Find average 'amplitude' for specific frequency ranges in Hz
    power = np.abs(fourier)  
    matrix[0]= int(np.mean(power[piff(0)    :piff(156):1]))
    matrix[1]= int(np.mean(power[piff(156)  :piff(313):1]))
    matrix[2]= int(np.mean(power[piff(313)  :piff(625):1]))
    matrix[3]= int(np.mean(power[piff(625)  :piff(1250):1]))
    matrix[4]= int(np.mean(power[piff(1250) :piff(2500):1]))
    matrix[5]= int(np.mean(power[piff(2500) :piff(5000):1]))
    matrix[6]= int(np.mean(power[piff(5000) :piff(10000):1]))
    matrix[7]= int(np.mean(power[piff(10000):piff(20000):1]))
    # Tidy up column values for the LED matrix
    matrix=np.divide(np.multiply(matrix,weighting),1000000)
    # Set floor at 0 and ceiling at 8 for LED matrix
    matrix=matrix.clip(0,8)
    return matrix

turn = 0;

# Process audio file  
print "Processing....."
data = wavfile.readframes(chunk)
while data!='':
    output.write(data)  
    matrix=calculate_levels(data, chunk,sample_rate)
    cmd = "";
    cmd_off = "";
    onoff = 0;

    for i in range (0,7):
        port = 7 + i
        if ( matrix[i] > 3 ):
            turn = 1
    if turn == 1:
        for i in range (0,4):
            port = 7 + i * 2
            port2 = port + 1
            rand = random.randint(0, 1)
            if rand == 1:
                cmd = cmd + str(port) + ":1;" + str(port2) + ":1;"
            else:
                cmd = cmd + str(port) + ":0;" + str(port2) + ":0;"
    turn = 0

    print cmd

    if ( cmd != "" ):
        conn = httplib.HTTPConnection("192.168.0.14")
        conn.request("GET", "/sec?cmd=" + cmd)
        conn.close()
   
    data = wavfile.readframes(chunk)

Нельзя сказать, что этот тип визуализации очень уж впечатляет, но для управления гирляндами, по-моему, вполне... Сначала я хотел использовать какую-нибудь новогоднюю песенку навроде "В лесу родилась елочка" или "Happy New Year", но потом решил - НЕТ! Только консоль, только Ethernet, только синяя изолента, только ХАРДКОР!

Вы, конечно, можете придумать любые собственные алгоритмы. Ну и... С Новым 2014 годом!

Запуск скриптов в ОС Windows (XP, Vista, Win7 и т.д.)

1. Устанавливаем python 2.7
Идем на сайт http://python.org/download/, выбираем и скачиваем подходящий дистрибутив python. Важно! Версия должна быть 2.7 (не 3.3) и 32 битная!
2. Устанавливаем библиотеку PyAudio
Идем на сайт http://people.csail.mit.edu/hubert/pyaudio/, скачиваем и устанавливаем PyAudio for Python 2.7 для Microsoft Windows
3. Устанавливаем библиотеку Numphy
Идем на сайт http://sourceforge.net/projects/numpy/files/NumPy/, скачиваем и устанавливаем последнюю версию Numphy для python 2.7
4. Скачиваем поправленные python-скрипты ab-log.ru/files/File/megad-lights-win.zip
5. Распаковываем скрипты и правим путь к проигрываемому WAV файлу.
6. Запускаем скрипт примерно так
C:python27python.exe megad-music1-win.py
 

Автор: Andrey_B
Любое использование материалов сайта возможно только с разрешения автора и с обязательным указанием источника.



Добавить комментарий:

(необязательно, не отображается на сайте)


Сортировка комментариев: Последние сверху | Первые сверху

2014-01-07 04:42:03 | Александр
Как раз во время статья.. Нужно попробовать у себя


2014-01-12 01:21:21 | Дмитрий
Насколько я помню статус каждого выхода сохраняется в энергонезависимой памяти атмеги? если так, то сколько часов музыки она переживет до 1го сбоя?


2014-01-12 10:57:05 | Andrey_B
Состояние порта в EEPROM не записывается, поэтому работать будет вечно.


2014-01-15 13:31:44 | Андриан
Спасибо за статью Андрей,новый уровень:-) песня просто не в бровь а в глаз!
Осталось только купить програматор починить своего гогу:-) А то гирлянды уличные простаивают в коробках:-)


2014-01-15 17:35:46 | Andrey_B
Андриан, а что с вашим "гогой"?


2014-05-19 21:42:09 | Марат
Извините, не вчитывался в код, а на сколько реально это сделать для 2-3 MegaD-328


2014-05-20 13:10:36 | Andrey_B
Марат, вы имеете ввиду сделать так, чтобы использовалось не 7 выходов и одно устройство, а разложить цветомузыку на 14-21 порт и 2-3 устройства? Теоретически сделать это можно. Думаю, должно работать. Скрипт отправляет в одном пакете команды сразу всем 7 портам. Поэтому они изменяют свои состояния почти мгновенно. Если мы имеем несколько устройств, нужно будет отправлять несколько пакетов разным устройствам. Вот в этом месте может в теории возникнуть рассинхронизация. Но учитывая скорость работы Ethernet, я думаю, вряд ли это будет заметно на глаз.


2015-01-26 21:24:26 | Олег
Как я понял в связи с изменением в прошивках, в коде нужно исправить conn.request("GET", "/sec?cmd=" + cmd) на conn.request("GET", "/sec/?cmd=" + cmd) иначе в первом варианте переключения не происходят.


2023-06-22 21:15:33 | Kef
Интересно, сколько в таком режиме проживут релюхи


2023-06-22 22:39:09 | Andrey_B
Kef, в статье указано, что использовался не релейный, а симисторный модуль. А он в таком режиме может прожить очень долго.