Pull to refresh

Креативное программирование: openFrameworks — установка и пример визуализации музыки

Reading time12 min
Views27K


Когда вы последний раз программировали на C++?

Может быть это ваша каждодневная работа, а мой последний (до вчерашнего дня) проект на С++ был в далеком 2000 году — дипломный проект на базе Visual Studio 4.2 (хорошая, кстати, система была), и с тех пор перешёл в веб-разработку — скриптовые языки.

То есть сейчас я — начинающий на C++, но это не помешало мне за пару часов развернуть инфраструктуру, сделать и собрать мультимедийное приложение на C++, которое визуализирует музыку с разными эффектами. И в этом мне помогли:
  • открытый фреймворк для создания интерактивных приложений — openFrameworks
  • бесплатное IDE Code::Blocks

Посмотреть, что же у меня получилось

А начиналось всё так — после очередного прослушивания музыки от одного композитора из Самары, я подумал — было бы интересно попробовать сделать визуализацию музыки, и обратился к Денису Перевалову (кто не первый год занимается созданием разнообразных интерактивных арт/перформанс систем) — он мне ответил, что это делается без проблем на базе openFrameworks и что в примерах к его книге (а он автор книги по openFrameworks), есть реализация такой задачи.

То есть мне нужно было всего лишь — установить фреймворк, доработать и собрать пример на С++… Об этом процессе — установке, настройки, и кратком описании openFrameworks и будет эта статья.

openFrameworks — это система с помощью которой можно запрограммировать интерактивное мультимедийное приложение, то есть арт, перформансы и т.п., она бесплатная, открытая и кроссплатформенная система (linux, mac, win), и так же есть версии для ARM (к примеру для RPi), и сборки для iPhone и Android.

Кстати на КДПВ — одна из инсталляций на базе openFrameworks (Семь Видеогидов. выставлено на ВДНХ в экспозиции Политехнического музея. Москва, 2014).

Что же такое openFrameworks? Это набор модулей — для интеграции с Arduino, с кинектом, с системой распознавания образов OpenCV, рисование 3д графики, работа со звуком, камерами и т.п. с помощью которых можно сделать интерактивное приложение. И всё это на базе C++.

В поле моего зрения openFrameworks попала, когда я вышел на роботизированные инсталляции созданные с её помощью.

План такой:
  • 1. Настройка openFrameworks
  • 2. Основные принципы openFrameworks приложения
  • 3. Тестовый пример


1. Настройка openFrameworks


Следующие шаги:
  • установка openFrameworks (для CodeBlocks)
  • установка IDE CodeBlocks
  • копирование библиотек openFrameworks для CodeBlocks компилятора


Установка openFrameworks

Согласно этой страничке download я выбрал версию openFrameworks, для моей ОС и для IDE на которой я планировал работать.



В моем случае win и code::blocks: скачиваем архив of_v0.8.4_win_cb_release.zip

Распаковываем, архив содержит следующие папки:
* addons
* apps
* docs
* examples
* export
* libs
* other
* projectGenerator
* scripts

Это C++ библиотеки openFrameworks, примеры, аддоны и т.п.

Для того чтобы создавать openFrameworks приложение, лучше использовать IDE среду.

Установка IDE CodeBlocks

В качестве IDE, я решил выбрать code::blocks (visual studio всё таки великовата будет для меня сейчас)

CodeBlocks — это бесплатная и открытая IDE, созданная на базе кроссплатформенной GUI библиотеки wxWidgets. Согласно этой странице openframeworks.cc/setup/codeblocks скачиваю IDE CodeBlocks. Версия Release 12.11 Отсюда. Эта сборка идёт вместе с MinGW — открытой средой разработки под win платформу.

Вот так выглядит IDE CodeBlocks


Копирование библиотек openFrameworks для CodeBlocks компилятора

Важный пункт — для того чтобы из IDE CodeBlocks, успешно собирались openFrameworks проекты, необходимо скопировать дополнительные файлы в MinGW.

Вот этот пункт.

Скачиваем Additions for Code::Blocks to work with openFrameworks zip архив.

Распаковываем во временной папке, и копируем в соответствующие папки в установленном CodeBlocks, согласно этой инструкции:
Add the contents of the folder «add_to_codeblocks_mingw_include» into "...\CodeBlocks\MinGW\include"
Add the contents of the folder «add_to_codeblocks_mingw_lib» into "...\CodeBlocks\MinGW\lib"

Всё, теперь мы готовы к сборке openFrameworks проектов!

2. Основные принципы openFrameworks приложения


Сборка тестового проекта

Откроем тестовый проект, для этого выберем со стартовой страницы IDE CodeBlocks выберем «Open an existing project...» (или в File — Import Project — Dev-C++ project… — и выбрав тип файлов *.*)

Переходим в папку где мы развернули openFrameworks, заходим в examples/empty/emptyExample, и открываем файл проекта emptyExample.

Вот так выглядит IDE после открытия проекта:


Попробуем сразу же стартовать проект — на картинке указана стрелкой иконка или нажать F9 — RUN.

Если приложение не собрано, то будет стартована сборка (после вашего подтверждения) и по окончании сборки — приложение стартуется.

Если всё настроено верно, то по окончании процесса сборки будет открыто консольное окно, и мы увидим это окно:


Поздравляю! Значит всё настроено верно. И в папочке bin появилось приложение emptyExample.exe, которое вы можете уже запускать независимо.

Файлы

Теперь посмотрим на файлы нашего emptyExample проекта, они находятся в папке src:
* main.cpp
* ofApp.h
* ofApp.cpp

Файл main.cpp:
Скрытый текст
#include "ofMain.h"
#include "ofApp.h"

//========================================================================
int main( ){

	ofSetupOpenGL(1024,768, OF_WINDOW);			// <-------- setup the GL context

	// this kicks off the running of my app
	// can be OF_WINDOW or OF_FULLSCREEN
	// pass in width and height too:
	ofRunApp( new ofApp());

}

В нем определяется окно нашего приложения, и далее создаётся экземпляр класса ofApp.

Файл ofApp.h:
Скрытый текст
#pragma once

#include "ofMain.h"

class ofApp : public ofBaseApp{
	public:
		void setup();
		void update();
		void draw();
		
		void keyPressed(int key);
		void keyReleased(int key);
		void mouseMoved(int x, int y);
		void mouseDragged(int x, int y, int button);
		void mousePressed(int x, int y, int button);
		void mouseReleased(int x, int y, int button);
		void windowResized(int w, int h);
		void dragEvent(ofDragInfo dragInfo);
		void gotMessage(ofMessage msg);
};

Здесь определяется класс наш класс ofApp, наследуется от ofBaseApp. И методы.

Основной класс приложения ofApp.cpp:
Скрытый текст
#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){

}

//--------------------------------------------------------------
void ofApp::update(){

}

//--------------------------------------------------------------
void ofApp::draw(){

}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){

}

//--------------------------------------------------------------
void ofApp::keyReleased(int key){

}

//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y){

}

//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::windowResized(int w, int h){

}

//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg){

}

//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo){ 

}


Как мы видим — ничего не реализовано, мы увидели просто пустое, но работающее openFrameworks приложение.

Цикл работы openFrameworks приложения

Основными методами нашего класса являются:
		void setup();
		void update();
		void draw();


Архитектура любого openFrameworks приложения следующая:


В методе setup прописываются настройки, подготовка ресурсов и т.п. Этот метод выполняется один раз при запуске приложения, перед началом основного цикла.

Основной цикл это update и draw, где в первом методе — происходят только расчеты, а во втором draw — рисование. И после этого цикл повторяется.

Выход происходит по нажатию Esc.

3. Тестовый пример


Вот мы подошли к нашей задаче — визуализации музыки.

На этом сайте представлены примеры к книге «Mastering openFrameworks: Creative Coding Demystified». Сами файлы можно бесплатно скачать с карточки книги (после регистрации).

Вот видео примеров.

Базовый пример Dancing Cloud

И вот тот пример, что я хотел взять за основу и модифицировать — называется Dancing Cloud (06-Sound/06-DancingCloud):


Я скачал этот пример, и распаковал архив в корне моей openFrameworks папки — это важно, т.к. папка проекта должна находиться на 2 уровня ниже.

Вот ВЕСЬ исходный код, проекта 06-Sound/06-DancingCloud:

main.cpp:
Скрытый текст
#include "testApp.h"
#include "ofAppGlutWindow.h"

//--------------------------------------------------------------
int main(){
	ofAppGlutWindow window; // create a window
	// set width, height, mode (OF_WINDOW or OF_FULLSCREEN)
	ofSetupOpenGL(&window, 1024, 768, OF_WINDOW);
	ofRunApp(new testApp()); // start the app
}


testApp.h:
Скрытый текст
#pragma once

#include "ofMain.h"

/*
This example draws points cloud and plays music track. 
Also it analyzes music spectrum and use this data for controlling
the radius and shuffle of the cloud.

It's the example 06-DancingCloud from the book 
"Mastering openFrameworks: Creative Coding Demystified",
Chapter 6 - Working with Sounds

Music track "surface.wav" by Ilya Orange (soundcloud.com/ilyaorange)

*/

class testApp : public ofBaseApp{
public:
	void setup();
	void update();
	void draw();
	void mousePressed(int x, int y, int button);

	ofSoundPlayer sound;	//Sound sample

	void keyPressed(int key);
	void keyReleased(int key);
	void mouseMoved(int x, int y);
	void mouseDragged(int x, int y, int button);
	void mouseReleased(int x, int y, int button);
	void windowResized(int w, int h);
	void dragEvent(ofDragInfo dragInfo);
	void gotMessage(ofMessage msg);
};


testApp.cpp
Скрытый текст
#include "testApp.h"

const int N = 256;		//Number of bands in spectrum
float spectrum[ N ];	//Smoothed spectrum values
float Rad = 500;		//Cloud raduis parameter
float Vel = 0.1;		//Cloud points velocity parameter
int bandRad = 2;		//Band index in spectrum, affecting Rad value
int bandVel = 100;		//Band index in spectrum, affecting Vel value

const int n = 300;		//Number of cloud points	

//Offsets for Perlin noise calculation for points
float tx[n], ty[n];				
ofPoint p[n];			//Cloud's points positions

float time0 = 0;		//Time value, used for dt computing

//--------------------------------------------------------------
void testApp::setup(){
	//Set up sound sample
	sound.loadSound( "surface.wav" );	
	sound.setLoop( true );
	sound.play();

	//Set spectrum values to 0
	for (int i=0; i<N; i++) {
		spectrum[i] = 0.0f;
	}

	//Initialize points offsets by random numbers
	for ( int j=0; j<n; j++ ) {
		tx[j] = ofRandom( 0, 1000 );	
		ty[j] = ofRandom( 0, 1000 );
	}
}

//--------------------------------------------------------------
void testApp::update(){	
	//Update sound engine
	ofSoundUpdate();	

	//Get current spectrum with N bands
	float *val = ofSoundGetSpectrum( N );
	//We should not release memory of val,
	//because it is managed by sound engine

	//Update our smoothed spectrum,
	//by slowly decreasing its values and getting maximum with val
	//So we will have slowly falling peaks in spectrum
	for ( int i=0; i<N; i++ ) {
		spectrum[i] *= 0.97;	//Slow decreasing
		spectrum[i] = max( spectrum[i], val[i] );
	}

	//Update particles using spectrum values

	//Computing dt as a time between the last
	//and the current calling of update() 	
	float time = ofGetElapsedTimef();
	float dt = time - time0;
	dt = ofClamp( dt, 0.0, 0.1 );	
	time0 = time; //Store the current time	

	//Update Rad and Vel from spectrum
	//Note, the parameters in ofMap's were tuned for best result
	//just for current music track
	Rad = ofMap( spectrum[ bandRad ], 1, 3, 400, 800, true );
	Vel = ofMap( spectrum[ bandVel ], 0, 0.1, 0.05, 0.5 );

	//Update particles positions
	for (int j=0; j<n; j++) {
		tx[j] += Vel * dt;	//move offset
		ty[j] += Vel * dt;	//move offset
		//Calculate Perlin's noise in [-1, 1] and
		//multiply on Rad
		p[j].x = ofSignedNoise( tx[j] ) * Rad;		
		p[j].y = ofSignedNoise( ty[j] ) * Rad;	
	}
}

//--------------------------------------------------------------
void testApp::draw(){
	ofBackground( 255, 255, 255 );	//Set up the background

	//Draw background rect for spectrum
	ofSetColor( 230, 230, 230 );
	ofFill();
	ofRect( 10, 700, N * 6, -100 );

	//Draw spectrum
	ofSetColor( 0, 0, 0 );
	for (int i=0; i<N; i++) {
		//Draw bandRad and bandVel by black color,
		//and other by gray color
		if ( i == bandRad || i == bandVel ) {
			ofSetColor( 0, 0, 0 ); //Black color
		}
		else {
			ofSetColor( 128, 128, 128 ); //Gray color
		}
		ofRect( 10 + i * 5, 700, 3, -spectrum[i] * 100 );
	}

	//Draw cloud

	//Move center of coordinate system to the screen center
	ofPushMatrix();
	ofTranslate( ofGetWidth() / 2, ofGetHeight() / 2 );

	//Draw points
	ofSetColor( 0, 0, 0 );
	ofFill();
	for (int i=0; i<n; i++) {
		ofCircle( p[i], 2 );
	}

	//Draw lines between near points
	float dist = 40;	//Threshold parameter of distance
	for (int j=0; j<n; j++) {
		for (int k=j+1; k<n; k++) {
			if ( ofDist( p[j].x, p[j].y, p[k].x, p[k].y )
				< dist ) {
					ofLine( p[j], p[k] );
			}
		}
	}

	//Restore coordinate system
	ofPopMatrix();
}

//--------------------------------------------------------------
void testApp::keyPressed(int key){
}

//--------------------------------------------------------------
void testApp::keyReleased(int key){

}

//--------------------------------------------------------------
void testApp::mouseMoved(int x, int y){

}

//--------------------------------------------------------------
void testApp::mouseDragged(int x, int y, int button){

}

//--------------------------------------------------------------
void testApp::mousePressed(int x, int y, int button){

}

//--------------------------------------------------------------
void testApp::mouseReleased(int x, int y, int button){

}

//--------------------------------------------------------------
void testApp::windowResized(int w, int h){

}

//--------------------------------------------------------------
void testApp::gotMessage(ofMessage msg){

}

//--------------------------------------------------------------
void testApp::dragEvent(ofDragInfo dragInfo){ 

}

Комментарий от Дениса (автора книги), по поводу алгоритма, что визуализирует музыку:
300 точек (const int n = 300;) движутся по траекториям шума Перлина, причем соседние точки соединяются отрезками.
Радиус облака и скорость движения — это два параметра, которые берутся из анализа звука.

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

Смотрим отличия от нашего emptyExample.

main.cpp — идентичен по сути.

В testApp.h, добавился атрибут sound, класса ofSoundPlayer:
	ofSoundPlayer sound;	//Sound sample

ofSoundPlayer — это базовый класс для работы со звуком, docs.

Самое интересное находится в testApp.cpp.

Вот переменные, что используются для реализации логики:
const int N = 256;		// Число полос спектра
float spectrum[ N ];		// массив для значений спектра
float Rad = 500;		// радиус облака
float Vel = 0.1;		// параметр скорости точек облака
int bandRad = 2;		// полоса спектра что будет модифицировать Rad параметр
int bandVel = 100;		// полоса спектра что будет модифицировать Vel параметр

const int n = 300;		// число точек в облаке

// рассчитанные смещения точке согласно шума Перлина
float tx[n], ty[n];				
ofPoint p[n];			// координаты точек облака

float time0 = 0;		// используется для вычисления dt - прошедшего времени между отображениями


Вот что прописано в методе testApp::setup() происходит инициализация музыки, переменных для отображения спектра, и точек облака:
void testApp::setup(){
	//Set up sound sample
	sound.loadSound( "surface.wav" );	
	sound.setLoop( true );
	sound.play();

	//Set spectrum values to 0
	for (int i=0; i<N; i++) {
		spectrum[i] = 0.0f;
	}

	//Initialize points offsets by random numbers
	for ( int j=0; j<n; j++ ) {
		tx[j] = ofRandom( 0, 1000 );	
		ty[j] = ofRandom( 0, 1000 );
	}
}

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

В методе testApp::update() — происходит вся «магия» по расчету размещения точек.
Скрытый текст
void testApp::update(){	
	//Update sound engine
	ofSoundUpdate();	

	//Get current spectrum with N bands
	float *val = ofSoundGetSpectrum( N );
	//We should not release memory of val,
	//because it is managed by sound engine

	//Update our smoothed spectrum,
	//by slowly decreasing its values and getting maximum with val
	//So we will have slowly falling peaks in spectrum
	for ( int i=0; i<N; i++ ) {
		spectrum[i] *= 0.97;	//Slow decreasing
		spectrum[i] = max( spectrum[i], val[i] );
	}

	//Update particles using spectrum values

	//Computing dt as a time between the last
	//and the current calling of update() 	
	float time = ofGetElapsedTimef();
	float dt = time - time0;
	dt = ofClamp( dt, 0.0, 0.1 );	
	time0 = time; //Store the current time	

	//Update Rad and Vel from spectrum
	//Note, the parameters in ofMap's were tuned for best result
	//just for current music track
	Rad = ofMap( spectrum[ bandRad ], 1, 3, 400, 800, true );
	Vel = ofMap( spectrum[ bandVel ], 0, 0.1, 0.05, 0.5 );

	//Update particles positions
	for (int j=0; j<n; j++) {
		tx[j] += Vel * dt;	//move offset
		ty[j] += Vel * dt;	//move offset
		//Calculate Perlin's noise in [-1, 1] and
		//multiply on Rad
		p[j].x = ofSignedNoise( tx[j] ) * Rad;		
		p[j].y = ofSignedNoise( ty[j] ) * Rad;	
	}
}

Вот метод рисования, здесь согласно рассчитанным данным происходит отображение спектра, точек облака, и линий между точками (при условии если они ближе float dist = 40):
void testApp::draw(){
	ofBackground( 255, 255, 255 );	//Set up the background

	//Draw background rect for spectrum
	ofSetColor( 230, 230, 230 );
	ofFill();
	ofRect( 10, 700, N * 6, -100 );

	//Draw spectrum
	ofSetColor( 0, 0, 0 );
	for (int i=0; i<N; i++) {
		//Draw bandRad and bandVel by black color,
		//and other by gray color
		if ( i == bandRad || i == bandVel ) {
			ofSetColor( 0, 0, 0 ); //Black color
		}
		else {
			ofSetColor( 128, 128, 128 ); //Gray color
		}
		ofRect( 10 + i * 5, 700, 3, -spectrum[i] * 100 );
	}

	//Draw cloud

	//Move center of coordinate system to the screen center
	ofPushMatrix();
	ofTranslate( ofGetWidth() / 2, ofGetHeight() / 2 );

	//Draw points
	ofSetColor( 0, 0, 0 );
	ofFill();
	for (int i=0; i<n; i++) {
		ofCircle( p[i], 2 );
	}

	//Draw lines between near points
	float dist = 40;	//Threshold parameter of distance
	for (int j=0; j<n; j++) {
		for (int k=j+1; k<n; k++) {
			if ( ofDist( p[j].x, p[j].y, p[k].x, p[k].y )
				< dist ) {
					ofLine( p[j], p[k] );
			}
		}
	}

	//Restore coordinate system
	ofPopMatrix();
}


Мои модификации

Я взял музыку volfworks: soundcloud.com/volfworks

Автор любезно согласился на мое использование его композиции Звезда.

Первым дело — я заменил wav на mp3 — openFrameworks поддерживает mp3. Так же сделал потоковое воспроизведение (иначе все 8Мб должны быть сразу загружены — добавил true вторым параметром, docs).

sound.loadSound( "zvezda.mp3", true );

Добавил загрузку фонового изображения:
stars.loadImage("stars.jpg");

Поменял цветовую гамму, сделал эффекты прозрачности зависимые от времени.

Фрагмент из ofApp::draw():
	// включение использования прозрачности, и рисование квадрата поверх фоновой картинки
	// с прозрачностью определяемой bg_transparent
	ofEnableAlphaBlending();
	ofSetColor(0, 0, 0, bg_transparent);
	ofRect(0, 0, 1000, 700);
	ofDisableAlphaBlending();

	// рисование текста указанного цвета, в координатах
	ofSetHexColor(0x606060);
	ofDrawBitmapString("Music by: volfworks", 800,610);


Весь проект выложен на github: github.com/nemilya/of_volfworks_example

Создание видео

В этом возникли некоторые сложности, и в конечном итоге было выполнено с помощью «Camtasia Recorder».

Ссылки


Основной сайт проекта: openframeworks.cc там предоставлены достаточно хорошие туториалы.

Если вы работаете с openFrameworks, или интересно попробовать, то приглашаю в русскоязычную группу по openFrameworks.
Tags:
Hubs:
Total votes 29: ↑24 and ↓5+19
Comments6

Articles