diff options
| -rw-r--r-- | rotord/src/cvimage.cpp | 30 | ||||
| -rw-r--r-- | rotord/src/cvimage.h | 6 | ||||
| -rw-r--r-- | rotord/src/graph.cpp | 106 | ||||
| -rw-r--r-- | rotord/src/nodes_audio_analysis.h | 17 | ||||
| -rw-r--r-- | rotord/src/nodes_drawing.h | 109 | ||||
| -rw-r--r-- | rotord/src/nodes_filters.h | 30 | ||||
| -rw-r--r-- | rotord/src/nodes_maths.h | 220 | ||||
| -rw-r--r-- | rotord/src/rendercontext.cpp | 15 | ||||
| -rwxr-xr-x | rotord/src/rotor.cpp | 121 | ||||
| -rwxr-xr-x | rotord/src/rotor.h | 1314 | ||||
| -rwxr-xr-x | rotord/src/rotord.h | 3 | ||||
| -rwxr-xr-x | rotord/src/tinyxml.cpp | 4 |
12 files changed, 871 insertions, 1104 deletions
diff --git a/rotord/src/cvimage.cpp b/rotord/src/cvimage.cpp index a03d81c..361999b 100644 --- a/rotord/src/cvimage.cpp +++ b/rotord/src/cvimage.cpp @@ -126,6 +126,15 @@ namespace Rotor { } return *this; } + Image & Image::operator=(const Image &other) { + //can be optimised? was trying to use other.data.clone() + setup(other.w,other.h); + //for (int i=0;i<h*w*3;i++) { + // rgb.data[i]=other.rgb.data[i]; + //} + memcpy(rgb.data,other.rgb.data,h*w*3); //saves ~2.4 ms copying a 640x360 image + return *this; + } //channel rearrangement //RGBAZ - these are channels 0-4 //HSB - can also have these virtual channels 5-7 @@ -135,7 +144,25 @@ namespace Rotor { //maybe this could not be the case if the data is owned by this image? //need to look into auto_ptr Image & Image::operator*=(const float &amount) { - rgb*=amount; + //cerr<<"amount: "<<amount<<endl; + //rgb*=amount; + uint8_t amt=(uint8_t)(amount*255.0f); + for (int i=0;i<h*w*3;i++) { + rgb.data[i]=pixels.multiply[rgb.data[i]][amt]; + } + //again this is faster + return *this; + } + Image & Image::operator+=(const float &amount) { + rgb+=(amount*255.0f); + return *this; + } + Image & Image::operator-=(const float &amount) { + rgb-=(amount*255.0f); + return *this; + } + Image & Image::operator/=(const float &amount) { + rgb/=amount; return *this; } Image * Image::operator*(const float &amount) { @@ -160,4 +187,5 @@ namespace Rotor { other->rgb=rgb/amount; return other; } + } diff --git a/rotord/src/cvimage.h b/rotord/src/cvimage.h index 336c41b..feb7298 100644 --- a/rotord/src/cvimage.h +++ b/rotord/src/cvimage.h @@ -32,7 +32,7 @@ namespace Rotor { multiply[i]=new uint8_t[256]; for (int j=0;j<256;j++){ add[i][j]=(uint8_t)std::min(i+j,0xFF); - multiply[i][j]=(uint8_t)((((float)i)/255.0f)*(((float)j)/255.0f)*255.0f); + multiply[i][j]=(uint8_t)(((float)i)*(((float)j)/255.0f)); } } mono_weights=new uint8_t*[3]; @@ -166,6 +166,7 @@ namespace Rotor { */ return t; } + Image & operator=(const Image &other); //believe these still work, don't know if these optimisations are better than opencvs.. Image & operator+=(const Image &other); Image & operator*=(const Image &other); @@ -176,6 +177,9 @@ namespace Rotor { Image & add_wrap(const Image &other); Image & divide_wrap(const Image &other); Image & operator*=(const float &amount); + Image & operator+=(const float &amount); + Image & operator-=(const float &amount); + Image & operator/=(const float &amount); Image * operator*(const float &amount); Image * operator+(const float &amount); Image * operator-(const float &amount); diff --git a/rotord/src/graph.cpp b/rotord/src/graph.cpp index 60af1c8..3aa2925 100644 --- a/rotord/src/graph.cpp +++ b/rotord/src/graph.cpp @@ -22,6 +22,7 @@ Node* Graph::find_node(const string &type){ } return nullptr; //can be tested against }; +/* bool Graph::signal_render(string &signal_xml,const float framerate) { if (find_node("signal_output")) { Signal_output *signal_output=dynamic_cast<Signal_output*>(find_node("signal_output")); @@ -31,6 +32,7 @@ bool Graph::signal_render(string &signal_xml,const float framerate) { return false; } +*/ bool Graph::video_render(const string &output_filename,const string &audio_filename,const float framerate,float& progress) { vector<Node*> loaders=find_nodes("video_loader"); for (auto i:loaders){ @@ -47,6 +49,7 @@ bool Graph::video_render(const string &output_filename,const string &audio_filen cerr<<"Rotor: video output node not found"<<endl; return false; } + bool Graph::set_resolution(int w,int h){ if (w>64&&h>48){ outW=w; @@ -83,61 +86,100 @@ bool Graph::parseXml(string media_path){ settings[attr]=xml.getAttribute("node",attr,"",i1); //cerr << "Got attribute: " << attr << ":" << xml.getAttribute("node",attr,"",i1) << endl; } - settings["description"]=xml.getValue("node","",i1); settings["media_path"]=media_path; Node* node=factory.create(settings); if (node) { string nodeID=xml.getAttribute("node","ID","",i1); if (nodes.find(nodeID)==nodes.end()){ + string nodetype=xml.getAttribute("node","type","",i1); cerr << "Rotor: creating node '"<<nodeID<<"': '"<< xml.getAttribute("node","type","",i1) << "'" << endl; nodes[nodeID]=node; if(xml.pushTag("node",i1)) { int n2=xml.getNumTags("signal_input"); for (int i2=0;i2<n2;i2++){ - nodes[nodeID]->create_signal_input(xml.getValue("signal_input","",i2)); - string fromID=xml.getAttribute("signal_input","from","",i2); - if (nodes.find(fromID)!=nodes.end()) { - if (!nodes[nodeID]->inputs[i2]->connect((Signal_node*)nodes[fromID])){ - cerr << "Rotor: graph loader cannot connect input " << i2 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; - return false; + //TODO expand + //nodes[nodeID]->create_signal_input(xml.getAttribute("signal_input","description","",i2),xml.getAttribute("signal_input","title","",i2)); + if ((nodes[nodeID])->inputs.size()>i2) { + string fromID=xml.getAttribute("signal_input","from","",i2); + if (nodes.find(fromID)!=nodes.end()) { + if (!nodes[nodeID]->inputs[i2]->connect((Signal_node*)nodes[fromID])){ + cerr << "Rotor: graph loader cannot connect input " << i2 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; + return false; + } + else cerr << "Rotor: linked input " << i2 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; } - else cerr << "Rotor: linked input " << i2 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; + else cerr << "Rotor: linking input " << i2 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl; } - else cerr << "Rotor: linking input " << i2 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl; + else cerr << "Rotor: input " << i2 << " of node: '" << nodeID << "' does not exist" << endl; + } int n3=xml.getNumTags("image_input"); for (int i3=0;i3<n3;i3++){ - ((Image_node*)nodes[nodeID])->create_image_input(xml.getValue("image_input","",i3)); - string fromID=xml.getAttribute("image_input","from","",i3); - if (nodes.find(fromID)!=nodes.end()) { - if (!(((Image_node*)nodes[nodeID])->image_inputs[i3]->connect((Image_node*)nodes[fromID]))){ - cerr << "Rotor: graph loader cannot connect image input " << i3 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; - return false; + //handle expandable inputs + if (((Image_node*)nodes[nodeID])->image_inputs.size()<=i3&((Image_node*)nodes[nodeID])->duplicate_inputs){ + string desc=((Image_node*)nodes[nodeID])->image_inputs[0]->description; + string title=((Image_node*)nodes[nodeID])->image_inputs[0]->title; + while(((Image_node*)nodes[nodeID])->image_inputs.size()<=i3){ + ((Image_node*)nodes[nodeID])->create_image_input(desc,title); + } + } + //((Image_node*)nodes[nodeID])->create_image_input(xml.getValue("image_input","",i3)); + if (((Image_node*)nodes[nodeID])->image_inputs.size()>i3) { + string fromID=xml.getAttribute("image_input","from","",i3); + if (nodes.find(fromID)!=nodes.end()) { + if (!(((Image_node*)nodes[nodeID])->image_inputs[i3]->connect((Image_node*)nodes[fromID]))){ + cerr << "Rotor: graph loader cannot connect image input " << i3 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; + return false; + } + else cerr << "Rotor: linked image input " << i3 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; } - else cerr << "Rotor: linked image input " << i3 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; + else cerr << "Rotor: linking image input " << i3 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl; } - else cerr << "Rotor: linking image input " << i3 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl; + else cerr << "Rotor: image input number " << i3 << " of node: '" << nodeID << "' does not exist" << endl; } - int n4=xml.getNumTags("parameter_input"); + int n4=xml.getNumTags("parameter"); for (int i4=0;i4<n4;i4++){ - nodes[nodeID]->create_parameter_input(xml.getAttribute("parameter_input","parameter","",i4),xml.getValue("parameter_input","",i4)); - string fromID=xml.getAttribute("parameter_input","from","",i4); - if (nodes.find(fromID)!=nodes.end()) { - if (!nodes[nodeID]->parameter_inputs[i4]->connect(nodes[fromID])){ - cerr << "Rotor: graph loader cannot connect parameter input " << i4 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; - return false; + string parameter=xml.getAttribute("parameter","name","",i4); + if (nodes[nodeID]->parameters.find(parameter)!=nodes[nodeID]->parameters.end()) { + string val=xml.getAttribute("parameter","value","",i4); + if (val!="") nodes[nodeID]->parameters.find(parameter)->second->value=ofToFloat(val); + string fromID=xml.getAttribute("parameter","from","",i4); + if (nodes.find(fromID)!=nodes.end()) { + if (!nodes[nodeID]->parameters[parameter]->connect(nodes[fromID])){ + cerr << "Rotor: graph loader cannot connect parameter input " << i4 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; + return false; + } + else cerr << "Rotor: linked parameter input " << i4 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; } - else cerr << "Rotor: linked parameter input " << i4 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; + else cerr << "Rotor: linking parameter input " << i4 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl; } - else cerr << "Rotor: linking parameter input " << i4 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl; + else cerr << "Rotor: cannot find parameter input '" << parameter << "' of "<<nodetype<<" "<< nodeID << endl; } - nodes[nodeID]->link_params(); - //extra key/value pairs that can be specific to sub-settings - int n5=xml.getNumTags("parameter"); - for (int i5=0;i5<n5;i5++){ - nodes[nodeID]->set_parameter(xml.getAttribute("parameter","name","",i5),xml.getAttribute("parameter","value","",i5)); + //still support old parameter_input + n4=xml.getNumTags("parameter_input"); + for (int i4=0;i4<n4;i4++){ + string parameter=xml.getAttribute("parameter_input","name","",i4); + if (nodes[nodeID]->parameters.find(parameter)!=nodes[nodeID]->parameters.end()) { + string val=xml.getAttribute("parameter_input","value","",i4); + if (val!="") nodes[nodeID]->parameters.find(parameter)->second->value=ofToFloat(val); + string fromID=xml.getAttribute("parameter_input","from","",i4); + if (nodes.find(fromID)!=nodes.end()) { + if (!nodes[nodeID]->parameters[parameter]->connect(nodes[fromID])){ + cerr << "Rotor: graph loader cannot connect parameter input " << i4 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; + return false; + } + else cerr << "Rotor: linked parameter input " << i4 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; + } + else cerr << "Rotor: linking parameter input " << i4 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl; + } + else cerr << "Rotor: cannot find parameter input '" << parameter << "' of "<<nodetype<<" "<< nodeID << endl; } - if (n5>0) cerr << "Rotor: found " << n5 << " extra parameters for node '" << nodeID << "'" << endl; + //extra key/value pairs that can be specific to sub-settings - WHAT WAS THIS + //int n5=xml.getNumTags("parameter"); + //for (int i5=0;i5<n5;i5++){ + // nodes[nodeID]->set_parameter(xml.getAttribute("parameter","name","",i5),xml.getAttribute("parameter","value","",i5)); + //} + //if (n5>0) cerr << "Rotor: found " << n5 << " extra parameters for node '" << nodeID << "'" << endl; xml.popTag(); } diff --git a/rotord/src/nodes_audio_analysis.h b/rotord/src/nodes_audio_analysis.h index e6c1e65..9252db4 100644 --- a/rotord/src/nodes_audio_analysis.h +++ b/rotord/src/nodes_audio_analysis.h @@ -5,15 +5,26 @@ #include "vampHost.h" namespace Rotor { - class Audio_analysis: public Base_audio_processor { + class Audio_analysis: public Audio_processor { public: - Audio_analysis(){}; - Audio_analysis(map<string,string> &settings) { + Audio_analysis(){ + //create_attribute("soname","Plugin library to use","Plugin library","vamp-example-plugins",{"horiz","vert","horizR","vertR"}); + //create_attribute("id","ID of Plugin to use","Plugin ID","percussiononsets",{"horiz","vert","horizR","vertR"}); + create_attribute("analyser","Analyser Plugin to use","Analyser plugin","barbeattracker",{"barbeattracker","segmenter"}); + create_parameter("outputNo","number","Plugin output to use","Output number",0.0f); + title="Audio analysis"; + description="Analyse audio and output"; + }; + Audio_analysis(map<string,string> &settings):Audio_analysis() { base_settings(settings); + vector< pair< string, string>> sonames={ + {"qm-vamp-plugins","qm-barbeattracker"} + }; soname=find_setting(settings,"soname"); id=find_setting(settings,"id"); outputNo=find_setting(settings,"outputNo",0); }; + ~Audio_analysis(){}; Audio_analysis* clone(map<string,string> &_settings) { return new Audio_analysis(_settings);}; bool init(int _channels,int _bits,int _samples,int _rate); void cleanup(); diff --git a/rotord/src/nodes_drawing.h b/rotord/src/nodes_drawing.h index 81ef590..c44527d 100644 --- a/rotord/src/nodes_drawing.h +++ b/rotord/src/nodes_drawing.h @@ -5,60 +5,50 @@ #include <cairo.h> namespace Rotor { - class Draw_node: public Image_node { //base class for drawing with cairo + class Draw_node: public Image_node { public: - Draw_node(){image=nullptr;}; - Draw_node(map<string,string> &settings) { - image=nullptr; + Draw_node(){ + create_image_input("image input","Image input"); + //no title or description as it isn't intended for the user }; - ~Draw_node(){ if (image) delete image;}; + Draw_node(map<string,string> &settings):Draw_node() { + }; + ~Draw_node(){}; Draw_node* clone(map<string,string> &_settings) { return new Draw_node(_settings);}; virtual void vector_output(cairo_t * cr,const Frame_spec &frame){}; Image *output(const Frame_spec &frame){ - //get new or input image to draw upon - if (image_inputs.size()) { - if (image_inputs[0]->connection){ - //copy incoming image **writable - if (image) delete image; - image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone(); - } - else { - if (!image) image =new Image(); - image->setup(frame.w,frame.h); - } + Image *in=image_inputs[0]->get(frame); + if (in){ + image=(*in); } - else { - if (!image) image =new Image(); - image->setup(frame.w,frame.h); //do this twice or use a goto - } //convert to 32 bit - this can probably be optimised further cv::Mat chans; - cv::cvtColor(image->rgb, chans, CV_BGR2RGBA, 4); + cv::cvtColor(image.rgb, chans, CV_BGR2RGBA, 4); cairo_surface_t * cs = cairo_image_surface_create_for_data (chans.data, CAIRO_FORMAT_RGB24, - image->w, - image->h, - image->w*4); + image.w, + image.h, + image.w*4); cairo_t * cr = cairo_create (cs); //do any kind of vector drawing vector_output(cr,frame); //convert frame back to 24 bits - cv::cvtColor(chans,image->rgb,CV_RGBA2BGR,3); + cv::cvtColor(chans,image.rgb,CV_RGBA2BGR,3); cairo_destroy(cr); cairo_surface_destroy(cs); - return image; + return ℑ } private: - Image *image; //is an image generator }; class Hello_draw: public Draw_node { public: - Hello_draw(){image=nullptr;}; + Hello_draw(){ + //no title or description as it isn't intended for the user + }; Hello_draw(map<string,string> &settings) { - image=nullptr; base_settings(settings); }; - ~Hello_draw(){ if (image) delete image;}; + ~Hello_draw(){}; Hello_draw* clone(map<string,string> &_settings) { return new Hello_draw(_settings);}; void vector_output(cairo_t * cr,const Frame_spec &frame){ cairo_text_extents_t te; @@ -72,54 +62,36 @@ namespace Rotor { cairo_fill(cr); } private: - Image *image; //is an image generator }; #define SHAPE_circle 1 #define SHAPE_square 2 #define SHAPE_triangle 3 class Shape: public Draw_node { public: - Shape(){image=nullptr;}; - Shape(map<string,string> &settings) { - image=nullptr; - base_settings(settings); - scale=find_setting(settings,"scale",1.0f); - rotation=find_setting(settings,"rotation",0.0f); - x=find_setting(settings,"x",0.0f); - y=find_setting(settings,"y",0.0f); - colour=Colour(find_setting(settings,"colour","FFFFFF")); - string _shape=find_setting(settings,"shape","square"); - if (_shape=="circle") shape=SHAPE_circle; - if (_shape=="square") shape=SHAPE_square; - if (_shape=="triangle") shape=SHAPE_triangle; + Shape(){ + title="Shape"; + description="Draws filled shapes"; + create_parameter("x","number","X coordinate","X",0.0f); + create_parameter("y","number","Y coordinate","Y",0.0f); + create_parameter("scale","number","Scale","Scale",1.0f); + create_parameter("rotation","number","Rotation","Rotation",0.0f); + create_attribute("colour","Colour to fill","Colour","FFFFFF"); + create_attribute("shape","Shape to draw","Shape","square",{"circle","square","triangle"}); }; - void link_params() { - for (auto p:parameter_inputs){ - if (p->parameter=="scale") { - p->receiver=&scale; - } - if (p->parameter=="rotation") { - p->receiver=&rotation; - } - if (p->parameter=="x") { - p->receiver=&x; - } - if (p->parameter=="y") { - p->receiver=&y; - } - } - + Shape(map<string,string> &settings):Shape() { + base_settings(settings); + colour=Colour(attributes["colour"]->value); }; - ~Shape(){ if (image) delete image;}; + ~Shape(){}; Shape* clone(map<string,string> &_settings) { return new Shape(_settings);}; void vector_output(cairo_t * cr,const Frame_spec &frame){ cairo_set_source_rgb(cr, colour.Rfloat(),colour.Gfloat(),colour.Bfloat()); cairo_save(cr); //not really even necessary? cairo_translate(cr, frame.w/2, frame.h/2); - cairo_translate(cr, x * frame.w, y * frame.h); - cairo_scale(cr, scale , scale ); - cairo_rotate(cr,(rotation/180.0f)*M_PI); - switch(shape) { + cairo_translate(cr, parameters["x"]->value * frame.w, parameters["y"]->value * frame.h); + cairo_scale(cr, parameters["scale"]->value , parameters["scale"]->value ); + cairo_rotate(cr,(parameters["rotation"]->value/180.0f)*M_PI); + switch(attributes["shape"]->intVal) { case SHAPE_square: cairo_rectangle(cr,-frame.w/2,-frame.w/2,frame.w,frame.w); break; @@ -138,13 +110,8 @@ namespace Rotor { cairo_fill(cr); } private: - Image *image; //is an image generator - int shape; - float scale; - float rotation; - float x,y; Colour colour; }; } -#endif
\ No newline at end of file +#endif diff --git a/rotord/src/nodes_filters.h b/rotord/src/nodes_filters.h index c6de606..263b4b2 100644 --- a/rotord/src/nodes_filters.h +++ b/rotord/src/nodes_filters.h @@ -6,35 +6,29 @@ namespace Rotor { class Blur: public Image_node { public: - Blur(){}; + Blur(){ + title="Blur"; + description="Gaussian blur filter"; + create_parameter("size","number","Blur window size","Size",1.0f); + create_image_input("image input","Image input"); + }; Blur(map<string,string> &settings) { base_settings(settings); - size=find_setting(settings,"size",0.0f); }; ~Blur(){ }; - void link_params() { - for (auto p:parameter_inputs){ - p->receiver=nullptr; - if (p->parameter=="size") p->receiver=&size; - } - }; Blur* clone(map<string,string> &_settings) { return new Blur(_settings);}; Image *output(const Frame_spec &frame){ - if (image_inputs.size()) { - if (image_inputs[0]->connection){ - Image *other=(((Image_node*)image_inputs[0]->connection)->get_output(frame)); - image.setup(other->w,other->h); - int ksize=max((ceil(size/2.0)*2)+1,1.0); - //nb this doesn't do the intended: create 'continuously variable' blur - cv::GaussianBlur((*other).rgb,image.rgb,cvSize(ksize,ksize),size); - return ℑ - } + Image *in=image_inputs[0]->get(frame); + if (in) { + int ksize=max((ceil(parameters["size"]->value/2.0)*2)+1,1.0); + //nb this doesn't do the intended: create 'continuously variable' blur + cv::GaussianBlur((*in).rgb,image.rgb,cvSize(ksize,ksize),parameters["size"]->value); + return ℑ } return nullptr; } private: - Image image; float size; }; } diff --git a/rotord/src/nodes_maths.h b/rotord/src/nodes_maths.h index d4b8eac..4ec2cf9 100644 --- a/rotord/src/nodes_maths.h +++ b/rotord/src/nodes_maths.h @@ -4,6 +4,7 @@ #include "rotor.h" #include <libnoise/noise.h> #include <libnoise/mathconsts.h> +#include "Poco/Logger.h" namespace Rotor { #define COMPARISON_Equal 1 @@ -14,54 +15,40 @@ namespace Rotor { #define COMPARISON_Less_or_equal 6 class Comparison: public Signal_node { public: - Comparison(){}; - Comparison(map<string,string> &settings) { + Comparison(){ + create_signal_input("signal","Signal"); + create_parameter("value","number","Value or signal for operation","Value",0.0f); + create_attribute("operator","Operator for comparison","operator","==",{"==","!=",">","<",">=","<="}); + description="Compares the signal with a value or signal according to the operator"; + }; + Comparison(map<string,string> &settings):Comparison() { base_settings(settings); - value=find_setting(settings,"value",0.0f); - string _op=find_setting(settings,"operator","=="); - if (_op=="==") op=COMPARISON_Equal; - if (_op=="!=") op=COMPARISON_Not_equal; - if (_op==">") op=COMPARISON_Greater; - if (_op=="<") op=COMPARISON_Less; - if (_op==">=") op=COMPARISON_Greater_or_equal; - if (_op=="<=") op=COMPARISON_Less_or_equal; } - void link_params() { - for (auto p:parameter_inputs){ - if (p->parameter=="value") p->receiver=&value; - } - }; Comparison* clone(map<string,string> &_settings) { return new Comparison(_settings);}; - const float output(const Time_spec &time) { - if (inputs.size()) { //there should there be a way to specify number of inputs in the code rather than in xml - if (inputs[0]->connection) { - float in= (((Signal_node*)inputs[0]->connection)->get_output(time)); - switch (op) { - case COMPARISON_Equal: - return fequal(value,in)?1.0f:0.0f; - break; - case COMPARISON_Not_equal: - return fequal(value,in)?0.0f:1.0f; - break; - case COMPARISON_Greater: - return fgreater(value,in)?1.0f:0.0f; - break; - case COMPARISON_Less: - return fless(value,in)?1.0f:0.0f; - break; - case COMPARISON_Greater_or_equal: - return fgreater_or_equal(value,in)?1.0f:0.0f; - break; - case COMPARISON_Less_or_equal: - return fless_or_equal(value,in)?1.0f:0.0f; - break; - } - } - } + const float output(const Time_spec &time) { + switch (attributes["operator"]->intVal) { + case COMPARISON_Equal: + return fequal(parameters["value"]->value,inputs[0]->get(time))?1.0f:0.0f; + break; + case COMPARISON_Not_equal: + return fequal(parameters["value"]->value,inputs[0]->get(time))?0.0f:1.0f; + break; + case COMPARISON_Greater: + return fgreater(parameters["value"]->value,inputs[0]->get(time))?1.0f:0.0f; + break; + case COMPARISON_Less: + return fless(parameters["value"]->value,inputs[0]->get(time))?1.0f:0.0f; + break; + case COMPARISON_Greater_or_equal: + return fgreater_or_equal(parameters["value"]->value,inputs[0]->get(time))?1.0f:0.0f; + break; + case COMPARISON_Less_or_equal: + return fless_or_equal(parameters["value"]->value,inputs[0]->get(time))?1.0f:0.0f; + break; + } + return 0.0f; } - int op; - float value; }; #define ARITHMETIC_plus 1 #define ARITHMETIC_minus 2 @@ -75,70 +62,58 @@ namespace Rotor { #define ARITHMETIC_jolt 10 class Arithmetic: public Signal_node { public: - Arithmetic(){}; - Arithmetic(map<string,string> &settings) { + Arithmetic(){ + create_signal_input("signal","Signal"); + create_parameter("value","number","Value or signal for operation","Value",1.0f); + create_attribute("operator","operator for image","Operator","+",{"+","-","*","/","%","^","sin","cos","ease","jolt"}); + title="Arithmetic"; + description="Performs arithmetic on a signal with a signal or value"; + }; + Arithmetic(map<string,string> &settings):Arithmetic() { base_settings(settings); - value=find_setting(settings,"value",0.0f); - string _op=find_setting(settings,"operator","+"); - if (_op=="+"||_op=="plus"||_op=="add") op=ARITHMETIC_plus; - if (_op=="-"||_op=="minus"||_op=="subtract") op=ARITHMETIC_minus; - if (_op=="*"||_op=="x"||_op=="multiply") op=ARITHMETIC_multiply; - if (_op=="/"||_op=="divide") op=ARITHMETIC_divide; - if (_op=="%"||_op=="mod"||_op=="modulo"||_op=="modulus") op=ARITHMETIC_modulo; - if (_op=="^"||_op=="power") op=ARITHMETIC_pow; - if (_op=="sin"||_op=="sine") op=ARITHMETIC_sin; - if (_op=="cos"||_op=="cos") op=ARITHMETIC_cos; - if (_op=="ease") op=ARITHMETIC_ease; - if (_op=="jolt") op=ARITHMETIC_jolt; - } - void link_params() { - for (auto p:parameter_inputs){ - p->receiver=nullptr; - if (p->parameter=="value") p->receiver=&value; - } }; Arithmetic* clone(map<string,string> &_settings) { return new Arithmetic(_settings);}; const float output(const Time_spec &time) { - if (op==ARITHMETIC_divide||op==ARITHMETIC_modulo){ + if (attributes["operator"]->intVal==ARITHMETIC_divide||attributes["operator"]->intVal==ARITHMETIC_modulo){ if (value==0.0f) { - Logger& logger = Logger::get("Rotor"); + Poco::Logger& logger = Poco::Logger::get("Rotor"); logger.error("Arithmetic node: caught division by zero, frame "+time.frame()); return 0.0f; } } if (inputs.size()) { //there should there be a way to specify number of inputs in the code rather than in xml if (inputs[0]->connection) { - float in= (((Signal_node*)inputs[0]->connection)->get_output(time)); - switch (op) { + float in= inputs[0]->get(time); + switch (attributes["operator"]->intVal) { case ARITHMETIC_plus: - return in+value; + return in+parameters["value"]->value; break; case ARITHMETIC_minus: - return in-value; + return in-parameters["value"]->value; break; case ARITHMETIC_multiply: - return in*value; + return in*parameters["value"]->value; break; case ARITHMETIC_divide: - return in/value; + return in/parameters["value"]->value; break; case ARITHMETIC_modulo: - return fmod(in,value); + return fmod(in,parameters["value"]->value); break; case ARITHMETIC_pow: - return pow(in,value); + return pow(in,parameters["value"]->value); break; case ARITHMETIC_sin: - return sin(in)*value; + return sin(in)*parameters["value"]->value; break; case ARITHMETIC_cos: - return cos(in)*value; + return cos(in)*parameters["value"]->value; break; case ARITHMETIC_ease: - return ((1.0-value)*in)+(value*(0.5f+((cos((fmod(in,1.0f)+1.0f)*M_PI))*0.5f))); + return ((1.0-parameters["value"]->value)*in)+(parameters["value"]->value*(0.5f+((cos((fmod(in,1.0f)+1.0f)*M_PI))*0.5f))); break; case ARITHMETIC_jolt: - return ((1.0-value)*in)+(value*(0.5f+((sin((fmod(in,1.0f)+1.0f)*M_PI))*0.5f))); + return ((1.0-parameters["value"]->value)*in)+(parameters["value"]->value*(0.5f+((sin((fmod(in,1.0f)+1.0f)*M_PI))*0.5f))); break; } } @@ -148,49 +123,32 @@ namespace Rotor { int op; float value; }; - class Signal_divide: public Signal_node { - public: - Signal_divide(){}; - Signal_divide(map<string,string> &settings) { - base_settings(settings); - divide_amount=ofToFloat(find_setting(settings,"amount")); - for (auto p:parameter_inputs){ - if (p->parameter=="amount") p->receiver=÷_amount; - } - }; - Signal_divide* clone(map<string,string> &_settings) { return new Signal_divide(_settings);}; - const float output(const Time_spec &time) { - if (inputs.size()) { //there should there be a way to specify number of inputs in the code rather than in xml - if (inputs[0]->connection) { - return (((Signal_node*)inputs[0]->connection)->get_output(time))/divide_amount; - } - } - return 0.0f; - } - float divide_amount; - }; class Is_new_integer: public Signal_node { public: - Is_new_integer(){}; - Is_new_integer(map<string,string> &settings) { + Is_new_integer(){ + title="New integer"; + description="Outputs 1 on the frame that a signal becomes a new integer"; + create_signal_input("signal","Signal"); + }; + Is_new_integer(map<string,string> &settings):Is_new_integer() { base_settings(settings); }; Is_new_integer* clone(map<string,string> &_settings) { return new Is_new_integer(_settings);}; const float output(const Time_spec &time) { - if (inputs[0]->connection) { - float s1=(((Signal_node*)(inputs[0]->connection))->get_output(time)); - float s2=(((Signal_node*)(inputs[0]->connection))->get_output(time.lastframe())); - if (((int)s1)>((int)s2)) { - return 1.0f; - } + if (((int)inputs[0]->get(time))>((int)inputs[0]->get(time.lastframe()))) { + return 1.0f; } return 0.0f; } }; class On_off: public Signal_node { public: - On_off(){}; - On_off(map<string,string> &settings) { + On_off(){ + title="On off"; + description="Outputs 1 if the integer floor of the signal is even"; + create_signal_input("signal","Signal"); + }; + On_off(map<string,string> &settings):On_off() { base_settings(settings); }; On_off* clone(map<string,string> &_settings) { return new On_off(_settings);}; @@ -228,47 +186,45 @@ namespace Rotor { return fnv1a(*ptr , hash); } class Random: public Signal_node { - //randomises integer while keeping fraction public: - Random(){}; + Random(){ + title="Random"; + description="Randomises integer part of signal (seedable)"; + create_signal_input("signal","Signal"); + create_parameter("seed","number","Seed value","Seed",1.0f); + }; Random(map<string,string> &settings) { base_settings(settings); - seed=(Seed+find_setting(settings,"seed",0)); }; Random* clone(map<string,string> &_settings) { return new Random(_settings);}; const float output(const Time_spec &time) { - if (inputs.size()) { - if (inputs[0]->connection) { - - //hash the integer part and add the fractional part back on - float o=(((Signal_node*)inputs[0]->connection)->get_output(time)); - uint32_t m=(int)o; - return ((float)(fnv1a(m,seed)%((uint32_t)time.duration)))+(o-m); - } - } - return 0.0f; + uint32_t seed=Seed+parameters["seed"]->value; + //hash the integer part and add the fractional part back on + float o=inputs[0]->get(time); + uint32_t m=(int)o; + return ((float)(fnv1a(m,seed)%((uint32_t)time.duration)))+(o-m); } - uint32_t seed; - private: }; class Noise: public Signal_node { //fractal noise public: - Noise(){}; + Noise(){ + title="Noise"; + description="Fractal noise (seedable)"; + create_signal_input("signal","Signal"); + create_parameter("seed","number","Seed value","Seed",1.0f); + }; Noise(map<string,string> &settings) { base_settings(settings); seed=find_setting(settings,"seed",0); }; Noise* clone(map<string,string> &_settings) { return new Noise(_settings);}; const float output(const Time_spec &time) { - if (inputs.size()) { - if (inputs[0]->connection) { - float o=(((Signal_node*)inputs[0]->connection)->get_output(time)); - - return o; - } - } - return 0.0f; + uint32_t seed=Seed+parameters["seed"]->value; + //hash the integer part and add the fractional part back on + float o=inputs[0]->get(time); + uint32_t m=(int)o; + return ((float)(fnv1a(m,seed)%((uint32_t)time.duration)))+(o-m); } uint32_t seed; private: diff --git a/rotord/src/rendercontext.cpp b/rotord/src/rendercontext.cpp index 6ef0420..e582dce 100644 --- a/rotord/src/rendercontext.cpp +++ b/rotord/src/rendercontext.cpp @@ -1,7 +1,9 @@ -#include "rotor.h" - +#include "rendercontext.h" using namespace Rotor; +using Poco::Net::HTTPResponse; +using Poco::Logger; + void Render_context::runTask() { while (!isCancelled()) { int cmd=0; @@ -13,11 +15,11 @@ void Render_context::runTask() { mutex.unlock(); if(cmd==ANALYSE_AUDIO) { state=ANALYSING_AUDIO; - vector<Base_audio_processor*> processors; + vector<Audio_processor*> processors; processors.push_back(audio_thumb); vector<Node*> analysers=graph.find_nodes("audio_analysis"); for (auto a: analysers) { - processors.push_back(dynamic_cast<Base_audio_processor*>(a)); + processors.push_back(dynamic_cast<Audio_processor*>(a)); } if (load_audio(audio_filename,processors)) { audio_loaded=true; @@ -218,7 +220,7 @@ void Render_context::session_command(const std::vector<std::string>& command,xml // framerate=ofToFloat(command[3]); //} string signal_xml; - if (graph.signal_render(signal_xml,framerate)){ + if (false) { //graph.signal_render(signal_xml,framerate)){ status=HTTPResponse::HTTP_OK; logger.information("rendering signal to xml"); XML.addValue("signal",signal_xml); //this doesn't work >> pseudo xml @@ -288,7 +290,6 @@ void Render_context::session_command(const std::vector<std::string>& command,xml XML.addValue("progress",ofToString(progress)); } else { - status=HTTPResponse::HTTP_BAD_REQUEST; logger.error("ERROR: Render progress requested but not rendering"); XML.addValue("error","Not rendering"); } @@ -325,7 +326,7 @@ void Render_context::session_command(const std::vector<std::string>& command,xml } } -bool Render_context::load_audio(const string &filename,vector<Base_audio_processor*> processors){ +bool Render_context::load_audio(const string &filename,vector<Audio_processor*> processors){ Logger& logger = Logger::get("Rotor"); logger.information("Starting audio analysis"); diff --git a/rotord/src/rotor.cpp b/rotord/src/rotor.cpp index c7e7bc7..99fe396 100755 --- a/rotord/src/rotor.cpp +++ b/rotord/src/rotor.cpp @@ -1,87 +1,74 @@ #include "rotor.h" #include "nodes_audio_analysis.h" -#include "nodes_drawing.h" #include "nodes_maths.h" +#include "nodes_drawing.h" #include "nodes_filters.h" using namespace Rotor; +using Poco::Logger; Node_factory::Node_factory(){ //for now, statically load prototype map in constructor - add_type("audio_analysis",new Audio_analysis()); - add_type("divide",new Signal_divide()); - add_type("bang",new Is_new_integer()); + add_type("time",new Time()); + add_type("track_time",new Track_time()); add_type("signal_output",new Signal_output()); add_type("testcard",new Testcard()); - add_type("video_output",new Video_output()); - add_type("video_loader",new Video_loader()); - add_type("on_off",new On_off()); add_type("invert",new Invert()); add_type("video_cycler",new Video_cycler()); - add_type("luma_levels",new Luma_levels()); - add_type("echo_trails",new Echo_trails()); - add_type("time",new Time()); - add_type("track_time",new Track_time()); - add_type("comparison",new Comparison()); //TODO: alias to symbols - add_type("arithmetic",new Arithmetic()); //TODO: alias to symbols add_type("signal_colour",new Signal_colour()); add_type("signal_greyscale",new Signal_greyscale()); add_type("image_arithmetic",new Image_arithmetic()); - add_type("random",new Random()); + add_type("luma_levels",new Luma_levels()); + add_type("echo_trails",new Echo_trails()); add_type("blend",new Blend()); add_type("mirror",new Mirror()); add_type("monochrome",new Monochrome()); add_type("transform",new Transform()); add_type("alpha_merge",new Alpha_merge()); + //nodes_audio_analysis.h + add_type("audio_analysis",new Audio_analysis()); + //nodes_maths.h + add_type("comparison",new Comparison()); //TODO: alias to symbols + add_type("arithmetic",new Arithmetic()); //TODO: alias to symbols + add_type("bang",new Is_new_integer()); + add_type("on_off",new On_off()); + add_type("random",new Random()); + //nodes_drawing.h add_type("shape",new Shape()); + add_type("hello",new Hello_draw()); + //nodes_filters.h add_type("blur",new Blur()); + //video nodes + add_type("video_loader",new Video_loader()); + add_type("video_output",new Video_output()); } - -bool Signal_input::connect(Signal_node* source) { - if (source->output_type=="signal") { - connection=(Node*)source; - return true; - } +bool Signal_input::connect(Node* source) { + connection=dynamic_cast<Signal_node*>(source); + if (connection) return true; else return false; } -void Parameter_input::update(const Time_spec& time){ //gets input and updates variable - if (receiver){ - *receiver=((Signal_node*)connection)->get_output(time); +float Signal_input::get(const Time_spec& time){ //gets input and updates variable + if (connection){ + return (((Signal_node*)connection)->get_output(time)); } + else return 0.0f; } -bool Image_input::connect(Image_node* source) { - if (source->output_type=="image") { - connection=(Node*)source; - return true; - } +bool Image_input::connect(Node* source) { + connection=dynamic_cast<Image_node*>(source); + if (connection) return true; else return false; } -void Node::update_params(const Time_spec& time){ //compute connected parameters - for (auto p:parameter_inputs){ - p->update(time); +Image* Image_input::get(const Frame_spec& time){ //gets input and updates variable + if (connection){ + return (((Image_node*)connection)->get_output(time)); } + else return nullptr; } -bool Signal_output::render(const float duration, const float framerate,string &xml_out){ - //testing signal routes - Logger& logger = Logger::get("Rotor"); - logger.information("SIgnal_output rendering "+ofToString(duration)+" seconds at "+ofToString(framerate)+" fps"); - - float step=1.0f/framerate; - float v=0.0f; - float min=10000000.0f; - float max=-10000000.0f; - for (float f=0.0f;f<duration;f+=step) { - float u=get_output(Time_spec(f,framerate,duration)); - if (!fequal(u,v)) { - xml_out+=("<signal time='"+ofToString(f)+"'>"+ofToString(u)+"</signal>\n"); - v=u; - if (v>max) max=v; - if (v<min) min=v; - } +float Parameter::get(const Time_spec& time){ //gets input and updates variable + if (connection){ + value = ((Signal_node*)connection)->get_output(time); } - xml_out+=("<signal_finished min='"+ofToString(min)+"' max='"+ofToString(max)+"'/>\n"); - return true; + return value; } - bool Audio_thumbnailer::init(int _channels,int _bits,int _samples,int _rate) { //base_audio_processor::init(_channels,_bits,_samples); channels=_channels; @@ -162,6 +149,7 @@ string Audio_thumbnailer::print(){ delete enc; return output.str(); } + bool Audio_analysis::init(int _channels,int _bits,int _samples, int _rate) { //need these to make sense of data channels=_channels; @@ -190,7 +178,6 @@ void Audio_analysis::print_features(){ } cerr<<endl; } - bool Video_output::render(const float duration, const float framerate,const string &output_filename,const string &audio_filename,float& progress,int outW,int outH){ // @@ -282,11 +269,11 @@ bool Video_loader::load(const string &_filename){ string uri="file://"+_filename; isLoaded=player.open(uri); if (isLoaded){ - logger.information("Video_loader loaded "+filename+": "+ofToString(player.getNumberOfFrames())+" frames, "+ofToString(player.getFrameRate())+" fps, "+ofToString(player.getWidth())+"x"+ofToString(player.getHeight())); + logger.information("Video_loader loaded "+_filename+": "+ofToString(player.getNumberOfFrames())+" frames, "+ofToString(player.getFrameRate())+" fps, "+ofToString(player.getWidth())+"x"+ofToString(player.getHeight())); return true; } - logger.error("Video_loader failed to load "+filename); + logger.error("Video_loader failed to load "+_filename); return false; } @@ -297,15 +284,15 @@ Image* Video_loader::output(const Frame_spec &frame){ //need to cache frames so as to avoid asking for a frame other than the next one. //need an algorithm to find the previous keyframe and seek forward - float clipframerate=(framerate==0.0f?player.getFrameRate():framerate); + float clipframerate=(parameters["framerate"]->value==0.0f?player.getFrameRate():parameters["framerate"]->value); - float clipspeed=(clipframerate/frame.framerate)*speed; + float clipspeed=(clipframerate/frame.framerate)*parameters["speed"]->value; int wanted=(((int) ((frame.time*frame.framerate*clipspeed)+0.5))%(player.getNumberOfFrames()-1))+1; //+1 is necessary because 1st frame in a video is number 1? if (wanted!=lastframe){ if (!player.fetchFrame(frame.w,frame.h,wanted)) { //seek fail Logger& logger = Logger::get("Rotor"); - logger.error("Video_loader failed to seek frame "+ofToString(wanted)+" of "+filename); + logger.error("Video_loader failed to seek frame "+ofToString(wanted)+" of "+attributes["filename"]->value); if (image.w>0) return ℑ //just return the previous frame if possible else return nullptr; @@ -316,22 +303,4 @@ Image* Video_loader::output(const Frame_spec &frame){ return ℑ } return nullptr; -}; -/* -bool CVideo_loader::load(const string &filename){ - - Poco::Path path; - string uri="file://"+filename; - isLoaded=player.open(filename); - if (isLoaded){ - cerr<<"Rotor::Video_loader: "<<filename<<", "<<player.get(CV_CAP_PROP_FRAME_COUNT)<<" frames "<<", "<<player.get(CV_CAP_PROP_FRAME_WIDTH)<<"x"<<player.get(CV_CAP_PROP_FRAME_HEIGHT)<<endl; - return true; - } - cerr<<"Rotor::Video_loader: failed to load "<<filename<<endl; - return false; -} -Image* CVideo_loader::output(const Frame_spec &frame){ - - return nullptr; -}; -*/
\ No newline at end of file +};
\ No newline at end of file diff --git a/rotord/src/rotor.h b/rotord/src/rotor.h index 2af582a..937a884 100755 --- a/rotord/src/rotor.h +++ b/rotord/src/rotor.h @@ -1,26 +1,7 @@ #ifndef ROTOR_H #define ROTOR_H -/* -nodes can have many inputs but only 1 output - -image nodes that use an image as input can pass on the incoming image only if its unchanged. - -TODO - parameter class that automatically links variable to correctly named inputs -TODO - use try.. catch and dynamic_cast to verify node connections rather than checking 'type' tag - -TODO - put the boilerplate code for checking inputs into the base class, finally call checked_output - -http://stackoverflow.com/questions/5261658/how-to-seek-in-ffmpeg-c-c - -300713 -trying to use opencv video loader for seeking -not so good - conflicts with included libav - seems to be incorrectly loaded - -would maybe need to do something like -http://stackoverflow.com/questions/12427928/configure-and-build-opencv-to-custom-ffmpeg-install - -*/ +//definitions of base classes and types for rendering graph #include <unordered_map> #include <deque> @@ -29,59 +10,13 @@ http://stackoverflow.com/questions/12427928/configure-and-build-opencv-to-custom #include <sys/time.h> #include <iostream> -#include "Poco/Net/HTTPServer.h" #include "Poco/Net/HTTPResponse.h" -#include "Poco/UUID.h" -#include "Poco/UUIDGenerator.h" -#include "Poco/Notification.h" -#include "Poco/NotificationCenter.h" -#include "Poco/Observer.h" -#include "Poco/ThreadPool.h" -#include "Poco/Thread.h" -#include "Poco/Task.h" -#include "Poco/Runnable.h" -#include "Poco/Mutex.h" -#include "Poco/Random.h" -#include "Poco/AutoPtr.h" -#include "Poco/File.h" -#include "Poco/Base64Encoder.h" -#include "Poco/Path.h" -#include "Poco/StringTokenizer.h" #include "Poco/Logger.h" - - -using Poco::UUID; -using Poco::UUIDGenerator; -using Poco::Net::HTTPResponse; -using Poco::Logger; - -/* -extern "C" { - #include <libavcodec/avcodec.h> - #include <libavformat/avformat.h> - #include <libavutil/opt.h> - #include <libavutil/channel_layout.h> - #include <libavutil/common.h> - #include <libavutil/imgutils.h> - #include <libavutil/mathematics.h> - #include <libavutil/samplefmt.h> - - #include <libavutil/dict.h> - //#include <libavutil/dict.c> stops the compiler error but causes a linker error. does libavcodec need to be statically linked? - #include <libavutil/imgutils.h> - #include <libavutil/samplefmt.h> - //#include <libavutil/timestamp.h> -} -*/ - - -#define AUDIO_INBUF_SIZE 20480 -#define AUDIO_REFILL_THRESH 4096 - -#include <highgui.h> //for opencv video IO +#include "Poco/Path.h" +#include "Poco/Base64Encoder.h" #include "xmlIO.h" -#include "utils.h" //fequal +#include "utils.h" #include "cvimage.h" #include "libavwrapper.h" @@ -97,46 +32,17 @@ namespace Rotor { #define ANALYSE_AUDIO 1 #define PREVIEW 2 #define RENDER 3 - - //forward declaration + //forward declarations class Node; class Signal_node; class Image_node; - class Parameter_input; - - //http://blog.tomaka17.com/2012/03/libavcodeclibavformat-tutorial/ - /* struct Packet { - explicit Packet(AVFormatContext* ctxt = nullptr) { - av_init_packet(&packet); - packet.data = nullptr; - packet.size=0; - if (ctxt) reset(ctxt); - } - - Packet(Packet&& other) : packet(std::move(other.packet)) { - other.packet.data = nullptr; - } + class Parameter; - ~Packet() { - if (packet.data) - av_free_packet(&packet); - } - - void reset(AVFormatContext* ctxt) { - if (packet.data) - av_free_packet(&packet); - if (av_read_frame(ctxt, &packet) < 0) - packet.data = nullptr; - } - - AVPacket packet; - }; - */ class Time_spec{ public: Time_spec(){}; Time_spec(float _time,float _framerate,float _duration){ time=_time; framerate=_framerate; duration=_duration;}; - float time; + float time; //num/denom ? float framerate; float duration; Time_spec lastframe() const{ @@ -148,16 +54,14 @@ namespace Rotor { }; class Frame_spec: public Time_spec{ public: - Frame_spec(float _time,float _framerate,float _duration,int _w,int _h){ time=_time; framerate=_framerate; duration=_duration; w=_w; h=_h;}; - Frame_spec(int _frame,float _framerate,float _duration,int _w,int _h){ time=((float)_frame)/_framerate; framerate=_framerate; duration=_duration; w=_w; h=_h;}; - //Frame_spec(time,_framerate,_duration,_w,_h);}; - - //float time; //this hould probably be implemented with a num/denom scheme eventually for accuracy - //float framerate; + Frame_spec(float _time,float _framerate,float _duration,int _w,int _h) + { time=_time; framerate=_framerate; duration=_duration; w=_w; h=_h;}; + Frame_spec(int _frame,float _framerate,float _duration,int _w,int _h) + { time=((float)_frame)/_framerate; framerate=_framerate; duration=_duration; w=_w; h=_h;}; int h,w; - //Frame_spec lastframe(){ - // return Frame_spec(time-(1.0f/framerate),framerate,w,h); - //} + Frame_spec lastframe(){ + return Frame_spec(time-(1.0f/framerate),framerate,duration,w,h); + } }; class Colour{ public: @@ -185,17 +89,6 @@ namespace Rotor { } uint8_t r,g,b; }; - - class Render_status{ - public: - int id; - float progress; - }; - class Render_requirements{ - public: - int num_performances; - int num_clips; - }; class Command_response{ public: Command_response() { status=Poco::Net::HTTPResponse::HTTP_OK; } @@ -204,75 +97,131 @@ namespace Rotor { }; class Input{ public: - Input(const string &_desc): connection(nullptr),description(_desc){}; + Input(const string &_desc,const string &_title): connection(nullptr),description(_desc),title(_title){}; Node* connection; string description; + string title; }; class Image_input: public Input{ public: - bool connect(Image_node *source); - Image_input(const string &_desc): Input(_desc){}; + virtual ~Image_input(){}; + bool connect(Node *source); + Image_input(const string &_desc,const string &_title,Node* _connect): Input(_desc,_title){ + connect(_connect); + }; + Image* get(const Frame_spec& time); }; class Signal_input: public Input{ public: - bool connect(Signal_node *source); - Signal_input(const string &_desc): Input(_desc){}; + virtual ~Signal_input(){}; + bool connect(Node *source); + Signal_input(const string &_desc,const string &_title,Node* _connect): Input(_desc,_title){ + connect(_connect); + }; + float get(const Time_spec& time); }; - class Parameter_input: public Signal_input{ + class Parameter: public Signal_input{ public: - Parameter_input(const string &_param,const string &_desc): Signal_input(_desc),receiver(nullptr),parameter(_param){}; - float *receiver; - void update(const Time_spec& time); - string parameter; + virtual ~Parameter(){}; + Parameter(const string &_type,const string &_desc,const string &_title,float _value,float _min,float _max,Node* _connect): Signal_input(_desc,_title,_connect),value(_value),min(_min),max(_max),type(_type){}; + float value,min,max; + float get(const Time_spec& time); + string type; + }; + class Attribute{ //description of a static attribute which can be an enumerated string array + public: + virtual ~Attribute(){}; + Attribute(const string &_desc,const string &_title,const string &_value,std::vector<std::string> _vals={}): description(_desc),title(_title),value(_value),intVal(0){ + vals=_vals; + }; + void init(const string &_key){ //inits int value from set::string vals index + value=_key; + std::vector<std::string>::iterator it=it = find(vals.begin(),vals.end(),value); + if (it!=vals.end()){ + intVal = std::distance(vals.begin(),it)+1; //using 1-index for enums + } + else intVal=0; + } + string value,description,title; + std::vector<std::string> vals; + int intVal; }; class Node{ public: - virtual Node* clone(map<string,string> &_settings)=0; - virtual ~Node(){}; - UUID uid; //every usable node has a UUID - int id; - vector<Signal_input*> inputs; //simple node can have signal inputs, output depends on node type - vector<Parameter_input*> parameter_inputs; //linked parameters can convert from settings to inputs - void create_signal_input(const string &description) {inputs.push_back(new Signal_input(description));}; - void create_parameter_input(const string ¶meter,const string &description) {parameter_inputs.push_back(new Parameter_input(parameter,description));}; + virtual Node* clone(map<string,string> &_settings)=0; //pure virtual + virtual ~Node(){ + duplicate_inputs=false; + }; + vector<Signal_input*> inputs; //simple node can have signal inputs, output depends on node type + unordered_map<string,Parameter*> parameters; //linked parameters can convert from settings to inputs + unordered_map<string,Attribute*> attributes; + void create_signal_input(const string &_desc,const string &_title,Node* _connect=nullptr ) { + inputs.push_back(new Signal_input(_desc,_title,_connect)); + }; + void create_parameter(const string &_name,const string &_type,const string &_desc,const string &_title,float _value=1.0f,float _min=0.0f,float _max=0.0f,Node* _connect=nullptr) { + parameters[_name]=new Parameter(_type,_desc,_title,_value,_min,_max,_connect); + }; + void create_attribute(const string &_attr,const string &_desc,const string &_title,const string &_value,std::vector<std::string> _vals={}) { + attributes[_attr]=new Attribute(_desc,_title,_value,_vals); + }; string description; string type; - string output_type; string ID; + string title; + bool duplicate_inputs; string find_setting(map<string,string> &settings,string key,string def=""){ if (settings.find(key)!=settings.end()) return settings[key]; else return def;}; float find_setting(map<string,string> &settings,string key,float def){ if (settings.find(key)!=settings.end()) return ofToFloat(settings[key]); else return def;}; int find_setting(map<string,string> &settings,string key,int def){ if (settings.find(key)!=settings.end()) return ofToInt(settings[key]); else return def;}; void base_settings(map<string,string> &settings) { description=find_setting(settings,"description"); type=find_setting(settings,"type"); - output_type=find_setting(settings,"output"); ID=find_setting(settings,"ID"); + title=find_setting(settings,"title"); + for (auto a: attributes){ + if (find_setting(settings,a.first,"")!="") { + attributes[a.first]->init(find_setting(settings,a.first,"")); + } + } + } + void update(const Time_spec &time){ + for (auto p: parameters){ + p.second->get(time); + } } virtual void set_parameter(const std::string &key,const std::string &value){}; - virtual void link_params(){}; //TODO make param classes that link automatically - void update_params(const Time_spec& time); }; class Signal_node: public Node{ - public: - virtual ~Signal_node(){}; - const float get_output(const Time_spec &time) { update_params(time); return output(time); }; - virtual const float output(const Time_spec &time) { return 0.0f; }; + public: + virtual ~Signal_node(){}; + const float get_output(const Time_spec &time) { + update(time); + return output(time); + }; + virtual const float output(const Time_spec &time) { return 0.0f; }; }; class Image_node: public Node{ public: virtual ~Image_node(){}; vector<Image_input*> image_inputs; //image node also has image inputs and outputs - void create_image_input(const string &description) {image_inputs.push_back(new Image_input(description));}; - Image *get_output(const Frame_spec &frame) { update_params((Time_spec)frame); return output(frame); }; + void create_image_input(const string &_title,const string &_desc,Node* _connect=nullptr) { + image_inputs.push_back(new Image_input(_desc,_title,_connect)); + }; + Image *get_output(const Frame_spec &frame) { + image.setup(frame.w,frame.h); + update((Time_spec)frame); + return output(frame); + } virtual const Image *output(const Frame_spec &frame)=0; Image *get_preview(const Frame_spec &frame); - Image *image; //this can be privately allocated or just passed on as the node see fit - private: - float image_time; + Image image; + private: + float image_time; //? could be used to detect image reuse? + }; - class Base_audio_processor: public Signal_node { + class Audio_processor: public Signal_node { public: - virtual ~Base_audio_processor(){}; + virtual Audio_processor(){}; + virtual ~Audio_processor(){}; virtual int process_frame(uint8_t *data,int samples)=0; virtual bool init(int _channels,int _bits,int _samples,int _rate)=0; virtual void cleanup()=0; @@ -282,8 +231,11 @@ namespace Rotor { //actual nodes------------------------------------------------- class Time: public Signal_node { public: - Time(){}; - Time(map<string,string> &settings) { + Time(){ + title="Time"; + description="Outputs the time in seconds as a signal"; + }; + Time(map<string,string> &settings): Time() { base_settings(settings); }; Time* clone(map<string,string> &_settings) { return new Time(_settings);}; @@ -293,8 +245,11 @@ namespace Rotor { }; class Track_time: public Signal_node { public: - Track_time(){}; - Track_time(map<string,string> &settings) { + Track_time(){ + title="Track time"; + description="Outputs the fraction of the track as a signal"; + }; + Track_time(map<string,string> &settings): Track_time() { base_settings(settings); }; Track_time* clone(map<string,string> &_settings) { return new Track_time(_settings);}; @@ -304,287 +259,230 @@ namespace Rotor { }; class Signal_output: public Signal_node { public: - Signal_output(){}; - Signal_output(map<string,string> &settings) { + Signal_output(){ + create_signal_input("signal","Signal Input"); + title="Signal output"; + description="Outputs a signal to xml for testing"; + }; + Signal_output(map<string,string> &settings): Signal_output() { base_settings(settings); }; Signal_output* clone(map<string,string> &_settings) { return new Signal_output(_settings);}; bool render(const float duration, const float framerate,string &xml_out); const float output(const Time_spec &time) { - if (inputs[0]->connection) { - return ((Signal_node*)(inputs[0]->connection))->get_output(time); - } - else return 0.0f; + return inputs[0]->get(time); } }; class Testcard: public Image_node { public: - Testcard(){image=nullptr;}; - Testcard(map<string,string> &settings) { + Testcard(){ + //internal testing node only + }; + Testcard(map<string,string> &settings): Testcard() { base_settings(settings); - image=new Image(); }; - ~Testcard(){ if (image) delete image;}; + ~Testcard(){}; Testcard* clone(map<string,string> &_settings) { return new Testcard(_settings);}; Image *output(const Frame_spec &frame){ - if (image->setup(frame.w,frame.h)) { - - } - //always create testcard - //float ws=(255.0f/frame.w); float hs=(255.0f/frame.h); for (int i=0;i<frame.h;i++){ for (int j=0;j<frame.w;j++){ - image->RGBdata[(i*frame.w+j)*3]=(uint8_t)((int)((i+(frame.time*25.0f)*hs))%255); - image->RGBdata[((i*frame.w+j)*3)+1]=(uint8_t)((int)((j+(frame.time*100.0f)*hs))%255); - image->RGBdata[((i*frame.w+j)*3)+2]=(uint8_t)(0); + image.RGBdata[(i*frame.w+j)*3]=(uint8_t)((int)((i+(frame.time*25.0f)*hs))%255); + image.RGBdata[((i*frame.w+j)*3)+1]=(uint8_t)((int)((j+(frame.time*100.0f)*hs))%255); + image.RGBdata[((i*frame.w+j)*3)+2]=(uint8_t)(0); //image->Adata[i*frame.w+j]=(uint8_t)255; //image->Zdata[i*frame.w+j]=(uint16_t)512; //1.0 in fixed point 8.8 bits } } - return image; + return ℑ } private: - Image *image; //is an image generator + }; class Invert: public Image_node { public: Invert(){ - image=nullptr; - create_image_input("image to invert"); + create_image_input("Image to invert","Image input"); + create_parameter("invert","number","Invert when greater than 0.0","Negative",1.0f,0.0f,1.0f); + title="Negative"; + description="Inverts the input picture"; }; - Invert(map<string,string> &settings) { + Invert(map<string,string> &settings) :Invert() { base_settings(settings); - image=new Image(); }; - ~Invert(){ if (image) delete image;}; + ~Invert(){}; Invert* clone(map<string,string> &_settings) { return new Invert(_settings);}; Image *output(const Frame_spec &frame){ - if (image_inputs.size()) { - if (image_inputs[0]->connection){ - if (inputs[0]->connection) { - if (fgreater_or_equal(1.0f,(((Signal_node*)inputs[0]->connection)->get_output((Time_spec)frame)))) { - Image *in=(((Image_node*)image_inputs[0]->connection)->get_output(frame)); - if (in){ - image->setup(frame.w,frame.h); - for (int i=0;i<in->w*in->h*3;i++) { - image->RGBdata[i]=255-in->RGBdata[i]; - } - return image; - } - } - } - return (((Image_node*)image_inputs[0]->connection)->get_output(frame)); - } - - } - if (image_inputs[0]->connection) { - return image; - } + Image *in=image_inputs[0]->get(frame); + if (in) { + if (parameters["invert"]->value>0.0f){ + for (int i=0;i<in->w*in->h*3;i++) { + image.RGBdata[i]=255-in->RGBdata[i]; + } + return ℑ + } + return in; + } return nullptr; } private: - Image *image; //is an image generator - //bool invert; }; class Video_cycler: public Image_node { - //cycles through video inputs in order public: - Video_cycler(){create_image_input("duplicatable");}; - Video_cycler(map<string,string> &settings) { + Video_cycler(){ + create_image_input("Image input","Image input"); + create_signal_input("Selector","Selector input"); + title="Video cycler"; + description="Cycles through video inputs according to selector signal"; + duplicate_inputs=true; + } + Video_cycler(map<string,string> &settings):Video_cycler() { base_settings(settings); }; ~Video_cycler(){}; bool load(const string &filename); Image *output(const Frame_spec &frame){ - int which_input=0; - if (inputs[0]->connection) { - which_input=((int)((Signal_node*)inputs[0]->connection)->get_output((Time_spec)frame))%image_inputs.size(); - } - if (image_inputs.size()) { - if (image_inputs[which_input]->connection){ - return (((Image_node*)image_inputs[which_input]->connection)->get_output(frame)); - } - } - return nullptr; + return image_inputs[((int)inputs[0]->get((Time_spec)frame))%image_inputs.size()]->get(frame); } Video_cycler* clone(map<string,string> &_settings) { return new Video_cycler(_settings);}; - private: }; class Signal_colour: public Image_node { - //cycles through video inputs in order public: Signal_colour(){ - create_signal_input("colour selector"); + create_signal_input("Selector","Selector input"); + create_attribute("colours","palette list of web colours","Colours","000000"); + title="Signal colour"; + description="Cycles through a palette of background colours according to selector signal"; }; - Signal_colour(map<string,string> &settings) { + Signal_colour(map<string,string> &settings):Signal_colour() { base_settings(settings); - string colours=find_setting(settings,"palette",""); - for (int i=0;i<colours.size()/6;i++){ - palette.push_back(Colour(colours.substr(i*6,6))); - } - for (auto i: palette) { - cerr << "Signal_colour found palette colour: "<<(int)i.r<<" "<<(int)i.g<<" "<<(int)i.b<<endl; + for (int i=0;i<attributes["colours"]->value.size()/6;i++){ + palette.push_back(Colour(attributes["colours"]->value.substr(i*6,6))); } prevcol=-1; }; ~Signal_colour(){}; Image *output(const Frame_spec &frame){ if (palette.size()) { - if (inputs.size()) { - if (inputs[0]->connection){ - int col= ((int)(((Signal_node*)inputs[0]->connection)->get_output(frame)))%palette.size(); - if (col!=prevcol||image.w!=frame.w||image.h!=frame.h){ - image.setup(frame.w,frame.h); - for (int i=0;i<image.w*image.h;i++){ - image.RGBdata[i*3]=palette[col].r; - image.RGBdata[i*3+1]=palette[col].g; - image.RGBdata[i*3+2]=palette[col].b; - } - prevcol=col; - } - return ℑ + int col=((int)inputs[0]->get((Time_spec)frame))%palette.size(); + if (col!=prevcol){ //how about when starting a new render? + for (int i=0;i<image.w*image.h;i++){ + image.RGBdata[i*3]=palette[col].r; + image.RGBdata[i*3+1]=palette[col].g; + image.RGBdata[i*3+2]=palette[col].b; } + prevcol=col; } + return ℑ } return nullptr; } Signal_colour* clone(map<string,string> &_settings) { return new Signal_colour(_settings);}; private: vector<Rotor::Colour> palette; - Image image; int prevcol; }; class Signal_greyscale: public Image_node { //Draws signal bars in greyscale public: - Signal_greyscale(){}; - Signal_greyscale(map<string,string> &settings) { + Signal_greyscale(){ + create_signal_input("Signal","Signal input"); + title="Signal greyscale"; + description="Renders signal level (0..1) as greyscale background"; + }; + Signal_greyscale(map<string,string> &settings):Signal_greyscale() { base_settings(settings); prevcol=-1; }; ~Signal_greyscale(){}; Image *output(const Frame_spec &frame){ - if (inputs.size()) { - if (inputs[0]->connection){ - float sig= ((((Signal_node*)inputs[0]->connection)->get_output(frame))); - uint8_t col=255-((uint8_t)(sig*255.0f)); - if (col!=prevcol||image.w!=frame.w||image.h!=frame.h){ - image.setup(frame.w,frame.h); - for (int i=0;i<image.w*image.h*3;i++){ - image.RGBdata[i]=col; - } - prevcol=col; - } - return ℑ + uint8_t col=255-((uint8_t)(inputs[0]->get((Time_spec)frame)*255.0f)); + if (col!=prevcol){ //how about when starting a new render? + for (int i=0;i<image.w*image.h*3;i++){ + image.RGBdata[i]=col; } + prevcol=col; } - return nullptr; + return ℑ + } Signal_greyscale* clone(map<string,string> &_settings) { return new Signal_greyscale(_settings);}; private: - Image image; uint8_t prevcol; }; -#define ARITHMETIC_plus 1 -#define ARITHMETIC_minus 2 -#define ARITHMETIC_multiply 3 -#define ARITHMETIC_divide 4 -#define ARITHMETIC_modulo 5 + #define ARITHMETIC_plus 1 + #define ARITHMETIC_minus 2 + #define ARITHMETIC_multiply 3 + #define ARITHMETIC_divide 4 + #define ARITHMETIC_modulo 5 class Image_arithmetic: public Image_node { - //Draws signal bars in greyscale public: - Image_arithmetic(){image=nullptr;}; - Image_arithmetic(map<string,string> &settings) { + Image_arithmetic(){ + create_image_input("image input","Image input"); + create_parameter("value","number","Value or signal for operation","Value",1.0f); + create_attribute("operator","operator for image","Operator","+",{"+","-","*","/"}); + title="Image arithmetic"; + description="Performs arithmetic on an image with a signal or value"; + }; + Image_arithmetic(map<string,string> &settings):Image_arithmetic() { base_settings(settings); - value=find_setting(settings,"value",0.0f); - string _op=find_setting(settings,"operator","+"); - if (_op=="+"||_op=="plus"||_op=="add") op=ARITHMETIC_plus; - if (_op=="-"||_op=="minus"||_op=="subtract") op=ARITHMETIC_minus; - if (_op=="*"||_op=="x"||_op=="multiply") op=ARITHMETIC_multiply; - if (_op=="/"||_op=="divide") op=ARITHMETIC_divide; - //if (_op=="%"||_op=="mod"||_op=="modulo"||_op=="modulus") op=ARITHMETIC_modulo; ??what would this even mean? - image=nullptr; } - void link_params() { - for (auto p:parameter_inputs){ - if (p->parameter=="value") { - p->receiver=&value; - } - } - - }; - ~Image_arithmetic(){if (image) delete image;}; + ~Image_arithmetic(){}; Image *output(const Frame_spec &frame){ - if (image_inputs.size()) { - if (image_inputs[0]->connection){ - if (image) delete image; //from the previous frame- this may not be ideal - //because operator* made a new image it should be deleted - Image *in=(((Image_node*)image_inputs[0]->connection)->get_output(frame)); - switch (op) { - case ARITHMETIC_plus: - image=(*in)+value; - break; - case ARITHMETIC_minus: - image=(*in)-value; - break; - case ARITHMETIC_multiply: - image=(*in)*value; - break; - case ARITHMETIC_divide: - image=(*in)/value; - break; - } - return image; - } + Image *in=image_inputs[0]->get(frame); + if (in){ + switch (attributes["operator"]->intVal) { + case ARITHMETIC_plus: + image=(*in); //could be poss without copy? + image+=parameters["value"]->value; + break; + case ARITHMETIC_minus: + image=(*in); + image-=parameters["value"]->value; + break; + case ARITHMETIC_multiply: + image=(*in); + image*=parameters["value"]->value; + break; + case ARITHMETIC_divide: + image=(*in); + image/=parameters["value"]->value; + break; + } } - return nullptr; + return ℑ } Image_arithmetic* clone(map<string,string> &_settings) { return new Image_arithmetic(_settings);}; private: - Image *image; - float value; - int op; }; - class Luma_levels: public Image_node { - //applies LUT To RGB channels equally public: - Luma_levels(){LUT=nullptr;image=nullptr;}; - Luma_levels(map<string,string> &settings) { - base_settings(settings); - levels_settings(settings); - image=new Image(); - } - void link_params() { - for (auto p:parameter_inputs){ - if (p->parameter=="black_in") p->receiver=&black_in; - if (p->parameter=="white_in") p->receiver=&white_in; - if (p->parameter=="gamma") p->receiver=γ - if (p->parameter=="black_out") p->receiver=&black_out; - if (p->parameter=="white_out") p->receiver=&white_out; - } - }; - ~Luma_levels(){if (LUT) {delete[] LUT;} if (image) delete image; }; - void levels_settings(map<string,string> &settings){ - black_in=find_setting(settings,"black_in",0.0f); - white_in=find_setting(settings,"white_in",1.0f); - gamma=find_setting(settings,"gamma",1.0f); - black_out=find_setting(settings,"black_out",0.0f); - white_out=find_setting(settings,"white_out",1.0f); + Luma_levels(){ + create_image_input("image input","Image input"); + create_parameter("black_in","number","input black point (0..1)","Input black point",0.0f,0.0f,1.0f); + create_parameter("white_in","number","input white point (0..1)","Input white point",1.0f,0.0f,1.0f); + create_parameter("gamma","number","gamma level","Gamma",1.0f,0.0f,10.0f); + create_parameter("black_out","number","output black point (0..1)","Output black point",0.0f,0.0f,1.0f); + create_parameter("white_out","number","output white point (0..1)","Output white point",1.0f,0.0f,1.0f); + title="Luma levels"; + description="Remap luma values of image"; LUT=nullptr; - generate_LUT(); + }; + Luma_levels(map<string,string> &settings):Luma_levels() { + base_settings(settings); } - void generate_LUT(){ //check this + ~Luma_levels(){if (LUT) { delete[] LUT;} }; + void generate_LUT(){ + //can check here if anything has changed if (LUT) delete[] LUT; LUT=new unsigned char[256]; float fltmax=(255.0f/256.0f); for (int i=0;i<256;i++){ - LUT[i]=(unsigned char)(((pow(min(fltmax,max(0.0f,(((((float)i)/256.0f)-black_in)/(white_in-black_in)))),(1.0/gamma))*(white_out-black_out))+black_out)*256.0f); + LUT[i]=(unsigned char)(((pow(min(fltmax,max(0.0f,(((((float)i)/256.0f)-parameters["black_in"]->value)/(parameters["white_in"]->value-parameters["black_in"]->value)))),(1.0/parameters["gamma"]->value))*(parameters["white_out"]->value-parameters["black_out"]->value))+parameters["black_out"]->value)*255.0f); } } void apply_LUT(const Image& in){ - apply_LUT(in,*image); + apply_LUT(in,image); } void apply_LUT(const Image& in,Image &out){ //facility to apply to other images for inherited classes out.setup(in.w,in.h); @@ -593,21 +491,16 @@ namespace Rotor { } } Image *output(const Frame_spec &frame){ - if (image_inputs.size()) { - if (image_inputs[0]->connection){ - if (LUT) { - apply_LUT(*(((Image_node*)image_inputs[0]->connection)->get_output(frame))); - return image; - } - } + Image *in=image_inputs[0]->get(frame); + if (in){ + generate_LUT(); + apply_LUT(*in); } - return nullptr; + return ℑ } Luma_levels* clone(map<string,string> &_settings) { return new Luma_levels(_settings);}; protected: unsigned char *LUT; - Image *image; - float black_in,white_in,gamma,black_out,white_out; }; class Echo_trails: public Luma_levels { //draw trail frames additively that fade off over time @@ -635,38 +528,25 @@ namespace Rotor { //or is it actually best to use alpha keying after all! public: - Echo_trails(){image=nullptr;}; - Echo_trails(map<string,string> &settings) { + Echo_trails(){ + //calls base class constructor first + create_parameter("number","number","number of echoes","Number echoes",25.0f); + create_parameter("fadeto","number","amount that echoes fade out (0..1)","Fadout amount",1.0f,0.0f,1.0f); + create_attribute("mode","blend mode for echoes","Blend mode","screen",{"screen","wrap"}); + title="Echo trails"; + description="Draw trail frames additively that fade off over time"; + }; + Echo_trails(map<string,string> &settings):Echo_trails() { base_settings(settings); - //duration=find_setting(settings,"duration",1.0f); - number=find_setting(settings,"number",1); - fadeto=find_setting(settings,"fadeto",1.0f); - levels_settings(settings); - image=nullptr; lastframe=-1; - mode=find_setting(settings,"mode",0.0f); } - void link_params() { - for (auto p:parameter_inputs){ - if (p->parameter=="black_in") p->receiver=&black_in; - if (p->parameter=="white_in") p->receiver=&white_in; - if (p->parameter=="gamma") p->receiver=γ - if (p->parameter=="black_out") p->receiver=&black_out; - if (p->parameter=="white_out") p->receiver=&white_out; - - //TODO: control an integer - if (p->parameter=="mode") p->receiver=&mode; - } - }; - //~Echo_trails(){if (image) {delete image;} }; ~Echo_trails(){ - if (image) delete image; - for (auto i:images) {if (image) delete i.second;} + for (auto i:images) delete i.second; }; Image *output(const Frame_spec &frame){ //check if cache is valid if (images.size()){ - if (frame.w!=image->w||frame.h!=image->h){ //or framerate changed? + if (frame.w!=image.w||frame.h!=image.h){ //or framerate changed? //clear cache and start over images.clear(); lastframe=-1; @@ -680,7 +560,7 @@ namespace Rotor { auto i = std::begin(images); while (i != std::end(images)) { // check if the image is in the range we need - if (thisframe-(*i).first>number||thisframe-(*i).first<0) { + if (thisframe-(*i).first>(int)parameters["number"]->value||thisframe-(*i).first<0) { delete (*i).second; i = images.erase(i); } @@ -688,144 +568,113 @@ namespace Rotor { ++i; } //if frame has already been calculated just return it - if (thisframe==lastframe) { - return image; - } - else { - if (image_inputs.size()) { - if (image_inputs[0]->connection){ - if (LUT) { - //need a better strategy here, should be able to get each image once - //copy incoming image **writable - if (image) image->free(); - image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone(); - images[thisframe]=new Image(frame.w,frame.h); - apply_LUT(*(image),*(images[thisframe])); - for (int i=1;i<number;i++){ - //check echo frame isn't at negative time - int absframe=thisframe-i; - if (absframe>-1){ - //check if image is in the cache - if (images.find(absframe)==images.end()){ - images[absframe]=new Image(frame.w,frame.h); - Frame_spec wanted=Frame_spec(absframe,frame.framerate,frame.duration,frame.w,frame.h); - apply_LUT(*(((Image_node*)image_inputs[0]->connection)->get_output(wanted)),*(images[absframe])); - } - //cerr<<"Rotor: about to apply image ("<<images[absframe].w<<"x"<<images[absframe].h<<")"<<endl; - if (fless(1.0f,fadeto)){ - float amount=((((float)number-i)/number)*(1.0f-fadeto))+(1.0f-fadeto); - Image *temp=*images[absframe]*amount; - if (mode<0.5) { - (*image)+=*temp; - } - else { - image->add_wrap(*temp); - } - delete temp; - } - else { - if (mode<0.5) (*image)+=*(images[absframe]); - else (*image)=image->add_wrap(*(images[absframe])); - } + if (thisframe!=lastframe) { + Image *in=image_inputs[0]->get(frame); + if (in) { + generate_LUT(); + //need a better strategy here, should be able to get each image once + //copy incoming image **writable + image=*(in); + images[thisframe]=new Image(frame.w,frame.h); + apply_LUT(image,*(images[thisframe])); + for (int i=1;i<(int)parameters["number"]->value;i++){ + //check echo frame isn't at negative time + int absframe=thisframe-i; + if (absframe>-1){ + //check if image is in the cache + if (images.find(absframe)==images.end()){ + images[absframe]=new Image(frame.w,frame.h); + Frame_spec wanted=Frame_spec(absframe,frame.framerate,frame.duration,frame.w,frame.h); + apply_LUT(*(((Image_node*)image_inputs[0]->connection)->get_output(wanted)),*(images[absframe])); + } + if (fless(1.0f,parameters["fadeto"]->value)){ + float amount=(((parameters["number"]->value-i)/parameters["number"]->value)*(1.0f-parameters["fadeto"]->value))+(1.0f-parameters["fadeto"]->value); + Image *temp=*images[absframe]*amount; + if (attributes["mode"]->value=="screen") { + image+=*temp; + } + else { + image.add_wrap(*temp); } + delete temp; + } + else { + if (attributes["mode"]->value=="screen") image+=*(images[absframe]); + else image=image.add_wrap(*(images[absframe])); } - //for (int i=0;i<frame.w*frame.h*3;i++){ - // image->RGBdata[i]=LUT[in->RGBdata[i]]; - //} - lastframe=thisframe; - return image; } } + //for (int i=0;i<frame.w*frame.h*3;i++){ + // image->RGBdata[i]=LUT[in->RGBdata[i]]; + //} + lastframe=thisframe; } } - return nullptr; + return ℑ } Echo_trails* clone(map<string,string> &_settings) { return new Echo_trails(_settings);}; protected: - float duration,fadeto; - int number; int interval,total,lastframe; //number of frames between displayed echoes unordered_map<int,Image*> images; - float mode; //TODO make int, enum string parameter types }; - #define BLEND_screen 1 - #define BLEND_multiply 2 - #define BLEND_blend 3 + #define BLEND_blend 1 + #define BLEND_screen 2 + #define BLEND_multiply 3 #define BLEND_alpha 4 - #define BLEND_screen_wrap 5 - #define BLEND_multiply_wrap 6 - #define BLEND_xor 7 + #define BLEND_wrap 5 + #define BLEND_xor 6 class Blend: public Image_node { public: - Blend(){image=nullptr;}; - Blend(map<string,string> &settings) { - base_settings(settings); - image=nullptr; - amount=find_setting(settings,"amount",1.0f); - string _mode=find_setting(settings,"mode","screen"); - if (_mode=="screen") mode=BLEND_screen; - if (_mode=="multiply") mode=BLEND_multiply; - if (_mode=="blend") mode=BLEND_blend; - if (_mode=="alpha") mode=BLEND_alpha; - if (_mode=="screen_wrap") mode=BLEND_screen_wrap; - if (_mode=="multiply_wrap") mode=BLEND_multiply_wrap; - if (_mode=="xor") mode=BLEND_xor; + Blend(){ + create_image_input("image input 1","Image input 1"); + create_image_input("image input 2","Image input 2"); + create_parameter("amount","number","amount to blend input 2","Blend amount",0.5f,0.0f,1.0f); + create_attribute("mode","Blend mode","Blend mode","blend",{"blend","screen","multiply","alpha","wrap","xor"}); + title ="Blend"; + description="Blend images in various modes"; }; - void link_params() { - for (auto p:parameter_inputs){ - if (p->parameter=="amount") p->receiver=&amount; - } + Blend(map<string,string> &settings):Blend() { + base_settings(settings); }; - ~Blend(){ if (image) delete image;}; + ~Blend(){}; Blend* clone(map<string,string> &_settings) { return new Blend(_settings);}; Image *output(const Frame_spec &frame){ - if (image_inputs.size()) { - if (image_inputs[0]->connection){ - if (image_inputs.size()>1) { - if (image_inputs[1]->connection) { - //copy incoming image **writable - if (image) delete image; - image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone(); - switch(mode){ - case BLEND_screen: - (*image)+=(*(((Image_node*)image_inputs[1]->connection)->get_output(frame))); - break; - case BLEND_multiply: - (*image)*=(*(((Image_node*)image_inputs[1]->connection)->get_output(frame))); - break; - case BLEND_xor: - (*image)^=(*(((Image_node*)image_inputs[1]->connection)->get_output(frame))); - break; - case BLEND_alpha: - (*image)=(*image).alpha_blend(*(((Image_node*)image_inputs[1]->connection)->get_output(frame))); - break; - case BLEND_screen_wrap: - (*image)=(*image).add_wrap(*(((Image_node*)image_inputs[1]->connection)->get_output(frame))); - break; - case BLEND_multiply_wrap: - (*image)=(*image).divide_wrap(*(((Image_node*)image_inputs[1]->connection)->get_output(frame))); - break; - case BLEND_blend: //has to be last because of initialser of *in? go figure - (*image)*=(1.0f-amount); - Image *in=(*(((Image_node*)image_inputs[1]->connection)->get_output(frame)))*amount; - (*image)+=(*in); - delete in; - break; - - } - return image; - } - } - //if there aren't 2 image inputs connected just return the first - return (((Image_node*)image_inputs[0]->connection)->get_output(frame)); - } - } + Image *in1=image_inputs[0]->get(frame); + if (in1){ + Image *in2=image_inputs[1]->get(frame); + if (in2) { + image=*(in1); + switch(attributes["mode"]->intVal){ + case BLEND_screen: + image+=(*in2); + break; + case BLEND_multiply: + image*=(*in2); + break; + case BLEND_xor: + image^=(*in2); + break; + case BLEND_alpha: + image=image.alpha_blend(*in2); + break; + case BLEND_wrap: + image=image.add_wrap(*in2); + break; + case BLEND_blend: //has to be last because of initialser of *in? go figure + image*=(1.0f-parameters["amount"]->value); + Image *in=(*in2)*parameters["amount"]->value; + image+=(*in); + delete in; + break; + } + return ℑ + } + //if there aren't 2 image inputs connected just return the first + return in1; + } return nullptr; } private: - Image *image; //is an image generator - int mode; - float amount; //for blend }; #define MIRROR_horiz 1 #define MIRROR_vert 2 @@ -833,99 +682,95 @@ namespace Rotor { #define MIRROR_vertR 4 class Mirror: public Image_node { public: - Mirror(){image=nullptr;}; - Mirror(map<string,string> &settings) { + Mirror(){ + create_image_input("image input","Image input"); + create_attribute("mode","Mirror mode","Mirror mode","horiz",{"horiz","vert","horizR","vertR"}); + title="Mirror"; + description="Mirror video across a central axis"; + }; + Mirror(map<string,string> &settings):Mirror() { base_settings(settings); - image=nullptr; - string _mode=find_setting(settings,"mode","horiz"); - if (_mode=="horiz") mode=MIRROR_horiz; - if (_mode=="vert") mode=MIRROR_vert; - if (_mode=="horizR") mode=MIRROR_horizR; - if (_mode=="vertR") mode=MIRROR_vertR; }; - ~Mirror(){ if (image) delete image;}; + ~Mirror(){ }; Mirror* clone(map<string,string> &_settings) { return new Mirror(_settings);}; Image *output(const Frame_spec &frame){ - if (image_inputs.size()) { - if (image_inputs[0]->connection){ - //copy incoming image **writable - if (image) delete image; - image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone(); - switch (mode) { - case MIRROR_horiz: - for (int i=0;i<image->w/2;i++){ - for (int j=0;j<image->h;j++){ - for (int k=0;k<3;k++){ - image->RGBdata[(((j*image->w)+((image->w/2)+i))*3)+k]=image->RGBdata[(((j*image->w)+((image->w/2)-i))*3)+k]; - } + Image *in=image_inputs[0]->get(frame); + if (in){ + //copy incoming image **writable + image=(*in); + //could be more efficient here by only copying once + switch (attributes["mode"]->intVal) { + case MIRROR_horiz: + for (int i=0;i<image.w/2;i++){ + for (int j=0;j<image.h;j++){ + for (int k=0;k<3;k++){ + image.RGBdata[(((j*image.w)+((image.w/2)+i))*3)+k]=image.RGBdata[(((j*image.w)+((image.w/2)-i))*3)+k]; } } - break; - case MIRROR_vert: - for (int i=0;i<image->w;i++){ - for (int j=0;j<image->h/2;j++){ - for (int k=0;k<3;k++){ - image->RGBdata[((((image->h/2+j)*image->w)+i)*3)+k]=image->RGBdata[((((image->h/2-j)*image->w)+i)*3)+k]; - } + } + break; + case MIRROR_vert: + for (int i=0;i<image.w;i++){ + for (int j=0;j<image.h/2;j++){ + for (int k=0;k<3;k++){ + image.RGBdata[((((image.h/2+j)*image.w)+i)*3)+k]=image.RGBdata[((((image.h/2-j)*image.w)+i)*3)+k]; } } - break; - case MIRROR_horizR: - for (int i=0;i<image->w/2;i++){ - for (int j=0;j<image->h;j++){ - for (int k=0;k<3;k++){ - image->RGBdata[(((j*image->w)+((image->w/2)-i))*3)+k]=image->RGBdata[(((j*image->w)+((image->w/2)+i))*3)+k]; - } + } + break; + case MIRROR_horizR: + for (int i=0;i<image.w/2;i++){ + for (int j=0;j<image.h;j++){ + for (int k=0;k<3;k++){ + image.RGBdata[(((j*image.w)+((image.w/2)-i))*3)+k]=image.RGBdata[(((j*image.w)+((image.w/2)+i))*3)+k]; } } - break; - case MIRROR_vertR: - for (int i=0;i<image->w;i++){ - for (int j=0;j<image->h/2;j++){ - for (int k=0;k<3;k++){ - image->RGBdata[((((image->h/2-j)*image->w)+i)*3)+k]=image->RGBdata[((((image->h/2+j)*image->w)+i)*3)+k]; - } + } + break; + case MIRROR_vertR: + for (int i=0;i<image.w;i++){ + for (int j=0;j<image.h/2;j++){ + for (int k=0;k<3;k++){ + image.RGBdata[((((image.h/2-j)*image.w)+i)*3)+k]=image.RGBdata[((((image.h/2+j)*image.w)+i)*3)+k]; } } - break; - } - return image; - } + } + break; + } + return ℑ } return nullptr; } private: - Image *image; //is an image generator - int mode; }; class Monochrome: public Image_node { public: - Monochrome(){}; - Monochrome(map<string,string> &settings) { + Monochrome(){ + create_image_input("image input","Image input"); + title="Monochrome"; + description="Render video greyscale"; + }; + Monochrome(map<string,string> &settings):Monochrome() { base_settings(settings); }; ~Monochrome(){ }; Monochrome* clone(map<string,string> &_settings) { return new Monochrome(_settings);}; Image *output(const Frame_spec &frame){ - if (image_inputs.size()) { - if (image_inputs[0]->connection){ - Image *other=(((Image_node*)image_inputs[0]->connection)->get_output(frame)); - image.setup(other->w,other->h); - for (int i=0;i<image.w;i++){ - for (int j=0;j<image.h;j++){ - uint8_t luma=0; - for (int l=0;l<3;l++) luma+=pixels.mono_weights[l][other->RGBdata[(((j*image.w)+i)*3)+l]]; - for (int k=0;k<3;k++) image.RGBdata[(((j*image.w)+i)*3)+k]=luma; - } + Image *in=image_inputs[0]->get(frame); + if (in){ + for (int i=0;i<image.w;i++){ + for (int j=0;j<image.h;j++){ + uint8_t luma=0; + for (int l=0;l<3;l++) luma+=pixels.mono_weights[l][in->RGBdata[(((j*image.w)+i)*3)+l]]; + for (int k=0;k<3;k++) image.RGBdata[(((j*image.w)+i)*3)+k]=luma; } - return ℑ - } + } + return ℑ } return nullptr; } private: - Image image; }; class Transform: public Image_node { //what is the best coordinate system to use? @@ -933,133 +778,128 @@ namespace Rotor { //units: pixel or fractional //aspect: scaled or homogenous public: - Transform(){}; - Transform(map<string,string> &settings) { + Transform(){ + create_image_input("image input","Image input"); + create_parameter("transformX","number","X transformation","Transform X",0.0f); + create_parameter("transformY","number","Y transformation","Transform X",0.0f); + create_parameter("originX","number","X transformation origin","Origin X",0.5f); + create_parameter("originY","number","Y transformation origin","Origin Y",0.5f); + create_parameter("rotation","number","Rotation about origin","Rotation",0.0f); + create_parameter("scale","number","Scale about origin","Scale",1.0f); + title="Transform"; + description="Apply 2D transformation"; + }; + Transform(map<string,string> &settings):Transform() { base_settings(settings); - tX=find_setting(settings,"transformX",0.0f); - tY=find_setting(settings,"transformY",0.0f); - oX=find_setting(settings,"originX",0.5f); - oY=find_setting(settings,"originX",0.5f); - r=find_setting(settings,"rotation",0.0f); - s=find_setting(settings,"scale",1.0f); }; ~Transform(){ }; - void link_params() { - for (auto p:parameter_inputs){ - if (p->parameter=="scale") p->receiver=&s; - if (p->parameter=="rotation") p->receiver=&r; - if (p->parameter=="transformX") p->receiver=&tX; - if (p->parameter=="transformY") p->receiver=&tY; - if (p->parameter=="originX") p->receiver=&oX; - if (p->parameter=="originY") p->receiver=&oY; - } - }; Transform* clone(map<string,string> &_settings) { return new Transform(_settings);}; Image *output(const Frame_spec &frame){ - if (image_inputs.size()) { - if (image_inputs[0]->connection){ - Image *other=(((Image_node*)image_inputs[0]->connection)->get_output(frame)); - if (other) { - image.setup(other->w,other->h); - //do opencv transform - cv::Point2f srcTri[3], dstTri[3]; - cv::Mat rot_mat(2,3,CV_32FC1); - cv::Mat trans_mat(2,3,CV_32FC1); + Image *in=image_inputs[0]->get(frame); + if (in){ + float tX=parameters["transformX"]->value; + float tY=parameters["transformY"]->value; + float oX=parameters["originX"]->value; + float oY=parameters["originY"]->value; + float r=parameters["rotation"]->value; + float s=parameters["scale"]->value; + //do opencv transform + cv::Point2f srcTri[3], dstTri[3]; + cv::Mat rot_mat(2,3,CV_32FC1); + cv::Mat trans_mat(2,3,CV_32FC1); - Image inter; - inter.setup(other->w,other->h); - // Compute matrix by creating triangle and transforming - //is there a better way - combine the 2? Just a bit of geometry - srcTri[0].x=0; - srcTri[0].y=0; - srcTri[1].x=other->w-1; - srcTri[1].y=0; - srcTri[2].x=0; - srcTri[2].y=other->h-1; - for (int i=0;i<3;i++){ - dstTri[i].x=srcTri[i].x+(tX*other->w); - dstTri[i].y=srcTri[i].y+(tY*other->h); - } - trans_mat=getAffineTransform( srcTri, dstTri ); - warpAffine( other->rgb, inter.rgb, trans_mat, inter.rgb.size(), cv::INTER_LINEAR, cv::BORDER_WRAP); + Image inter; + inter.setup(in->w,in->h); + // Compute matrix by creating triangle and transforming + //is there a better way - combine the 2? Just a bit of geometry + srcTri[0].x=0; + srcTri[0].y=0; + srcTri[1].x=in->w-1; + srcTri[1].y=0; + srcTri[2].x=0; + srcTri[2].y=in->h-1; + for (int i=0;i<3;i++){ + dstTri[i].x=srcTri[i].x+(tX*in->w); + dstTri[i].y=srcTri[i].y+(tY*in->h); + } + trans_mat=getAffineTransform( srcTri, dstTri ); + warpAffine( in->rgb, inter.rgb, trans_mat, inter.rgb.size(), cv::INTER_LINEAR, cv::BORDER_WRAP); - // Compute rotation matrix - // - cv::Point centre = cv::Point( oX*other->w, oY*other->h ); + // Compute rotation matrix + // + cv::Point centre = cv::Point( oX*in->w, oY*in->h ); - rot_mat = getRotationMatrix2D( centre, r, s ); - // Do the transformation - // - warpAffine( inter.rgb, image.rgb, rot_mat, image.rgb.size(), cv::INTER_LINEAR, cv::BORDER_WRAP); - //BORDER_WRAP + rot_mat = getRotationMatrix2D( centre, r, s ); + // Do the transformation + // + warpAffine( inter.rgb, image.rgb, rot_mat, image.rgb.size(), cv::INTER_LINEAR, cv::BORDER_WRAP); + //BORDER_WRAP - //INTER_NEAREST - a nearest-neighbor interpolation - //INTER_LINEAR - a bilinear interpolation (used by default) - //INTER_AREA - resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method. - //INTER_CUBIC - a bicubic interpolation over 4x4 pixel neighborhood - //INTER_LANCZOS4 - a Lanczos interpolation over 8x8 pixel neighborhood + //INTER_NEAREST - a nearest-neighbor interpolation + //INTER_LINEAR - a bilinear interpolation (used by default) + //INTER_AREA - resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method. + //INTER_CUBIC - a bicubic interpolation over 4x4 pixel neighborhood + //INTER_LANCZOS4 - a Lanczos interpolation over 8x8 pixel neighborhood - return ℑ - } + return ℑ } - } return nullptr; } private: - Image image; - float tX,tY,oX,oY,r,s; //todo - quality settings }; class Alpha_merge: public Image_node { public: - Alpha_merge(){image=nullptr;}; - Alpha_merge(map<string,string> &settings) { + Alpha_merge(){ + create_image_input("image input","Image input"); + create_image_input("alpha input","Alpha input"); + create_parameter("transformX","number","X transformation","Transform X",0.0f); + title="Alpha merge"; + description="Alpha merge two images"; + }; + Alpha_merge(map<string,string> &settings):Alpha_merge() { base_settings(settings); - image=nullptr; }; - ~Alpha_merge(){ if (image) delete image;}; + ~Alpha_merge(){}; Alpha_merge* clone(map<string,string> &_settings) { return new Alpha_merge(_settings);}; Image *output(const Frame_spec &frame){ - if (image_inputs.size()) { - if (image_inputs[0]->connection){ - //copy incoming image **writable - if (image) delete image; - image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone(); - if (image_inputs.size()>1) { - if (image_inputs[1]->connection) { - image->alpha_merge(*((Image_node*)image_inputs[1]->connection)->get_output(frame)); - } - } - //if there aren't 2 image inputs connected just return the first - return image; - } - } + Image *in1=image_inputs[0]->get(frame); + if (in1){ + //copy incoming image **writable + Image *in2=image_inputs[1]->get(frame); + if (in2) { + image=(*in1); + image.alpha_merge(*in2); + return ℑ + } + //if there aren't 2 image inputs connected just return the first + return in1; + } return nullptr; } private: - Image *image; //is an image generator }; #define VIDEOFRAMES_still 1 #define VIDEOFRAMES_blend 2 class Video_loader: public Image_node { public: - Video_loader(){}; - Video_loader(map<string,string> &settings) { + Video_loader(){ + create_parameter("speed","number","video playback speed","Speed",1.0f,0.0f,0.0f); + create_parameter("framerate","number","framerate override","Frame rate",0.0f,0.0f,0.0f); + create_attribute("filename","name of video file to load","File name",""); + create_attribute("mode","frame mode","Mode","still",{"still","blend"}); + title="Video loader"; + description="Loads a video file"; + }; + Video_loader(map<string,string> &settings): Video_loader() { base_settings(settings); isLoaded=false; - filename=find_setting(settings,"filename",""); - speed=find_setting(settings,"speed",1.0f); - framerate=find_setting(settings,"framerate",0.0f); - //0.0f signifies to use the internal framerate - if (filename!="") { - load(find_setting(settings,"media_path","")+filename); + if (attributes["filename"]->value!="") { + load(find_setting(settings,"media_path","")+attributes["filename"]->value); } - string frame_op=find_setting(settings,"mode","still"); - if (frame_op=="still") mode=VIDEOFRAMES_still; - if (frame_op=="blend") mode=VIDEOFRAMES_blend; lastframe=0; }; ~Video_loader(){}; @@ -1069,55 +909,22 @@ namespace Rotor { bool isLoaded; private: libav::decoder player; - Image image; - string filename; - float speed; - float framerate; int mode; int lastframe; }; - /* - class CVideo_loader: public Image_node { - - // attempt - - // /usr/bin/ld: warning: libavcodec.so.53, needed by /usr/lib/gcc/i686-linux-gnu/4.7/../../../../lib/libopencv_highgui.so, may conflict with libavcodec.so.55 - // /usr/bin/ld: warning: libavformat.so.53, needed by /usr/lib/gcc/i686-linux-gnu/4.7/../../../../lib/libopencv_highgui.so, may conflict with libavformat.so.55 - // /usr/bin/ld: warning: libavutil.so.51, needed by /usr/lib/gcc/i686-linux-gnu/4.7/../../../../lib/libopencv_highgui.so, may conflict with libavutil.so.52 - - // No URL Protocols are registered. Missing call to av_register_all()? - // libav::Error: file:///mnt/rotor/media/newsins1_360.mp4 Protocol not found - // Rotor::Video_loader: failed to load /mnt/rotor/media/newsins1_360.mp4 - // 30-07-2013 09:35:31 Rotor: ERROR: could not load newsins1_360.mp4 into video node 03 - - public: - CVideo_loader(){}; - CVideo_loader(map<string,string> &settings) { - base_settings(settings); - isLoaded=false; - }; - ~CVideo_loader(){}; - bool load(const string &filename); - Image *output(const Frame_spec &frame); - CVideo_loader* clone(map<string,string> &_settings) { return new CVideo_loader(_settings);}; - bool isLoaded; - private: - cv::VideoCapture player; - Image image; - }; - */ class Video_output: public Image_node { public: - Video_output(){}; - Video_output(map<string,string> &settings) { + Video_output(){ + create_image_input("image to output","Image input"); + title="Video output"; + description="Outputs to video from here"; + }; + Video_output(map<string,string> &settings):Video_output() { base_settings(settings); }; ~Video_output(){ }; Image *output(const Frame_spec &frame){ - if (image_inputs[0]->connection) { - return ((Image_node*)(image_inputs[0]->connection))->get_output(frame); - } - else return nullptr; + return image_inputs[0]->get(frame); }; Video_output* clone(map<string,string> &_settings) { return new Video_output(_settings);}; bool render(const float duration, const float framerate,const string &output_filename,const string &audio_filename,float& progress,int w,int h); @@ -1125,7 +932,7 @@ namespace Rotor { private: }; - //------------------------------------------------------------------- + //------------------------------------------------------------------- class Node_factory{ public: Node_factory(); @@ -1146,26 +953,71 @@ namespace Rotor { void list_nodes(xmlIO XML){ int i=0; for (auto& type: type_map) { //c++11 - XML.addTag("node"); - XML.addAttribute("node","name",type.first,i); - if (dynamic_cast<Signal_node*> (type.second)!=nullptr) XML.addAttribute("node","type","signal",i); - if (dynamic_cast<Image_node*> (type.second)!=nullptr) XML.addAttribute("node","type","image",i); - XML.pushTag("node",i); - int j=0; - for (auto& input: type.second->inputs) { - XML.addTag("signal_input"); - XML.setValue("signal_input",input->description,j); - j++; - } - if (dynamic_cast<Image_node*> (type.second)!=nullptr) { - for (auto& input: (dynamic_cast<Image_node*>(type.second))->image_inputs) { - XML.addTag("image_input"); - XML.setValue("image_input",input->description,j); + if (type.second->description!="") { //blank description = internal/ testing node + XML.addTag("node"); + XML.addAttribute("node","type",type.first,i); + XML.addAttribute("node","inputs",type.second->duplicate_inputs?"expandable":"fixed",i); + XML.addAttribute("node","title",type.second->title,i); + XML.addAttribute("node","description",type.second->description,i); + if (dynamic_cast<Signal_node*> (type.second)!=nullptr) XML.addAttribute("node","output","signal",i); + if (dynamic_cast<Image_node*> (type.second)!=nullptr) XML.addAttribute("node","output","image",i); + XML.pushTag("node",i); + //if (type.second->description!="") { + // XML.addTag("description"); + // XML.setValue("description",type.second->description,0); + //} + int j=0; + for (auto& input: type.second->inputs) { + XML.addTag("signal_input"); + XML.addAttribute("signal_input","title",input->title,j); + XML.addAttribute("signal_input","description",input->description,j); j++; } + j=0; + if (dynamic_cast<Image_node*> (type.second)!=nullptr) { + for (auto& input: (dynamic_cast<Image_node*>(type.second))->image_inputs) { + XML.addTag("image_input"); + XML.addAttribute("image_input","title",input->title,j); + XML.addAttribute("image_input","description",input->description,j); + j++; + } + } + j=0; + for (auto& parameter: type.second->parameters) { + XML.addTag("parameter"); + XML.addAttribute("parameter","name",parameter.first,j); + XML.addAttribute("parameter","type",parameter.second->type,j); + XML.addAttribute("parameter","title",parameter.second->title,j); + XML.addAttribute("parameter","description",parameter.second->description,j); + XML.addAttribute("parameter","value",parameter.second->value,j); + XML.addAttribute("parameter","min",parameter.second->min,j); + XML.addAttribute("parameter","max",parameter.second->max,j); + j++; + } + j=0; + for (auto& attribute: type.second->attributes) { + XML.addTag("attribute"); + XML.addAttribute("attribute","name",attribute.first,j); + XML.addAttribute("attribute","title",attribute.second->title,j); + XML.addAttribute("attribute","description",attribute.second->description,j); + XML.addAttribute("attribute","value",attribute.second->value,j); + if (attribute.second->vals.size()){ //document attribute enumeration + XML.addAttribute("attribute","type","enum",j); + XML.pushTag("attribute",j); + int k=0; + for (auto val: attribute.second->vals){ + XML.addTag("option"); + XML.addAttribute("option","value",val,k); + k++; + } + XML.popTag(); + } + else XML.addAttribute("attribute","type","string",j); + j++; + } + XML.popTag(); + i++; } - XML.popTag(); - i++; } } private: @@ -1190,12 +1042,10 @@ namespace Rotor { Node* find_node(const string &type); bool signal_render(string &signal_xml,const float framerate); bool video_render(const string &output_filename,const string &audio_filename,const float framerate,float& progress); - int load(Poco::UUID uid); bool load(string data,string media_path); bool loadFile(string &filename,string media_path); bool parseXml(string media_path); bool set_resolution(int w,int h); - UUID save(); //save to DB, returns UUID of saved graph bool loaded; float duration; const string toString(); @@ -1204,7 +1054,7 @@ namespace Rotor { Node_factory factory; int outW,outH; }; - class Audio_thumbnailer: public Base_audio_processor { + class Audio_thumbnailer: public Audio_processor { public: Audio_thumbnailer(){ height=128; @@ -1226,62 +1076,6 @@ namespace Rotor { int offset; double scale,accum; }; - class Render_context: public Poco::Task { //Poco task object - //manages a 'patchbay' - //high level interfaces for the wizard - //and low level interface onto the graph - public: - Render_context(const std::string& name): Task(name) { - audio_thumb=new Audio_thumbnailer(); - state=IDLE; - output_framerate=25.0f; - audio_loaded=false; - - xmlIO xml; - if(xml.loadFile("settings.xml") ){ - graph_dir=xml.getAttribute("Rotor","graph_dir","",0); - media_dir=xml.getAttribute("Rotor","media_dir","",0); - output_dir=xml.getAttribute("Rotor","output_dir","",0); - } - - else cerr<<"Rotor: settings.xml not found, using defaults"<<endl; - }; - ~Render_context(){delete audio_thumb;}; - void runTask(); - void add_queue(int item); - Command_response session_command(const std::vector<std::string>& command); - void session_command(const std::vector<std::string>& command,xmlIO& XML,HTTPResponse::HTTPStatus& status); - Render_status get_status(); - void cancel(); //interrupt locking process - int make_preview(int nodeID, float time); //starts a frame preview - returns status code - how to retrieve? - bool load_audio(const string &filename,vector<Base_audio_processor*> processors); - bool _load_audio(const string &filename,vector<Base_audio_processor*> processors); - Render_requirements get_requirements(); - bool load_video(const string &nodeID,const string &filename);//can be performance or clip - private: - int state; - float progress; //for a locking process: audio analysis or rendering - //thread only does one thing at once - std::deque<int> work_queue; - Poco::Mutex mutex; //lock for access from parent thread - std::string audio_filename; - std::string output_filename; - std::string graph_dir; - std::string media_dir; - std::string output_dir; - - Audio_thumbnailer *audio_thumb; - Graph graph; - Node_factory factory; - float output_framerate; - bool audio_loaded; - - }; } -/* -coding style -Types begin with capitals 'New_type' -variables/ instances use lower case with underscore as a seperator -*/ -#endif +#endif
\ No newline at end of file diff --git a/rotord/src/rotord.h b/rotord/src/rotord.h index 7656c28..5d4398b 100755 --- a/rotord/src/rotord.h +++ b/rotord/src/rotord.h @@ -6,6 +6,7 @@ #include "Poco/Net/HTTPServerResponse.h" #include "Poco/Net/HTTPServerParams.h" #include "Poco/Net/ServerSocket.h" +#include "Poco/UUIDGenerator.h" #include "Poco/Timestamp.h" #include "Poco/DateTimeFormatter.h" #include "Poco/DateTimeFormat.h" @@ -67,7 +68,7 @@ using Poco::Message; using Poco::AutoPtr; -#include "rotor.h" +#include "rendercontext.h" class RenderContextHandler: public HTTPRequestHandler diff --git a/rotord/src/tinyxml.cpp b/rotord/src/tinyxml.cpp index 0c79683..9b26b96 100755 --- a/rotord/src/tinyxml.cpp +++ b/rotord/src/tinyxml.cpp @@ -1790,7 +1790,7 @@ bool TiXmlPrinter::VisitEnter( const TiXmlElement& element, const TiXmlAttribute && element.FirstChild()->ToText()->CDATA() == false ) { simpleTextPrint = true; - // no DoLineBreak()! + //DoLineBreak()! } else { @@ -1846,7 +1846,7 @@ bool TiXmlPrinter::Visit( const TiXmlText& text ) } else { - DoIndent(); + //DoIndent(); TIXML_STRING str; TiXmlBase::EncodeString( text.ValueTStr(), &str ); buffer += str; |
