diff options
| -rw-r--r-- | rotord/src/graph.cpp | 12 | ||||
| -rw-r--r-- | rotord/src/nodes_audio_analysis.cpp | 135 | ||||
| -rw-r--r-- | rotord/src/nodes_audio_analysis.h | 26 | ||||
| -rw-r--r-- | rotord/src/nodes_maths.h | 6 | ||||
| -rw-r--r-- | rotord/src/nodes_transform.h | 6 | ||||
| -rw-r--r-- | rotord/src/rendercontext.cpp | 25 | ||||
| -rwxr-xr-x | rotord/src/rotor.cpp | 118 | ||||
| -rwxr-xr-x | rotord/src/rotor.h | 2 | ||||
| -rw-r--r-- | rotord/src/vampHost.cpp | 17 | ||||
| -rw-r--r-- | rotord/src/vampHost.h | 9 |
10 files changed, 221 insertions, 135 deletions
diff --git a/rotord/src/graph.cpp b/rotord/src/graph.cpp index 03192f3..3d0a88b 100644 --- a/rotord/src/graph.cpp +++ b/rotord/src/graph.cpp @@ -33,6 +33,18 @@ bool Graph::signal_render(string &signal_xml,const float framerate) { return false; } */ +bool Graph::print_features(xmlIO &XML,string &node){ + if (nodes.find(node)!=nodes.end()){ + if (dynamic_cast<Audio_processor*>(nodes[node])){ + XML.addValue("features",dynamic_cast<Audio_processor*>(nodes[node])->get_features()); + return true; + } + XML.addValue("error","node /"+node+"/ is not an Audio processor"); + return false; + } + XML.addValue("error","could not find node /"+node+"/"); + return false; +} bool Graph::preview(xmlIO &XML,string &node,string &_format,int frame,int w,int h){ if (nodes.find(node)!=nodes.end()){ float t=frame/framerate; diff --git a/rotord/src/nodes_audio_analysis.cpp b/rotord/src/nodes_audio_analysis.cpp new file mode 100644 index 0000000..925858f --- /dev/null +++ b/rotord/src/nodes_audio_analysis.cpp @@ -0,0 +1,135 @@ +#include "nodes_audio_analysis.h" + +namespace Rotor{ + bool Audio_thumbnailer::init(int _channels,int _bits,int _samples,int _rate) { + //base_audio_processor::init(_channels,_bits,_samples); + channels=_channels; + bits=_bits; + samples=_samples; + samples_per_column=samples/width; + column=0; //point thumbnail bitmap + out_sample=0; //sample in whole track + offset=0x1<<(bits-1); //signed audio + scale=1.0f/offset; + sample=0; + samples=0; + accum=0.0; + return true; + } + int Audio_thumbnailer::process_frame(uint8_t *_data,int samples_in_frame){ + //begin by processing remaining samples + //samples per column could be larger than a frame! (probably is) + //but all we are doing is averaging + int bytes=(bits>>3); + int stride=channels*bytes; + int in_sample=0; + while (in_sample<samples_in_frame&&column<width) { + //continue the column + while (sample<samples_per_column&&in_sample<samples_in_frame) { + //accumulate samples for this column until we run out of samples + for (int i=0;i<channels;i++) { + unsigned int this_val=0; + for (int j=0;j<bytes;j++) { + this_val+=_data[(in_sample*stride)+(i*bytes)+j]<<(j*8); + } + //convert from integer data format - i.e s16p - to audio signal in -1..1 range + //presume 16 bits for now... + double val=((double)((int16_t)this_val))*scale; + accum+=val*val; + samples++; + } + in_sample++; + sample++; + out_sample++; + } + if (sample==samples_per_column) { //finished a column + //get root-mean + double mean=pow(accum/samples,0.5); + //if (column==0) { + // cerr << "first column total: "<< accum << " in " << samples << " samples, average " << (accum/samples)<<endl; + //} + int colheight=height*mean*0.5; + int hh=height>>1; + for (int i=0;i<height;i++) { + data[i*width+column]=abs(i-hh)<colheight?0xff:0x00; + } + vectordata[column]=mean; + column++; + sample=0; + samples=0; + accum=0.0; + } + } + return out_sample; + } + string Audio_thumbnailer::print(){ + //base64 encode the image data output it + + stringstream output; + Poco::Base64Encoder *enc=new Poco::Base64Encoder(output); + + enc->write((char*)data,width*height); + //tring output; + /* + for (int j=0;j<height;j++) { + for (int i=0;i<width;i++) { + output+=data[j*width+i]<0x7f?"0":"1"; + } + output +="\n"; + } + */ + enc->close(); + delete enc; + return output.str(); + } + void Audio_thumbnailer::print_vector(xmlIO XML){ + string vdata; + for (int i=0;i<width;i++){ + if (i>0) vdata+=","; + vdata+=ofToString(vectordata[i]); + } + XML.addValue("data",vdata); + } + bool Audio_analysis::init(int _channels,int _bits,int _samples, int _rate) { + //need these to make sense of data + channels=_channels; + bits=_bits; + samples=_samples; + + return analyser.init(soname,id,_channels,_bits,_samples,_rate,outputNo,params); + + + //attempt to load vamp plugin and prepare to receive frames of data + //should the audio analysis contain a vamphost or should it inherit? + //maybe neater to contain it in terms of headers etc + + } + int Audio_analysis::process_frame(uint8_t *data,int samples_in_frame) { + analyser.process_frame(data,samples_in_frame); + return 1; + } + void Audio_analysis::cleanup() { + analyser.cleanup(); + //print_features(); + } + string Audio_analysis::get_features(){ + string data; + for (auto i: analyser.features) { + data=data+" ["+ofToString(i.second.number)+":"+ofToString(i.first); + if (i.second.values.size()) { + data+=" ("; + bool first=true; + for (auto j: i.second.values) { + if (first){ + first=false; + } + else data+=","; + data=data+ofToString(j); + } + data+=") "; + } + data+="]"; + } + return data; + } +}
\ No newline at end of file diff --git a/rotord/src/nodes_audio_analysis.h b/rotord/src/nodes_audio_analysis.h index 9252db4..fba4bcc 100644 --- a/rotord/src/nodes_audio_analysis.h +++ b/rotord/src/nodes_audio_analysis.h @@ -5,12 +5,17 @@ #include "vampHost.h" namespace Rotor { +#define VAMPHOST_Timeline 1 +#define VAMPHOST_Timesteps 2 +#define VAMPHOST_Valueline 3 +#define VAMPHOST_Values 4 class Audio_analysis: public Audio_processor { public: 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_attribute("mode","Data output mode","Mode","timeline",{"timeline","timesteps","valueline","values"}); create_parameter("outputNo","number","Plugin output to use","Output number",0.0f); title="Audio analysis"; description="Analyse audio and output"; @@ -31,19 +36,32 @@ namespace Rotor { 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()) { + 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; + float v1,v2; + v1=v2=0.0f; + if (i->second.values.size()) v2=i->second.values[0]; i--; float lk=i->first; - int ln=i->second; - return (((time.time-lk)/(uk-lk))+ln); + int ln=i->second.number; + if (i->second.values.size()) v1=i->second.values[0]; + switch (attributes["mode"]->intVal){ + case VAMPHOST_Timeline: + return (((time.time-lk)/(uk-lk))+ln); + case VAMPHOST_Timesteps: + return (float)ln; + case VAMPHOST_Valueline: + return (((time.time-lk)/(v2-v1))+v1); + case VAMPHOST_Values: + return v1; + } } } return 0.0f; } - void print_features(); + string get_features(); void print_summary(){ cerr<<"vamp plugin "<<id<<" of library "<<soname<<" found "<<analyser.features.size()<<" features "<<endl; }; diff --git a/rotord/src/nodes_maths.h b/rotord/src/nodes_maths.h index 760040d..1853e2c 100644 --- a/rotord/src/nodes_maths.h +++ b/rotord/src/nodes_maths.h @@ -63,12 +63,13 @@ namespace Rotor { #define ARITHMETIC_jolt 10 #define ARITHMETIC_floor 11 #define ARITHMETIC_2pow 12 +#define ARITHMETIC_reciprocal 13 class Arithmetic: public Signal_node { public: 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","floor","2pow"}); + create_attribute("operator","operator for image","Operator","+",{"+","-","*","/","%","^","sin","cos","ease","jolt","floor","2pow","reciprocal"}); title="Arithmetic"; description="Performs arithmetic on a signal with a signal or value"; }; @@ -124,6 +125,9 @@ namespace Rotor { case ARITHMETIC_2pow: return pow(2,in); break; + case ARITHMETIC_reciprocal: + return parameters["value"]->value/in; + break; } } } diff --git a/rotord/src/nodes_transform.h b/rotord/src/nodes_transform.h index bb3a04f..fd6b7be 100644 --- a/rotord/src/nodes_transform.h +++ b/rotord/src/nodes_transform.h @@ -25,11 +25,6 @@ namespace Rotor { create_parameter("scale","number","Scale about origin","Scale",1.0f); create_attribute("filter","Filtering mode","Filter mode","linear",{"nearest","linear","area","cubic","lanczos"}); }; - Transformer(map<string,string> &settings):Transformer() { - base_settings(settings); - }; - ~Transformer(){ - }; Image *transform(Image *in){ if (in){ //INTER_NEAREST - a nearest-neighbor interpolation @@ -85,7 +80,6 @@ namespace Rotor { trans_mat=getAffineTransform( srcTri, dstTri ); warpAffine( in->rgb, inter.rgb, trans_mat, inter.rgb.size(), filtmode, cv::BORDER_WRAP); - // Compute rotation matrix // cv::Point centre = cv::Point( oX*image.w, oY*image.h ); diff --git a/rotord/src/rendercontext.cpp b/rotord/src/rendercontext.cpp index 7d85cce..3f20923 100644 --- a/rotord/src/rendercontext.cpp +++ b/rotord/src/rendercontext.cpp @@ -342,6 +342,31 @@ void Render_context::session_command(const Session_command& command,xmlIO& XML,H XML.addValue("error","Bad request"); } } + if (command.commands[1]=="features") { + if (command.method=="GET") { + if(state==IDLE){ + //parse json to get preview spec, return XML? this is a mess + string features_node=command.commands[2]; + if (graph.print_features(XML,features_node)) { + status=HTTPResponse::HTTP_OK; + } + else { + status=HTTPResponse::HTTP_BAD_REQUEST; + logger.error("ERROR: Could not print features for node "+features_node); + } + } + else { + status=HTTPResponse::HTTP_BAD_REQUEST; + logger.error("ERROR: Session busy"); + XML.addValue("error","Session busy"); + } + } + else { + status=HTTPResponse::HTTP_BAD_REQUEST; + logger.error("ERROR: Bad request"); + XML.addValue("error","Bad request"); + } + } if (command.commands[1]=="render") { if (command.method=="GET") { if(state==RENDERING){ diff --git a/rotord/src/rotor.cpp b/rotord/src/rotor.cpp index f1a2605..3246240 100755 --- a/rotord/src/rotor.cpp +++ b/rotord/src/rotor.cpp @@ -77,123 +77,6 @@ float Parameter::get(const Time_spec& time){ //gets input and updates variable } return value; } -bool Audio_thumbnailer::init(int _channels,int _bits,int _samples,int _rate) { - //base_audio_processor::init(_channels,_bits,_samples); - channels=_channels; - bits=_bits; - samples=_samples; - samples_per_column=samples/width; - column=0; //point thumbnail bitmap - out_sample=0; //sample in whole track - offset=0x1<<(bits-1); //signed audio - scale=1.0f/offset; - sample=0; - samples=0; - accum=0.0; - return true; -} -int Audio_thumbnailer::process_frame(uint8_t *_data,int samples_in_frame){ - //begin by processing remaining samples - //samples per column could be larger than a frame! (probably is) - //but all we are doing is averaging - int bytes=(bits>>3); - int stride=channels*bytes; - int in_sample=0; - while (in_sample<samples_in_frame&&column<width) { - //continue the column - while (sample<samples_per_column&&in_sample<samples_in_frame) { - //accumulate samples for this column until we run out of samples - for (int i=0;i<channels;i++) { - unsigned int this_val=0; - for (int j=0;j<bytes;j++) { - this_val+=_data[(in_sample*stride)+(i*bytes)+j]<<(j*8); - } - //convert from integer data format - i.e s16p - to audio signal in -1..1 range - //presume 16 bits for now... - double val=((double)((int16_t)this_val))*scale; - accum+=val*val; - samples++; - } - in_sample++; - sample++; - out_sample++; - } - if (sample==samples_per_column) { //finished a column - //get root-mean - double mean=pow(accum/samples,0.5); - //if (column==0) { - // cerr << "first column total: "<< accum << " in " << samples << " samples, average " << (accum/samples)<<endl; - //} - int colheight=height*mean*0.5; - int hh=height>>1; - for (int i=0;i<height;i++) { - data[i*width+column]=abs(i-hh)<colheight?0xff:0x00; - } - vectordata[column]=mean; - column++; - sample=0; - samples=0; - accum=0.0; - } - } - return out_sample; -} -string Audio_thumbnailer::print(){ - //base64 encode the image data output it - - stringstream output; - Poco::Base64Encoder *enc=new Poco::Base64Encoder(output); - - enc->write((char*)data,width*height); - //tring output; - /* - for (int j=0;j<height;j++) { - for (int i=0;i<width;i++) { - output+=data[j*width+i]<0x7f?"0":"1"; - } - output +="\n"; - } - */ - enc->close(); - delete enc; - return output.str(); -} -void Audio_thumbnailer::print_vector(xmlIO XML){ - string vdata; - for (int i=0;i<width;i++){ - if (i>0) vdata+=","; - vdata+=ofToString(vectordata[i]); - } - XML.addValue("data",vdata); -} -bool Audio_analysis::init(int _channels,int _bits,int _samples, int _rate) { - //need these to make sense of data - channels=_channels; - bits=_bits; - samples=_samples; - - return analyser.init(soname,id,_channels,_bits,_samples,_rate,outputNo,params); - - - //attempt to load vamp plugin and prepare to receive frames of data - //should the audio analysis contain a vamphost or should it inherit? - //maybe neater to contain it in terms of headers etc - -} -int Audio_analysis::process_frame(uint8_t *data,int samples_in_frame) { - analyser.process_frame(data,samples_in_frame); - return 1; -} -void Audio_analysis::cleanup() { - analyser.cleanup(); - //print_features(); -} -void Audio_analysis::print_features(){ - for (auto i: analyser.features) { - cerr<<" ["<<i.second<<":"<<i.first<<"]"; - } - 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){ // @@ -202,7 +85,6 @@ bool Video_output::render(const float duration, const float framerate,const stri AVCodecID codecId=AV_CODEC_ID_H264; //MPEG4; std::string container ="mp4"; - //at the moment it crashes if you render before audio is loaded and also on 2nd render libav::exporter exporter; diff --git a/rotord/src/rotor.h b/rotord/src/rotor.h index cb466d2..e96fffd 100755 --- a/rotord/src/rotor.h +++ b/rotord/src/rotor.h @@ -249,6 +249,7 @@ namespace Rotor { virtual bool init(int _channels,int _bits,int _samples,int _rate)=0; virtual void cleanup()=0; virtual void print_summary(){}; + virtual string get_features(){}; int channels,bits,samples,rate; }; class LUT { @@ -1127,6 +1128,7 @@ namespace Rotor { bool parseJson(string &data,string &media_path); bool set_resolution(int w,int h); bool preview(xmlIO &XML,string &node,string &format,int frame,int w,int h); + bool print_features(xmlIO &XML,string &node); bool loaded; float duration; float framerate; diff --git a/rotord/src/vampHost.cpp b/rotord/src/vampHost.cpp index 65755eb..de7bb53 100644 --- a/rotord/src/vampHost.cpp +++ b/rotord/src/vampHost.cpp @@ -605,7 +605,7 @@ bool vampHost::Analyser::init(const string &soname,const string &id,const int &_ scale=(1.0f/pow(2.0f,bits)); features.clear(); //in case of reuse - features[0.0f]=0; + features[0.0f]=feature(); loader = PluginLoader::getInstance(); key = loader->composePluginKey(soname, id); @@ -763,7 +763,10 @@ void vampHost::Analyser::process_frame(uint8_t *data,int samples_in_frame){ Plugin::FeatureSet feat=plugin->process(plugbuf, rt); for (unsigned int i = 0; i < feat[outputNo].size(); ++i) { - features[((float)feat[outputNo][i].timestamp.sec)+(((float)feat[outputNo][i].timestamp.nsec)*.000000001)]=featureNo; + feature f; + f.number=featureNo; + f.values=feat[outputNo][i].values; + features[((float)feat[outputNo][i].timestamp.sec)+(((float)feat[outputNo][i].timestamp.nsec)*.000000001)]=f; featureNo++; } @@ -794,14 +797,20 @@ void vampHost::Analyser::cleanup(){ Plugin::FeatureSet feat=plugin->process(plugbuf, rt); for (unsigned int i = 0; i < feat[outputNo].size(); ++i) { - features[((float)feat[outputNo][i].timestamp.sec)+(((float)feat[outputNo][i].timestamp.nsec)*.000000001)]=featureNo; + feature f; + f.number=featureNo; + f.values=feat[outputNo][i].values; + features[((float)feat[outputNo][i].timestamp.sec)+(((float)feat[outputNo][i].timestamp.nsec)*.000000001)]=f; featureNo++; } feat=plugin->getRemainingFeatures(); for (unsigned int i = 0; i < feat[outputNo].size(); ++i) { - features[((float)feat[outputNo][i].timestamp.sec)+(((float)feat[outputNo][i].timestamp.nsec)*.000000001)]=featureNo; + feature f; + f.number=featureNo; + f.values=feat[outputNo][i].values; + features[((float)feat[outputNo][i].timestamp.sec)+(((float)feat[outputNo][i].timestamp.nsec)*.000000001)]=f; featureNo++; } diff --git a/rotord/src/vampHost.h b/rotord/src/vampHost.h index d1dfc81..241ffce 100644 --- a/rotord/src/vampHost.h +++ b/rotord/src/vampHost.h @@ -32,7 +32,11 @@ using Vamp::HostExt::PluginInputDomainAdapter; #define HOST_VERSION "1.5" namespace vampHost { - + struct feature{ + feature():number(0){}; + int number; + vector<float> values; + }; class Settings{ public: Settings(string _so="",string _filter="",string _input="") { @@ -60,7 +64,8 @@ namespace vampHost { void process_frame(uint8_t *data,int samples_in_frame); void cleanup(); - map<double,int> features; + //map<double,int> features; + map<double,feature> features; //map<time,featureNo> //this is the best way to store features: because map allows to search for the key below and above the present time |
