Когда вы последний раз программировали на 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 "...CodeBlocksMinGWinclude"
Add the contents of the folder «add_to_codeblocks_mingw_lib» into "...CodeBlocksMinGWlib"
Всё, теперь мы готовы к сборке 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.
Автор: nemilya