/* 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 */ #include #include #include #include #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 using Poco::UUID; using Poco::UUIDGenerator; using Poco::Net::HTTPResponse; /* extern "C" { #include #include #include #include #include #include #include #include #include //#include stops the compiler error but causes a linker error. does libavcodec need to be statically linked? #include #include //#include } */ #define AUDIO_INBUF_SIZE 20480 #define AUDIO_REFILL_THRESH 4096 #include "vampHost.h" #include "xmlIO.h" #include "utils.h" //fequal #include "libavwrapper.h" namespace Rotor { #define IDLE 0 #define ANALYSING_AUDIO 1 #define AUDIO_READY 2 #define CREATING_PREVIEW 3 #define PREVIEW_READY 4 #define RENDERING 5 #define RENDER_READY 6 #define ANALYSE_AUDIO 1 #define PREVIEW 2 #define RENDER 3 //forward declaration 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; } ~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 pixeltables{ //handy pixel arithmetic lookup tables as nested arrays //so - pixels.add[0x78][0x66]; will give the precalculated result of adding with saturation public: pixeltables(){ add=new uint8_t*[256]; multiply=new uint8_t*[256]; for (int i=0;i<256;i++){ add[i]=new uint8_t[256]; multiply[i]=new uint8_t[256]; for (int j=0;j<256;j++){ add[i][j]=(uint8_t)min(i+j,0xFF); multiply[i][j]=(uint8_t)((((float)i)/255.0f)*(((float)j)/255.0f)*255.0f); } } } virtual ~pixeltables(){ for (int i=0;i<256;i++){ delete[] add[i]; delete[] multiply[i]; } delete[] add; delete[] multiply; } uint8_t **add; uint8_t **multiply; }; static pixeltables pixels; class Time_spec{ public: Time_spec(){}; Time_spec(float _time,float _framerate,float _duration){ time=_time; framerate=_framerate; duration=_duration;}; float time; float framerate; float duration; Time_spec lastframe() const{ return Time_spec(time-(1.0f/framerate),framerate,duration); } }; 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; int h,w; //Frame_spec lastframe(){ // return Frame_spec(time-(1.0f/framerate),framerate,w,h); //} int frame(){ return (int)((time*framerate)+0.5); //rounded to the nearest frame } }; class Colour{ public: Colour(){ r=g=b=0; } Colour(int c){ r=c&0xFF; g=(c&0xFF00)>>8; b=(c&0xFF0000)>>16; } Colour(std::string s){ r=(uint8_t)ofHexToChar(s.substr(0,2)); g=(uint8_t)ofHexToChar(s.substr(2,2)); b=(uint8_t)ofHexToChar(s.substr(4,2)); } uint8_t r,g,b; }; class Image{ public: Image(){ zero(); }; Image(int _w,int _h){ zero(); setup(_w,_h); }; ~Image() { free(); }; void free(){ if (RGBdata&&ownsRGBdata) delete[] RGBdata; if (Adata&&ownsAdata) delete[] Adata; if (Zdata&&ownsZdata) delete[] Zdata; zero(); } void zero(){ RGBdata=nullptr; Adata=nullptr; Zdata=nullptr; w=0; h=0; ownsRGBdata=ownsAdata=ownsZdata=false; } bool setup(int _w,int _h){ //set up with internal data if (w!=_w||h!=_h||!ownsRGBdata||!ownsAdata||!ownsZdata){ free(); w=_w; h=_h; RGBdata=new uint8_t[w*h*3]; Adata=new uint8_t[w*h]; Zdata=new uint16_t[w*h]; ownsRGBdata=ownsAdata=ownsZdata=true; return true; } else return false; } bool setup_fromRGB(int _w,int _h,uint8_t *pRGBdata){ //possibility of just resetting pointer? if (w!=_w||h!=_h||ownsRGBdata||!ownsAdata||!ownsZdata){ free(); w=_w; h=_h; RGBdata=pRGBdata; Adata=new uint8_t[w*h]; Zdata=new uint16_t[w*h]; ownsRGBdata=false; ownsAdata=ownsZdata=true; return true; } return false; } Image* clone(){ Image *t=new Image(w,h); for (int i=0;iRGBdata[i]=RGBdata[i]; } return t; } Image & operator+=(const Image &other) { if (other.w!=w||other.h!=h) { cerr<<"Rotor: cannot add images with different sizes! (wanted "<RGBdata[i]=LUT[RGBdata[i]]; } delete[] LUT; return other; } Image * operator+(const float &amount) { Image *other=new Image(w,h); uint8_t *LUT=new uint8_t[256]; for (int i=0;i<256;i++) { LUT[i]=(uint8_t)min(0xFF,max(0,(int)(i+(amount*255.0f)))); } for (int i=0;iRGBdata[i]=LUT[RGBdata[i]]; } delete[] LUT; return other; } Image * operator-(const float &amount) { Image *other=new Image(w,h); uint8_t *LUT=new uint8_t[256]; for (int i=0;i<256;i++) { LUT[i]=(uint8_t)min(0xFF,max(0,(int)(i-(amount*255.0f)))); } for (int i=0;iRGBdata[i]=LUT[RGBdata[i]]; } delete[] LUT; return other; } Image * operator/(const float &amount) { Image *other=new Image(w,h); uint8_t *LUT=new uint8_t[256]; for (int i=0;i<256;i++) { LUT[i]=(uint8_t)min(0xFF,max(0,(int)(i/amount))); } for (int i=0;iRGBdata[i]=LUT[RGBdata[i]]; } delete[] LUT; return other; } uint8_t *RGBdata; uint8_t *Adata; uint16_t *Zdata; int h,w; bool ownsRGBdata,ownsAdata,ownsZdata; //better done through auto_ptr? }; 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; } std::string description; Poco::Net::HTTPResponse::HTTPStatus status; }; class Input{ public: Input(const string &_desc): connection(nullptr),description(_desc){}; Node* connection; string description; }; class Image_input: public Input{ public: bool connect(Image_node *source); Image_input(const string &_desc): Input(_desc){}; }; class Signal_input: public Input{ public: bool connect(Signal_node *source); Signal_input(const string &_desc): Input(_desc){}; }; class Parameter_input: 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; }; class Node{ public: virtual Node* clone(map &_settings)=0; virtual ~Node(){}; UUID uid; //every usable node has a UUID int id; vector inputs; //simple node can have signal inputs, output depends on node type vector 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));}; string description; string type; string output_type; string ID; string find_setting(map &settings,string key,string def=""){ if (settings.find(key)!=settings.end()) return settings[key]; else return def;}; float find_setting(map &settings,string key,float def){ if (settings.find(key)!=settings.end()) return ofToFloat(settings[key]); else return def;}; int find_setting(map &settings,string key,int def){ if (settings.find(key)!=settings.end()) return ofToInt(settings[key]); else return def;}; void base_settings(map &settings) { description=find_setting(settings,"description"); type=find_setting(settings,"type"); output_type=find_setting(settings,"output"); ID=find_setting(settings,"ID"); } 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; }; }; class Image_node: public Node{ public: virtual ~Image_node(){}; vector 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); }; 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; }; class Base_audio_processor: public Signal_node { public: virtual ~Base_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; virtual void print_summary(){}; int channels,bits,samples,rate; }; //actual nodes------------------------------------------------- class Audio_analysis: public Base_audio_processor { public: Audio_analysis(){}; Audio_analysis(map &settings) { base_settings(settings); soname=find_setting(settings,"soname"); id=find_setting(settings,"id"); outputNo=find_setting(settings,"outputNo",0); }; Audio_analysis* clone(map &_settings) { return new Audio_analysis(_settings);}; bool init(int _channels,int _bits,int _samples,int _rate); void cleanup(); void set_parameter(const std::string &key,const std::string &value){params[key]=ofToFloat(value);}; int process_frame(uint8_t *data,int samples_in_frame); const float output(const Time_spec &time) { if (analyser.features.size()) { auto i=analyser.features.upper_bound(time.time); //the first element in the container whose key is considered to go after k if (i!=analyser.features.end()){ float uk=i->first; i--; float lk=i->first; int ln=i->second; return (((time.time-lk)/(uk-lk))+ln); } } return 0.0f; } void print_features(); void print_summary(){ cerr<<"vamp plugin "< params; }; class Time: public Signal_node { public: Time(){}; Time(map &settings) { base_settings(settings); }; Time* clone(map &_settings) { return new Time(_settings);}; const float output(const Time_spec &time) { return time.time; } }; class Track_time: public Signal_node { public: Track_time(){}; Track_time(map &settings) { base_settings(settings); }; Track_time* clone(map &_settings) { return new Track_time(_settings);}; const float output(const Time_spec &time) { return time.time/time.duration; } }; #define COMPARISON_Equal 1 #define COMPARISON_Not_equal 2 #define COMPARISON_Greater 3 #define COMPARISON_Less 4 #define COMPARISON_Greater_or_equal 5 #define COMPARISON_Less_or_equal 6 class Comparison: public Signal_node { public: Comparison(){}; Comparison(map &settings) { 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 &_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; } } } return 0.0f; } int op; float value; }; #define ARITHMETIC_plus 1 #define ARITHMETIC_minus 2 #define ARITHMETIC_multiply 3 #define ARITHMETIC_divide 4 #define ARITHMETIC_modulo 5 class Arithmetic: public Signal_node { public: Arithmetic(){}; Arithmetic(map &settings) { base_settings(settings); value=find_setting(settings,"value",0.0f); string _op=find_setting(settings,"operator","+"); if (_op=="+") op=ARITHMETIC_plus; if (_op=="-") op=ARITHMETIC_minus; if (_op=="*") op=ARITHMETIC_multiply; if (_op=="/") op=ARITHMETIC_divide; if (_op=="%") op=ARITHMETIC_modulo; } void link_params() { for (auto p:parameter_inputs){ p->receiver=nullptr; if (p->parameter=="value") p->receiver=&value; } }; Arithmetic* clone(map &_settings) { return new Arithmetic(_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 ARITHMETIC_plus: return in+value; break; case ARITHMETIC_minus: return in-value; break; case ARITHMETIC_multiply: return in*value; break; case ARITHMETIC_divide: return in/value; break; case ARITHMETIC_modulo: return fmod(in,value); break; } } } return 0.0f; } int op; float value; }; class Signal_divide: public Signal_node { public: Signal_divide(){}; Signal_divide(map &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 &_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 &settings) { base_settings(settings); }; Is_new_integer* clone(map &_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; } } return 0.0f; } }; class On_off: public Signal_node { public: On_off(){}; On_off(map &settings) { base_settings(settings); }; On_off* clone(map &_settings) { return new On_off(_settings);}; const float output(const Time_spec &time) { if (inputs[0]->connection) { float s1=(((Signal_node*)(inputs[0]->connection))->get_output(time)); if ((int)s1%2) return 1.0f; } return 0.0f; } }; //pseudo random repeatable hash function //http://create.stephan-brumme.com/fnv-hash/ const uint32_t Prime = 0x01000193; // 16777619 const uint32_t Seed = 0x811C9DC5; // 2166136261 /// hash a byte inline uint32_t fnv1a(unsigned char oneByte, uint32_t hash = Seed) { return (oneByte ^ hash) * Prime; } /// hash a short (two bytes) inline uint32_t fnv1a(unsigned short twoBytes, uint32_t hash = Seed) { const unsigned char* ptr = (const unsigned char*) &twoBytes; hash = fnv1a(*ptr++, hash); return fnv1a(*ptr , hash); } /// hash a 32 bit integer (four bytes) inline uint32_t fnv1a(uint32_t fourBytes, uint32_t hash = Seed) { const unsigned char* ptr = (const unsigned char*) &fourBytes; hash = fnv1a(*ptr++, hash); hash = fnv1a(*ptr++, hash); hash = fnv1a(*ptr++, hash); return fnv1a(*ptr , hash); } class Random: public Signal_node { public: Random(){}; Random(map &settings) { base_settings(settings); seed=(Seed+find_setting(settings,"seed",0)); cerr<<"random:: seed "< &_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; private: }; class Signal_output: public Signal_node { public: Signal_output(){}; Signal_output(map &settings) { base_settings(settings); }; Signal_output* clone(map &_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; } }; class Testcard: public Image_node { public: Testcard(){}; Testcard(map &settings) { base_settings(settings); image=new Image(); }; ~Testcard(){ delete image;}; Testcard* clone(map &_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;iRGBdata[(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; } private: Image *image; //is an image generator }; class Invert: public Image_node { public: Invert(){}; Invert(map &settings) { base_settings(settings); image=new Image(); }; ~Invert(){ delete image;}; Invert* clone(map &_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;iw*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; } return nullptr; } private: Image *image; //is an image generator //bool invert; }; class Video_output: public Image_node { public: Video_output(){}; Video_output(map &settings) { 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; }; Video_output* clone(map &_settings) { return new Video_output(_settings);}; bool render(const float duration, const float framerate,const string &output_filename,const string &audio_filename,float& progress); private: }; class Video_loader: public Image_node { public: Video_loader(){}; Video_loader(map &settings) { base_settings(settings); isLoaded=false; }; ~Video_loader(){}; bool load(const string &filename); Image *output(const Frame_spec &frame); Video_loader* clone(map &_settings) { return new Video_loader(_settings);}; private: libav::decoder player; Image image; bool isLoaded; }; class Video_cycler: public Image_node { //cycles through video inputs in order public: Video_cycler(){}; Video_cycler(map &settings) { 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; } Video_cycler* clone(map &_settings) { return new Video_cycler(_settings);}; private: }; class Signal_colour: public Image_node { //cycles through video inputs in order public: Signal_colour(){}; Signal_colour(map &settings) { base_settings(settings); string colours=find_setting(settings,"palette",""); for (int i=0;iconnection){ 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 &_settings) { return new Signal_colour(_settings);}; private: vector palette; Image image; int prevcol; }; class Signal_greyscale: public Image_node { //Draws signal bars in greyscale public: Signal_greyscale(){}; Signal_greyscale(map &settings) { 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 &_settings) { return new Signal_greyscale(_settings);}; private: Image image; uint8_t prevcol; }; class Image_arithmetic: public Image_node { //Draws signal bars in greyscale public: Image_arithmetic(){}; Image_arithmetic(map &settings) { base_settings(settings); value=find_setting(settings,"value",0.0f); string _op=find_setting(settings,"operator","+"); if (_op=="+") op=ARITHMETIC_plus; if (_op=="-") op=ARITHMETIC_minus; if (_op=="*") op=ARITHMETIC_multiply; if (_op=="/") op=ARITHMETIC_divide; //if (_op=="%") 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 *output(const Frame_spec &frame){ if (image_inputs.size()) { if (image_inputs[0]->connection){ if (image) delete image; 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; } } return nullptr; } Image_arithmetic* clone(map &_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 &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 &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); LUT=nullptr; generate_LUT(); } void generate_LUT(){ //check this 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); } } void apply_LUT(const Image& in){ apply_LUT(in,*image); } void apply_LUT(const Image& in,Image &out){ //facility to apply to other images for ingherited classes out.setup(in.w,in.h); for (int i=0;iconnection){ if (LUT) { apply_LUT(*(((Image_node*)image_inputs[0]->connection)->get_output(frame))); return image; } } } return nullptr; } Luma_levels* clone(map &_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 //the hard thing here is how to cache frames, if its done cleverly it could have no impact when //used linearly //Image needs to overload operator+ //need a clever data structure to cache frames - maybe a map of Image pointers //we know the frames we want to overlay as offsets ie -25,-20,-15,-10,-5 //do we keep 25 frames loaded in order to benefit? 25 PAL frames is 60MB so probably so //OK so: //make a new set of pointers //identify if any of the new pointers can inherit old frames //delete unneeded old frames //load new frames //do the calculations //new set of pointers? or track frames by absolute frame number? //with relative pointers and switching frames, could use auto_ptr? //this cache mechanism should maybe be inheritable too? //it could be hugely beneficial to only do the LUT once? //although maybe the way to do the fading is to have a LUT for each frame? //2 inputs - "This frame" and "keyed" //hmm, that is clumsy, could it inherit from luma_levels //or is it actually best to use alpha keying after all! public: Echo_trails(){}; Echo_trails(map &settings) { 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=new Image(); 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(){ for (auto i:images) {delete i.second;} }; Image *output(const Frame_spec &frame){ //check if cache is valid if (frame.w!=image->w||frame.h!=image->h){ //or framerate changed? //clear cache and start over images.clear(); lastframe=-1; //calculate frame interval //interval=(int)(((duration/number)*frame.framerate)+0.5); //total=interval*number; } int thisframe=frame.frame(); //iterate cache and throw out any obsolete frames 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) { delete (*i).second; i = images.erase(i); } else ++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) { //copy incoming image **writable image->free(); image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone(); for (int i=1;i-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 ("<add_wrap(*temp); } delete temp; } else { if (mode<0.5) (*image)+=*(images[absframe]); else (*image)=image->add_wrap(*(images[absframe])); } } } //for (int i=0;iRGBdata[i]=LUT[in->RGBdata[i]]; //} lastframe=thisframe; return image; } } } } return nullptr; } Echo_trails* clone(map &_settings) { return new Echo_trails(_settings);}; protected: float duration,fadeto; int number; int interval,total,lastframe; //number of frames between displayed echoes unordered_map images; float mode; //TODO make int, enum string parameter types }; #define BLEND_screen 1 #define BLEND_multiply 2 #define BLEND_blend 3 class Blend: public Image_node { public: Blend(){}; Blend(map &settings) { base_settings(settings); image=new Image(); 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; }; void link_params() { for (auto p:parameter_inputs){ if (p->parameter=="amount") p->receiver=&amount; } }; ~Blend(){ delete image;}; Blend* clone(map &_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 image->free(); 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_blend: (*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)); } } return nullptr; } private: Image *image; //is an image generator int mode; float amount; //for blend }; #define MIRROR_horiz 1 #define MIRROR_vert 1 /* class Mirror: public Image_node { public: Mirror(){}; Mirror(map &settings) { base_settings(settings); image=new Image(); amount=find_setting(settings,"amount",1.0f); string _mode=find_setting(settings,"mode","screen"); if (_mode=="horiz") mode=BLEND_screen; if (_mode=="vert") mode=BLEND_multiply; }; ~Mirror(){ delete image;}; Mirror* clone(map &_settings) { return new Mirror(_settings);}; Image *output(const Frame_spec &frame){ if (image_inputs.size()) { if (image_inputs[0]->connection){ //if there aren't 2 image inputs connected just return the first return (((Image_node*)image_inputs[0]->connection)->get_output(frame)); } } return nullptr; } private: Image *image; //is an image generator int mode; }; */ //------------------------------------------------------------------- class Node_factory{ public: Node_factory(); ~Node_factory(){ for (auto t:type_map) delete t.second; } void add_type(string type,Node* proto){ type_map[type]=proto; }; Node *create(map &settings){ if (settings.find("type")!=settings.end()) { if (type_map.find(settings["type"])!=type_map.end()) { return type_map[settings["type"]]->clone(settings); } } return NULL; }; private: unordered_map type_map; }; class Graph{ public: Graph(){duration=10.0f;loaded = false;}; Graph(const string& _uid,const string& _desc){init(_uid,_desc);}; void init(const string& _uid,const string& _desc){ uid=_uid;description=_desc;duration=10.0f;}; string uid; //every version of a graph has a UUID, no particular need to actually read its data(?) //?? is it faster than using strings?? string description; std::unordered_map nodes; vector find_nodes(const string &type); //could be a way of finding a set based on capabilities? 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 &graph_filename); UUID save(); //save to DB, returns UUID of saved graph bool loaded; float duration; const string toString(); private: Node_factory factory; xmlIO xml; }; class Audio_thumbnailer: public Base_audio_processor { public: Audio_thumbnailer(){ height=128; width=512; //fit data=new uint8_t[height*width]; memset(data,0,height*width); }; ~Audio_thumbnailer(){ delete[] data; }; Audio_thumbnailer* clone(map &_settings) { return new Audio_thumbnailer();}; bool init(int _channels,int _bits,int _samples,int _rate); void cleanup(){}; int process_frame(uint8_t *data,int samples_in_frame); string print(); uint8_t *data; int height,width,samples_per_column; int column,out_sample,sample,samples; 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"<& command); 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 processors); bool _load_audio(const string &filename,vector 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 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 */