Инфодизайн в IT

индекс
139,56

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

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

Введение


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

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




Код


Создаем новый проект, который в терминах Processing'a называется скетч. Скетч будет состоять из трех файлов. Первый — BeatListener, который мы просто перетянем из примера FrequencyEnergy, он нужен чтобы детектить ритм музыки. Второй — класс нашего кубика, выглядит он так:
class Box {
 //позиция в пространстве
 int x,y,z;
 //размер
 int boxSize;
 //яркость (от 0 до 255)
 int bright;
 
 Box(int x, int y, int z, int boxSize) {
   this.x = x;
   this.y = y;
   this.z = z;
   this.boxSize = boxSize;
   this.bright = 0; //по дефолту черный
 }
 
 //установить яркость
 void setBright(int bright) {
  if (bright > 255) bright = 255;
  if (bright < 0) bright = 0;
  this.bright = bright;
 }
 
 //получить яркость
 int getBright() {
  return bright;
 }
 
 //нарисовать кубик
 void display() {
   //установить яркость
   fill(bright);
   //сохранить предыдущую матрицу преобразований
   pushMatrix();
   //переместить кубик в заданные координаты
   translate(x,y,z);
   //нарисовать
   box(boxSize);
   //вернуть предыдущую матрицу преобразований
   popMatrix();
 }
}


* This source code was highlighted with Source Code Highlighter.


Ну и третий файл, который собственно и производит все полезные ништяки:
//подключение библиотек
import ddf.minim.*;
import ddf.minim.signals.*;
import ddf.minim.analysis.*;
import ddf.minim.effects.*;

import processing.opengl.*;

//размер кубика
int boxSize = 40;
//кол-во кубиков в ширину
int build_width = 20;
//кол-во кубиков в длину
int build_length = 20;
//кол-во кубиков в высоту
int build_height = 10;
//расстояние между кубиками
int space = 10;
//массив кубиков
Box[][][] build;

//плеер, детектор ритма, и т.д., все взято из примера FrequencyEnergy
Minim minim;
AudioPlayer song;
BeatDetect beat;
BeatListener bl;

//процедура setup вызывается в самом начале работы
void setup() {
 //устанавливаем размер окна и тип рендера
 size(800,600, OPENGL);
 //фон черный
 background(0);
 build = new Box[build_width][build_length][build_height];
 //делаем пол из кубиков
 for (int i = 0; i < build_width; i++) {
  for (int j = 0; j < build_length; j++) {
    build[i][j][0] = new Box((boxSize+space)*i,(boxSize+space)*j,0,boxSize);
  }
 } 
 //делаем стенку из кубиков
 for (int i = 0; i < build_width; i++) {
   for (int j = 1; j < build_height; j++) {
    build[i][build_length-1][j] = new Box((boxSize+space)*i,(boxSize+space)*(build_length-1),(boxSize+space)*j,boxSize);
   }
 }
 //делаем другую стенку из кубиков
 for (int i = 0; i < build_length; i++) {
   for (int j = 1; j < build_height; j++) {
    build[build_width-1][i][j] = new Box((boxSize+space)*(build_width-1),(boxSize+space)*i,(boxSize+space)*j,boxSize);
   }
 }
 //делаем другую стенку из кубиков
 for (int i = 0; i < build_width; i++) {
   for (int j = 1; j < build_height; j++) {
    build[i][0][j] = new Box((boxSize+space)*i,0,(boxSize+space)*j,boxSize);
   }
 }
 //делаем другую стенку из кубиков
 for (int i = 0; i < build_length; i++) {
   for (int j = 1; j < build_height; j++) {
    build[0][i][j] = new Box(0,(boxSize+space)*i,(boxSize+space)*j,boxSize);
   }
 }
 //делаем потолок из кубиков
 for (int i = 0; i < build_width; i++) {
  for (int j = 0; j < build_length; j++) {
    build[i][j][build_height-1] = new Box((boxSize+space)*i,(boxSize+space)*j,(boxSize+space)*(build_height-1),boxSize);
  }
 }
 
 //добавляем детали
 build[9][9][1] = new Box((boxSize+space)*9,(boxSize+space)*9,(boxSize+space),boxSize);
 build[10][9][1] = new Box((boxSize+space)*10,(boxSize+space)*9,(boxSize+space),boxSize);
 build[9][10][1] = new Box((boxSize+space)*9,(boxSize+space)*10,(boxSize+space),boxSize);
 build[10][10][1] = new Box((boxSize+space)*10,(boxSize+space)*10,(boxSize+space),boxSize);
 
 build[18][10][1] = new Box((boxSize+space)*18,(boxSize+space)*10,(boxSize+space),boxSize);
 build[17][10][1] = new Box((boxSize+space)*17,(boxSize+space)*10,(boxSize+space),boxSize);
 build[16][10][1] = new Box((boxSize+space)*16,(boxSize+space)*10,(boxSize+space),boxSize);
 build[16][9][1] = new Box((boxSize+space)*16,(boxSize+space)*9,(boxSize+space),boxSize);
 build[16][8][1] = new Box((boxSize+space)*16,(boxSize+space)*8,(boxSize+space),boxSize);
 build[17][8][1] = new Box((boxSize+space)*17,(boxSize+space)*8,(boxSize+space),boxSize);
 build[18][8][1] = new Box((boxSize+space)*18,(boxSize+space)*8,(boxSize+space),boxSize);
 
 build[10][18][1] = new Box((boxSize+space)*10,(boxSize+space)*18,(boxSize+space),boxSize);
 build[10][17][1] = new Box((boxSize+space)*10,(boxSize+space)*17,(boxSize+space),boxSize);
 build[10][16][1] = new Box((boxSize+space)*10,(boxSize+space)*16,(boxSize+space),boxSize);
 build[9][16][1] = new Box((boxSize+space)*9,(boxSize+space)*16,(boxSize+space),boxSize);
 build[8][16][1] = new Box((boxSize+space)*8,(boxSize+space)*16,(boxSize+space),boxSize);
 build[8][17][1] = new Box((boxSize+space)*8,(boxSize+space)*17,(boxSize+space),boxSize);
 build[8][18][1] = new Box((boxSize+space)*8,(boxSize+space)*18,(boxSize+space),boxSize);
 
 //устанавливаем камеру
 //x,y,z глаз, x,y,z точки, куда смотрим, вектор нормали показывает, где верх
 camera(50, 50, 250, 500, 500, 150, 0, 0, -1);
 
 //звуковая библиотечка
 minim = new Minim(this);
 //запрашиваем путь к звуковому файлу (вроде не распознает русские буквы)
 String loadPath = selectInput();
 //грузим песенку
 song = minim.loadFile(loadPath, 2048);
 //запускаем на воспроизведение
 song.play();
 //это все относится к детектору ритма
 beat = new BeatDetect(song.bufferSize(), song.sampleRate());
 //фиксировать следующий бит не раньше, чем через 100 мс после предыдущего
 beat.setSensitivity(100); 
 bl = new BeatListener(beat, song); 
}

//функция draw вызывается для прорисовки каждый кадр
void draw() {
 //рисуем кубики
 for (int i = 0; i < build_width; i++) {
  for (int j = 0; j < build_length; j++) {
   for (int k = 0; k < build_height; k++) {
    if (build[i][j][k] != null) {
     build[i][j][k].display();
     //очень хитроумный алгоритм изменения яркости кубиков
     if (build[i][j][k].getBright() > 0)
     {
      
       if (i-1 >= 0 && build[i-1][j][k] != null)
       {
        build[i-1][j][k].setBright(build[i-1][j][k].getBright()/2 + 18*build[i][j][k].getBright() / 32);
       }
       if (i+1 < build_width && build[i+1][j][k] != null)
       {
        build[i+1][j][k].setBright(build[i+1][j][k].getBright()/2 + 17*build[i][j][k].getBright() / 32);
       }
       if (j-1 >= 0 && build[i][j-1][k] != null)
       {
        build[i][j-1][k].setBright(build[i][j-1][k].getBright()/2 + 18*build[i][j][k].getBright() / 32);
       }
       if (j+1 < build_length && build[i][j+1][k] != null)
       {
        build[i][j+1][k].setBright(build[i][j+1][k].getBright()/2 + 17*build[i][j][k].getBright() / 32);
       }
       if (k-1 >= 0 && build[i][j][k-1] != null)
       {
        build[i][j][k-1].setBright(build[i][j][k-1].getBright()/2 + 17*build[i][j][k].getBright() / 32);
       }
       if (k+1 < build_height && build[i][j][k+1] != null)
       {
        build[i][j][k+1].setBright(build[i][j][k+1].getBright()/2 + 17*build[i][j][k].getBright() / 32);
       }

      build[i][j][k].setBright(3 * build[i][j][k].getBright() / 4);
     }
    }
   }
  }
 }
 
 //при возникновении бита какой-нибудь группы частот
 //устанавливаем максимальную яркость соответствующего кубика
 if ( beat.isHat() ) {
  process(15,15,0,255);
 }
 if ( beat.isKick() ) {
  process(15,5,0,255);
 }
 if ( beat.isSnare() ) {
  process(5,15,0,255);
 }
}

//процедура устанавливает яркость кубика, заданного x,y,z
void process(int x, int y, int z, int bright) {
 build[x][y][z].setBright(bright);
}


* This source code was highlighted with Source Code Highlighter.


_________
Текст подготовлен в ХабраРедакторе
+33
3 января 2010, 23:07
26

комментарии (41)

+1
FTM #
А скомпилированную версию можно?

ЗЫ: есть, кстати, еще игра AudioSurf, ее лично я считаю за эталон звуковых игрушек.
+2
SkywalkerY #
вот скомпильнул, только нужно, чтобы java стояла
линк
0
frenzis #
У меня проц. п4 2.8 не осилил
0
SkywalkerY #
1.73 все летает хз
0
Mort #
аналогично, жуткие тормоза =(
0
Moran #
Красиво, напомнило демосцену)
Есть только один минус — долго на это не полюбуешься, глаза устают (хотя возможно это только у меня под конец дня).

Ах, да. Исполнителя в ролике не подскажете?
+1
SkywalkerY #
дада, пока тестил тоже глаза убил:)
Исполняет Edenbridge — The Most Beautiful Place
<шепот>коварный замысел по продвижению готик и симфоник металла на хабре работает...</шепот>
+1
Danov #
темно очень
–1
SkywalkerY #
так и было задумано, тут ставилась во главу интересность идеи, а не юзабельность
+1
AmirL #
Ну дык не видно в темноте идею та :(. Я увидел комнату, в которой изредка на полу что то светиться. Больше ничего не видно — темно очень.
НЛО прилетело и опубликовало эту надпись здесь
+1
gooddy #
не мешало бы добавить хоть какой-то ambient light чтобы небыло таких перепадов в темноту, глаза тогда бы не вылазили так на лоб :)
и думаю веслее смотрелось, если бы квадратики увеличивались пропорционально подсветке, чтобы хоть какое-то движение было… имхо конечно :)
НЛО прилетело и опубликовало эту надпись здесь
0
TroLLik #
Красиво, чёрт побери. Автор молодец
+1
fata1ex #
посмотрим, выглядит впечатляюще
и укажите теги через запятую ;)
НЛО прилетело и опубликовало эту надпись здесь
0
SkywalkerY #
к сожалению даже не знаю в чем проблема, у меня проц 1.73, видюха ноутбучная, винда ХР — как можете видеть в ролике не тормозит, фпс стабильно идет 30. Может с версией java машины что-то или дровами opengl. Или из-за винды новой, если у вас не ХР.

Вот скомпилил с принудительной установкой фпс в 30, также уменьшил размер окна до 640x480 (было 800x600). Попробуйте.

link
0
SkywalkerY #
извиняюсь, лучше попробуйте вот эту версию, я убрал 2 стенки и потолок, они все равно не видны, у меня фпс увеличился с 30 до 55.
link
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
+3
Gibbzy #
//массив кубиков
Box[][][] build;

мне эта строка понравилась, сразу понятно что массив кубиков.
0
SkywalkerY #
ладно хоть я не написал
//если яркость больше 0
if (build[i][j][k].getBright() > 0)
а то ведь была такая идея
0
Levsha100 #
Декомпозиция тут не помешала бы.
0
danilissimus #
Inter Core2 Duo E8400 @ 3.00GHz
подтормаживает, но проц загружен лишь на 30-40%
0
SkywalkerY #
попробуйте вот эту версию, я убрал 2 стенки и потолок, они все равно не видны, у меня фпс увеличился с 30 до 55, еще размер окна уменьшил
link
+1
danilissimus #
не тормозит, проц загружен на 100%
0
RiderSx #
Эта лучше работает, намного :) Вот только одно не большая просьба, можно фулскрин версию :)
0
SkywalkerY #
processing пока не поддерживает фулскрин, хотя вроде в версии 1.4.8 уже есть какие-то костыли, ну пока ждемс…
+1
impwx #
omg, magic numbers!
0
Chikey #
для линукса покомпиль пжлст. Потом в пм можешь линк кинуть)
0
SkywalkerY #
link
если не запустится, что делать не знаю. я просто поставил галочку linux в компиляторе)
0
Mokkey #
Интересно, но как-то динамичности не хватает. Возможно, ракурс не самый удачный.
0
Alaunquirie #
Предложение такое — в хависимости от частоты звучания изменяется анимация. Высокие частоты отвечают за стробоскопическое подсвечивание, средние — за гармонично плавающие «прожекторные лучи», низкие — за колебания самих коробок. Будет куда динмаичней и эффетней.
0
Usmekhaiouschiysia #
Технически наверное это хорошо, ну и первую минуту-другую даже интересно.
Но мигает не в такт. А потом обнаруживаешь, что это просто волны света, бегут по комнате — видел первую минуту, всё, ничего нового :(
0
Tonna #
Я один ждал когда после ч/б эффектов появиться цвет?
0
snch #
увы, но я не заметил никакой зависимости между играемым и показываемым
0
Nashev #
Аналогично. Интересно, те кто восхищается — чем именно восхищаются?..
0
ttyv00 #
Не вникал в детали кода, но выглядит прикольно!
0
Astral #
Такой бы плагин визуализации для аимпа.
0
Nashev #
А что именно Вас в этом привлекает?
0
Astral #
Вместо «Analog Meter», намного бы лучше смотрелось.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.