summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rotord/src/graph.cpp12
-rw-r--r--rotord/src/nodes_audio_analysis.cpp135
-rw-r--r--rotord/src/nodes_audio_analysis.h26
-rw-r--r--rotord/src/nodes_maths.h6
-rw-r--r--rotord/src/nodes_transform.h6
-rw-r--r--rotord/src/rendercontext.cpp25
-rwxr-xr-xrotord/src/rotor.cpp118
-rwxr-xr-xrotord/src/rotor.h2
-rw-r--r--rotord/src/vampHost.cpp17
-rw-r--r--rotord/src/vampHost.h9
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