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

Мой визуализатор музыки

Время на прочтение 7 мин
Количество просмотров 15K

Давно хотел написать какой-никакой визуализатор музыки, но интересных идей не было. Потом увидел вот это — Аудио игра «Devil's Tuning Fork» и захотел сделать нечто похожее.



Введение


Писать решил на языке Processing, чтобы заодно посмотреть, что это за зверь.


В папке с языком валяется множество примеров и, что более важно, присутствует библиотека для работы со звуком, в которой уже реализовано FFT. Есть даже более важный для нас пример, где частоты делятся на три группы и на экране три слова прыгают под ритм музыки (пример называется FrequencyEnergy).



Демонстрация работы




Код


Создаем новый проект, который в терминах Processing'a называется скетч. Скетч будет состоять из трех файлов. Первый — BeatListener, который мы просто перетянем из примера FrequencyEnergy, он нужен чтобы детектить ритм музыки. Второй — класс нашего кубика, выглядит он так:

class Box {<br> //позиция в пространстве<br> int x,y,z;<br> //размер<br> int boxSize;<br> //яркость (от 0 до 255)<br> int bright;<br> <br> Box(int x, int y, int z, int boxSize) {<br>   this.x = x;<br>   this.y = y;<br>   this.z = z;<br>   this.boxSize = boxSize;<br>   this.bright = 0; //по дефолту черный<br> }<br> <br> //установить яркость<br> void setBright(int bright) {<br>  if (bright > 255) bright = 255;<br>  if (bright < 0) bright = 0;<br>  this.bright = bright;<br> }<br> <br> //получить яркость<br> int getBright() {<br>  return bright; <br> }<br> <br> //нарисовать кубик<br> void display() {<br>   //установить яркость<br>   fill(bright);<br>   //сохранить предыдущую матрицу преобразований<br>   pushMatrix();<br>   //переместить кубик в заданные координаты<br>   translate(x,y,z);<br>   //нарисовать<br>   box(boxSize);<br>   //вернуть предыдущую матрицу преобразований<br>   popMatrix();<br> }<br>}<br><br>* This source code was highlighted with Source Code Highlighter.


Ну и третий файл, который собственно и производит все полезные ништяки:

//подключение библиотек<br>import ddf.minim.*;<br>import ddf.minim.signals.*;<br>import ddf.minim.analysis.*;<br>import ddf.minim.effects.*;<br><br>import processing.opengl.*;<br><br>//размер кубика<br>int boxSize = 40;<br>//кол-во кубиков в ширину<br>int build_width = 20;<br>//кол-во кубиков в длину<br>int build_length = 20;<br>//кол-во кубиков в высоту<br>int build_height = 10;<br>//расстояние между кубиками<br>int space = 10;<br>//массив кубиков<br>Box[][][] build;<br><br>//плеер, детектор ритма, и т.д., все взято из примера FrequencyEnergy<br>Minim minim;<br>AudioPlayer song;<br>BeatDetect beat;<br>BeatListener bl;<br><br>//процедура setup вызывается в самом начале работы<br>void setup() {<br> //устанавливаем размер окна и тип рендера<br> size(800,600, OPENGL);<br> //фон черный<br> background(0);<br> build = new Box[build_width][build_length][build_height];<br> //делаем пол из кубиков<br> for (int i = 0; i < build_width; i++) {<br>  for (int j = 0; j < build_length; j++) {<br>    build[i][j][0] = new Box((boxSize+space)*i,(boxSize+space)*j,0,boxSize);<br>  }<br> } <br> //делаем стенку из кубиков<br> for (int i = 0; i < build_width; i++) {<br>   for (int j = 1; j < build_height; j++) {<br>    build[i][build_length-1][j] = new Box((boxSize+space)*i,(boxSize+space)*(build_length-1),(boxSize+space)*j,boxSize);<br>   }<br> }<br> //делаем другую стенку из кубиков<br> for (int i = 0; i < build_length; i++) {<br>   for (int j = 1; j < build_height; j++) {<br>    build[build_width-1][i][j] = new Box((boxSize+space)*(build_width-1),(boxSize+space)*i,(boxSize+space)*j,boxSize);<br>   }<br> }<br> //делаем другую стенку из кубиков<br> for (int i = 0; i < build_width; i++) {<br>   for (int j = 1; j < build_height; j++) {<br>    build[i][0][j] = new Box((boxSize+space)*i,0,(boxSize+space)*j,boxSize);<br>   }<br> }<br> //делаем другую стенку из кубиков<br> for (int i = 0; i < build_length; i++) {<br>   for (int j = 1; j < build_height; j++) {<br>    build[0][i][j] = new Box(0,(boxSize+space)*i,(boxSize+space)*j,boxSize);<br>   }<br> }<br> //делаем потолок из кубиков<br> for (int i = 0; i < build_width; i++) {<br>  for (int j = 0; j < build_length; j++) {<br>    build[i][j][build_height-1] = new Box((boxSize+space)*i,(boxSize+space)*j,(boxSize+space)*(build_height-1),boxSize);<br>  }<br> } <br> <br> //добавляем детали<br> build[9][9][1] = new Box((boxSize+space)*9,(boxSize+space)*9,(boxSize+space),boxSize);<br> build[10][9][1] = new Box((boxSize+space)*10,(boxSize+space)*9,(boxSize+space),boxSize);<br> build[9][10][1] = new Box((boxSize+space)*9,(boxSize+space)*10,(boxSize+space),boxSize);<br> build[10][10][1] = new Box((boxSize+space)*10,(boxSize+space)*10,(boxSize+space),boxSize);<br> <br> build[18][10][1] = new Box((boxSize+space)*18,(boxSize+space)*10,(boxSize+space),boxSize);<br> build[17][10][1] = new Box((boxSize+space)*17,(boxSize+space)*10,(boxSize+space),boxSize);<br> build[16][10][1] = new Box((boxSize+space)*16,(boxSize+space)*10,(boxSize+space),boxSize);<br> build[16][9][1] = new Box((boxSize+space)*16,(boxSize+space)*9,(boxSize+space),boxSize);<br> build[16][8][1] = new Box((boxSize+space)*16,(boxSize+space)*8,(boxSize+space),boxSize);<br> build[17][8][1] = new Box((boxSize+space)*17,(boxSize+space)*8,(boxSize+space),boxSize);<br> build[18][8][1] = new Box((boxSize+space)*18,(boxSize+space)*8,(boxSize+space),boxSize);<br> <br> build[10][18][1] = new Box((boxSize+space)*10,(boxSize+space)*18,(boxSize+space),boxSize);<br> build[10][17][1] = new Box((boxSize+space)*10,(boxSize+space)*17,(boxSize+space),boxSize);<br> build[10][16][1] = new Box((boxSize+space)*10,(boxSize+space)*16,(boxSize+space),boxSize);<br> build[9][16][1] = new Box((boxSize+space)*9,(boxSize+space)*16,(boxSize+space),boxSize);<br> build[8][16][1] = new Box((boxSize+space)*8,(boxSize+space)*16,(boxSize+space),boxSize);<br> build[8][17][1] = new Box((boxSize+space)*8,(boxSize+space)*17,(boxSize+space),boxSize);<br> build[8][18][1] = new Box((boxSize+space)*8,(boxSize+space)*18,(boxSize+space),boxSize);<br> <br> //устанавливаем камеру<br> //x,y,z глаз, x,y,z точки, куда смотрим, вектор нормали показывает, где верх<br> camera(50, 50, 250, 500, 500, 150, 0, 0, -1);<br> <br> //звуковая библиотечка<br> minim = new Minim(this);<br> //запрашиваем путь к звуковому файлу (вроде не распознает русские буквы)<br> String loadPath = selectInput();<br> //грузим песенку<br> song = minim.loadFile(loadPath, 2048);<br> //запускаем на воспроизведение<br> song.play();<br> //это все относится к детектору ритма<br> beat = new BeatDetect(song.bufferSize(), song.sampleRate());<br> //фиксировать следующий бит не раньше, чем через 100 мс после предыдущего<br> beat.setSensitivity(100); <br> bl = new BeatListener(beat, song); <br>}<br><br>//функция draw вызывается для прорисовки каждый кадр<br>void draw() {<br> //рисуем кубики<br> for (int i = 0; i < build_width; i++) {<br>  for (int j = 0; j < build_length; j++) {<br>   for (int k = 0; k < build_height; k++) {<br>    if (build[i][j][k] != null) {<br>     build[i][j][k].display();<br>     //очень хитроумный алгоритм изменения яркости кубиков<br>     if (build[i][j][k].getBright() > 0)<br>     {<br>      <br>       if (i-1 >= 0 && build[i-1][j][k] != null)<br>       {<br>        build[i-1][j][k].setBright(build[i-1][j][k].getBright()/2 + 18*build[i][j][k].getBright() / 32);<br>       } <br>       if (i+1 < build_width && build[i+1][j][k] != null)<br>       {<br>        build[i+1][j][k].setBright(build[i+1][j][k].getBright()/2 + 17*build[i][j][k].getBright() / 32); <br>       }<br>       if (j-1 >= 0 && build[i][j-1][k] != null)<br>       {<br>        build[i][j-1][k].setBright(build[i][j-1][k].getBright()/2 + 18*build[i][j][k].getBright() / 32); <br>       }<br>       if (j+1 < build_length && build[i][j+1][k] != null)<br>       {<br>        build[i][j+1][k].setBright(build[i][j+1][k].getBright()/2 + 17*build[i][j][k].getBright() / 32);<br>       }<br>       if (k-1 >= 0 && build[i][j][k-1] != null)<br>       {<br>        build[i][j][k-1].setBright(build[i][j][k-1].getBright()/2 + 17*build[i][j][k].getBright() / 32); <br>       }<br>       if (k+1 < build_height && build[i][j][k+1] != null)<br>       {<br>        build[i][j][k+1].setBright(build[i][j][k+1].getBright()/2 + 17*build[i][j][k].getBright() / 32); <br>       }<br><br>      build[i][j][k].setBright(3 * build[i][j][k].getBright() / 4); <br>     }<br>    }<br>   }<br>  }<br> }<br> <br> //при возникновении бита какой-нибудь группы частот<br> //устанавливаем максимальную яркость соответствующего кубика<br> if ( beat.isHat() ) {<br>  process(15,15,0,255);<br> }<br> if ( beat.isKick() ) {<br>  process(15,5,0,255);<br> }<br> if ( beat.isSnare() ) {<br>  process(5,15,0,255);<br> }<br>}<br><br>//процедура устанавливает яркость кубика, заданного x,y,z<br>void process(int x, int y, int z, int bright) {<br> build[x][y][z].setBright(bright);<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

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

Публикации

Истории

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

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн
PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн