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



    Когда вы последний раз программировали на 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.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 6

      –1
      Если видео сделать все-таки удалось — было бы очень здорово увидеть его здесь.
        –1
        UPD: Извините, не разглядел.
        Зря запрятали свой результат – это ведь «лицо» вашего труда :)
        Кстати, а к чему фото с космонавтом в начале статьи?
          +1
          На главной картинке — один из реальных примеров использования openFrameworks
          Подробности здесь: kuflex.com/Museum-Guides

          Семь Видеогидов. Это семь интерактивных инсталляций, имитирующих поведение музейных людей-гидов.
          Каждый гид рассказывает посетителям информацию о некоторой части музейной экспозиции.
          Выставлено на ВДНХ в экспозиции Политехнического музея.
          Москва, 2014.


      • UFO just landed and posted this here
          0
          Согласен с вами, возможно я излишне дотошен — но консольное окон открывается, именно то про которое вы говорите. Я не стал приводить его скриншот)
          0
          Здорово!

          Only users with full accounts can post comments. Log in, please.