summaryrefslogtreecommitdiff
path: root/rotord
diff options
context:
space:
mode:
authorTim Redfern <tim@herge.(none)>2013-04-04 18:03:14 +0100
committerTim Redfern <tim@herge.(none)>2013-04-04 18:03:14 +0100
commit332c7c24301700ca0a4ceb104051bcd3a3b3bc4b (patch)
tree5d4d0c12c749bc1af40effae6e3d943149fa06ae /rotord
parentef217eb0c2450e50a25e6ae2aee36178fcdd54c7 (diff)
analyser loader
Diffstat (limited to 'rotord')
-rw-r--r--rotord/01.xml2
-rw-r--r--rotord/rotor.cpp101
-rwxr-xr-xrotord/rotor.h74
-rw-r--r--rotord/vampHost.cpp20
-rw-r--r--rotord/vampHost.h9
5 files changed, 129 insertions, 77 deletions
diff --git a/rotord/01.xml b/rotord/01.xml
index 830c3a3..e351184 100644
--- a/rotord/01.xml
+++ b/rotord/01.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<patchbay ID="0f7aa258-7c2f-11e2-abbd-133252267708">Off and on template ©Rotor 2013
- <node ID="01" type="audio_analysis" soname="qm-vamp-plugins" id="beats" output="signal">beats
+ <node ID="01" type="audio_analysis" soname="qm-vamp-plugins" id="qm-tempotracker" output="signal">beats
</node>
<node ID="02" type="audio_analysis" soname="qm-vamp-plugins" id="qm-segmenter" output="signal">segmenter
</node>
diff --git a/rotord/rotor.cpp b/rotord/rotor.cpp
index f7c017d..cd288e3 100644
--- a/rotord/rotor.cpp
+++ b/rotord/rotor.cpp
@@ -21,9 +21,13 @@ void Render_context::runTask() {
mutex.unlock();
if (cmd==ANALYSE_AUDIO) {
state=ANALYSING_AUDIO;
- vector<base_audio_processor*> proc;
- proc.push_back(audio_thumb);
- if (load_audio(audio_filename,proc)) {
+ vector<Base_audio_processor*> processors;
+ processors.push_back(audio_thumb);
+ vector<Node*> analysers=graph.find_nodes("audio_analysis");
+ for (auto a: analysers) {
+ processors.push_back(a);
+ }
+ if (load_audio(audio_filename,processors)) {
state=AUDIO_READY;
}
else state=IDLE;
@@ -118,9 +122,9 @@ Command_response Render_context::session_command(const std::vector<std::string>&
}
if (command[2]=="graph") {
if (command[0]=="GET") {
- if (xml.bDocLoaded) {
+ if (graph.loaded) {
response.status=HTTPResponse::HTTP_OK;
- xml.copyXmlToString(response.description);
+ response.description=graph.toString();
}
else {
response.description="<status>Rotor: graph not loaded</status>\n";
@@ -134,14 +138,14 @@ Command_response Render_context::session_command(const std::vector<std::string>&
Poco::File f=Poco::File(command[3]);
if (f.exists()) {
string graph_filename=command[3];
- if (load_graph(graph_filename)) {
+ if (graph.load(graph_filename)) {
response.status=HTTPResponse::HTTP_OK;
//response.description="<status context='"+command[1]+"'>Rotor: loaded graph "+command[3]+"</status>\n";
- string xmlstring;
- xml.copyXmlToString(xmlstring);
- response.description=xmlstring;
+ response.description=graph.toString();
//the graph could actually contain an xml object and we could just print it here?
//or could our nodes even be subclassed from xml nodes?
+ //the graph or the audio could load first- have to analyse the audio with vamp after the graph is loaded
+ //for now the graph must load 1st
}
else {
response.status=HTTPResponse::HTTP_INTERNAL_SERVER_ERROR; //~/sources/poco-1.4.6-all/Net/include/Poco/Net/HTTPResponse.h
@@ -248,13 +252,10 @@ Command_response Render_context::session_command(const std::vector<std::string>&
return response;
}
-
-
-
//http://blog.tomaka17.com/2012/03/libavcodeclibavformat-tutorial/
//great to use c++11 features
-bool Render_context::load_audio(const string &filename,vector<base_audio_processor*> processors){
+bool Render_context::load_audio(const string &filename,vector<Base_audio_processor*> processors){
av_register_all();
@@ -325,14 +326,12 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process
std::cout << "The data is in format " <<codecContext->sample_fmt<< " (aka "<< av_get_sample_fmt_name(codecContext->sample_fmt) << ") "<<std::endl;
for (auto p: processors) {
- p->init(codecContext->channels,16,samples);
+ p->init(codecContext->channels,16,samples,codecContext->sample_rate);
}
AVPacket packet;
av_init_packet(&packet);
int sample_processed=0;
-
- bool diag=true;
while (true)
{
@@ -361,11 +360,7 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process
//av_get_channel_layout_string (char *buf, int buf_size, int nb_channels, uint64_t channel_layout)
- if (diag) {
- cerr << "first frame: "<<bytes << ", "<<frame->nb_samples<<" samples in "<<av_get_sample_fmt_name(frame->format)<<" format with channel layout "<<frame->channel_layout<< std::endl;
- diag=false;
- }
-
+
//now we can pass the data to the processor(s)
for (auto p: processors) {
sample_processed=p->process_frame(frame->data[0],frame->nb_samples);
@@ -408,11 +403,19 @@ bool Render_context::load_audio(const string &filename,vector<base_audio_process
return true;
}
-
-bool Render_context::load_graph(string &filename){
- printf("loading %s\n",filename.c_str());
+const string Graph::toString(){
+ string xmlgraph;
+ if (loaded) {
+ xml.copyXmlToString(xmlgraph);
+ return xmlgraph;
+ }
+ else return "";
+}
+bool Graph::load(string &filename){
+ loaded=false;
+ printf("loading graph: %s\n",filename.c_str());
if(xml.loadFile(filename) ){
- graph=Graph(xml.getAttribute("patchbay","ID","",0),xml.getValue("patchbay","",0));
+ init(xml.getAttribute("patchbay","ID","",0),xml.getValue("patchbay","",0));
if(xml.pushTag("patchbay")) {
int n1=xml.getNumTags("node");
for (int i1=0;i1<n1;i1++){
@@ -428,14 +431,14 @@ bool Render_context::load_graph(string &filename){
if (node) {
cerr << "Rotor: created '" << xml.getAttribute("node","type","",i1) << "'" << endl;
string nodeID=xml.getAttribute("node","ID","",i1);
- graph.nodes[nodeID]=node;
+ nodes[nodeID]=node;
if(xml.pushTag("node",i1)) {
int n2=xml.getNumTags("signal_input");
for (int i2=0;i2<n2;i2++){
- graph.nodes[nodeID]->create_signal_input(xml.getValue("signal_input","",i2));
+ nodes[nodeID]->create_signal_input(xml.getValue("signal_input","",i2));
string fromID=xml.getAttribute("signal_input","from","",i2);
- if (graph.nodes.find(fromID)!=graph.nodes.end()) {
- if (!graph.nodes[nodeID]->inputs[i2]->connect((Signal_node*)graph.nodes[fromID])){
+ 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;
}
@@ -453,6 +456,7 @@ bool Render_context::load_graph(string &filename){
}
xml.popTag();
}
+ loaded=true;
return true;
}
else return false;
@@ -465,15 +469,11 @@ Node_factory::Node_factory(){
add_type("==",new Is_new_integer());
add_type("signal_output",new Signal_output());
}
-void audio_thumbnailer::init(int _channels,int _bits,int _samples) {
+void Audio_thumbnailer::init(int _channels,int _bits,int _samples,int _rate) {
//base_audio_processor::init(_channels,_bits,_samples);
channels=_channels;
bits=_bits;
samples=_samples;
-
- cerr << "init audio thumbnailer with "<<channels<<" channels, "<<samples<<" samples of "<< bits<<" bits."<<endl;
-
-
samples_per_column=samples/width;
column=0; //point thumbnail bitmap
out_sample=0; //sample in whole track
@@ -483,7 +483,7 @@ void audio_thumbnailer::init(int _channels,int _bits,int _samples) {
samples=0;
accum=0.0;
}
-int audio_thumbnailer::process_frame(uint8_t *_data,int samples_in_frame){
+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
@@ -501,17 +501,7 @@ int audio_thumbnailer::process_frame(uint8_t *_data,int samples_in_frame){
this_val+=_data[(sample*stride)+(i*bytes)+j]<<(j*8);
}
//convert from integer data format - i.e s16p - to audio signal in -1..1 range
- //don't know how many bytes we are dealing with necessarily?
- //double val=((double)(this_val-offset))*scale;
-
- //
- //cerr << this_val << " ";
- //
-
-
- //if (this_val>offset) this_val=-(this_val-(offset*2));
-
- //this_val -=offset;
+ //presume 16 bits for now...
double val=((double)((int16_t)this_val))*scale;
accum+=val*val;
samples++;
@@ -539,7 +529,7 @@ int audio_thumbnailer::process_frame(uint8_t *_data,int samples_in_frame){
}
return out_sample;
}
-string audio_thumbnailer::print(){
+string Audio_thumbnailer::print(){
string output;
for (int j=0;j<height;j++) {
for (int i=0;i<width;i++) {
@@ -549,3 +539,20 @@ string audio_thumbnailer::print(){
}
return output;
}
+void Audio_analysis::init(int _channels,int _bits,int _samples, int _rate) {
+ //need these to make sense of data
+ channels=_channels;
+ bits=_bits;
+ samples=_samples;
+
+ analyser.init(soname,id,_rate);
+
+ //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) {
+ //the standard vamp process takes a file
+ return 1;
+}
diff --git a/rotord/rotor.h b/rotord/rotor.h
index 436de10..11e1170 100755
--- a/rotord/rotor.h
+++ b/rotord/rotor.h
@@ -222,18 +222,31 @@ namespace Rotor {
private:
float image_time;
};
+ class Base_audio_processor: public Signal_node {
+ public:
+ virtual int process_frame(uint8_t *data,int samples)=0;
+ virtual void init(int _channels,int _bits,int _samples,int _rate)=0;
+ int channels,bits,samples,rate;
+ };
//actual nodes-------------------------------------------------
- class Audio_analysis: public Signal_node {
+ class Audio_analysis: public Base_audio_processor {
public:
Audio_analysis(){};
Audio_analysis(map<string,string> &settings) {
base_settings(settings);
+ soname=check(settings,"soname");
+ id=check(settings,"id");
};
Audio_analysis* clone(map<string,string> &_settings) { return new Audio_analysis(_settings);};
+ void init(int _channels,int _bits,int _samples,int _rate);
+ int process_frame(uint8_t *data,int samples_in_frame);
float get_output(const float &time) {
float t=time;
return t;
}
+ private:
+ string soname,id;
+ vampHost::Analyser analyser;
};
class Signal_divide: public Signal_node {
//divides incoming signal by a fixed amount
@@ -308,52 +321,61 @@ namespace Rotor {
};
class Graph{
public:
- Graph(){framerate=25.0f;duration=10.0f;};
- Graph(const string& _uid,const string& _desc){ uid=_uid;description=_desc;framerate=25.0f;duration=10.0f;};
+ Graph(){framerate=25.0f;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;framerate=25.0f;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<string,Node*> nodes;
- Node* find(const string &type){
+ vector<Node*> find_nodes(const string &type){
+ vector<Node*> found;
+ for (std::unordered_map<string,Node*>::iterator it=nodes.begin();it!=nodes.end();++it) {
+ if (it->second->type==type) found.push_back(it->second);
+ }
+ return found;
+ };
+ Node* find_node(const string &type){
for (std::unordered_map<string,Node*>::iterator it=nodes.begin();it!=nodes.end();++it) {
if (it->second->type==type) return it->second;
}
- return NULL;
+ return nullptr; //can be tested against
};
bool signal_render(const float _fr,string &signal_xml) {
if (_fr>.001) framerate=_fr;
- if (find("signal_output")) {
- Signal_output *signal_output=dynamic_cast<Signal_output*>(find("signal_output"));
+ if (find_node("signal_output")) {
+ Signal_output *signal_output=dynamic_cast<Signal_output*>(find_node("signal_output"));
return signal_output->render(duration,framerate,signal_xml);
}
else return false;
}
+ int load(Poco::UUID uid);
+ bool load(string &graph_filename);
+ UUID save(); //save to DB, returns UUID of saved graph
+ bool loaded;
+ const string toString();
private:
Node_factory factory;
float framerate;
float duration;
+ xmlIO xml;
};
- class base_audio_processor {
- public:
- virtual int process_frame(uint8_t *data,int samples)=0;
- virtual void init(int _channels,int _bits,int _samples)=0;
- int channels,bits,samples;
- };
- class audio_thumbnailer: public base_audio_processor {
+ class Audio_thumbnailer: public Base_audio_processor {
//how to deal with the fact that frames don't correspond with pixels?
//buffer the data somehow
//draw pixels based on rms value
public:
- audio_thumbnailer(){
- height=48;
- width=128; //fit
+ Audio_thumbnailer(){
+ height=32;
+ width=64; //fit
data=new uint8_t[height*width];
memset(data,0,height*width);
- }
- ~audio_thumbnailer(){
+ };
+ ~Audio_thumbnailer(){
delete[] data;
- }
- void init(int _channels,int _bits,int _samples);
+ };
+ Audio_thumbnailer* clone(map<string,string> &_settings) { return new Audio_thumbnailer();};
+ void init(int _channels,int _bits,int _samples,int _rate);
int process_frame(uint8_t *data,int samples_in_frame);
string print();
uint8_t *data;
@@ -369,7 +391,7 @@ namespace Rotor {
//and low level interface onto the graph
public:
Render_context(const std::string& name): Task(name) {
- audio_thumb=new audio_thumbnailer();
+ audio_thumb=new Audio_thumbnailer();
state=IDLE;
};
void runTask();
@@ -378,10 +400,7 @@ namespace Rotor {
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?
- int load_graph(Poco::UUID uid);
- bool load_graph(string &graph_filename); //should eventually be as above
- UUID save_graph(); //returns UUID of saved graph
- 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();
int load_video(int num,string &filename); //can be performance or clip
@@ -392,9 +411,8 @@ namespace Rotor {
std::deque<int> work_queue;
Poco::Mutex mutex; //lock for access from parent thread
std::string audio_filename;
- audio_thumbnailer *audio_thumb;
+ Audio_thumbnailer *audio_thumb;
vampHost::QMAnalyser audio_analyser;
- xmlIO xml;
Graph graph;
Node_factory factory;
};
diff --git a/rotord/vampHost.cpp b/rotord/vampHost.cpp
index 947e49a..b9b0781 100644
--- a/rotord/vampHost.cpp
+++ b/rotord/vampHost.cpp
@@ -587,4 +587,22 @@ float vampHost::QMAnalyser::get_progress(){
p=progress;
mutex.unlock();
return p;
-} \ No newline at end of file
+}
+void vampHost::Analyser::init(const string &soname,const string &id,const int &rate){
+
+ loader = PluginLoader::getInstance();
+ key = loader->composePluginKey(soname, id);
+ Plugin *plugin = loader->loadPlugin(key, rate, PluginLoader::ADAPT_ALL_SAFE);
+ if (!plugin) {
+ cerr << ": ERROR: Failed to load plugin \"" << id
+ << "\" from library \"" << soname << "\"" << endl;
+ return;
+ }
+
+ cerr << "Running plugin: \"" << plugin->getIdentifier() << "\"..." << endl;
+
+ delete plugin;
+
+ return;
+}
+
diff --git a/rotord/vampHost.h b/rotord/vampHost.h
index dfff408..de6ae02 100644
--- a/rotord/vampHost.h
+++ b/rotord/vampHost.h
@@ -53,6 +53,15 @@ namespace vampHost {
float progress;
Poco::Mutex mutex; //lock for progress data
};
+ class Analyser{
+ public:
+ void init(const string &soname,const string &id,const int &rate);
+ vector<float> beats;
+ private:
+ PluginLoader *loader;
+ PluginLoader::PluginKey key;
+ Plugin *plugin;
+ };
string getQMBeats(const string soundfile);
void printFeatures(int, int, int, Plugin::FeatureSet, ostream &, bool frames);