Мой визуализатор музыки
Давно хотел написать какой-никакой визуализатор музыки, но интересных идей не было. Потом увидел вот это — Аудио игра «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._________
Текст подготовлен в ХабраРедакторе



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