diff options
Diffstat (limited to 'passadesgui')
| -rw-r--r-- | passadesgui/Makefile | 13 | ||||
| -rw-r--r-- | passadesgui/addons.make | 12 | ||||
| -rw-r--r-- | passadesgui/config.make | 142 | ||||
| -rwxr-xr-x | passadesgui/libs/libHeliosDacAPI.dylib | bin | 0 -> 24208 bytes | |||
| -rw-r--r-- | passadesgui/libs/libndi.3.dylib | bin | 0 -> 2263988 bytes | |||
| -rw-r--r-- | passadesgui/src/AudioPlotter.cpp | 114 | ||||
| -rw-r--r-- | passadesgui/src/AudioPlotter.h | 57 | ||||
| -rw-r--r-- | passadesgui/src/lineSegmenter.cpp | 109 | ||||
| -rw-r--r-- | passadesgui/src/lineSegmenter.h | 48 | ||||
| -rw-r--r-- | passadesgui/src/lineTransformer.cpp | 126 | ||||
| -rw-r--r-- | passadesgui/src/lineTransformer.h | 21 | ||||
| -rw-r--r-- | passadesgui/src/main.cpp | 48 | ||||
| -rw-r--r-- | passadesgui/src/ofApp.cpp | 1294 | ||||
| -rw-r--r-- | passadesgui/src/ofApp.h | 243 | ||||
| -rwxr-xr-x | passadesgui/start | 3 |
15 files changed, 2230 insertions, 0 deletions
diff --git a/passadesgui/Makefile b/passadesgui/Makefile new file mode 100644 index 0000000..177e172 --- /dev/null +++ b/passadesgui/Makefile @@ -0,0 +1,13 @@ +# Attempt to load a config.make file. +# If none is found, project defaults in config.project.make will be used. +ifneq ($(wildcard config.make),) + include config.make +endif + +# make sure the the OF_ROOT location is defined +ifndef OF_ROOT + OF_ROOT=$(realpath ../../..) +endif + +# call the project makefile! +include $(OF_ROOT)/libs/openFrameworksCompiled/project/makefileCommon/compile.project.mk diff --git a/passadesgui/addons.make b/passadesgui/addons.make new file mode 100644 index 0000000..6122519 --- /dev/null +++ b/passadesgui/addons.make @@ -0,0 +1,12 @@ +ofxNDI +ofxGui +ofxHelios +ofxOpenCV +ofxSVG +ofxXmlSettings +ofxClipper +ofxMidi +ofxGist +ofxOpenALSoundPlayer +ofxHistoryPlot +ofxAChaoslib diff --git a/passadesgui/config.make b/passadesgui/config.make new file mode 100644 index 0000000..469c495 --- /dev/null +++ b/passadesgui/config.make @@ -0,0 +1,142 @@ +################################################################################ +# CONFIGURE PROJECT MAKEFILE (optional) +# This file is where we make project specific configurations. +################################################################################ + +################################################################################ +# OF ROOT +# The location of your root openFrameworks installation +# (default) OF_ROOT = ../../.. +################################################################################ + OF_ROOT = ../../openFrameworks + +################################################################################ +# PROJECT ROOT +# The location of the project - a starting place for searching for files +# (default) PROJECT_ROOT = . (this directory) +# +################################################################################ +# PROJECT_ROOT = . + +################################################################################ +# PROJECT SPECIFIC CHECKS +# This is a project defined section to create internal makefile flags to +# conditionally enable or disable the addition of various features within +# this makefile. For instance, if you want to make changes based on whether +# GTK is installed, one might test that here and create a variable to check. +################################################################################ +# None + +################################################################################ +# PROJECT EXTERNAL SOURCE PATHS +# These are fully qualified paths that are not within the PROJECT_ROOT folder. +# Like source folders in the PROJECT_ROOT, these paths are subject to +# exlclusion via the PROJECT_EXLCUSIONS list. +# +# (default) PROJECT_EXTERNAL_SOURCE_PATHS = (blank) +# +# Note: Leave a leading space when adding list items with the += operator +################################################################################ +# PROJECT_EXTERNAL_SOURCE_PATHS = + +################################################################################ +# PROJECT EXCLUSIONS +# These makefiles assume that all folders in your current project directory +# and any listed in the PROJECT_EXTERNAL_SOURCH_PATHS are are valid locations +# to look for source code. The any folders or files that match any of the +# items in the PROJECT_EXCLUSIONS list below will be ignored. +# +# Each item in the PROJECT_EXCLUSIONS list will be treated as a complete +# string unless teh user adds a wildcard (%) operator to match subdirectories. +# GNU make only allows one wildcard for matching. The second wildcard (%) is +# treated literally. +# +# (default) PROJECT_EXCLUSIONS = (blank) +# +# Will automatically exclude the following: +# +# $(PROJECT_ROOT)/bin% +# $(PROJECT_ROOT)/obj% +# $(PROJECT_ROOT)/%.xcodeproj +# +# Note: Leave a leading space when adding list items with the += operator +################################################################################ +# PROJECT_EXCLUSIONS = + +################################################################################ +# PROJECT LINKER FLAGS +# These flags will be sent to the linker when compiling the executable. +# +# (default) PROJECT_LDFLAGS = -Wl,-rpath=./libs +# +# Note: Leave a leading space when adding list items with the += operator +################################################################################ + +# Currently, shared libraries that are needed are copied to the +# $(PROJECT_ROOT)/bin/libs directory. The following LDFLAGS tell the linker to +# add a runtime path to search for those shared libraries, since they aren't +# incorporated directly into the final executable application binary. +# TODO: should this be a default setting? +# PROJECT_LDFLAGS=-Wl,-rpath=./libs + +################################################################################ +# PROJECT DEFINES +# Create a space-delimited list of DEFINES. The list will be converted into +# CFLAGS with the "-D" flag later in the makefile. +# +# (default) PROJECT_DEFINES = (blank) +# +# Note: Leave a leading space when adding list items with the += operator +################################################################################ +# PROJECT_DEFINES = + +################################################################################ +# PROJECT CFLAGS +# This is a list of fully qualified CFLAGS required when compiling for this +# project. These CFLAGS will be used IN ADDITION TO the PLATFORM_CFLAGS +# defined in your platform specific core configuration files. These flags are +# presented to the compiler BEFORE the PROJECT_OPTIMIZATION_CFLAGS below. +# +# (default) PROJECT_CFLAGS = (blank) +# +# Note: Before adding PROJECT_CFLAGS, note that the PLATFORM_CFLAGS defined in +# your platform specific configuration file will be applied by default and +# further flags here may not be needed. +# +# Note: Leave a leading space when adding list items with the += operator +################################################################################ +# PROJECT_CFLAGS = + +################################################################################ +# PROJECT OPTIMIZATION CFLAGS +# These are lists of CFLAGS that are target-specific. While any flags could +# be conditionally added, they are usually limited to optimization flags. +# These flags are added BEFORE the PROJECT_CFLAGS. +# +# PROJECT_OPTIMIZATION_CFLAGS_RELEASE flags are only applied to RELEASE targets. +# +# (default) PROJECT_OPTIMIZATION_CFLAGS_RELEASE = (blank) +# +# PROJECT_OPTIMIZATION_CFLAGS_DEBUG flags are only applied to DEBUG targets. +# +# (default) PROJECT_OPTIMIZATION_CFLAGS_DEBUG = (blank) +# +# Note: Before adding PROJECT_OPTIMIZATION_CFLAGS, please note that the +# PLATFORM_OPTIMIZATION_CFLAGS defined in your platform specific configuration +# file will be applied by default and further optimization flags here may not +# be needed. +# +# Note: Leave a leading space when adding list items with the += operator +################################################################################ +# PROJECT_OPTIMIZATION_CFLAGS_RELEASE = +# PROJECT_OPTIMIZATION_CFLAGS_DEBUG = + +################################################################################ +# PROJECT COMPILERS +# Custom compilers can be set for CC and CXX +# (default) PROJECT_CXX = (blank) +# (default) PROJECT_CC = (blank) +# Note: Leave a leading space when adding list items with the += operator +################################################################################ +# PROJECT_CXX = +# PROJECT_CC = diff --git a/passadesgui/libs/libHeliosDacAPI.dylib b/passadesgui/libs/libHeliosDacAPI.dylib Binary files differnew file mode 100755 index 0000000..2544dff --- /dev/null +++ b/passadesgui/libs/libHeliosDacAPI.dylib diff --git a/passadesgui/libs/libndi.3.dylib b/passadesgui/libs/libndi.3.dylib Binary files differnew file mode 100644 index 0000000..d079763 --- /dev/null +++ b/passadesgui/libs/libndi.3.dylib diff --git a/passadesgui/src/AudioPlotter.cpp b/passadesgui/src/AudioPlotter.cpp new file mode 100644 index 0000000..7c775fa --- /dev/null +++ b/passadesgui/src/AudioPlotter.cpp @@ -0,0 +1,114 @@ +#include "Audioplotter.h" + +colourPolyline Audioplotter::compute_chaos(colourPolyline& poly,float colourFade){ + colourPolyline tempPoly; + for (int i=0;i<poly.size();i++){ + REAL iv[6]={(poly[i].x-(ofGetWidth()/2))/chaosscale,(poly[i].y-(ofGetHeight()/2))/chaosscale,chaos_a,chaos_b,chaos_k,chaos_p}; + //ofLog() << i<<": calculating chaos with: "<<poly[i].x<<"->"<<((poly[i].x-(ofGetWidth()/2))/chaosscale)<<" "<<poly[i].y<<"->"<<((poly[i].y-(ofGetHeight()/2))/chaosscale)<<" "<<attractor.a<<" "<<attractor.b<<" "<<attractor.k<<" "<<attractor.p; + attractor.set(iv); + attractor.calc(); + //ofLog() << i<<": got points: "<<attractor.nx<<" "<<attractor.ny; + tempPoly.addVertex(ofPoint((attractor.nx*chaosscale)+(ofGetWidth()/2),(attractor.ny*chaosscale)+(ofGetHeight()/2)),poly.getColourAt(i)*colourFade); + } + return tempPoly; +} +vector <colourPolyline> Audioplotter::output(float plotscale,float plotdecay){ + + ofMatrix4x4 x = ofMatrix4x4::newIdentityMatrix(); + x.translate((-ofGetWidth()/2)+((ofVec2f)translate).x,(-ofGetHeight()/2)+((ofVec2f)translate).y,0); + x.rotate(rotate,0,0,1); + + x.scale(((ofVec2f)scale).x,((ofVec2f)scale).y,1.0f); + + x.translate((ofGetWidth()/2),(ofGetHeight()/2),0); + + + ofMatrix4x4 flip = ofMatrix4x4::newIdentityMatrix(); + flip.translate((-ofGetWidth()/2),(-ofGetHeight()/2),0); + flip.scale(-1.0f,1.0f,1.0f); + flip.translate((ofGetWidth()/2),(ofGetHeight()/2),0); + //destructive or non? + float fadefactor=plotdecay<0.0f?1.0f-(1.0f/history_size):plotdecay; + + vector <colourPolyline> outdata; + + for (int i=0;i<data.size();i++){ + vector <colourPolyline> newdata; + for (int j=0;j<data[i].size();j++){ + colourPolyline line=lineTransformer::polyLineTransform(x,data[i][j]); //,fadefactor); + line.setColour((line.getColourAt(0)*fadefactor)+(((ofColor)endColour)*(1.0f-fadefactor))); + + if (usechaos) { + colourPolyline chaosline=compute_chaos(line); //,fadefactor); + line.mix(chaosline,chaosamount); + } + + + //ofLog() << "set colour to "<<col; + newdata.push_back(line); + outdata.push_back(line); + + } + data[i]=newdata; + if (mirror){ + for (int j=0;j<data[i].size();j++){ + outdata.push_back(lineTransformer::polyLineTransform(flip,data[i][j])); + } + } + } + return outdata; +} +void Audioplotter::addpoints(vector <float> &audio){ + int num=min((int)num_points,(int)audio.size()); + float step=((float)audio.size())/(num+1); + vector <colourPolyline> newdata; + if (joined){ + colourPolyline line; + if (bars&&num>1){ + for (int i=0;i<num-1;i++){ + line.addVertex(((step*(i+1))*ofGetWidth())/audio.size(),(0.5f+audio[step*(i+1)])*ofGetHeight(),startColour); + line.addVertex(((step*(i+2))*ofGetWidth())/audio.size(),(0.5f+audio[step*(i+1)])*ofGetHeight(),startColour); + line.addVertex(((step*(i+2))*ofGetWidth())/audio.size(),(0.5f+audio[step*(i+2)])*ofGetHeight(),startColour); + } + } + else { + for (int i=0;i<num;i++){ + line.addVertex(((step*(i+1))*ofGetWidth())/audio.size(),(0.5f+audio[step*(i+1)])*ofGetHeight(),startColour); + } + } + newdata.push_back(line); + } + else{ + for (int i=0;i<num;i++){ + colourPolyline line; + if (random){ + float in=ofRandom(0.0f,audio.size()); + line.addVertex(((in*ofGetWidth())/audio.size())-(width/2),(0.5f+audio[in])*ofGetHeight(),startColour); + line.addVertex(((in*ofGetWidth())/audio.size())+(width/2),(0.5f+audio[in])*ofGetHeight(),startColour); + } + else { + line.addVertex(((step*(i+1))*ofGetWidth())/audio.size()-(width/2),(0.5f+audio[step*(i+1)])*ofGetHeight(),startColour); + line.addVertex(((step*(i+1))*ofGetWidth())/audio.size()+(width/2),(0.5f+audio[step*(i+1)])*ofGetHeight(),startColour); + } + newdata.push_back(line); + } + } + data.insert(data.begin(),newdata); + while (data.size()>history_size) { + data.pop_back(); + } +} +void Audioplotter::blankframe(){ + vector <colourPolyline> newdata; + data.insert(data.begin(),newdata); + while (data.size()>history_size) { + data.pop_back(); + } +} +int Audioplotter::numpoints(){ + int num=0; + for (auto d=data.begin();d!=data.end();d++){ + num+=d->size(); + } + return num; +}
\ No newline at end of file diff --git a/passadesgui/src/AudioPlotter.h b/passadesgui/src/AudioPlotter.h new file mode 100644 index 0000000..9cd37ae --- /dev/null +++ b/passadesgui/src/AudioPlotter.h @@ -0,0 +1,57 @@ +#include "ofMain.h" +#include "ofxAChaoslib.h" + +#include "lineTransformer.h" + +class Audioplotter{ + //store and draw a numbr of audio samples + //how best to handle transforms - maybe pass in a transform to be added to 2nd and subsequent + //how best to handle length of history data - fixed number that can be set, or line budget? +public: + Audioplotter(int _size=1,bool _joined=true,bool _bars=false,int _width=2){ + setup(_size,_joined,_bars,_width); + } + void setup(int _size=1,bool _joined=true,bool _bars=false,int _width=2){ + history_size=_size; + joined=_joined; + bars=_bars; + width=_width; + startColour=ofColor(255,255,255); + endColour=ofColor(0,0,0); + attractor.setup(); + } + colourPolyline compute_chaos(colourPolyline& poly,float colourFade=1.0f); + vector <colourPolyline> output(float scale=1.0f,float decay=-1.0f); + void addpoints(vector <float> &audio); + void blankframe(); + int numpoints(); + + ofParameter<bool> random; + ofParameter<bool> joined; + ofParameter<bool> bars; + ofParameter<bool> mirror; + ofParameter<int> width; + ofParameter<int> history_size; + ofParameter<int> num_points; + +//feedback transformation + ofParameter<ofVec2f> translate; + ofParameter<float> rotate; + ofParameter<ofVec2f> scale; + + ofParameter<ofColor> startColour; + ofParameter<ofColor> endColour; + + ofParameter<bool> usechaos; + ofParameter<float> chaosamount; + ofParameter<float> chaosscale; + ofParameter<float> chaos_a; + ofParameter<float> chaos_b; + ofParameter<float> chaos_k; + ofParameter<float> chaos_p; + +private: + vector < vector<colourPolyline>> data; + AChaosIkeda attractor; + +};
\ No newline at end of file diff --git a/passadesgui/src/lineSegmenter.cpp b/passadesgui/src/lineSegmenter.cpp new file mode 100644 index 0000000..3f434b1 --- /dev/null +++ b/passadesgui/src/lineSegmenter.cpp @@ -0,0 +1,109 @@ + #include "lineSegmenter.h" + +const vector <ofPolyline> & lineSegmenter::getSegments(int num,float coverage, float phase){ + //num - number of segments + //coverage - amount that each segment fills it's slot from 0-1 + //phase - from 0-1 + + //if the path is closed, we can make a segment that crosses the end/beginning + //however we want to be able to deal with open paths + +/* + +segments 0...n - 1 +phase 0...1 + +phase 0 + +segment 0 is (0 -> coverage) / n +segment n - 1 is ((0 -> coverage) + (n-1)) /n + +phase 1: has to be the loop target, it has to look identical + +segment 0 is (1 -> coverage) / n +segment n - 1 is (1 - > coverage) + (n-1) + +*/ + + + segments.clear(); + + for (int i=0;i<num;i++){ + float startIndex=line.getIndexAtPercent((phase+i)/num); //always <1 + float endPoint=(phase+i+coverage)/num; //can be >1 + float endIndex=line.getIndexAtPercent(endPoint>1.0f?endPoint-1.0f:endPoint); + ofPolyline segment; + segment.addVertex(line.getPointAtIndexInterpolated(startIndex)); + for (int j=(int)ceil(startIndex);j<(endPoint>1?line.size():(int)ceil(endIndex));j++){ + segment.addVertex(line[j]); + } + if (endPoint>1){ + segments.push_back(segment); + segment.clear(); + for (int j=0;j<(int)ceil(endIndex);j++){ + segment.addVertex(line[j]); + } + segment.addVertex(line.getPointAtIndexInterpolated(endIndex)); + } + else { + segment.addVertex(line.getPointAtIndexInterpolated(endIndex)); + } + segments.push_back(segment); + } + + return segments; +} + +void lineSegmenter::draw(){ + line.draw(); + return; +} +int lineSegmenter::size(){ + return line.size(); +} + +const vector <colourPolyline> & colourLineSegmenter::getSegments(int num,float coverage, float phase){ + + segments.clear(); + + for (int i=0;i<num;i++){ + float startIndex=line.getIndexAtPercent((phase+i)/num); //always <1 + float endPoint=(phase+i+coverage)/num; //can be >1 + float endIndex=line.getIndexAtPercent(endPoint>1.0f?endPoint-1.0f:endPoint); + colourPolyline segment; + segment.addVertex(line.getPointAtIndexInterpolated(startIndex),line.getColourAtIndexInterpolated(startIndex)); + for (int j=(int)ceil(startIndex);j<(endPoint>1?line.size():(int)ceil(endIndex));j++){ + segment.addVertex(line[j],line.getColourAt(j)); + } + if (endPoint>1){ + segments.push_back(segment); + segment.clear(); + for (int j=0;j<(int)ceil(endIndex);j++){ + segment.addVertex(line[j],line.getColourAt(j)); + } + segment.addVertex(line.getPointAtIndexInterpolated(endIndex),line.getColourAtIndexInterpolated(endIndex)); + } + else { + segment.addVertex(line.getPointAtIndexInterpolated(endIndex),line.getColourAtIndexInterpolated(endIndex) ); + } + segments.push_back(segment); + } + + return segments; +} + +void colourLineSegmenter::draw(){ + line.draw(); + return; +} +int colourLineSegmenter::size(){ + return line.size(); +} + + +/* + + + + +*/
\ No newline at end of file diff --git a/passadesgui/src/lineSegmenter.h b/passadesgui/src/lineSegmenter.h new file mode 100644 index 0000000..2467c6a --- /dev/null +++ b/passadesgui/src/lineSegmenter.h @@ -0,0 +1,48 @@ +#pragma once + +#include "ofMain.h" +#include "colourPolyline.h" + +class lineSegmenter{ + public: + lineSegmenter(ofPolyline &_line){ + line=_line; + if (line.isClosed()){ + line.addVertex(line[0]); + } + } + const vector <ofPolyline> &getSegments(int num,float coverage, float phase); + ofPolyline getPoly(){ + return line; + } + void draw(); + int size(); + private: + ofPolyline line; + vector <ofPolyline> segments; +}; + +class colourLineSegmenter{ + public: + colourLineSegmenter(colourPolyline &_line){ + line=_line; + //if (line.isClosed()){ + // line.addVertex(line[0]); + //} + } + colourLineSegmenter(ofPolyline &_line,const ofColor color=ofColor(255,255,255)){ + line=colourPolyline(_line,color); + if (_line.isClosed()){ + line.addVertex(line[0],line.getColourAt(0)); + } + } + const vector <colourPolyline> &getSegments(int num,float coverage, float phase); + colourPolyline getPoly(){ + return line; + } + void draw(); + int size(); + private: + colourPolyline line; + vector <colourPolyline> segments; +};
\ No newline at end of file diff --git a/passadesgui/src/lineTransformer.cpp b/passadesgui/src/lineTransformer.cpp new file mode 100644 index 0000000..8e2bd80 --- /dev/null +++ b/passadesgui/src/lineTransformer.cpp @@ -0,0 +1,126 @@ +#include "lineTransformer.h" + + +void lineTransformer::drawWarpFrame(glm::vec2 warpframe[4]){ + ofSetColor(255,255,255); + ofNoFill(); + for (int i=0;i<4;i++){ + ofDrawCircle(warpframe[i],25); + ofDrawLine(warpframe[i],warpframe[(i+1)%4]); + } +} + +void lineTransformer::gaussianElimination(float * input, int n) +{ + auto i = 0; + auto j = 0; + auto m = n - 1; + + while (i < m && j < n) + { + auto iMax = i; + for (auto k = i + 1; k < m; ++k) + { + if (fabs(input[k * n + j]) > fabs(input[iMax * n + j])) + { + iMax = k; + } + } + + if (input[iMax * n + j] != 0) + { + if (i != iMax) + { + for (auto k = 0; k < n; ++k) + { + auto ikIn = input[i * n + k]; + input[i * n + k] = input[iMax * n + k]; + input[iMax * n + k] = ikIn; + } + } + + float ijIn = input[i * n + j]; + for (auto k = 0; k < n; ++k) + { + input[i * n + k] /= ijIn; + } + + for (auto u = i + 1; u < m; ++u) + { + auto ujIn = input[u * n + j]; + for (auto k = 0; k < n; ++k) + { + input[u * n + k] -= ujIn * input[i * n + k]; + } + } + + ++i; + } + ++j; + } + + for (auto i = m - 2; i >= 0; --i) + { + for (auto j = i + 1; j < n - 1; ++j) + { + input[i * n + m] -= input[i * n + j] * input[j * n + m]; + } + } +} + +glm::mat4 lineTransformer::getPerspectiveTransformMatrix(const glm::vec2 src[4], const glm::vec2 dst[4]) +{ + float p[8][9] = + { + { -src[0][0], -src[0][1], -1, 0, 0, 0, src[0][0] * dst[0][0], src[0][1] * dst[0][0], -dst[0][0] }, // h11 + { 0, 0, 0, -src[0][0], -src[0][1], -1, src[0][0] * dst[0][1], src[0][1] * dst[0][1], -dst[0][1] }, // h12 + { -src[1][0], -src[1][1], -1, 0, 0, 0, src[1][0] * dst[1][0], src[1][1] * dst[1][0], -dst[1][0] }, // h13 + { 0, 0, 0, -src[1][0], -src[1][1], -1, src[1][0] * dst[1][1], src[1][1] * dst[1][1], -dst[1][1] }, // h21 + { -src[2][0], -src[2][1], -1, 0, 0, 0, src[2][0] * dst[2][0], src[2][1] * dst[2][0], -dst[2][0] }, // h22 + { 0, 0, 0, -src[2][0], -src[2][1], -1, src[2][0] * dst[2][1], src[2][1] * dst[2][1], -dst[2][1] }, // h23 + { -src[3][0], -src[3][1], -1, 0, 0, 0, src[3][0] * dst[3][0], src[3][1] * dst[3][0], -dst[3][0] }, // h31 + { 0, 0, 0, -src[3][0], -src[3][1], -1, src[3][0] * dst[3][1], src[3][1] * dst[3][1], -dst[3][1] }, // h32 + }; + + gaussianElimination(&p[0][0], 9); + + return glm::mat4(p[0][8], p[3][8], 0, p[6][8], + p[1][8], p[4][8], 0, p[7][8], + 0, 0, 1, 0, + p[2][8], p[5][8], 0, 1); +} + +ofPolyline lineTransformer::polyLineTransform(const ofMatrix4x4 xform, const ofPolyline& poly){ + ofPolyline tempPoly; + for (auto& p:poly){ + tempPoly.addVertex(ofVec3f(p)*xform); + } + return tempPoly; +} + +colourPolyline lineTransformer::polyLineTransform(const ofMatrix4x4 xform,colourPolyline& poly,float colourFade){ + colourPolyline tempPoly; + for (int i=0;i<poly.size();i++){ + tempPoly.addVertex(ofVec3f(poly[i])*xform,poly.getColourAt(i)*colourFade); + } + return tempPoly; +} + +ofPolyline lineTransformer::makePolygon(int num,float diam){ + ofPolyline poly; + float step=PI*2/num; + for (int i=0;i<=num;i++){ + poly.addVertex(cos(step*i)*diam,sin(step*i)*diam); + } + return poly; +} + +void lineTransformer::drawPoly(ofPolyline poly,float x,float y){ + glPushMatrix(); + ofTranslate(x,y); + poly.draw(); + for (int i=0;i<poly.size();i++){ + ofDrawBitmapString(poly.getDegreesAtIndex(i),poly[i].x+10,poly[i].y+10,0); + } + glPopMatrix(); +} diff --git a/passadesgui/src/lineTransformer.h b/passadesgui/src/lineTransformer.h new file mode 100644 index 0000000..f5ec22f --- /dev/null +++ b/passadesgui/src/lineTransformer.h @@ -0,0 +1,21 @@ +#pragma once + +#include "ofMain.h" +#include "colourPolyline.h" + +class lineTransformer { + + public: + lineTransformer(){ + } + void static drawWarpFrame(glm::vec2 warpframe[4]); + void static gaussianElimination(float * input, int n); + glm::mat4 static getPerspectiveTransformMatrix(const glm::vec2 src[4], const glm::vec2 dst[4]); + ofPolyline static polyLineTransform(const ofMatrix4x4 xform,const ofPolyline& poly); + ofPolyline static polyLineTransform(ofPoint (*transferFunction)(const ofPoint),const ofPolyline& poly); + colourPolyline static polyLineTransform(const ofMatrix4x4 xform,colourPolyline& poly,float colourFade=1.0f); + colourPolyline static polyLineTransform(ofPoint (*transferFunction)(const ofPoint),colourPolyline& poly,float colourFade=1.0f); + ofPolyline static makePolygon(int num,float diam); + void static drawPoly(ofPolyline poly,float x,float y); + +};
\ No newline at end of file diff --git a/passadesgui/src/main.cpp b/passadesgui/src/main.cpp new file mode 100644 index 0000000..b1a8365 --- /dev/null +++ b/passadesgui/src/main.cpp @@ -0,0 +1,48 @@ +#include "ofMain.h" +#include "ofApp.h" + + +//======================================================================== +int main(int argc, char *argv[]){ + + + ofGLFWWindowSettings settings; + + + + settings.decorated = true; + + settings.setSize(1200,900); + settings.setPosition(ofVec2f(1700,0)); + settings.resizable = false; + + shared_ptr<ofAppBaseWindow> mainWindow = ofCreateWindow(settings); + mainWindow->setVerticalSync(false); + + settings.setSize(700,1100); + settings.setPosition(ofVec2f(0,0)); + settings.resizable = true; + + // share OpenGL resources with other windows + settings.shareContextWith = mainWindow; + + //settings.decorated = false; //doesn't suppress FS title bar + shared_ptr<ofAppBaseWindow> guiWindow = ofCreateWindow(settings); + guiWindow->setVerticalSync(false); + + shared_ptr<ofApp> mainApp(new ofApp); + //mainApp->setupGui(); + + ofAddListener(mainWindow->events().update,mainApp.get(),&ofApp::updateOutput); + ofAddListener(mainWindow->events().draw,mainApp.get(),&ofApp::drawOutput); + ofAddListener(mainWindow->events().windowResized,mainApp.get(),&ofApp::outputWindowResized); + ofAddListener(mainWindow->events().keyPressed,mainApp.get(),&ofApp::outputKeyPressed); + ofAddListener(mainWindow->events().keyReleased,mainApp.get(),&ofApp::outputKeyReleased); + ofAddListener(mainWindow->events().mouseDragged,mainApp.get(),&ofApp::outputMouseDragged); + ofAddListener(mainWindow->events().mousePressed,mainApp.get(),&ofApp::outputMousePressed); + ofAddListener(mainWindow->events().mouseReleased,mainApp.get(),&ofApp::outputMouseReleased); + + ofRunApp(guiWindow, mainApp); + ofRunMainLoop(); +} +
\ No newline at end of file diff --git a/passadesgui/src/ofApp.cpp b/passadesgui/src/ofApp.cpp new file mode 100644 index 0000000..36e0785 --- /dev/null +++ b/passadesgui/src/ofApp.cpp @@ -0,0 +1,1294 @@ +#include "ofApp.h" +#include "glew.h" + +const ofPoint outputWindowSize=ofPoint(1200,900); +const float guiScale=560.0f/4096.0f; + + +string sourcenames[5]={ + "TEST", +// "NDI", + "Player", + "SVG outlines", + "SVG segmenters", + "Audio" +}; + +//-------------------------------------------------------------- +void ofApp::setup(){ + + source=Audio; +/* + //==================================================== ofxNDI + + senderName[0] = 0; // The sender name used for display + nSenders = 0; // Total number of NDI senders + senderWidth = 0; // Sender width + senderHeight = 0; // Sender height + bNDIreceiver = false; // Receiver creation + + // Create an intial receiving image + ndiImage.allocate(ofGetWidth(), ofGetHeight(), OF_IMAGE_COLOR_ALPHA); + + // For received frame fps calculations - independent of the rendering rate + startTime = lastTime = frameTime = 0; + fps = frameRate = 1; // starting value +*/ + + //============================= Audio + + plotter.setup(5,true); + + bufferSize = 2048; + sampleRate = 44100; + + gist.setUseForOnsetDetection(GIST_PEAK_ENERGY); + gist.setThreshold(GIST_PEAK_ENERGY, .05);// + + ofAddListener(GistEvent::ON,this,&ofApp::onNoteOn); + ofAddListener(GistEvent::OFF,this,&ofApp::onNoteOff); + + // ofAddListener(ofApp::useMic->Value,this,&ofApp::useMicChanged); + + soundStream.setup(this,0, 1, sampleRate, bufferSize, 1); + + //loadSong("12 Ferric Appetite.aiff"); + loadSong("passades/1 The Ninth Set-sector1:sector2pt.1.aiff"); + + //============================ gui + + lasergui.setup("laser","",230,820); + lasergui.add(laser_power.set("power", false)); + lasergui.add(laser_intensity.set("intensity", 30, 0, 255)); + lasergui.add(laser_points.set("points", 30000, 0, 40000)); + lasergui.add(laser_subdivide.set("subdivide", 15, 1, 100)); + lasergui.add(laser_blank_num.set("blank points", 8, 0, 32)); + lasergui.add(laser_max_angle.set("max angle", 15.0f, 1.0f, 90.0f)); + + responsegui.setup("audio response","",230,970); + responsegui.add(onset_threshold.set("onset threshold", 0.05f, 0.0f, 1.0f )); + responsegui.add(use_onset.set("trigger onset",false)); + responsegui.add(onset_duration.set("duration", 10, 1, 100)); + + onset_frame=0; + + chaosgui.setup("chaos","",460,820); + chaosgui.add(plotter.usechaos.set("use",false)); + chaosgui.add(plotter.chaosamount.set("amount", 0.0f, -0.1f, 0.1f)); + chaosgui.add(plotter.chaosscale.set("scale", 100.0f, 1.0f, 1000.0f)); + chaosgui.add(plotter.chaos_a.set("a", 0.85f, 0.0f, 10.0f)); + chaosgui.add(plotter.chaos_b.set("b", 0.9f, 0.0f, 10.0f)); + chaosgui.add(plotter.chaos_k.set("k", 0.4f, 0.0f, 10.0f)); + chaosgui.add(plotter.chaos_p.set("p", 7.7f, 0.0f, 10.0f)); + + drawgui.setup("drawing","",10,0); + drawgui.add(contour_threshold.setup("threshold", 140, 0, 255)); + drawgui.add(contour_simplify.setup("simplify", 0.8, 0.0, 10.0)); + drawgui.add(contour_useColour.setup("use colour", true)); + drawgui.add(laser_R.setup("red", 140, 0, 255)); + drawgui.add(laser_G.setup("green", 140, 0, 255)); + drawgui.add(laser_B.setup("blue", 140, 0, 255)); + drawgui.add(video_speed.setup("playback speed", 1.0, 0.0, 3.0)); + drawgui.add(shapes_randomise.setup("randomise shapes", true)); + drawgui.add(shapes_amount.setup("shapes amount", 0.2, 0.0, 0.8)); + drawgui.add(shapes_duration.setup("shape duration", 5, 0, 25)); + drawgui.add(use_mask.setup("use mask", true)); + drawgui.add(invert_mask.setup("invert mask", false)); + drawgui.add(use_segmenter.setup("use segmenter", false)); + drawgui.add(segmenter_speed.setup("segmenter speed", 0.2, -1.0, 1.0)); + drawgui.add(segmenter_length.setup("segmenter length", 0.2, 0.0, 1.0)); + drawgui.add(segmenter_number.setup("segmenter number", 1, 1, 8)); + drawgui.add(use_rotate.setup("XF rotate", false)); + drawgui.add(xf_rotate.setup("rotate speed", 0.0, -1.0, 1.0)); + drawgui.add(use_scale.setup("XF scale", false)); + drawgui.add(xf_scale_speed.setup("scale speed", 1.0, 0.0, 10.0)); + drawgui.add(xf_scale_min.setup("scale min", 1.0, 0.0, 3.0)); + drawgui.add(xf_scale_max.setup("scale max", 2.0, 0.0, 3.0)); + + audiogui.setup("audio","",10,450); + audiogui.add(useMic.set("mic",false)); + audiogui.add(useFft.set("fft",false)); + audiogui.add(scalePlot.set("plotscale", 0.1f, 0.0f, 1.0f )); //parameters are recognised by name only? + audiogui.add(decayPlot.set("decay", 0.9f, 0.0f, 1.0f )); + audiogui.add(plotter.random.set("random",true)); + audiogui.add(plotter.joined.set("joined",true)); + audiogui.add(plotter.bars.set("bars",true)); + audiogui.add(plotter.mirror.set("mirror",false)); + audiogui.add(plotter.width.set("point width", 2, 1, 256)); + audiogui.add(plotter.num_points.set("num points", 50, 1, 64)); + audiogui.add(plotter.history_size.set("num plots", 5, 1, 64)); + audiogui.add(plotter.translate.set("translate",ofVec2f(0,0),ofVec2f(-50,-50),ofVec2f(50,50))); + audiogui.add(plotter.rotate.set("rotate",0.0f,-10.0f,10.0f)); + audiogui.add(plotter.scale.set("scale",ofVec2f(1.0f,1.0f),ofVec2f(0.5f,0.5f),ofVec2f(2.0f,2.0f))); + audiogui.add(plotter.startColour.set("start",ofColor(255,255,255))); + audiogui.add(plotter.endColour.set("end",ofColor(0,0,0))); + + framecounter=0; + + //============================ MIDI + + midiIn.listInPorts(); + midiIn.openPort(0); + midiIn.addListener(this); + + //======================================= //positioning interface + + safety_frame.addVertex(0,0); //etc + + bShowPositionInterface=false; + bOutputSelected=false; + + outputOffsetScale=1.0f; + commandPressed=false; + + + select_warpframe=-1; + bDrawFrame=false; + + if( XML.loadFile("settings.xml") ){ + cout << "settings.xml loaded!" <<std::endl; + + } + else{ + cout << "unable to load settings.xml"<<std::endl; + } + + warpframe[0]=glm::vec2( + XML.getValue("WARP:p0:X", 0), + XML.getValue("WARP:p0:Y", 0) + ); + warpframe[1]=glm::vec2( + XML.getValue("WARP:p1:X", outputWindowSize.x), + XML.getValue("WARP:p1:Y", 0) + ); + warpframe[2]=glm::vec2( + XML.getValue("WARP:p2:X", outputWindowSize.x), + XML.getValue("WARP:p2:Y", outputWindowSize.y) + ); + warpframe[3]=glm::vec2( + XML.getValue("WARP:p3:X", 0), + XML.getValue("WARP:p3:Y", outputWindowSize.y) + ); + + outputPosition=ofPoint( + XML.getValue("POSITION:X", 0), + XML.getValue("POSITION:Y", 0) + ); + + outputScale=XML.getValue("SCALE", 1.0f); + +} + +//====================== audio functions + +void ofApp::loadSong(string str){ + + cout<<"loadSong "<<str<<endl; + + player.stop(); + player.loadSound(str); + player.setLoop(true); + player.play(); + useMic = 0; + gist.clearHistory(); +} + +void ofApp::onNoteOn(GistEvent &e){ + //ofLog() << "<<NOTE ON>>"; + onset_frame=0; + onset_number++; + //noteOnRadius = 100; +}; + + +void ofApp::onNoteOff(GistEvent &e){ + //ofLog() << "<<NOTE OFF>>"; + //turn off? + //noteOnRadius = 0; +}; + +void ofApp::processAudio(float * input, int bufferSize, int nChannels){ + //convert float array to vector + + + left.resize(bufferSize/nChannels); + right.resize(bufferSize/nChannels); + centre.resize(bufferSize/nChannels); + + float max=0.0f; + + + if (nChannels==2){ + + + for (int i = 0; i < bufferSize/nChannels; i++){ + left[i] = input[i*nChannels]; + right[i] = input[i*nChannels+1]; + centre[i] = (left[i]+right[i])*0.5; + if (centre[i]>max){ + max=centre[i]; + } + } + + + } + else if (nChannels==1){ + for (int i = 0; i < bufferSize;i++){ + left[i] = input[i]; + right[i] = input[i]; + centre[i] = input[i]; + if (centre[i]>max){ + max=centre[i]; + } + } + } + + //ofLog()<<"audio data max "<<max; + //float audio data is +- 0.5 + + + vector<float>buffer; + buffer.assign(&input[0],&input[bufferSize]); + + gist.processAudio(buffer, bufferSize, nChannels,sampleRate); +} + + +void ofApp::audioIn(float * input, int bufferSize, int nChannels){ + if(!useMic){ + return; + } + + //ofLog() << "processing "<<bufferSize<<" samples from mic"; + + processAudio(input, bufferSize, nChannels); + +} + +void ofApp::useMicChanged(){ + if (useMic) { + player.stop(); + } + else { + player.play(); + } +} + +//====================== settings + +void ofApp::default_settings(){ + warpframe[0]=glm::vec2(0,0); + warpframe[1]=glm::vec2(outputWindowSize.x,0); + warpframe[2]=glm::vec2(outputWindowSize.x,outputWindowSize.y); + warpframe[3]=glm::vec2(0,outputWindowSize.y); + outputPosition=ofPoint(0,0); + outputScale=1.0f; +} + +void ofApp::save_settings(){ + XML.setValue("WARP:p0:X", warpframe[0].x); + XML.setValue("WARP:p0:Y", warpframe[0].y); + XML.setValue("WARP:p1:X", warpframe[1].x); + XML.setValue("WARP:p1:Y", warpframe[1].y); + XML.setValue("WARP:p2:X", warpframe[2].x); + XML.setValue("WARP:p2:Y", warpframe[2].y); + XML.setValue("WARP:p3:X", warpframe[3].x); + XML.setValue("WARP:p3:Y", warpframe[3].y); + + XML.setValue("POSITION:X", outputPosition.x); + XML.setValue("POSITION:Y", outputPosition.y); + + XML.setValue("SCALE", outputScale); + + XML.saveFile("settings.xml"); + cout << "settings.xml saved!" <<std::endl; +} + +//-------------------------------------------------------------- +void ofApp::updateOutput(ofEventArgs & args){ +//todo: set up listener for laser params or move them into a lasergui class + laser.set_pts(laser_points); + laser.set_subdivide(laser_subdivide); + laser.set_blanknum(laser_blank_num); + laser.set_maxangle(laser_max_angle); + + laser.set_intensity(laser_intensity); + + movie.setSpeed(video_speed); + + if(!useMic){ + if(player.isLoaded()){ + if (player.isPlaying()){ + vector<float> output = player.getCurrentBuffer(bufferSize); + processAudio(&output[0], bufferSize, 2); + // + if (useFft){ + fftSmoothed = player.getFFT(); + /* + float * fft = ofSoundGetSpectrum(plotter.num_points); + fftSmoothed.resize(plotter.num_points); + //printf("fft: "); + for (int i=0;i<plotter.num_points;i++){ + fftSmoothed[i]=fft[i]; + //printf("%f,",fftSmoothed[i]); + } + //printf("\r\n"); + */ + plotter.addpoints(fftSmoothed); + } + else { + plotter.addpoints(centre); + } + } + else { + player.play(); + } + } + + } + else { + if (player.isPlaying()){ + player.stop(); + } + if (centre.size()){ + if (useFft){ + float * fft = ofSoundGetSpectrum(plotter.num_points); + fftSmoothed.resize(plotter.num_points); + printf("fft: "); + for (int i=0;i<plotter.num_points;i++){ + fftSmoothed[i]=fft[i]; + printf("%f,",fftSmoothed[i]); + } + printf("\r\n"); + plotter.addpoints(fftSmoothed); + } + else { + if (!use_onset||onset_frame<onset_duration){ + plotter.addpoints(centre); + } + else { + plotter.blankframe(); + } + } + } + } + +} + +void ofApp::update(){ +/* + // Update the NDI sender list to find new senders + // There is no delay if no new senders are found + nSenders = ndiReceiver.FindSenders(); + + if(nSenders > 0) { + + // Has the user changed the sender index ? + if(ndiReceiver.SenderSelected()) { + // Release the current receiver. + // A new one is then created from the selected sender index. + ndiReceiver.ReleaseReceiver(); + bNDIreceiver = false; + } + + // Create a new receiver if one does not exist. + // We don't know the sender dimensions until a frame is received. + if(!bNDIreceiver) { + + // The receiver will detect which format a sender is using and convert + // the pixel buffer to BGRA for return. However, we can specify here + // that RGBA is the preferred format. + bNDIreceiver = ndiReceiver.CreateReceiver(NDIlib_recv_color_format_e_RGBX_RGBA); + // bNDIreceiver = ndiReceiver.CreateReceiver(); // default is BRRA + + // + // A receiver is created from an index into a list of sender names. + // The current user selected index is saved in the NDIreceiver class + // and is used to create the receiver unless you specify a particular index. + // + // The name of the sender can also be retrieved if you need it. + // If you specified a particular sender index to create the receiver + // use that index to retrieve it's name. + // + // In this application we use it to display the sender name. + // + + ndiReceiver.GetSenderName(senderName); + if(bNDIreceiver) + cout << "Created NDI receiver for " << senderName << endl; + + // Reset the starting values for frame rate calulations + fps = frameRate = 1; + + } + + } + + if(bNDIreceiver) { + + unsigned int width = 0; + unsigned int height = 0; + + // If the NDI sender uses BGRA format, the received buffer is converted to rgba by ReceiveImage. + // Optionally you can flip the image if necessary. + + // !CHECKME MACOS + // ReceiveImage needs an unsigned char* so changed + // ndiImage.getPixels() to ndiImage.getPixels().getData() + // to avoid type error + if(ndiReceiver.ReceiveImage(ndiImage.getPixels().getData(), width, height, false)) { // receives as rgba + + ndiImage.update(); + + // ---------------------------- + // Calculate received frame fps + lastTime = startTime; + startTime = ofGetElapsedTimeMicros(); + frameTime = (startTime - lastTime)/1000000; // seconds + if( frameTime > 0.01) { + frameRate = floor(1.0/frameTime + 0.5); + // damping from a starting fps value + fps *= 0.95; + fps += 0.05*frameRate; + } + // ---------------------------- + + // Have the NDI sender dimensions changed ? + if(senderWidth != width || senderHeight != height) { + + // Update the sender dimensions + senderWidth = width; + senderHeight = height; + + // Update the receiving image size + ndiImage.allocate(senderWidth, senderHeight, OF_IMAGE_COLOR_ALPHA); + + } + } + + } +*/ + if (movie.isLoaded()){ + movie.update(); + } + + gist.setThreshold(GIST_PEAK_ENERGY,onset_threshold); + + onset_frame++; +} + +const ofPoint previewframesize=ofPoint(320,240); + +//-------------------------------------------------------------- GUI +void ofApp::draw(){ + ofBackground(0); + + ofSetColor(255); + ofNoFill(); + + if (bShowPositionInterface){ + + glPushMatrix(); + + glTranslatef(20,20,0); + + ofDrawRectangle(0,0,560,560); + + glTranslatef(outputOffset.x,outputOffset.y,0); + + glScalef(guiScale,guiScale,guiScale ); + + glTranslatef(2048.0f+outputPosition.x,2048.0f+outputPosition.y,0); + + if (bOutputSelected) { + if (commandPressed) { + ofSetColor(0,255,0); + } + else { + ofSetColor(255,0,0); + } + } + + ofDrawRectangle( + (-outputWindowSize.x/2)*outputScale*outputOffsetScale, + (-outputWindowSize.y/2)*outputScale*outputOffsetScale, + outputWindowSize.x*outputScale*outputOffsetScale, + outputWindowSize.y*outputScale*outputOffsetScale); + + glPopMatrix(); + } + else { + lasergui.draw(); + drawgui.draw(); + audiogui.draw(); + chaosgui.draw(); + responsegui.draw(); + + //================================== NDI +/* + glPushMatrix(); + + glTranslatef(230,0,0); + + ofDrawRectangle(20,20,previewframesize.x+4,previewframesize.y+4); + + char str[256]; + + if(bNDIreceiver) { + + ndiImage.draw(22, 22, previewframesize.x, previewframesize.y); + + // Show fps etc. + if(nSenders > 0) { + if(bNDIreceiver) { + #ifdef _MSC_VER + sprintf_s(str, 256, "[%s] (%dx%d) - fps %2.0f", senderName, senderWidth, senderHeight, fps); + #else + // !CHECK MACOS + snprintf(str, 256, "[%s] (%dx%d) - fps %2.0f", senderName, senderWidth, senderHeight, fps); + str[255] = 0; + #endif + ofDrawBitmapString(str, 20, previewframesize.y+42); + } + + if(nSenders == 1) { + ofDrawBitmapString("1 network source", 25, 32); + } + else { + #ifdef _MSC_VER + sprintf_s(str, 256, "%d network sources", nSenders); + #else + // !CHECK MACOS + snprintf(str, 256, "%d network sources", nSenders); + str[255] = 0; + #endif + ofDrawBitmapString(str, 25, 32); + //ofDrawBitmapString("'SPACE' to list senders or RH click to open sender dialog", 20, ofGetHeight()-20); + } + } + } + else { + ofDrawBitmapString("Connecting . . .", 25, 32); + } + + glPopMatrix(); + + //================================== NDI + + glPushMatrix(); + + glTranslatef(230,300,0); + */ + //================================== video + + glPushMatrix(); + + glTranslatef(230,0,0); + + ofDrawRectangle(20,20,previewframesize.x+4,previewframesize.y+4); + + if (movie.isLoaded()){ + movie.draw(22, 22, previewframesize.x, previewframesize.y); + } + + glPopMatrix(); + + //================================== SVG + mask + + float scale=previewframesize.x/outputWindowSize.x; + + //================================== SVG + + glPushMatrix(); + + + glTranslatef(230,270,0); + + ofDrawRectangle(20,20,previewframesize.x+4,previewframesize.y+4); + + glTranslatef(22,22,0); + + glScalef(scale,scale,scale); + + for (auto shape=segmenters.begin();shape!=segmenters.end();shape++){ + shape->getPoly().draw(); + } + + glPopMatrix(); + + //================================== Mask + + glPushMatrix(); + + glTranslatef(230,540,0); + + ofDrawRectangle(20,20,previewframesize.x+4,previewframesize.y+4); + + ofFill(); + + glTranslatef(22,22,0); + + glScalef(scale,scale,scale); + + for (auto& shape:mask){ + shape.draw(); + } + + ofNoFill(); + + glPopMatrix(); + + } + + ofSetColor(255); + + ofDrawBitmapString(ofToString(onset_number)+":"+(onset_frame==0?"BEAT":ofToString(onset_frame)),10,ofGetHeight()-15); + +} + +void ofApp::drawOutput(ofEventArgs & args){ + ofBackground(0); + //composite output window + + ofSetColor(255,255,255); + + vector <colourPolyline> polyOutput; + + float interval=ofGetElapsedTimef()-prev_time; + rotate_amt+=interval*xf_rotate*5; + + phase=fmod(phase+(interval*segmenter_speed),1); + prev_time=ofGetElapsedTimef(); + + while (phase<0.0f) { + phase+=1.0f; + } + + scale_phase+=(interval*xf_scale_speed); + + scale_amt=(((sin(scale_phase)*0.5)+0.5)*(xf_scale_max-xf_scale_min))+xf_scale_min; + + switch (source){ + case TEST:{ + ofMatrix4x4 m = ofMatrix4x4::newIdentityMatrix(); + m.rotateRad(ofGetElapsedTimef(),0,0,1); + m.translate(ofGetWidth()/2,ofGetHeight()/2,0); + + glm::vec2 src[]={ + glm::vec2(0,0), + glm::vec2(ofGetWidth(),0), + glm::vec2(ofGetWidth(),ofGetHeight()), + glm::vec2(0,ofGetHeight()) + }; + + ofMatrix4x4 warp =lineTransformer::getPerspectiveTransformMatrix(src,warpframe); + + //drawPoly(polyLineTransform(makePolygon(4,200),m),200,200); + //drawPoly(polyLineTransform(makePolygon(5,200),m),-200,200); + //drawPoly(polyLineTransform(makePolygon(6,200),m),-200,-200); + + ofPolyline poly=lineTransformer::polyLineTransform(warp, + lineTransformer::polyLineTransform(m, + lineTransformer::makePolygon(6,200) + ) + ); + polyOutput.push_back(colourPolyline(poly,ofColor(laser_R,laser_G,laser_B))); + + } +/* + case NDI:{ + ofPoint scale=ofPoint(outputWindowSize.x/ndiImage.getWidth(),outputWindowSize.x/ndiImage.getHeight()); + + //does not work no matter what the fuck you do + + //grayImage.setFromPixels(pixels.getData(),ndiImage.getWidth(),ndiImage.getHeight()); + + //grayImage.draw(0,0,outputWindowSize.x,outputWindowSize.y); + + grayImage = colorImg; + + grayImage.threshold(contour_threshold); + + //virtual int findContours( ofxCvGrayscaleImage& input, + // int minArea, int maxArea, + // int nConsidered, bool bFindHoles, + // bool bUseApproximation = true); + contourFinder.findContours(grayImage, 20, (340*240)/3, 10, true); + + for (int i = 0; i < contourFinder.nBlobs; i++){ + colourPolyline shape; + for (auto& point:contourFinder.blobs[i].pts){ + ofVec3f p=point*scale; + ofColor c=colorImg.getPixels().getColor(point.x,point.y); + shape.addVertex(p,contour_useColour?c:ofColor(laser_R,laser_G,laser_B)); + } + shape.simplify(contour_simplify); + polyOutput.push_back(shape); + } + break; + + } +*/ + case Player:{ + if (!use_onset||onset_frame<onset_duration){ + if (movie.isLoaded()){ + ofPoint scale=ofPoint(outputWindowSize.x/movie.getWidth(),outputWindowSize.y/movie.getHeight()); + colorImg.setFromPixels(movie.getPixels()); + if (grayImage.getWidth()!=colorImg.getWidth()||grayImage.getHeight()!=colorImg.getHeight()){ + grayImage.clear(); + } + grayImage=colorImg; + grayImage.threshold(contour_threshold); + contourFinder.findContours(grayImage, 20, (340*240)/3, 10, true); + for (int i = 0; i < contourFinder.nBlobs; i++){ + colourPolyline shape; + for (auto& point:contourFinder.blobs[i].pts){ + ofVec3f p=point*scale; + ofColor c=colorImg.getPixels().getColor(point.x,point.y); + shape.addVertex(p,contour_useColour?c:ofColor(laser_R,laser_G,laser_B)); + } + shape.simplify(contour_simplify); + polyOutput.push_back(shape); + } + } + } + break; + } + case SVG_outlines:{ + if (shapes_randomise){ + if (framecounter==0){ + select_random_shapes(); + framecounter=shapes_duration; + } + for (auto s:shape_selection){ + if (use_segmenter){ + auto segments=segmenters[s].getSegments(segmenter_number,segmenter_length,phase); + for (auto segment=segments.begin();segment!=segments.end();segment++){ + polyOutput.push_back(colourPolyline(*segment,ofColor(laser_R,laser_G,laser_B))); + } + } + else { + polyOutput.push_back(colourPolyline(segmenters[s].getPoly(),ofColor(laser_R,laser_G,laser_B))); + } + } + framecounter--; + } + else { + for (auto shape=segmenters.begin();shape!=segmenters.end();shape++){ + if (use_segmenter){ + auto segments=shape->getSegments(segmenter_number,segmenter_length,phase); + for (auto segment=segments.begin();segment!=segments.end();segment++){ + polyOutput.push_back(colourPolyline(*segment,ofColor(laser_R,laser_G,laser_B))); + } + } + else { + polyOutput.push_back(colourPolyline(shape->getPoly(),ofColor(laser_R,laser_G,laser_B))); + + } + } + } + break; + } + case Audio:{ + ofMatrix4x4 x=ofMatrix4x4(1.0f,0.0f,0.0f,0.0f, + 0.0f,1.0f,0.0f,0.0f, + 0.0f,0.0f,1.0f,0.0f, + 0.0f,0.0f,0.0f,1.0f); + polyOutput=plotter.output(scalePlot,decayPlot); + break; + } + default: { + break; + } + } + + vector <colourPolyline> transformedOutput; + + if (use_rotate||use_scale){ + ofMatrix4x4 rm = ofMatrix4x4::newIdentityMatrix(); + rm.translate(-outputWindowSize.x/2,-outputWindowSize.y/2,0); + if (use_rotate){ + rm.rotateRad(rotate_amt,0,0,1); + } + if (use_scale){ + rm.scale(scale_amt,scale_amt,scale_amt); + } + rm.translate(outputWindowSize.x/2,outputWindowSize.y/2,0); + for (auto& shape:polyOutput){ + transformedOutput.push_back(lineTransformer::polyLineTransform(rm,shape)); + } + } + else { + transformedOutput=polyOutput; + } + + vector <colourPolyline> clippedOutput; + + if (mask.size()&&use_mask){ + clipper.Clear(); + clipper.addPolylines(mask, ClipperLib::ptClip); + vector <ofPolyline> shapes; //TODO make clipper clip colourpolylines + for (auto& poly: transformedOutput) + { + shapes.push_back(poly); + } + clipper.addPolylines(shapes,ClipperLib::ptSubject); + vector <ofPolyline> clipped; + if (invert_mask){ + clipped = clipper.getClippedLines(ClipperLib::ctDifference); + }else { + clipped = clipper.getClippedLines(ClipperLib::ctIntersection); + } + for (auto& clip: clipped) + { + clip.simplify(contour_simplify); + clippedOutput.push_back(colourPolyline(clip,ofColor(laser_R,laser_G,laser_B))); + } + } + else { + clippedOutput=transformedOutput; + } + + glm::vec2 src[]={ + glm::vec2(0,0), + glm::vec2(ofGetWidth(),0), + glm::vec2(ofGetWidth(),ofGetHeight()), + glm::vec2(0,ofGetHeight()) + }; + + glm::vec2 mp=glm::vec2(outputWindowSize.x/2,outputWindowSize.y/2); + + glm::vec2 scaled_dest[]={ + ((warpframe[0]-mp)*outputScale*outputOffsetScale)+mp, + ((warpframe[1]-mp)*outputScale*outputOffsetScale)+mp, + ((warpframe[2]-mp)*outputScale*outputOffsetScale)+mp, + ((warpframe[3]-mp)*outputScale*outputOffsetScale)+mp + }; + + ofMatrix4x4 scaled_warp =lineTransformer::getPerspectiveTransformMatrix(src,scaled_dest); + ofMatrix4x4 warp =lineTransformer::getPerspectiveTransformMatrix(src,warpframe); + + vector <colourPolyline> warpedOutput; + vector <colourPolyline> scaledWarpedOutput; + + for (auto s:clippedOutput){ + warpedOutput.push_back(lineTransformer::polyLineTransform(warp,s)); + scaledWarpedOutput.push_back(lineTransformer::polyLineTransform(scaled_warp,s)); + } + + int num = 0; + int pnum=0; + + + if (laser_power&&polyOutput.size()) { + num=laser.draw(scaledWarpedOutput); + } + else { + colourPolyline blank; + for (int i=0;i<100;i++){ + blank.addVertex(ofGetWidth()/2,ofGetHeight()/2,0,0,0); + } + laser.draw(blank); + } + for (auto& shape:warpedOutput){ + shape.draw(); + pnum+=shape.size(); + } + + + if (bDrawFrame){ + lineTransformer::drawWarpFrame(warpframe); + } + + if (num>0){ + ofSetWindowTitle(sourcenames[source]+": "+ofToString(ofGetFrameRate(), 2)+" fps laser points: "+ofToString(num)); + } + else { + + ofSetWindowTitle(sourcenames[source]+": "+ofToString(ofGetFrameRate(), 2)+" fps laser error points: "+ofToString(pnum)); + } + +} + +//-------------------------------------------------------------- +void ofApp::exit() { + audiogui.clear(); + +} + + + +//-------------------------------------------------------------- +void ofApp::outputKeyPressed(ofKeyEventArgs &args){ + + keyPressed(args); + + +} + +void ofApp::keyPressed(ofKeyEventArgs &args){ + if (args.key==OF_KEY_COMMAND){ + commandPressed=true; + } + + switch(args.key){ + case '`':{ + bShowPositionInterface=!bShowPositionInterface; + break; + } + case 'q':{ + source--; + if (source<0){ + source=Source_end-1; + } + break; + } + case 'p':{ + source=(source+1)%Source_end; + break; + } + case 'w':{ + bDrawFrame=!bDrawFrame; + break; + } + case 'd':{ + default_settings(); + break; + } + case 's':{ + save_settings(); + break; + } + } +} + +//-------------------------------------------------------------- +void ofApp::outputKeyReleased(ofKeyEventArgs &args){ + + outputKeyReleased(args); + + +} + +void ofApp::keyReleased(ofKeyEventArgs &args){ + if (args.key==OF_KEY_COMMAND){ + commandPressed=false; + } +} + + +//-------------------------------------------------------------- +void ofApp::mouseMoved(int x, int y ){ + +} + +//-------------------------------------------------------------- +void ofApp::outputMouseDragged(ofMouseEventArgs & args){ + if (select_warpframe>-1){ + warpframe[select_warpframe]=glm::vec2(args.x,args.y); + } +} + +void ofApp::mouseDragged(int x, int y, int button){ + if (bOutputSelected){ + if (commandPressed){ + float startDistance=((outputPosition*guiScale)+ofPoint(300,300)).distance(outputSelectionPoint); + float currentDistance=((outputPosition*guiScale)+ofPoint(300,300)).distance(ofPoint(x,y)); + outputOffsetScale=currentDistance/startDistance; + } + else { + outputOffset=ofPoint(x,y)-outputSelectionPoint; + laser.set_centre(ofPoint( + outputPosition.x+(outputOffset.x/guiScale), + outputPosition.y+(outputOffset.y/guiScale) + )); + } + } +} + +//-------------------------------------------------------------- +void ofApp::outputMousePressed(ofMouseEventArgs & args){ + for (int i=0;i<4;i++){ + if (ofPoint(args.x,args.y).distance(warpframe[i])<25){ + select_warpframe=i; + } + } +} + +void ofApp::mousePressed(int x, int y, int button){ + if (bShowPositionInterface){ + if (x>(300+((outputPosition.x-((outputWindowSize.x/2)*outputScale))*guiScale))&& + x<(300+((outputPosition.x+((outputWindowSize.x/2)*outputScale))*guiScale))&& + y>(300+((outputPosition.y-((outputWindowSize.y/2)*outputScale))*guiScale))&& + y<(300+((outputPosition.y+((outputWindowSize.y/2)*outputScale))*guiScale)) + ){ + outputSelectionPoint=ofPoint(x,y); + bOutputSelected=true; + } + } + +} + +//-------------------------------------------------------------- +void ofApp::outputMouseReleased(ofMouseEventArgs & args){ + select_warpframe=-1; +} + +void ofApp::mouseReleased(int x, int y, int button){ + if (bOutputSelected){ + if (commandPressed){ + outputScale*=outputOffsetScale; + } + else { + outputPosition+=outputOffset/guiScale; + } + bOutputSelected=false; + outputOffset=ofPoint(0,0); + outputOffsetScale=1.0f; + } + +} + +//-------------------------------------------------------------- +void ofApp::mouseEntered(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseExited(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::outputWindowResized(ofResizeEventArgs &resizeargs){ + +} + +void ofApp::windowResized(int w, int h){ + +} + +void ofApp::select_random_shapes(){ + shape_selection.clear(); + while (shape_selection.size()<(segmenters.size()*shapes_amount)){ + int selection=rand()%segmenters.size(); + if (shape_selection.find(selection)==shape_selection.end()){ + shape_selection.insert(selection); + } + } + std::stringstream strm; + for (auto& s:shape_selection){ + strm << s <<" "; + } + //cout << "randomly selected (" << strm.str() <<") \n"; +} + +//-------------------------------------------------------------- +void ofApp::dragEvent(ofDragInfo dragInfo){ + + if (dragInfo.position.x>250&&dragInfo.position.x<570&&dragInfo.position.y>20&&dragInfo.position.y<266){ + std::string filename= *dragInfo.files.begin(); + if (movie.load(filename)){ + cout << "loaded "<< filename<<std::endl; + movie.setLoopState(OF_LOOP_NORMAL); + movie.setVolume(0.0f); + movie.play(); + } + else { + cout << "failed to load"<<std::endl; + } + } + + if (dragInfo.position.x>250&&dragInfo.position.x<570&&dragInfo.position.y>320&&dragInfo.position.y<566){ + std::string filename= *dragInfo.files.begin(); + svg.load(filename); + vector <ofPath> imagepaths= svg.getPaths(); + + std::stringstream strm; + + if (imagepaths.size()){ + segmenters.clear(); + for (auto& path:imagepaths){ + path.setPolyWindingMode(OF_POLY_WINDING_ODD); + + vector <ofPolyline> outlines= path.getOutline(); + for (auto& outline:outlines){ + strm << outline.size() << "->"; + outline.simplify(contour_simplify); + strm << outline.size() << " "; + segmenters.push_back(lineSegmenter(outline)); + } + strm << " , "; + } + + cout << "SVG: found " << imagepaths.size() << " paths with " << segmenters.size() << " shapes [ " << strm.str() << " ]" <<std::endl; + select_random_shapes(); + } + + } + + if (dragInfo.position.x>250&&dragInfo.position.x<570&&dragInfo.position.y>620&&dragInfo.position.y<866){ + std::string filename= *dragInfo.files.begin(); + svg.load(filename); + vector <ofPath> imagepaths= svg.getPaths(); + + std::stringstream strm; + + if (imagepaths.size()){ + mask.clear(); + for (auto& path:imagepaths){ + path.setPolyWindingMode(OF_POLY_WINDING_ODD); + + vector <ofPolyline> outlines= path.getOutline(); + for (auto& outline:outlines){ + strm << outline.size() << "->"; + outline.simplify(contour_simplify); + strm << outline.size() << " "; + mask.push_back(outline); + } + strm << " , "; + } + + cout << "Mask: found " << imagepaths.size() << " paths with " << mask.size() << " shapes [ " << strm.str() << " ]" <<std::endl; + + } + + } + +} + +void ofApp::newMidiMessage(ofxMidiMessage& msg) { + //column 0 for general controls + //printf("Midi: %i %i %i\n",msg.channel,msg.control,msg.value); + + int offset; + + //=============================================== + offset=0; + + if (msg.channel==1&&msg.control==1+offset){ + //pot + xf_rotate=(((float)msg.value)/64.0f)-1.0f; + } + if (msg.channel==1&&msg.control==33+offset){ + //pot button + use_rotate=use_rotate?false:true; + } + if (msg.channel==1&&msg.control==65+offset){ + //top button + rotate_amt=ofRandom(5.0f); + } + if (msg.channel==1&&msg.control==73+offset){ + //bottom button + } + if (msg.channel==1&&msg.control==81+offset){ + //fader + laser_intensity=msg.value*2; + } + + //=============================================== + offset=1; + + if (msg.channel==1&&msg.control==1+offset){ + //pot + xf_scale_speed=(((float)msg.value)/128.0f)*10.0f; + } + if (msg.channel==1&&msg.control==33+offset){ + //pot button + use_scale=use_scale?false:true; + } + if (msg.channel==1&&msg.control==65+offset){ + //top button + scale_phase=ofRandom(5.0f); + } + if (msg.channel==1&&msg.control==73+offset){ + //bottom button + } + if (msg.channel==1&&msg.control==81+offset){ + //fader + laser_R=msg.value*2; + } + + //=============================================== + offset=2; + + if (msg.channel==1&&msg.control==1+offset){ + //pot + xf_scale_min=(((float)msg.value)/128.0f); + } + if (msg.channel==1&&msg.control==33+offset){ + //pot button + } + if (msg.channel==1&&msg.control==65+offset){ + //top button + } + if (msg.channel==1&&msg.control==73+offset){ + //bottom button + } + if (msg.channel==1&&msg.control==81+offset){ + //fader + laser_G=msg.value*2; + } + + //=============================================== + offset=3; + + if (msg.channel==1&&msg.control==1+offset){ + //pot + xf_scale_max=(((float)msg.value)/128.0f); + } + if (msg.channel==1&&msg.control==33+offset){ + //pot button + } + if (msg.channel==1&&msg.control==65+offset){ + //top button + } + if (msg.channel==1&&msg.control==73+offset){ + //bottom button + } + if (msg.channel==1&&msg.control==81+offset){ + //fader + laser_B=msg.value*2; + } + + //=============================================== + offset=4; + + if (msg.channel==1&&msg.control==1+offset){ + //pot + } + if (msg.channel==1&&msg.control==33+offset){ + //pot button + } + if (msg.channel==1&&msg.control==65+offset){ + //top button + } + if (msg.channel==1&&msg.control==73+offset){ + //bottom button + } + if (msg.channel==1&&msg.control==81+offset){ + //fader + video_speed=((float)msg.value)/40.0f; + } + + //=============================================== + offset=5; + + if (msg.channel==1&&msg.control==1+offset){ + //pot + } + if (msg.channel==1&&msg.control==33+offset){ + //pot button + } + if (msg.channel==1&&msg.control==65+offset){ + //top button + } + if (msg.channel==1&&msg.control==73+offset){ + //bottom button + } + if (msg.channel==1&&msg.control==81+offset){ + //fader + segmenter_speed=(((float)msg.value)/64.0f)-1.0f; + } +} + diff --git a/passadesgui/src/ofApp.h b/passadesgui/src/ofApp.h new file mode 100644 index 0000000..24ab46e --- /dev/null +++ b/passadesgui/src/ofApp.h @@ -0,0 +1,243 @@ + #pragma once + +#include "ofMain.h" + +//#include "ofxNDI.h" +#include "ofxGui.h" +#include "ofxXmlSettings.h" +#include "ofxOpenCv.h" +#include "ofxSVG.h" +#include "ofxClipper.h" +#include "ofxMidi.h" + +#include "ofxHelios.h" +#include "lineTransformer.h" +#include "lineSegmenter.h" + +#include "ofxGist.h" +//This is included only as a way of getting buffer out of loaded sound. +//There are many other ways you can do that. +//This player includes a version of kissFFT. You can remove the one included in Gist. +//https://github.com/borg/ofxOpenALSoundPlayer +#include "ofxOpenALSoundPlayer.h" +//Slightly modified to add a dynamic getVariable method to be able to plot based on +//gist feature list +//https://github.com/local-projects/ofxHistoryPlot +#include "ofxHistoryPlot.h" +#include "Audioplotter.h" + +enum Source{ + TEST, +// NDI, + Player, + SVG_outlines, + SVG_segmenters, + Audio, + Source_end +}; + + +class ofApp: public ofBaseApp, public ofxMidiListener { + + public: + void setup(); + void update(); + void draw(); + void exit(); + + void keyPressed(ofKeyEventArgs &keyargs); + void keyReleased(ofKeyEventArgs & args); + 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 mouseEntered(int x, int y); + void mouseExited(int x, int y); + void windowResized(int w, int h); + void dragEvent(ofDragInfo dragInfo); + + void newMidiMessage(ofxMidiMessage& eventArgs); + + void updateOutput(ofEventArgs & args); + void drawOutput(ofEventArgs & args); + void outputKeyPressed(ofKeyEventArgs & args); + void outputKeyReleased(ofKeyEventArgs & args); + void outputMouseDragged(ofMouseEventArgs & args); + void outputMousePressed(ofMouseEventArgs & args); + void outputMouseReleased(ofMouseEventArgs & args); + void outputWindowResized(ofResizeEventArgs &resizeargs); + + void select_random_shapes(); + + void default_settings(); + void save_settings(); + + bool commandPressed; + + glm::vec2 warpframe[4]; + int select_warpframe; + bool bDrawFrame; + + + ofxHelios laser; + + int source; + + //======================================== //audio + + ofxGist gist; + void onNoteOn(GistEvent &e); + void onNoteOff(GistEvent &e); + + ofSoundStream soundStream; + ofxOpenALSoundPlayer player; + + void processAudio(float * input, int bufferSize, int nChannels); + void audioIn(float * input, int bufferSize, int nChannels); + + void loadSong(string str); + + int bufferSize; + int sampleRate; + + vector<float>fftSmoothed; + vector <float> left; + vector <float> right; + vector <float> centre; + + Audioplotter plotter; + + //======== audio gui + + ofxPanel audiogui; + ofParameter<bool> useMic; + ofParameter<bool> useFft; + ofParameter<float> scalePlot; + ofParameter<float> decayPlot; + + void useMicChanged(); + + //======= audio response gui + + ofxPanel responsegui; + ofParameter<float> onset_threshold; + ofParameter<bool> use_onset; + ofParameter<int> onset_duration; + + int onset_frame; + int onset_number; + + //======== chaos gui + + ofxPanel chaosgui; + + + //======================================== //thresholding + + ofxCvColorImage colorImg; + ofxCvGrayscaleImage grayImage; + ofxCvContourFinder contourFinder; + + //======================================== //ofxNDI +/* + ofxNDIreceiver ndiReceiver; + ofImage ndiImage; + char senderName[256]; + int nSenders; + unsigned int senderWidth; + unsigned int senderHeight; + bool bNDIreceiver; + + // For received frame fps calculations + double startTime, lastTime, frameTime, frameRate, fps; +*/ + //====== drawing gui + + ofxPanel drawgui; + + ofxIntSlider contour_threshold; + ofxFloatSlider contour_simplify; + ofxToggle contour_useColour; + ofxIntSlider laser_R; + ofxIntSlider laser_G; + ofxIntSlider laser_B; + + //====== video + + ofxFloatSlider video_speed; + + //svg gui + ofxToggle shapes_randomise; + ofxFloatSlider shapes_amount; + ofxIntSlider shapes_duration; + ofxToggle use_mask; + ofxToggle invert_mask; + + //segmenter + ofxToggle use_segmenter; + ofxFloatSlider segmenter_speed; + ofxFloatSlider segmenter_length; + ofxIntSlider segmenter_number; + + //transforms + ofxToggle use_rotate; + ofxFloatSlider xf_rotate; + ofxToggle use_scale; + ofxFloatSlider xf_scale_speed; + ofxFloatSlider xf_scale_min; + ofxFloatSlider xf_scale_max; + + + //======= laser gui + + ofxPanel lasergui; + ofParameter<bool> laser_power; + ofParameter<int> laser_intensity; + ofParameter<int> laser_points; + ofParameter<int> laser_subdivide; + ofParameter<int> laser_blank_num; + ofParameter<float> laser_max_angle; + + //======================================= //MIDI + + ofxMidiIn midiIn; + ofxMidiMessage midiMessage; + + //======================================= //video player + + ofVideoPlayer movie; + + //======================================= //SVG player + + ofxSVG svg; + vector <lineSegmenter> segmenters; + set <int> shape_selection; + int framecounter; + float phase,prev_time; //to calculate phase + float rotate_amt; + float scale_phase,scale_amt; + + //======================================= //Mask clipping + + ofPolyline safety_frame; + + vector <ofPolyline> mask; + ofx::Clipper clipper; + + //======================================= //positioning interface + + bool bShowPositionInterface; + bool bOutputSelected; + ofPoint outputSelectionPoint; + ofPoint outputPosition; + ofPoint outputOffset; + float outputScale; + float outputOffsetScale; + + //======================================= //saving settings + + ofxXmlSettings XML; + + + +}; diff --git a/passadesgui/start b/passadesgui/start new file mode 100755 index 0000000..a8863b9 --- /dev/null +++ b/passadesgui/start @@ -0,0 +1,3 @@ +mkdir bin/passadesgui.app/Contents/MacOS/@rpath +cp libs/libndi.3.dylib bin/passadesgui.app/Contents/MacOS/@rpath/ +cp libs/libHeliosDacAPI.dylib bin/passadesgui.app/Contents/MacOS/ |
