Программируем арт: openFrameworks — первый контакт

от автора

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

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

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

  • открытый фреймворк для создания интерактивных приложений — openFrameworks
  • бесплатное IDE Code::Blocks

Что получилось — можно посмотреть в спойлере, ниже.

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

То есть мне нужно было всего лишь — установить фреймворк, доработать и собрать пример на С++… Об этом процессе — установке, настройки, и кратком описании 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.

ссылка на оригинал статьи http://habrahabr.ru/post/244265/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *