summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rotord/src/Pixels.cpp90
-rw-r--r--rotord/src/Pixels.h28
-rw-r--r--rotord/src/cvimage.cpp156
-rw-r--r--rotord/src/cvimage.h191
-rw-r--r--rotord/src/graph.cpp150
-rw-r--r--rotord/src/image.h242
-rwxr-xr-xrotord/src/libavwrapper.cpp1657
-rwxr-xr-xrotord/src/libavwrapper.h279
-rw-r--r--rotord/src/nodes_audio_analysis.h47
-rw-r--r--rotord/src/nodes_drawing.h43
-rwxr-xr-xrotord/src/ofUtils.cpp745
-rwxr-xr-xrotord/src/ofUtils.h223
-rw-r--r--rotord/src/params.h0
-rw-r--r--rotord/src/rendercontext.cpp386
-rwxr-xr-xrotord/src/rotor.cpp361
-rwxr-xr-xrotord/src/rotor.h1391
-rwxr-xr-xrotord/src/rotord.cpp230
-rwxr-xr-xrotord/src/rotord.h136
-rw-r--r--rotord/src/system.h75
-rwxr-xr-xrotord/src/tinyxml.cpp1888
-rwxr-xr-xrotord/src/tinyxml.h1807
-rwxr-xr-xrotord/src/tinyxmlerror.cpp53
-rwxr-xr-xrotord/src/tinyxmlparser.cpp1719
-rw-r--r--rotord/src/utils.cpp29
-rw-r--r--rotord/src/utils.h10
-rw-r--r--rotord/src/vampHost.cpp815
-rw-r--r--rotord/src/vampHost.h92
-rwxr-xr-xrotord/src/xmlIO.cpp673
-rwxr-xr-xrotord/src/xmlIO.h169
29 files changed, 13685 insertions, 0 deletions
diff --git a/rotord/src/Pixels.cpp b/rotord/src/Pixels.cpp
new file mode 100644
index 0000000..78f4bbb
--- /dev/null
+++ b/rotord/src/Pixels.cpp
@@ -0,0 +1,90 @@
+#include "Pixels.h"
+Pixels::Pixels(){
+ pixels=nullptr;
+ pixelsOwner=false;
+}
+Pixels::~Pixels(){
+ clear();
+}
+void Pixels::allocate(int w, int h, int _channels){
+ if (w < 0 || h < 0) {
+ return;
+ }
+
+ //we check if we are already allocated at the right size
+ if(bAllocated && w == width && h == height && channels ==_channels){
+ return; //we don't need to allocate
+ }
+
+ //we do need to allocate, clear the data
+ clear();
+
+ channels = _channels;
+ width= w;
+ height = h;
+
+ pixels = new uint8_t[w * h * channels];
+ bAllocated = true;
+ pixelsOwner = true;
+}
+void Pixels::clear(){
+ if(pixels){
+ if(pixelsOwner) delete[] pixels;
+ pixels = nullptr;
+ }
+
+ width = 0;
+ height = 0;
+ channels = 0;
+ bAllocated = false;
+}
+bool Pixels::isAllocated() const{
+ return bAllocated;
+}
+
+void Pixels::setFromExternalPixels(uint8_t * newPixels,int w, int h, int _channels){
+ clear();
+ channels = _channels;
+ width= w;
+ height = h;
+
+ pixels = newPixels;
+ pixelsOwner = false;
+ bAllocated = true;
+}
+
+uint8_t * Pixels::getPixels(){
+ return &pixels[0];
+}
+
+int Pixels::getWidth() const{
+ return width;
+}
+
+int Pixels::getHeight() const{
+ return height;
+}
+
+int Pixels::getBytesPerPixel() const{
+ return channels;
+}
+
+int Pixels::getNumChannels() const{
+ return channels;
+}
+
+void Pixels::swap(Pixels & pix){
+ std::swap(pixels,pix.pixels);
+ std::swap(width, pix.width);
+ std::swap(height,pix.height);
+ std::swap(channels,pix.channels);
+ std::swap(pixelsOwner, pix.pixelsOwner);
+ std::swap(bAllocated, pix.bAllocated);
+}
+
+void Pixels::set(uint8_t val){
+ int size = width * height * channels;
+ for(int i = 0; i < size; i++){
+ pixels[i] = val;
+ }
+} \ No newline at end of file
diff --git a/rotord/src/Pixels.h b/rotord/src/Pixels.h
new file mode 100644
index 0000000..b6f5865
--- /dev/null
+++ b/rotord/src/Pixels.h
@@ -0,0 +1,28 @@
+#include <stdint.h>
+#include <algorithm>
+//for now always uint8_t* rather than templated
+
+class Pixels{
+ public:
+ Pixels();
+ ~Pixels();
+ void allocate(int w, int h, int channels);
+ bool isAllocated() const;
+ void setFromExternalPixels(uint8_t * newPixels,int w, int h, int channels);
+ uint8_t * getPixels();
+ int getWidth() const;
+ int getHeight() const;
+ void clear();
+ void swap(Pixels & pix);
+ int getBytesPerPixel() const;
+ int getNumChannels() const;
+ void set(uint8_t val);
+ private:
+ uint8_t * pixels;
+ int width;
+ int height;
+ int channels;
+ bool bAllocated;
+ bool pixelsOwner; // if set from external data don't delete it
+};
+
diff --git a/rotord/src/cvimage.cpp b/rotord/src/cvimage.cpp
new file mode 100644
index 0000000..c18c585
--- /dev/null
+++ b/rotord/src/cvimage.cpp
@@ -0,0 +1,156 @@
+#include "cvimage.h"
+
+using namespace std;
+
+namespace Rotor {
+ Image & Image::operator+=(const Image &other) {
+ if (other.w!=w||other.h!=h) {
+ cerr<<"Rotor: cannot add images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl;
+ }
+ else {
+ rgb+=other.rgb;
+ }
+ return *this;
+ }
+ Image & Image::operator*=(const Image &other) {
+ if (other.w!=w||other.h!=h) {
+ cerr<<"Rotor: cannot multiply images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl;
+ }
+ else {
+ //rgb/=other.rgb; //does funny glitchy stuff
+ //could use cv::Mat.mul() here
+ for (int i=0;i<w*h*3;i++){
+ //calculate with tables
+ rgb.data[i]=pixels.multiply[rgb.data[i]][other.rgb.data[i]];
+ }
+ }
+ return *this;
+ }
+ Image & Image::operator^=(const Image &other) {
+ if (other.w!=w||other.h!=h) {
+ cerr<<"Rotor: cannot add images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl;
+ }
+ else {
+ rgb^=other.rgb;
+ }
+ return *this;
+ }
+ Image & Image::add_wrap(const Image &other) {
+ if (other.w!=w||other.h!=h) {
+ cerr<<"Rotor: cannot add images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl;
+ }
+ else {
+ for (int i=0;i<w*h*3;i++){
+ //creates rainbow overload, openCV doesn't do this
+ rgb.data[i]=(unsigned char)(((int)other.rgb.data[i]+(int)rgb.data[i]));
+ }
+ }
+ return *this;
+ }
+ Image & Image::divide_wrap(const Image &other) {
+ if (other.w!=w||other.h!=h) {
+ cerr<<"Rotor: cannot add images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl;
+ }
+ else {
+ for (int i=0;i<w*h*3;i++){
+ //creates rainbow overload, openCV doesn't do this
+ rgb/=other.rgb; //does funny glitchy stuff
+ }
+ }
+ return *this;
+ }
+ //THIS OPENCV VERSION IS SLOWER THAN THE OLDSKOOL VERSION BELOW
+ Image & Image::alpha_blend_cv(const Image &other) {
+ if (other.w!=w||other.h!=h) {
+ cerr<<"Rotor: cannot blend images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl;
+ //why not??
+ }
+ else if (!other.alpha.data){
+ //default to full on alpha
+ rgb=other.rgb.clone();
+ }
+ else {
+ //overlay the other image based on its alpha values
+ //https://gist.github.com/Brick85/5009046 - this is a dumb way to do it?
+ //how to invert a matrix?
+ //'invert' is matrix invert - different
+ //subtract from a scalar (1) ?
+ vector<cv::Mat> ichans,ochans;
+ vector<cv::Mat> compchans;
+ cv::split(rgb,ichans);
+ cv::split(other.rgb,ochans);
+ uint8_t b=0xFF;
+ cv::Mat iA=b-other.alpha;
+ for (int i=0;i<3;i++) {
+ compchans.push_back(ichans[i].mul(iA,1.0/255.0)+ochans[i].mul(other.alpha,1.0/255.0));
+ }
+ merge(compchans,rgb);
+ //rgb+=other.rgb;
+ //for (int i=0;i<w*h*3;i++) {
+ // rgb.data[i]=(uint8_t)(((((int)rgb.data[i])*(0xFF-other.alpha.data[i/3]))>>8)+((((int)other.rgb.data[i])*((int)other.alpha.data[i/3]))>>8));
+ //}
+ }
+
+ return *this;
+ }
+ Image & Image::alpha_blend(const Image &other) {
+ if (other.w!=w||other.h!=h) {
+ cerr<<"Rotor: cannot blend images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl;
+ //why not??
+ }
+ else if (!other.alpha.data){
+ //default to full on alpha
+ rgb=other.rgb.clone();
+ }
+ else {
+ for (int i=0;i<w*h*3;i++) {
+ rgb.data[i]=(uint8_t)(((((int)rgb.data[i])*(0xFF-other.alpha.data[i/3]))>>8)+((((int)other.rgb.data[i])*((int)other.alpha.data[i/3]))>>8));
+ }
+ }
+ return *this;
+ }
+ Image & Image::alpha_merge(const Image &other) {
+ //converts the incoming image to monochrome and inserts it into the alpha channel of this image
+ if (other.w!=w||other.h!=h) {
+ cerr<<"Rotor: cannot merge alpha with different size! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl;
+ }
+ else {
+ cv::cvtColor(other.rgb,alpha,CV_RGB2GRAY);
+ }
+ return *this;
+ }
+ //channel rearrangement
+ //RGBAZ - these are channels 0-4
+ //HSB - can also have these virtual channels 5-7
+ //convert channels- int outChannel[5] - {0,1,2,-5,4} - this mapping sends inverted brightness to alpha
+
+ //scalar operations allocate a new image.
+ //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;
+ return *this;
+ }
+ Image * Image::operator*(const float &amount) {
+ Image *other=new Image(w,h);
+ other->rgb=rgb*amount;
+ return other;
+ }
+ Image * Image::operator+(const float &amount) {
+ uint8_t amt=(uint8_t)(amount*255.0f);
+ Image *other=new Image(w,h);
+ other->rgb=rgb+amt;
+ return other;
+ }
+ Image * Image::operator-(const float &amount) {
+ uint8_t amt=(uint8_t)(amount*255.0f);
+ Image *other=new Image(w,h);
+ other->rgb=rgb-amt;
+ return other;
+ }
+ Image * Image::operator/(const float &amount) {
+ Image *other=new Image(w,h);
+ other->rgb=rgb/amount;
+ return other;
+ }
+}
diff --git a/rotord/src/cvimage.h b/rotord/src/cvimage.h
new file mode 100644
index 0000000..2f9ed3b
--- /dev/null
+++ b/rotord/src/cvimage.h
@@ -0,0 +1,191 @@
+#ifndef ROTOR_CVIMAGE
+#define ROTOR_CVIMAGE
+
+#include <math.h>
+#include <cv.h>
+
+//converting to use a cv image...
+//cv::Mat supports most of what we want here
+//need to think
+//http://answers.opencv.org/question/8202/using-external-image-data-in-a-cvmat/
+
+//all access to the image is presently through a pointer to the data
+//cv::Mat supports this
+
+//how will copying work?
+//Rotor::Image will contain a cv::Mat object which may own its data or inherit it
+//cv::Mat should take care of reference counting
+
+//can cv::Mat
+
+namespace Rotor {
+ class pixeltables{
+ //handy pixel arithmetic lookup tables as nested arrays
+ //so - pixels.add[0x78][0x66]; will give the precalculated result of adding with saturation
+ // pixels.mono_weights[0][0x100]; will give the red component to convert to mono
+ 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)std::min(i+j,0xFF);
+ multiply[i][j]=(uint8_t)((((float)i)/255.0f)*(((float)j)/255.0f)*255.0f);
+ }
+ }
+ mono_weights=new uint8_t*[3];
+ float weights[3]={0.2989, 0.5870, 0.1140};
+ for (int i=0;i<3;i++) {
+ mono_weights[i]=new uint8_t[256];
+ for (int j=0;j<256;j++){
+ mono_weights[i][j]=(uint8_t)(((float)j)*weights[i]);
+ }
+ }
+ }
+ virtual ~pixeltables(){
+ for (int i=0;i<256;i++){
+ delete[] add[i];
+ delete[] multiply[i];
+ }
+ delete[] add;
+ delete[] multiply;
+ for (int i=0;i<3;i++) {
+ delete[] mono_weights[i];
+ }
+ delete[] mono_weights;
+ }
+ uint8_t **add;
+ uint8_t **multiply;
+ uint8_t **mono_weights;
+ };
+ static pixeltables pixels;
+ 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;
+ }
+ int getStride(){
+ return w*3;
+ }
+ bool setup(int _w,int _h){ //set up with internal data
+ rgb.create(_h,_w,CV_8UC3);
+ RGBdata=rgb.data; //can move to use the bare pointer eventually
+ ownsRGBdata=false; //will not be necessary
+ w=_w;
+ h=_h;
+ return true;
+ /*
+ 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,int linepadding=0){
+ //here the data belongs to libavcodec or other
+ //could move to using cv::Mat there also and just passing cv:Mat over
+
+ //linepadding causes crash, but it doesn't seem to be necessary
+ // Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)
+ rgb=cv::Mat(_h,_w,CV_8UC3,pRGBdata,(_w*3)+linepadding);
+ //std::cerr<<"created cv::Mat with step= "<<rgb.step<<",should be "<<((_w*3)+linepadding)<<std::endl;
+
+ RGBdata=rgb.data; //can move to use the bare pointer eventually
+ ownsRGBdata=false; //will not be necessary
+ w=_w;
+ h=_h;
+ return true;
+ /*
+ 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;
+ */
+ }
+ bool setup_fromMat(cv::Mat& othermat){
+ //here the mat belongs to another Image object
+ rgb=cv::Mat(othermat);
+ RGBdata=rgb.data; //can move to use the bare pointer eventually
+ ownsRGBdata=false; //will not be necessary
+ w=rgb.rows;
+ h=rgb.cols;
+ return true;
+ }
+ Image* clone(){
+ Image *t=new Image();
+ t->rgb=rgb.clone();
+ t->w=w;
+ t->h=h;
+ t->RGBdata=t->rgb.data; //can move to use the bare pointer eventually
+ t->ownsRGBdata=false; //will not be necessary
+ /*
+ for (int i=0;i<w*h*3;i++) {
+ t->RGBdata[i]=RGBdata[i];
+ }
+ */
+ return t;
+ }
+ //believe these still work, don't know if these optimisations are better than opencvs..
+ Image & operator+=(const Image &other);
+ Image & operator*=(const Image &other);
+ Image & operator^=(const Image &other);
+ Image & alpha_blend(const Image &other);
+ Image & alpha_blend_cv(const Image &other);
+ Image & alpha_merge(const Image &other);
+ 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);
+ uint8_t *RGBdata;
+ uint8_t *Adata;
+ uint16_t *Zdata;
+ int h,w;
+ bool ownsRGBdata,ownsAdata,ownsZdata; //better done through auto_ptr?
+
+ cv::Mat rgb;
+ cv::Mat alpha;
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/rotord/src/graph.cpp b/rotord/src/graph.cpp
new file mode 100644
index 0000000..59f7361
--- /dev/null
+++ b/rotord/src/graph.cpp
@@ -0,0 +1,150 @@
+#include "rotor.h"
+
+using namespace Rotor;
+const string Graph::toString(){
+ string xmlgraph;
+ if (loaded) {
+ xml.copyXmlToString(xmlgraph);
+ return xmlgraph;
+ }
+ else return "";
+}
+vector<Node*> Graph::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* Graph::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 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"));
+ return signal_output->render(duration,framerate,signal_xml);
+ }
+ cerr<<"Rotor: signal output node not found"<<endl;
+
+ 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){
+ if (!dynamic_cast<Video_loader*>(i)->isLoaded) {
+ cerr<<"Rotor: all loaders must be populated before rendering"<<endl;
+ return false;
+ }
+ }
+ if (find_node("video_output")) {
+ Video_output *video_output=dynamic_cast<Video_output*>(find_node("video_output"));
+ return video_output->render(duration,framerate,output_filename,audio_filename,progress,outW,outH);
+ }
+
+ 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;
+ outH=h;
+ return true;
+ }
+ else return false;
+}
+bool Graph::load(string data){
+ if (xml.loadFromBuffer(data)){
+ return parseXml();
+ }
+ return false;
+}
+bool Graph::loadFile(string &filename){
+ loaded=false;
+ printf("loading graph: %s\n",filename.c_str());
+ if(xml.loadFile(filename) ){
+ return parseXml();
+ }
+ else return false;
+}
+bool Graph::parseXml(){
+ 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++){
+ map<string,string> settings;
+ vector<string> attrs;
+ xml.getAttributeNames("node",attrs,i1);
+ for (auto& attr: attrs) {
+ settings[attr]=xml.getAttribute("node",attr,"",i1);
+ //cerr << "Got attribute: " << attr << ":" << xml.getAttribute("node",attr,"",i1) << endl;
+ }
+ settings["description"]=xml.getValue("node","",i1);
+ Node* node=factory.create(settings);
+ if (node) {
+ string nodeID=xml.getAttribute("node","ID","",i1);
+ cerr << "Rotor: created 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ int n4=xml.getNumTags("parameter_input");
+ 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;
+ }
+ 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;
+ }
+ 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));
+ }
+ if (n5>0) cerr << "Rotor: found " << n5 << " extra parameters for node '" << nodeID << "'" << endl;
+
+ xml.popTag();
+ }
+ }
+ else {
+ cerr << "Rotor: graph loader cannot find node '" << xml.getAttribute("node","type","",i1) << "'" << endl;
+ return false;
+ }
+ }
+ xml.popTag();
+ }
+ loaded=true;
+ return true;
+} \ No newline at end of file
diff --git a/rotord/src/image.h b/rotord/src/image.h
new file mode 100644
index 0000000..68aea09
--- /dev/null
+++ b/rotord/src/image.h
@@ -0,0 +1,242 @@
+#include <cv.h>
+
+namespace Rotor {
+ class pixeltables{
+ //handy pixel arithmetic lookup tables as nested arrays
+ //so - pixels.add[0x78][0x66]; will give the precalculated result of adding with saturation
+ // pixels.mono_weights[0][0x100]; will give the red component to convert to mono
+ 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);
+ }
+ }
+ mono_weights=new uint8_t*[3];
+ float weights[3]={0.2989, 0.5870, 0.1140};
+ for (int i=0;i<3;i++) {
+ mono_weights[i]=new uint8_t[256];
+ for (int j=0;j<256;j++){
+ mono_weights[i][j]=(uint8_t)(((float)j)*weights[i]);
+ }
+ }
+ }
+ virtual ~pixeltables(){
+ for (int i=0;i<256;i++){
+ delete[] add[i];
+ delete[] multiply[i];
+ }
+ delete[] add;
+ delete[] multiply;
+ for (int i=0;i<3;i++) {
+ delete[] mono_weights[i];
+ }
+ delete[] mono_weights;
+ }
+ uint8_t **add;
+ uint8_t **multiply;
+ uint8_t **mono_weights;
+ };
+ static pixeltables pixels;
+ class cvImage{
+ public:
+ bool setup(int _w,int _h){ //set up with internal data
+ cv::Mat mat =cv::Mat(_w, _h, CV_8UC1);
+ //is this goint to save time or cost it in the long run?
+ //wrap cv::Mat or use it directly?
+ //its reference counted etc.. could save a lot of headaches
+
+ /*
+ 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;
+ */
+ return false;
+ }
+ uint8_t *RGBdata;
+ uint8_t *Adata;
+ uint16_t *Zdata;
+ int h,w;
+ bool ownsRGBdata,ownsAdata,ownsZdata; //better done through auto_ptr?
+ };
+ 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;i<w*h*3;i++) {
+ t->RGBdata[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 "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl;
+ }
+ else {
+ for (int i=0;i<w*h*3;i++){
+ //calculate with tables
+ RGBdata[i]=pixels.add[RGBdata[i]][other.RGBdata[i]];
+ }
+ }
+ return *this;
+ }
+ Image & operator*=(const Image &other) {
+ if (other.w!=w||other.h!=h) {
+ cerr<<"Rotor: cannot multiply images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl;
+ }
+ else {
+ for (int i=0;i<w*h*3;i++){
+ //calculate with tables
+ uint8_t p1=RGBdata[i];
+ uint8_t p2=other.RGBdata[i];
+ RGBdata[i]=pixels.multiply[RGBdata[i]][other.RGBdata[i]];
+ }
+ }
+ return *this;
+ }
+ Image & add_wrap(const Image &other) {
+ if (other.w!=w||other.h!=h) {
+ cerr<<"Rotor: cannot add images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl;
+ }
+ else {
+ for (int i=0;i<w*h*3;i++){
+ //creates rainbow overload
+ RGBdata[i]=(unsigned char)(((int)other.RGBdata[i]+(int)RGBdata[i]));
+ }
+ }
+ return *this;
+ }
+ //scalar operations allocate a new image.
+ //maybe this could not be the case if the data is owned by this image?
+ //need to look into auto_ptr
+ Image & operator*=(const float &amount) {
+ 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;i<w*h*3;i++){
+ //calculate with table
+ RGBdata[i]=LUT[RGBdata[i]];
+ }
+ delete[] LUT;
+ return *this;
+ }
+ 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;i<w*h*3;i++){
+ other->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;i<w*h*3;i++){
+ other->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;i<w*h*3;i++){
+ other->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)));
+ }
+ for (int i=0;i<w*h*3;i++){
+ other->RGBdata[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?
+ };
+} \ No newline at end of file
diff --git a/rotord/src/libavwrapper.cpp b/rotord/src/libavwrapper.cpp
new file mode 100755
index 0000000..a19e01a
--- /dev/null
+++ b/rotord/src/libavwrapper.cpp
@@ -0,0 +1,1657 @@
+#include "libavwrapper.h"
+
+extern Poco::Mutex mutex; //application wide mutex
+static Poco::Mutex mutex;
+
+
+extern "C"
+{
+#include <libswscale/swscale.h>
+}
+
+
+#include <stdexcept>
+#include <iostream>
+#include <cassert>
+
+using namespace std;
+
+// Translated to C++ by Christopher Bruns May 2012
+// from ffmeg_adapt.c in whisk package by Nathan Clack, Mark Bolstadt, Michael Meeuwisse
+
+
+// Avoid link error on some macs
+#ifdef __APPLE__
+extern "C" {
+#include <stdlib.h>
+#include <errno.h>
+
+}
+#endif
+
+// Custom read function so FFMPEG does not need to read from a local file by name.
+// But rather from a stream derived from a URL or whatever.
+extern "C" {
+
+int readFunction(void* opaque, uint8_t* buf, int buf_size)
+{
+ //QIODevice* stream = (QIODevice*)opaque;
+ ifstream* stream = (ifstream*)opaque;
+ //int numBytes =
+ stream->read((char*)buf, (streamsize)buf_size);
+ return stream->gcount(); //?? is this right
+ //numBytes; //TODO work out
+}
+
+// http://cdry.wordpress.com/2009/09/09/using-custom-io-callbacks-with-ffmpeg/
+int64_t seekFunction(void* opaque, int64_t offset, int whence)
+{
+ //QIODevice* stream = (QIODevice*)opaque;
+ ifstream* stream = (ifstream*)opaque;
+ if (stream == NULL)
+ return -1;
+ else if (whence == AVSEEK_SIZE)
+ return -1; // "size of my handle in bytes"
+ //else if (stream->isSequential())
+ // return -1; // cannot seek a sequential stream //presume this would be certain kind of network stream
+ else if (whence == SEEK_CUR) { // relative to start of file
+ if (! stream->seekg(offset,ios_base::cur)) //stream->pos() + offset) )
+ return -1;
+ }
+ else if (whence == SEEK_END) { // relative to end of file
+ assert(offset < 0);
+ if (! stream->seekg(offset,ios_base::end)) //stream->size() + offset) )
+ return -1;
+ }
+ else if (whence == SEEK_SET) { // relative to start of file
+ if (! stream->seekg(offset) )
+ return -1;
+ }
+ else {
+ assert(false);
+ }
+ return stream->tellg();
+}
+
+}
+
+
+/////////////////////////////
+// AVPacketWrapper methods //
+/////////////////////////////
+
+
+class AVPacketWrapper
+{
+public:
+ AVPacketWrapper();
+ virtual ~AVPacketWrapper();
+ void free();
+
+ AVPacket packet;
+};
+
+
+AVPacketWrapper::AVPacketWrapper()
+{
+ packet.destruct = NULL;
+}
+
+/* virtual */
+AVPacketWrapper::~AVPacketWrapper()
+{
+ free();
+}
+
+void AVPacketWrapper::free()
+{
+ av_free_packet(&packet);
+}
+
+
+//bool libav::b_is_one_time_inited = false;
+
+/////////////////////////
+// decoder methods //
+/////////////////////////
+
+libav::decoder::decoder(PixelFormat pixelFormat)
+ : isOpen(false)
+{
+ mutex.lock();
+ initialize();
+ format = pixelFormat;
+ mutex.unlock();
+}
+
+
+
+void libav::decoder::cleanup(){
+
+ mutex.lock();
+ if (NULL != Sctx) {
+ sws_freeContext(Sctx);
+ Sctx = NULL;
+ }
+ if (NULL != pRaw) {
+ av_free(pRaw);
+ pRaw = NULL;
+ }
+ if (NULL != pFrameRGB) {
+ av_free(pFrameRGB);
+ pFrameRGB = NULL;
+ }
+ if (NULL != pCtx) {
+ avcodec_close(pCtx);
+ pCtx = NULL;
+ }
+ if (NULL != container) {
+ avformat_close_input(&container);
+ container = NULL;
+ }
+ if (NULL != buffer) {
+ av_free(buffer);
+ buffer = NULL;
+ }
+ if (NULL != blank) {
+ av_free(blank);
+ blank = NULL;
+ }
+ mutex.unlock();
+ /*
+ if (NULL != avioContext) {
+ av_free(avioContext);
+ avioContext = NULL;
+ }
+ */
+ // Don't need to free pCodec?
+
+}
+
+/* virtual */
+libav::decoder::~decoder()
+{
+ cleanup();
+}
+
+
+// file name based method for historical continuity
+bool libav::decoder::open(char* fileName, enum PixelFormat formatParam){
+
+ if (!avtry( avformat_open_input(&container, fileName, NULL, NULL), string(fileName) ))
+ return false;
+ return openUsingInitializedContainer(formatParam);
+}
+bool libav::decoder::open(string& fileName, enum PixelFormat formatParam)
+{
+ // Open file, check usability
+
+ if (!avtry( avformat_open_input(&container, fileName.c_str(), NULL, NULL), fileName ))
+ return false;
+ return openUsingInitializedContainer(formatParam);
+}
+
+
+bool libav::decoder::openUsingInitializedContainer(enum PixelFormat formatParam)
+{
+ format = formatParam;
+ sc = getNumberOfChannels();
+
+ if (!avtry( avformat_find_stream_info(container, NULL), "Cannot find stream information." ))
+ return false;
+ if (!avtry( videoStream=av_find_best_stream(container, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec, 0), "Cannot find a video stream." ))
+ return false;
+ pCtx=container->streams[videoStream]->codec;
+ width = pCtx->width;
+ height = pCtx->height;
+ if (!avtry( avcodec_open2(pCtx, pCodec, NULL), "Cannot open video decoder." ))
+ return false;
+
+ /* Frame rate fix for some codecs */
+ if( pCtx->time_base.num > 1000 && pCtx->time_base.den == 1 )
+ pCtx->time_base.den = 1000;
+
+ //cerr<<"stream frame rate:"<<container->streams[videoStream]->r_frame_rate.num<<"/"<<container->streams[videoStream]->r_frame_rate.den<<endl;
+
+ //cerr<<"video duration: "<<container->duration<<endl;
+ //cerr<<"video time base: "<<pCtx->time_base.num<<"/"<<pCtx->time_base.den<<endl;
+ //cerr<<"AV time base: "<<AV_TIME_BASE<<endl;
+
+ /* Compute the total number of frames in the file */
+ /* duration is in microsecs */
+ //numFrames = (int)(( container->duration / (double)AV_TIME_BASE ) * pCtx->time_base.den + 0.5);
+ //this approach just seems wrong!
+
+
+
+ numFrames=container->streams[videoStream]->nb_frames-1;
+
+ if (numFrames<1){
+ //some codecs don't keep this info in the header
+ float fr=((float)container->streams[videoStream]->r_frame_rate.num)/container->streams[videoStream]->r_frame_rate.den;
+ numFrames = (int)(( container->duration / (double)AV_TIME_BASE ) * fr );
+ //this approach still doesn't seem to give quite the right answer- comes out a little too big
+ //could alternatively just redefine the length if the reader fails
+ }
+
+
+
+ init_buffers_and_scaler();
+
+ /* Give some info on stderr about the file & stream */
+ //dump_format(container, 0, fname, 0);
+
+ previousFrameIndex = -1;
+ return true;
+}
+bool libav::decoder::reinit_buffers_and_scaler(){
+ mutex.lock();
+ if (NULL != Sctx) {
+ sws_freeContext(Sctx);
+ Sctx = NULL;
+ }
+ if (NULL != pRaw) {
+ av_free(pRaw);
+ pRaw = NULL;
+ }
+ if (NULL != pFrameRGB) {
+ av_free(pFrameRGB);
+ pFrameRGB = NULL;
+ }
+ mutex.unlock();
+ init_buffers_and_scaler();
+}
+
+bool libav::decoder::init_buffers_and_scaler(){
+ /* Get framebuffers */
+ if (! (pRaw = avcodec_alloc_frame()) )
+ throw std::runtime_error("");
+ if (! (pFrameRGB = avcodec_alloc_frame()) )
+ throw std::runtime_error("");
+
+ /* Create data buffer */
+ if (format == PIX_FMT_NONE) {
+ numBytes = 0;
+ buffer = NULL;
+ blank = NULL;
+ pFrameRGB = NULL;
+ Sctx = NULL;
+ }
+ else {
+ numBytes = avpicture_get_size( format, width, height ); // RGB24 format
+ if (! (buffer = (uint8_t*)av_malloc(numBytes + FF_INPUT_BUFFER_PADDING_SIZE)) ) // RGB24 format
+ throw std::runtime_error("");
+ if (! (blank = (uint8_t*)av_mallocz(avpicture_get_size(pCtx->pix_fmt,width,height))) ) // native codec format
+ throw std::runtime_error("");
+
+ /* Init buffers */
+ avpicture_fill( (AVPicture * ) pFrameRGB, buffer, format,
+ width, height );
+
+ /* Init scale & convert */
+ if (! (Sctx=sws_getContext(
+ pCtx->width,
+ pCtx->height,
+ pCtx->pix_fmt,
+ width,
+ height,
+ format,
+ SWS_POINT, // fastest?
+ NULL,NULL,NULL)) )
+ throw std::runtime_error("");
+ }
+}
+
+bool libav::decoder::fetchFrame(int w, int h,int targetFrameIndex)
+{
+ if (w!=width||h!=height){
+ width=w;
+ height=h;
+ cerr<<"libav::decoder reiniting to "<<width<<"x"<<height<<endl; //does not seem to be aware of wrong frame
+ reinit_buffers_and_scaler();
+ }
+
+ //seems to crash out on the last frame, if it can be caught should maybe decrement number of frames
+
+ return fetchFrame(targetFrameIndex);
+}
+
+bool libav::decoder::fetchFrame(int targetFrameIndex)
+{
+ if ((targetFrameIndex < 0) || (targetFrameIndex > numFrames))
+ return false;
+ if (targetFrameIndex == (previousFrameIndex + 1)) {
+ if (! readNextFrame(targetFrameIndex+1)) //frame indexing starts at 1
+ return false;
+ }
+ else {
+ int64_t response=seekToFrame(targetFrameIndex+1); //frame indexing starts at 1
+ if (response < 0)
+ return false;
+ if (response!=targetFrameIndex+1) {
+ cerr<<"libav::decoder asked for "<<targetFrameIndex<<", got "<<(response-1)<<endl; //does not seem to be aware of wrong frame
+ }
+ }
+ previousFrameIndex = targetFrameIndex;
+ return true;
+}
+
+// \returns current frame on success, otherwise -1
+int libav::decoder::seekToFrame(int targetFrameIndex)
+{
+ int64_t duration = container->streams[videoStream]->duration;
+ int64_t ts = av_rescale(duration,targetFrameIndex,numFrames);
+ int64_t tol = av_rescale(duration,1,2*numFrames);
+ if ( (targetFrameIndex < 0) || (targetFrameIndex >= numFrames) ) {
+ return -1;
+ }
+ int result = avformat_seek_file( container, //format context
+ videoStream,//stream id
+ 0, //min timestamp 0?
+ ts, //target timestamp
+ ts, //max timestamp
+ 0);//flags AVSEEK_FLAG_ANY //doesn't seem to work great
+ if (result < 0)
+ return -1;
+
+ avcodec_flush_buffers(pCtx);
+ if (! readNextFrame(targetFrameIndex))
+ return -1;
+
+ return targetFrameIndex;
+}
+
+bool libav::decoder::readNextFrame(int targetFrameIndex)
+{
+ AVPacket packet = {0};
+ av_init_packet(&packet);
+ bool result = readNextFrameWithPacket(targetFrameIndex, packet, pRaw);
+ av_free_packet(&packet);
+ return result;
+}
+
+// WARNING this method can raise an exception
+bool libav::decoder::readNextFrameWithPacket(int targetFrameIndex, AVPacket& packet, AVFrame* pYuv)
+{
+ int finished = 0;
+ do {
+ finished = 0;
+ av_free_packet(&packet);
+ int result;
+ //if (!avtry(av_read_frame( container, &packet ), "Failed to read frame"))
+ if (!avtry(av_read_packet( container, &packet ), "Failed to read packet"))
+ return false; // !!NOTE: see docs on packet.convergence_duration for proper seeking
+ if( packet.stream_index != videoStream ) /* Is it what we're trying to parse? */
+ continue;
+ if (!avtry(avcodec_decode_video2( pCtx, pYuv, &finished, &packet ), "Failed to decode video"))
+ return false;
+ // handle odd cases and debug
+ if((pCtx->codec_id==CODEC_ID_RAWVIDEO) && !finished)
+ {
+ avpicture_fill( (AVPicture * ) pYuv, blank, pCtx->pix_fmt,width, height ); // set to blank frame
+ finished = 1;
+ }
+#if 0 // very useful for debugging, very
+ cout << "Packet - pts:" << (int)packet.pts;
+ cout << " dts:" << (int)packet.dts;
+ cout << " - flag: " << packet.flags;
+ cout << " - finished: " << finished;
+ cout << " - Frame pts:" << (int)pYuv->pts;
+ cout << " " << (int)pYuv->best_effort_timestamp;
+ cout << endl;
+ /* printf("Packet - pts:%5d dts:%5d (%5d) - flag: %1d - finished: %3d - Frame pts:%5d %5d\n",
+ (int)packet.pts,(int)packet.dts,
+ packet.flags,finished,
+ (int)pYuv->pts,(int)pYuv->best_effort_timestamp); */
+#endif
+ if(!finished) {
+ if (packet.pts == AV_NOPTS_VALUE)
+ packet.pts = 0;
+ //throw std::runtime_error("");
+ //why does it want to throw an error here, isn't the frame succesfully decoded?
+ //
+ //when we allow these packets through we get
+ //[swscaler @ 0x9ef0c80] bad src image pointers
+ //trying to ignore timestamp below
+ if (packet.size == 0) // packet.size==0 usually means EOF
+ break;
+ }
+ } while ( (!finished) || (pYuv->best_effort_timestamp < targetFrameIndex));
+ // } while (!finished);
+
+ av_free_packet(&packet);
+
+ if (format != PIX_FMT_NONE) {
+ sws_scale(Sctx, // sws context
+ pYuv->data, // src slice
+ pYuv->linesize, // src stride
+ 0, // src slice origin y
+ pCtx->height, // src slice height
+ pFrameRGB->data, // dst
+ pFrameRGB->linesize ); // dst stride
+ }
+
+ previousFrameIndex = targetFrameIndex;
+ return true;
+}
+
+uint8_t libav::decoder::getPixelIntensity(int x, int y, Channel c) const
+{
+ return *(pFrameRGB->data[0] + y * pFrameRGB->linesize[0] + x * sc + c);
+}
+
+int libav::decoder::getNumberOfFrames() const { return numFrames; }
+
+int libav::decoder::getWidth() const { return width; }
+
+int libav::decoder::getHeight() const { return height; }
+
+int libav::decoder::getNumberOfChannels() const
+{
+ switch(format)
+ {
+ case PIX_FMT_BGRA:
+ return 4;
+ break;
+ case PIX_FMT_RGB24:
+ return 3;
+ break;
+ case PIX_FMT_GRAY8:
+ return 1;
+ break;
+ default:
+ return 0;
+ break;
+ }
+ return 0;
+}
+
+void libav::decoder::initialize()
+{
+ Sctx = NULL;
+ pRaw = NULL;
+ pFrameRGB = NULL;
+ pCtx = NULL;
+ container = NULL;
+ buffer = NULL;
+ blank = NULL;
+ pCodec = NULL;
+ format = PIX_FMT_NONE;
+ //network stuff
+ //reply = NULL;
+ //ioBuffer = NULL;
+ //avioContext = NULL;
+ maybeInitFFMpegLib();
+}
+
+void libav::maybeInitFFMpegLib()
+{
+ if (b_is_one_time_inited)
+ return;
+ av_register_all();
+ avcodec_register_all();
+ avformat_network_init();
+ b_is_one_time_inited = true;
+}
+
+bool libav::decoder::avtry(int result, const std::string& msg) {
+ if ((result < 0) && (result != AVERROR_EOF)) {
+ char buf[1024];
+ av_strerror(result, buf, sizeof(buf));
+ std::string message = std::string("libav::Error: ") + msg + " "+ buf;
+ //qDebug() << QString(message.c_str());
+ cerr<<message<<endl;
+ return false;
+ }
+ return true;
+}
+
+
+
+
+///////////////////////////
+// encoder methods //
+///////////////////////////
+
+
+libav::encoder::encoder(const char * file_name, int width, int height, float _framerate,enum AVCodecID codec_id)
+ : picture_yuv(NULL)
+ , picture_rgb(NULL)
+ , container(NULL)
+{
+ //multiply float seconds by this to get pts
+ timebase=((float)AV_TIME_BASE_Q.den)/(AV_TIME_BASE_Q.num*_framerate*3.125f); //no idea where the 3.125 comes from
+
+ if (0 != (width % 2))
+ cerr << "WARNING: Video width is not a multiple of 2" << endl;
+ if (0 != (height % 2))
+ cerr << "WARNING: Video height is not a multiple of 2" << endl;
+
+ maybeInitFFMpegLib();
+
+ container = avformat_alloc_context();
+ if (NULL == container)
+ throw std::runtime_error("Unable to allocate format context");
+
+ AVOutputFormat * fmt = av_guess_format(NULL, file_name, NULL);
+ if (!fmt)
+ fmt = av_guess_format("mpeg", NULL, NULL);
+ if (!fmt)
+ throw std::runtime_error("Unable to deduce video format");
+ container->oformat = fmt;
+
+ fmt->video_codec = codec_id;
+ // fmt->video_codec = CODEC_ID_H264; // fails to write
+
+ video_st = avformat_new_stream(container, NULL);
+
+ pCtx = video_st->codec;
+ pCtx->codec_id = fmt->video_codec;
+ pCtx->codec_type = AVMEDIA_TYPE_VIDEO;
+ // resolution must be a multiple of two
+ pCtx->width = width;
+ pCtx->height = height;
+
+ // bit_rate determines image quality
+ pCtx->bit_rate = width * height * 4; // ?
+ // pCtx->qmax = 50; // no effect?
+
+ // "high quality" parameters from http://www.cs.ait.ac.th/~on/mplayer/pl/menc-feat-enc-libavcodec.html
+ // vcodec=mpeg4:mbd=2:mv0:trell:v4mv:cbp:last_pred=3:predia=2:dia=2:vmax_b_frames=2:vb_strategy=1:precmp=2:cmp=2:subcmp=2:preme=2:vme=5:naq:qns=2
+ if (false) // does not help
+ // if (pCtx->codec_id == CODEC_ID_MPEG4)
+ {
+ pCtx->mb_decision = 2;
+ pCtx->last_predictor_count = 3;
+ pCtx->pre_dia_size = 2;
+ pCtx->dia_size = 2;
+ pCtx->max_b_frames = 2;
+ pCtx->b_frame_strategy = 2;
+ pCtx->trellis = 2;
+ pCtx->compression_level = 2;
+ pCtx->global_quality = 300;
+ pCtx->pre_me = 2;
+ pCtx->mv0_threshold = 1;
+ // pCtx->quantizer_noise_shaping = 2; // deprecated
+ // TODO
+ }
+
+ pCtx->time_base = (AVRational){1, 25}; /////TODO FIX TO SUPPORT OTHER RATES
+ // pCtx->time_base = (AVRational){1, 10};
+ pCtx->gop_size = 12; // emit one intra frame every twelve frames
+ // pCtx->max_b_frames = 0;
+ pCtx->pix_fmt = PIX_FMT_YUV420P;
+ if (fmt->flags & AVFMT_GLOBALHEADER)
+ pCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
+
+ if (pCtx->codec_id == CODEC_ID_H264)
+ {
+ // http://stackoverflow.com/questions/3553003/encoding-h-264-with-libavcodec-x264
+ pCtx->coder_type = 1; // coder = 1
+ pCtx->flags|=CODEC_FLAG_LOOP_FILTER; // flags=+loop
+ pCtx->me_cmp|= 1; // cmp=+chroma, where CHROMA = 1
+ // pCtx->partitions|=X264_PART_I8X8+X264_PART_I4X4+X264_PART_P8X8+X264_PART_B8X8; // partitions=+parti8x8+parti4x4+partp8x8+partb8x8
+ pCtx->me_method=ME_HEX; // me_method=hex
+ pCtx->me_subpel_quality = 7; // subq=7
+ pCtx->me_range = 16; // me_range=16
+ pCtx->gop_size = 250; // g=250
+ pCtx->keyint_min = 25; // keyint_min=25
+ pCtx->scenechange_threshold = 40; // sc_threshold=40
+ pCtx->i_quant_factor = 0.71; // i_qfactor=0.71
+ pCtx->b_frame_strategy = 1; // b_strategy=1
+ pCtx->qcompress = 0.6; // qcomp=0.6
+ pCtx->qmin = 10; // qmin=10
+ pCtx->qmax = 51; // qmax=51
+ pCtx->max_qdiff = 4; // qdiff=4
+ pCtx->max_b_frames = 3; // bf=3
+ pCtx->refs = 3; // refs=3
+ // pCtx->directpred = 1; // directpred=1
+ pCtx->trellis = 1; // trellis=1
+ // pCtx->flags2|=CODEC_FLAG2_BPYRAMID+CODEC_FLAG2_MIXED_REFS+CODEC_FLAG2_WPRED+CODEC_FLAG2_8X8DCT+CODEC_FLAG2_FASTPSKIP; // flags2=+bpyramid+mixed_refs+wpred+dct8x8+fastpskip
+ // pCtx->weighted_p_pred = 2; // wpredp=2
+ // libx264-main.ffpreset preset
+ // pCtx->flags2|=CODEC_FLAG2_8X8DCT;
+ // pCtx->flags2^=CODEC_FLAG2_8X8DCT; // flags2=-dct8x8
+ }
+
+ AVCodec * codec = avcodec_find_encoder(pCtx->codec_id);
+ if (NULL == codec)
+ throw std::runtime_error("Unable to find Mpeg4 codec");
+ if (codec->pix_fmts)
+ pCtx->pix_fmt = codec->pix_fmts[0];
+ {
+ //QMutexLocker lock(&decoder::mutex);
+ mutex.lock();
+ if (avcodec_open2(pCtx, codec, NULL) < 0)
+ throw std::runtime_error("Error opening codec");
+ mutex.unlock();
+ }
+
+ /* Get framebuffers */
+ if (! (picture_yuv = avcodec_alloc_frame()) ) // final frame format
+ throw std::runtime_error("");
+ if (! (picture_rgb = avcodec_alloc_frame()) ) // rgb version I can understand easily
+ throw std::runtime_error("");
+ /* the image can be allocated by any means and av_image_alloc() is
+ * just the most convenient way if av_malloc() is to be used */
+ if ( av_image_alloc(picture_yuv->data, picture_yuv->linesize,
+ pCtx->width, pCtx->height, pCtx->pix_fmt, 1) < 0 )
+ throw std::runtime_error("Error allocating YUV frame buffer");
+ if ( av_image_alloc(picture_rgb->data, picture_rgb->linesize,
+ pCtx->width, pCtx->height, PIX_FMT_RGB24, 1) < 0 )
+ throw std::runtime_error("Error allocating RGB frame buffer");
+
+ /* Init scale & convert */
+ if (! (Sctx=sws_getContext(
+ width,
+ height,
+ PIX_FMT_RGB24,
+ pCtx->width,
+ pCtx->height,
+ pCtx->pix_fmt,
+ SWS_BICUBIC,NULL,NULL,NULL)) )
+ throw std::runtime_error("");
+
+//
+//
+// added audio init
+ fmt->audio_codec = AV_CODEC_ID_MP3;
+ // fmt->video_codec = CODEC_ID_H264; // fails to write
+
+ audio_st = avformat_new_stream(container, NULL);
+
+ aCtx = audio_st->codec;
+ aCtx->codec_id = fmt->audio_codec;
+ aCtx->codec_type = AVMEDIA_TYPE_AUDIO;
+
+ aCtx->sample_fmt=AV_SAMPLE_FMT_S16P; //s16p is invalid or not supported by aac: S16 not by mp3
+ aCtx->channels=2;
+ aCtx->sample_rate=44100;
+ aCtx->channel_layout=AV_CH_LAYOUT_STEREO;
+ aCtx->bit_rate = 64000;
+
+
+
+ AVCodec * acodec = avcodec_find_encoder(aCtx->codec_id);
+ mutex.lock();
+ int ret = avcodec_open2(aCtx, acodec, NULL);
+ mutex.unlock();
+ if (ret < 0) {
+ throw std::runtime_error("Could not open audio codec:");
+
+ }
+
+ if (aCtx->codec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE)
+ audio_input_frame_size = 10000;
+ else
+ audio_input_frame_size = aCtx->frame_size;
+
+
+ if (container->oformat->flags & AVFMT_GLOBALHEADER)
+ aCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
+
+
+ audiostep=((float)audio_input_frame_size)/(aCtx->sample_rate);
+
+
+
+
+// are we supposed to use the same codeccontext?
+//
+
+ /* open the output file */
+ if (!(fmt->flags & AVFMT_NOFILE))
+ {
+ //QMutexLocker lock(&decoder::mutex);
+ mutex.lock();
+ if (avio_open(&container->pb, file_name, AVIO_FLAG_WRITE) < 0)
+ throw std::runtime_error("Error opening output video file");
+ mutex.unlock();
+ }
+ avformat_write_header(container, NULL);
+}
+
+void libav::encoder::setPixelIntensity(int x, int y, int c, uint8_t value)
+{
+ uint8_t * ptr = picture_rgb->data[0] + y * picture_rgb->linesize[0] + x * 3 + c;
+ *ptr = value;
+}
+
+void libav::encoder::write_frame(float seconds,uint8_t *rgbdata)
+{
+ picture_rgb->data[0]=rgbdata;
+
+ // convert from RGB24 to YUV
+ sws_scale(Sctx, // sws context
+ picture_rgb->data, // src slice
+ picture_rgb->linesize, // src stride
+ 0, // src slice origin y
+ pCtx->height, // src slice height
+ picture_yuv->data, // dst
+ picture_yuv->linesize ); // dst stride
+
+ /* encode the image */
+ // use non-deprecated avcodec_encode_video2(...)
+ AVPacket packet={0};
+ av_init_packet(&packet);
+ packet.data = NULL;
+ packet.size = 0;
+
+ //no time stamps as is
+ //http://dranger.com/ffmpeg/tutorial07.html
+
+ picture_yuv->pts=(uint64_t)(seconds*timebase); //
+
+ int got_packet;
+ int ret = avcodec_encode_video2(pCtx,
+ &packet,
+ picture_yuv,
+ &got_packet);
+
+ //packet.pts=(uint64_t)(seconds*timebase); //added 0606
+ packet.stream_index = video_st->index;; //added 0606
+
+ if (ret < 0)
+ throw std::runtime_error("Video encoding failed");
+ if (got_packet)
+ {
+ // std::cout << "encoding frame" << std::endl;
+ int result = av_write_frame(container, &packet);
+ av_destruct_packet(&packet);
+ }
+}
+void libav::encoder::write_frame(float seconds,uint16_t *audiodata){
+ audio_frame = avcodec_alloc_frame();
+ AVPacket pkt = { 0 }; // data and size must be 0;
+ int got_packet, ret;
+ av_init_packet(&pkt);
+ audio_frame->nb_samples = audio_input_frame_size;
+ uint8_t *sampleptr;
+ int bufsize=audio_input_frame_size * av_get_bytes_per_sample(aCtx->sample_fmt) *aCtx->channels;
+ if (audiodata) {
+ sampleptr=(uint8_t*)audiodata;
+ }
+ else {
+ sampleptr=new uint8_t[bufsize];
+ memset(sampleptr,0,bufsize);
+ }
+
+ audio_frame->pts=(uint64_t)(seconds*timebase); //
+
+ avcodec_fill_audio_frame(audio_frame, aCtx->channels, aCtx->sample_fmt,
+ sampleptr,
+ audio_input_frame_size *
+ av_get_bytes_per_sample(aCtx->sample_fmt) *
+ aCtx->channels, 0); //;
+
+
+
+ ret = avcodec_encode_audio2(aCtx, &pkt, audio_frame, &got_packet);
+
+ pkt.stream_index = audio_st->index; //hardcoded stream index added 0606
+ //pkt.pts=(uint64_t)(seconds*timebase); //added 060613
+
+ if (!audiodata) {
+ delete[] sampleptr;
+ }
+ if (ret < 0) {
+ throw std::runtime_error("Audio encoding failed");
+ }
+
+ if (!got_packet)
+ return;
+
+ // ? pkt.stream_index = st->index;
+
+ ret = av_interleaved_write_frame(container, &pkt);
+ avcodec_free_frame(&audio_frame);
+}
+
+/* virtual */
+libav::encoder::~encoder()
+{
+
+ //avcodec_flush_buffers(pCtx); ???? from exporter version
+
+
+ int result = av_write_frame(container, NULL); // flush
+ result = av_write_trailer(container);
+ //QMutexLocker lock(&decoder::mutex);
+ mutex.lock();
+ avio_close(container->pb);
+ mutex.unlock();
+
+ //added 0706
+ video_st=nullptr;
+ audio_st=nullptr;
+ //
+
+ for (int i = 0; i < container->nb_streams; ++i) {
+ av_freep(container->streams[i]); //CRASHING HERE ON STREAM 1, OUTPUT IS VALID BUT AUDIO INAUDIBLE - 060613
+ }
+ av_free(container);
+ container = nullptr;
+ //QMutexLocker lock(&decoder::mutex);
+ mutex.lock();
+ avcodec_close(aCtx);
+ avcodec_close(pCtx);
+ mutex.unlock();
+ av_free(pCtx);
+ pCtx = NULL;
+ av_free(aCtx);
+ aCtx=nullptr;
+ av_free(picture_yuv->data[0]);
+ av_free(picture_yuv);
+ picture_yuv = NULL;
+ av_free(picture_rgb->data[0]);
+ av_free(picture_rgb);
+ picture_rgb = NULL;
+
+}
+
+bool libav::exporter::setup(int w,int h, int bitRate, int frameRate, std::string container){
+
+ maybeInitFFMpegLib();
+
+ this->w=w;
+ this->h=h;
+ this->bitRate=bitRate;
+ this->frameRate=frameRate;
+ this->container=container;
+
+ return true;
+}
+
+bool libav::exporter::record(std::string filename){
+
+ // allocate the output media context //
+ avformat_alloc_output_context2(&oc, NULL, NULL, filename.c_str());
+ if (!oc) {
+ printf("Could not deduce output format from file extension: using MPEG.\n");
+ avformat_alloc_output_context2(&oc, NULL, "mpeg", filename.c_str());
+ }
+ if (!oc) {
+ return false;
+ }
+ fmt = oc->oformat;
+
+ // Add the audio and video streams using the default format codecs
+ // * and initialize the codecs. //
+ video_st = NULL;
+ audio_st = NULL;
+
+ fmt->video_codec=AV_CODEC_ID_MPEG4;
+
+ if (fmt->video_codec != AV_CODEC_ID_NONE) {
+ video_st = add_stream(oc, &video_codec, fmt->video_codec);
+ }
+ if (fmt->audio_codec != AV_CODEC_ID_NONE) {
+ audio_st = add_stream(oc, &audio_codec, fmt->audio_codec);
+ }
+
+ //set initial video params
+ video_st->codec->width=w;
+ video_st->codec->height=h;
+ video_st->codec->time_base.num = 1;//codecCtx->ticks_per_frame;
+ video_st->codec->time_base.den = frameRate;
+ video_st->time_base = video_st->codec->time_base;
+ //audioStream->time_base = codecCtx->time_base; //???has the capability of crashing
+
+ video_st->codec->gop_size = 10; /* emit one intra frame every ten frames */
+ video_st->codec->pix_fmt = PIX_FMT_YUV420P;
+
+ // Now that all the parameters are set, we can open the audio and
+ // * video codecs and allocate the necessary encode buffers. //
+ if (video_st)
+ open_video(oc, video_codec, video_st);
+ if (audio_st) {
+ audioframesize=open_audio(oc, audio_codec, audio_st);
+ audiostep=((float)audioframesize)/(audio_st->codec->sample_rate);
+ std::cerr << "opened audio codec with "<<audioframesize<<" frame size and "<<audiostep<<" seconds per frame"<<std::endl;
+ }
+
+
+ av_dump_format(oc, 0, filename.c_str(), 1);
+
+ // open the output file, if needed //
+ if (!(fmt->flags & AVFMT_NOFILE)) {
+ mutex.lock();
+ int ret = avio_open(&oc->pb, filename.c_str(), AVIO_FLAG_WRITE);
+ mutex.unlock();
+ if (ret < 0) {
+ std::cerr <<"Could not open " << filename.c_str() << std::endl;
+ return false;
+ }
+ }
+
+ // Write the stream header, if any. //
+ int ret = avformat_write_header(oc, NULL);
+ if (ret < 0) {
+ //std::cerr <<"Error occurred when opening output file:" << av_err2str(ret) << std::endl;
+ return false;
+ }
+
+ if (frame)
+ frame->pts = 0;
+
+ outputframe=0;
+
+ return true;
+}
+bool libav::exporter::encodeFrame(unsigned char *pixels,uint16_t *samples){
+ // Compute current audio and video time. //
+ if (audio_st)
+ audio_pts = (double)audio_st->pts.val * audio_st->time_base.num / audio_st->time_base.den;
+ else
+ audio_pts = 0.0;
+
+ if (video_st)
+ video_pts = (double)video_st->pts.val * video_st->time_base.num /
+ video_st->time_base.den;
+ else
+ video_pts = 0.0;
+
+ // write interleaved audio and video frames //
+ if (!video_st || (video_st && audio_st && audio_pts < video_pts)) {
+ write_audio_frame(oc, audio_st, samples);
+ } else {
+ write_video_frame(oc, video_st, pixels);
+
+ frame->pts += av_rescale_q(1, video_st->codec->time_base, video_st->time_base);
+ }
+
+ //std::cerr << "encoded frame " << outputframe << std::endl;
+ outputframe++;
+
+ return true;
+}
+bool libav::exporter::encodeFrame(unsigned char *pixels,AVPacket *audio){
+ // Compute current audio and video time. //
+ if (audio_st)
+ audio_pts = (double)audio_st->pts.val * audio_st->time_base.num / audio_st->time_base.den;
+ else
+ audio_pts = 0.0;
+
+ if (video_st)
+ video_pts = (double)video_st->pts.val * video_st->time_base.num /
+ video_st->time_base.den;
+ else
+ video_pts = 0.0;
+
+ // write interleaved audio and video frames //
+ if (!video_st || (video_st && audio_st && audio_pts < video_pts)) {
+ write_audio_frame(oc, audio_st, audio);
+ } else {
+ write_video_frame(oc, video_st, pixels);
+
+ frame->pts += av_rescale_q(1, video_st->codec->time_base, video_st->time_base);
+ }
+
+ //std::cerr << "encoded frame " << outputframe << std::endl;
+ outputframe++;
+
+ return true;
+}
+bool libav::exporter::encodeFrame(unsigned char *pixels){
+ video_pts = (double)video_st->pts.val * video_st->time_base.num / video_st->time_base.den;
+ write_video_frame(oc, video_st, pixels);
+ frame->pts += av_rescale_q(1, video_st->codec->time_base, video_st->time_base);
+ outputframe++;
+ return true;
+}
+bool libav::exporter::encodeFrame(uint16_t *samples){
+ audio_pts = (double)audio_st->pts.val * audio_st->time_base.num / audio_st->time_base.den;
+ write_audio_frame(oc, audio_st, samples);
+ return true;
+}
+void libav::exporter::finishRecord(){
+
+ av_write_trailer(oc);
+ // Close each codec. //
+ if (video_st)
+ close_video(oc, video_st);
+ if (audio_st)
+ close_audio(oc, audio_st);
+
+ if (!(fmt->flags & AVFMT_NOFILE)) {
+ // Close the output file. //
+ mutex.lock();
+ avio_close(oc->pb);
+ mutex.unlock();
+ }
+
+ // free the stream //
+ avformat_free_context(oc);
+}
+
+AVStream* libav::exporter::add_stream(AVFormatContext *oc, AVCodec **codec,enum AVCodecID codec_id)
+ {
+ AVCodecContext *c;
+ AVStream *st;
+
+ // find the encoder //
+ *codec = avcodec_find_encoder(codec_id);
+ if (!(*codec)) {
+ //fprintf(stderr, "Could not find encoder for '%s'\n",
+ // avcodec_get_name(codec_id));
+ exit(1);
+ }
+
+ st = avformat_new_stream(oc, *codec);
+ if (!st) {
+ //fprintf(stderr, "Could not allocate stream\n");
+ exit(1);
+ }
+ st->id = oc->nb_streams-1;
+ c = st->codec;
+
+ switch ((*codec)->type) {
+ case AVMEDIA_TYPE_AUDIO:
+ st->id = 1;
+ c->sample_fmt = AV_SAMPLE_FMT_S16;
+ c->bit_rate = 64000;
+ c->sample_rate = 44100;
+ c->channels = 2;
+ c->channel_layout=AV_CH_LAYOUT_STEREO;
+ break;
+
+ case AVMEDIA_TYPE_VIDEO:
+ c->codec_id = codec_id;
+
+ c->bit_rate = 400000;
+ // Resolution must be a multiple of two. //
+ c->width = 352;
+ c->height = 288;
+ // timebase: This is the fundamental unit of time (in seconds) in terms
+ // * of which frame timestamps are represented. For fixed-fps content,
+ // * timebase should be 1/framerate and timestamp increments should be
+ // * identical to 1. //
+ c->time_base.den = frameRate;
+ c->time_base.num = 1;
+ c->gop_size = 12; // emit one intra frame every twelve frames at most //
+ c->pix_fmt = AV_PIX_FMT_YUV420P; //ADDED HARDCODED TJR 280513
+ if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
+ // just for testing, we also add B frames //
+ c->max_b_frames = 2;
+ }
+ if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) {
+ // Needed to avoid using macroblocks in which some coeffs overflow.
+ // * This does not happen with normal video, it just happens here as
+ // * the motion of the chroma plane does not match the luma plane. //
+ c->mb_decision = 2;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // Some formats want stream headers to be separate. //
+ if (oc->oformat->flags & AVFMT_GLOBALHEADER)
+ c->flags |= CODEC_FLAG_GLOBAL_HEADER;
+
+ return st;
+ }
+
+void libav::exporter::open_video(AVFormatContext *oc, AVCodec *codec, AVStream *st)
+ {
+ int ret;
+ AVCodecContext *c = st->codec;
+
+ // open the codec //
+ mutex.lock();
+ ret = avcodec_open2(c, codec, NULL);
+ mutex.unlock();
+ if (ret < 0) {
+ //fprintf(stderr, "Could not open video codec: %s\n", av_err2str(ret));
+ exit(1);
+ }
+
+ // allocate and init a re-usable frame //
+ frame = avcodec_alloc_frame();
+ // moved to constructor and freeing in destructor -- stills crashes the same
+ if (!frame) {
+ //fprintf(stderr, "Could not allocate video frame\n");
+ exit(1);
+ }
+
+ // Allocate the encoded raw picture. //
+ ret = avpicture_alloc(&dst_picture, c->pix_fmt, c->width, c->height);
+ if (ret < 0) {
+ //fprintf(stderr, "Could not allocate picture: %s\n", av_err2str(ret));
+ exit(1);
+ }
+
+ // If the output format is not YUV420P, then a temporary YUV420P
+ // * picture is needed too. It is then converted to the required
+ // * output format. //
+ if (c->pix_fmt != AV_PIX_FMT_YUV420P) {
+ ret = avpicture_alloc(&src_picture, AV_PIX_FMT_RGB24, c->width, c->height);
+ if (ret < 0) {
+ //fprintf(stderr, "Could not allocate temporary picture: %s\n",
+ // av_err2str(ret));
+ exit(1);
+ }
+ }
+
+ // copy data and linesize picture pointers to frame //
+ *((AVPicture *)frame) = dst_picture;
+
+ outPixels = (uint8_t*)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, st->codec->width,st->codec->height));
+ }
+
+ int libav::exporter::open_audio(AVFormatContext *oc, AVCodec *codec, AVStream *st)
+ {
+ AVCodecContext *c;
+ int ret;
+
+ c = st->codec;
+
+ // open it //
+ mutex.lock();
+ ret = avcodec_open2(c, codec, NULL);
+ mutex.unlock();
+ if (ret < 0) {
+ //fprintf(stderr, "Could not open audio codec: %s\n", av_err2str(ret));
+ exit(1);
+ }
+
+ // init signal generator //
+ t = 0;
+ tincr = 2 * M_PI * 110.0 / c->sample_rate;
+ // increment frequency by 110 Hz per second //
+ tincr2 = 2 * M_PI * 110.0 / c->sample_rate / c->sample_rate;
+
+ if (c->codec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE)
+ audio_input_frame_size = 10000;
+ else
+ audio_input_frame_size = c->frame_size;
+
+ /*
+ samples = av_malloc(audio_input_frame_size *
+ av_get_bytes_per_sample(c->sample_fmt) *
+ c->channels);
+ if (!samples) {
+ //fprintf(stderr, "Could not allocate audio samples buffer\n");
+ exit(1);
+ }
+ */
+ return audio_input_frame_size;
+ }
+
+ void libav::exporter::write_audio_frame(AVFormatContext *oc, AVStream *st,uint16_t *samples)
+ {
+ AVCodecContext *c;
+ AVPacket pkt = { 0 }; // data and size must be 0;
+ AVFrame *frame = avcodec_alloc_frame();
+ int got_packet, ret;
+
+ av_init_packet(&pkt);
+ c = st->codec;
+
+ //get_audio_frame(samples, audio_input_frame_size, c->channels);
+ frame->nb_samples = audio_input_frame_size;
+ uint8_t *sampleptr;
+ int bufsize=audio_input_frame_size * av_get_bytes_per_sample(c->sample_fmt) *c->channels;
+ if (samples) {
+ sampleptr=(uint8_t*)samples;
+ }
+ else {
+ sampleptr=new uint8_t[bufsize];
+ memset(sampleptr,0,bufsize);
+ }
+
+ avcodec_fill_audio_frame(frame, c->channels, c->sample_fmt,
+ sampleptr,
+ audio_input_frame_size *
+ av_get_bytes_per_sample(c->sample_fmt) *
+ c->channels, 0); //;
+ //frame->sample_rate=44100; //hard coded input rate- nope, this doesn't help
+ //frame->format=AV_SAMPLE_FMT_S16P;
+ //?? why is ffmpeg reporting fltp as the sample format??? doesn't seem to have an effect to change this though
+ ret = avcodec_encode_audio2(c, &pkt, frame, &got_packet);
+ if (!samples) {
+ delete[] sampleptr;
+ }
+ if (ret < 0) {
+ //fprintf(stderr, "Error encoding audio frame: %s\n", av_err2str(ret));
+ exit(1);
+ }
+
+ if (!got_packet)
+ return;
+
+ pkt.stream_index = st->index;
+
+ // Write the compressed frame to the media file. //
+ ret = av_interleaved_write_frame(oc, &pkt);
+ if (ret != 0) {
+ //fprintf(stderr, "Error while writing audio frame: %s\n",
+ // av_err2str(ret));
+ exit(1);
+ }
+ avcodec_free_frame(&frame);
+ }
+
+ void libav::exporter::write_audio_frame(AVFormatContext *oc, AVStream *st,AVPacket *pkt)
+ {
+ /*
+ AVCodecContext *c;
+ AVPacket pkt = { 0 }; // data and size must be 0;
+ AVFrame *frame = avcodec_alloc_frame();
+ int got_packet, ret;
+
+ av_init_packet(&pkt);
+ c = st->codec;
+
+ //get_audio_frame(samples, audio_input_frame_size, c->channels);
+ frame->nb_samples = audio_input_frame_size;
+ uint8_t *sampleptr;
+ int bufsize=audio_input_frame_size * av_get_bytes_per_sample(c->sample_fmt) *c->channels;
+ if (samples) {
+ sampleptr=(uint8_t*)samples;
+ }
+ else {
+ sampleptr=new uint8_t[bufsize];
+ memset(sampleptr,0,bufsize);
+ }
+ avcodec_fill_audio_frame(frame, c->channels, c->sample_fmt,
+ sampleptr,
+ audio_input_frame_size *
+ av_get_bytes_per_sample(c->sample_fmt) *
+ c->channels, 1);
+
+ ret = avcodec_encode_audio2(c, &pkt, frame, &got_packet);
+ if (!samples) {
+ free(sampleptr);
+ }
+ if (ret < 0) {
+ //fprintf(stderr, "Error encoding audio frame: %s\n", av_err2str(ret));
+ exit(1);
+ }
+
+ if (!got_packet)
+ return;
+ */
+
+ pkt->stream_index = st->index;
+
+ // Write the compressed frame to the media file. //
+ int ret = av_interleaved_write_frame(oc, pkt);
+ if (ret != 0) {
+ //fprintf(stderr, "Error while writing audio frame: %s\n",
+ // av_err2str(ret));
+ exit(1);
+ }
+ //avcodec_free_frame(&frame);
+ av_free_packet(pkt);
+ }
+
+ void libav::exporter::close_audio(AVFormatContext *oc, AVStream *st)
+ {
+ mutex.lock();
+ avcodec_close(st->codec);
+ mutex.unlock();
+
+ }
+
+ void libav::exporter::write_video_frame(AVFormatContext *oc, AVStream *st, uint8_t *pixels)
+ {
+ int ret;
+
+ AVCodecContext *c = st->codec;
+
+/*
+ if (frame_count >= STREAM_NB_FRAMES) {
+ // No more frames to compress. The codec has a latency of a few
+ // * frames if using B-frames, so we get the last frames by
+ // * passing the same picture again. //
+ } else {
+ if (c->pix_fmt != AV_PIX_FMT_YUV420P) {
+ // as we only generate a YUV420P picture, we must convert it
+ // * to the codec pixel format if needed //
+ if (!sws_ctx) {
+ sws_ctx = sws_getContext(c->width, c->height, AV_PIX_FMT_YUV420P,
+ c->width, c->height, c->pix_fmt,
+ sws_flags, NULL, NULL, NULL);
+ if (!sws_ctx) {
+ //fprintf(stderr,
+ // "Could not initialize the conversion context\n");
+ exit(1);
+ }
+ }
+ fill_yuv_image(&src_picture, frame_count, c->width, c->height);
+ sws_scale(sws_ctx,
+ (const uint8_t * const *)src_picture.data, src_picture.linesize,
+ 0, c->height, dst_picture.data, dst_picture.linesize);
+ } else {
+ fill_yuv_image(&dst_picture, frame_count, c->width, c->height);
+ }
+ }
+*/
+ //always convert RGB to YUV
+ //should be context allocated once per render instead of per frame??
+ //
+ //
+ sws_ctx = sws_getContext(c->width, c->height, AV_PIX_FMT_RGB24,
+ c->width, c->height, AV_PIX_FMT_YUV420P,
+ sws_flags, NULL, NULL, NULL);
+
+ avpicture_fill(&src_picture, pixels, PIX_FMT_RGB24, c->width,c->height);
+ //avpicture_fill(&dst_picture, outPixels, PIX_FMT_YUV420P, c->width,c->height);
+
+ sws_scale(sws_ctx, src_picture.data, src_picture.linesize, 0, c->height, dst_picture.data, dst_picture.linesize);
+ //fill_yuv_image(&dst_picture, frame_count, c->width, c->height);
+ if (oc->oformat->flags & AVFMT_RAWPICTURE) {
+ // Raw video case - directly store the picture in the packet //
+ AVPacket pkt;
+ av_init_packet(&pkt);
+
+ pkt.flags |= AV_PKT_FLAG_KEY;
+ pkt.stream_index = st->index;
+ pkt.data = dst_picture.data[0];
+ pkt.size = sizeof(AVPicture);
+
+ ret = av_interleaved_write_frame(oc, &pkt);
+ } else {
+ AVPacket pkt = { 0 };
+ int got_packet;
+ av_init_packet(&pkt);
+
+ // encode the image //
+
+ // 2nd time you render it crashes right after here
+
+ // where the hell is frame being allocated? is the problem caused by it being freed? (see removeal of avframe_free in cleanup)
+ ret = avcodec_encode_video2(c, &pkt, frame, &got_packet);
+ if (ret < 0) {
+ //fprintf(stderr, "Error encoding video frame: %s\n", av_err2str(ret));
+ exit(1);
+ }
+ // If size is zero, it means the image was buffered. //
+
+ if (!ret && got_packet && pkt.size) {
+ pkt.stream_index = st->index;
+
+ // Write the compressed frame to the media file. //
+ ret = av_interleaved_write_frame(oc, &pkt);
+ } else {
+ ret = 0;
+ }
+ }
+
+ //
+ // added 22 may in memory leak run
+ //
+ sws_freeContext(sws_ctx); //should be done once per render instead of per frame??
+
+ if (ret != 0) {
+ //fprintf(stderr, "Error while writing video frame: %s\n", av_err2str(ret));
+ exit(1);
+ }
+ frame_count++;
+
+ //avcodec_free_frame(&frame);
+ }
+
+ void libav::exporter::close_video(AVFormatContext *oc, AVStream *st)
+ {
+ mutex.lock();
+ //avcodec_close(st->codec); //change 0706 to trace 2nd render issue
+ avcodec_close(audio_st->codec);
+ avcodec_close(video_st->codec);
+ //
+ //
+
+
+ //av_free(src_picture.data[0]); //removed to explore weird 2nd render crash.. seems to WORK -- seems that the picture data is owned elsewhere
+ av_free(dst_picture.data[0]);
+ av_free(frame); //removed to explore crash 2nd time render
+ //gives *** Error in `./rotord': corrupted double-linked list: 0x00007fd8b005bd60 ***
+ //where is frame initialised???
+ //moved to destructor
+
+
+ av_free(outPixels); //SIGSEV here???
+ mutex.unlock();
+ }
+
+bool libav::audioloader::setup(const std::string &filename){
+
+ maybeInitFFMpegLib();
+
+ frame = avcodec_alloc_frame();
+ if (!frame)
+ {
+ std::cout << "Error allocating the frame" << std::endl;
+ return false;
+ }
+
+ formatContext = NULL;
+ mutex.lock();
+ if (avformat_open_input(&formatContext, filename.c_str(), NULL, NULL) != 0)
+ {
+ av_free(frame);
+ std::cout << "Error opening the file" << std::endl;
+ mutex.unlock();
+ return false;
+ }
+ mutex.unlock();
+
+ if (avformat_find_stream_info(formatContext, NULL) < 0)
+ {
+ mutex.lock();
+ av_free(frame);
+ avformat_close_input(&formatContext);
+ mutex.unlock();
+ std::cout << "Error finding the stream info" << std::endl;
+ return false;
+ }
+
+ //use the first audio stream found
+
+ audioStream = NULL;
+ for (unsigned int i = 0; i < formatContext->nb_streams; ++i)
+ {
+ if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
+ {
+ audioStream = formatContext->streams[i];
+ break;
+ }
+ }
+
+ if (audioStream == NULL)
+ {
+ mutex.lock();
+ av_free(frame);
+ avformat_close_input(&formatContext);
+ mutex.unlock();
+ std::cout << "Could not find any audio stream in the file" << std::endl;
+ return false;
+ }
+
+ codecContext = audioStream->codec;
+
+ codecContext->codec = avcodec_find_decoder(codecContext->codec_id);
+ mutex.lock();
+ if (codecContext->codec == NULL)
+ {
+
+ av_free(frame);
+ avformat_close_input(&formatContext);
+ mutex.unlock();
+ std::cout << "Couldn't find a proper decoder" << std::endl;
+ return false;
+ }
+ else if (avcodec_open2(codecContext, codecContext->codec, NULL) != 0)
+ {
+ av_free(frame);
+ avformat_close_input(&formatContext);
+ mutex.unlock();
+ std::cout << "Couldn't open the context with the decoder" << std::endl;
+ return false;
+ }
+ mutex.unlock();
+
+ av_dump_format(formatContext, 0, 0, false); //avformat.h line 1256
+ int samples = ((formatContext->duration + 5000)*codecContext->sample_rate)/AV_TIME_BASE;
+
+ std::cout << "This stream has " << codecContext->channels << " channels, a sample rate of " << codecContext->sample_rate << "Hz and "<<samples <<" samples" << std::endl;
+ std::cout << "The data is in format " <<codecContext->sample_fmt<< " (aka "<< av_get_sample_fmt_name(codecContext->sample_fmt) << ") "<<std::endl;
+
+ isPlanar=(av_sample_fmt_is_planar(codecContext->sample_fmt)==1);
+
+ if(isPlanar) { cerr<<"found planar audio"<<endl; }
+
+
+ av_init_packet(&packet);
+ //sample_processed=0;
+ ready=true;
+ return true;
+ }
+
+ AVFrame* libav::audioloader::get_frame() {
+
+ if (!ready) return nullptr;
+
+ int frameFinished = 0;
+ while (!frameFinished) {
+ int ret=av_read_frame(formatContext, &packet);
+ if (ret<0) {
+ std::cerr << "finished with code "<<ret <<(ret==AVERROR_EOF?" ,EOF":"")<<std::endl;
+ ready=false;
+ return nullptr;
+ }
+ if (packet.stream_index == audioStream->index)
+ {
+ //int bytes =
+ avcodec_decode_audio4(codecContext, frame, &frameFinished, &packet);
+
+ // Some frames rely on multiple packets, so we have to make sure the frame is finished before
+ // we can use it
+ }
+ // You *must* call av_free_packet() after each call to av_read_frame() or else you'll leak memory
+ av_free_packet(&packet);
+ }
+ return frame;
+
+ }
+ AVPacket* libav::audioloader::get_packet() {
+
+ if (!ready) return nullptr;
+
+ int ret=av_read_frame(formatContext, &packet);
+ if (ret<0) {
+ std::cerr << "finished with code "<<ret <<(ret==AVERROR_EOF?" ,EOF":"")<<std::endl;
+ ready=false;
+ return nullptr;
+ }
+ //if (packet.stream_index == audioStream->index)
+ //{
+ //int bytes =
+ // avcodec_decode_audio4(codecContext, frame, &frameFinished, &packet);
+
+ // Some frames rely on multiple packets, so we have to make sure the frame is finished before
+ // we can use it
+ //}
+ // You *must* call av_free_packet() after each call to av_read_frame() or else you'll leak memory
+ //av_free_packet(&packet);?????
+ //}
+ return &packet;
+
+ }
+ uint16_t* libav::audioloader::get_samples(int num){ //presumes 16bpc here
+ //std::cerr << "request "<<num<<" samples: "<<(ready?"ready":"not ready")<<std::endl;
+ //if(!ready) return nullptr;
+ //shuffle down samples
+
+ if (sample_start>0){
+ for (int i=0;i<sample_end-sample_start;i++){
+ for (int j=0;j<channels;j++) {
+ buffer[(i*channels)+j]=buffer[((sample_start+i)*channels)+j];
+ }
+ }
+ sample_start=sample_end-sample_start;
+ }
+
+ sample_end=sample_start;
+ while (sample_end<num) {
+ frame=get_frame();
+ if (frame) {
+ channels=av_frame_get_channels(frame); //will always reach here 1st
+ if (((sample_end+std::max(num,frame->nb_samples))*channels)>buffer.size()){
+ int m=buffer.size();
+ int s=((sample_end+std::max(num,frame->nb_samples))*channels);
+ buffer.reserve(s);
+ std::cerr << "audioloader reserved buffer to " << s << std::endl;
+ for (int i=m;i<s;i++) buffer.push_back(0);
+ }
+ for (int i=0;i<frame->nb_samples;i++) {
+ for (int j=0;j<channels;j++) {
+ //int frame->format
+ //format of the frame, -1 if unknown or unset Values correspond to enum AVPixelFormat for video frames, enum AVSampleFormat for audio)
+ //int ff=frame->format;
+ //uint64_t frame->channel_layout
+ //Channel layout of the audio data.
+ //uint64_t fcl=frame->channel_layout;
+ //int frame->nb_extended_buf
+ //Number of elements in extended_buf.
+ //int fnb=frame->nb_extended_buf;
+ //int frame->decode_error_flags
+ //decode error flags of the frame, set to a combination of FF_DECODE_ERROR_xxx flags if the decoder produced a frame, but there were errors during the decoding.
+ //int fde=frame->decode_error_flags;
+
+
+ //uint16_t s=((uint16_t*) frame->buf[j]->data)[i];
+ uint16_t s;
+ if (isPlanar) {
+ s=((uint16_t*) frame->buf[j]->data)[i];
+ }else {
+ s=((uint16_t*) frame->buf[0]->data)[j*channels+i];
+ }
+
+ //where is audio grunge coming from? signed/ unsigned? doesn't seem to be byte order..
+ // add +1 to data subscript with no effect
+
+
+ //which? must be determined by format or layout of the channels
+ //ALSO some kind of HEINOUS memory leak??
+ buffer[((sample_end+i)*frame->channels)+j]=s;
+ //buffer[(j*frame->channels)+(sample_end+i)]= ((uint16_t*) frame->buf[j]->data)[i]; ??planar?? nope
+ }
+ }
+ sample_end+=frame->nb_samples;
+ }
+ else {
+ for (int i=sample_end;i<num;i++){
+ for (int j=0;j<channels;j++) {
+ buffer[(channels*i)+j]=0;
+ }
+ }
+ sample_end=num;
+ }
+ //std::cerr<<"filling buffer to "<<((sample_end+frame->nb_samples)*frame->channels)<<std::endl;
+
+
+ //avcodec_free_frame(&frame);
+ }
+ if (sample_end>num) {
+ sample_start=num;
+ }
+ else {
+ sample_start=0;
+ }
+ return (uint16_t*)(&buffer[0]);
+}
+
+bool libav::audioloader::close() {
+ mutex.lock();
+ av_free(frame);
+ avcodec_close(codecContext);
+ avformat_close_input(&formatContext);
+ mutex.unlock();
+ ready=false;
+ sample_start=0;
+ sample_end=0;
+ return true;
+}
diff --git a/rotord/src/libavwrapper.h b/rotord/src/libavwrapper.h
new file mode 100755
index 0000000..656f885
--- /dev/null
+++ b/rotord/src/libavwrapper.h
@@ -0,0 +1,279 @@
+#ifndef libavwrapper_H
+#define libavwrapper_H
+
+/*
+ * libavwrapper.h
+ * May 2012 Christopher Bruns
+ * The libavwrapper class is a C++ wrapper around the poorly documented
+ * libavcodec movie API used by ffmpeg. I made extensive use of Nathan
+ * Clack's implemention in the whisk project.
+ *
+ * The libavwrapper.h and libavwrapper.cpp files depend only on the libavcodec
+ * and allied sets of libraries. To compartmentalize and reduce dependencies
+ * I placed the Vaa3d specific use of this class into a separate set of
+ * source files: loadV3dFFMpeg.h/cpp
+ */
+
+////////////////////////
+//now that we have guards
+//instead of crashing instantly when the 2nd thread tries to encode a frame, we get an error
+
+ //*** Error in `./rotord': corrupted double-linked list: 0x00007f3c31b1b630 ***
+
+ //or
+
+ //*** Error in `./rotord': double free or corruption (out): 0x00007f3bf8210080 ***
+ ///////////////////////
+
+
+//http://blog.tomaka17.com/2012/03/libavcodeclibavformat-tutorial/
+//great to use c++11 features
+
+#ifndef UINT64_C
+#define UINT64_C(c) (c ## ULL)
+#endif
+
+#include "Poco/Mutex.h"
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavutil/pixfmt.h>
+#include <libavutil/opt.h>
+#include <libavutil/imgutils.h>
+#include <libavutil/samplefmt.h>
+
+#include <libswscale/swscale.h> //?
+}
+
+/*
+#include <QFile>
+#include <QNetworkAccessManager>
+#include <QMutex>
+#include <QUrl>
+#include <QBuffer>
+*/
+
+
+#include <string>
+#include <stdexcept>
+#include <iostream>
+#include <fstream>
+#include <math.h>
+#include <vector>
+
+
+
+namespace libav {
+
+
+
+ static bool b_is_one_time_inited=false;
+ // Some libavcodec calls are not reentrant
+
+ void maybeInitFFMpegLib();
+
+ static int sws_flags = SWS_BICUBIC;
+
+// Translated to C++ by Christopher Bruns May 2012
+// from ffmeg_adapt.c in whisk package by Nathan Clack, Mark Bolstadt, Michael Meeuwisse
+ class decoder
+ {
+ public:
+ enum Channel {
+ RED = 0,
+ GRAY = 0,
+ GREEN = 1,
+ BLUE = 2,
+ ALPHA = 3
+ };
+
+
+ decoder(PixelFormat pixelFormat=PIX_FMT_RGB24);
+ //decoder(QUrl url, PixelFormat pixelFormat=PIX_FMT_RGB24);
+ void cleanup();
+ virtual ~decoder();
+ //bool open(QUrl url, enum PixelFormat formatParam = PIX_FMT_RGB24);
+ //bool open(QIODevice& fileStream, QString& fileName, enum PixelFormat formatParam = PIX_FMT_RGB24);
+ bool reinit_buffers_and_scaler();
+ bool init_buffers_and_scaler();
+ uint8_t getPixelIntensity(int x, int y, Channel c = GRAY) const;
+ bool fetchFrame(int targetFrameIndex = 0);
+ bool fetchFrame(int w,int h,int targetFrameIndex = 0);
+ int getNumberOfFrames() const;
+ int getWidth() const;
+ int getHeight() const;
+ int getNumberOfChannels() const;
+ bool readNextFrame(int targetFrameIndex = 0);
+ bool readNextFrameWithPacket(int targetFrameIndex, AVPacket& packet, AVFrame* pYuv);
+ int seekToFrame(int targetFrameIndex = 0);
+
+ // make certain members public, for use by Fast3DTexture class
+ AVFrame *pFrameRGB;
+ AVFrame *pRaw;
+ AVFormatContext *container;
+ AVCodecContext *pCtx;
+ int videoStream;
+ int previousFrameIndex;
+ bool isOpen;
+
+ bool open(std::string& fileName, enum PixelFormat formatParam = PIX_FMT_RGB24);
+ bool open(char* fileName, enum PixelFormat formatParam = PIX_FMT_RGB24);
+
+ protected:
+
+
+ void initialize();
+
+ bool openUsingInitializedContainer(enum PixelFormat formatParam = PIX_FMT_RGB24 );
+ static bool avtry(int result, const std::string& msg);
+
+ AVCodec *pCodec;
+ uint8_t *buffer,
+ *blank;
+ //struct
+ SwsContext *Sctx;
+ int width, height;
+ PixelFormat format;
+ size_t numBytes;
+ int numFrames;
+ int sc; // number of color channels
+
+ // For loading from URL
+ /*
+ static const int ioBufferSize = 32768;
+ unsigned char * ioBuffer;
+ QNetworkAccessManager networkManager;
+ AVIOContext* avioContext;
+ QFile fileStream;
+ QNetworkReply* reply;
+ QBuffer fileBuffer;
+ QByteArray byteArray;
+ */
+ };
+
+
+ // TODO - finish refactoring based on
+ // http://svn.gnumonks.org/trunk/21c3-video/ffmpeg/ffmpeg-0.4.9-pre1/output_example.c
+ class encoder
+ {
+ public:
+ //typedef encoder::Channel Channel;
+
+ encoder(const char * file_name, int width, int height, float _framerate=25.0f, enum AVCodecID codec_id = CODEC_ID_H264);
+ virtual ~encoder();
+ void setPixelIntensity(int x, int y, int c, uint8_t value);
+ void write_frame(float seconds,uint8_t *rgbdata);
+ void write_frame(float seconds,uint16_t *audiodata);
+ int get_audio_framesize(){ return audio_input_frame_size; }
+ float get_audio_step(){return audiostep;};
+
+ protected:
+ AVFormatContext *container;
+ AVCodecContext *pCtx;
+ AVFrame *picture_yuv;
+ AVFrame *picture_rgb;
+ AVFrame *audio_frame;
+ float timebase;
+ struct SwsContext *Sctx;
+
+ AVStream *audio_st;
+ AVStream *video_st;
+
+ AVCodecContext *aCtx;
+ int audio_input_frame_size;
+ float audiostep;
+ };
+
+
+ class exporter {
+ public:
+ virtual ~exporter(){};
+ bool setup(int w,int h, int bitRate, int frameRate, std::string container);
+ bool record(std::string filename);
+ bool encodeFrame(unsigned char *pixels, uint16_t *samples);
+ bool encodeFrame(unsigned char *pixels,AVPacket *audiopkt); //is possible to just copy the packets?
+ bool encodeFrame(unsigned char *pixels);
+ bool encodeFrame(uint16_t *samples);
+ void finishRecord();
+ int get_audio_framesize(){return audioframesize;};
+ float get_audio_step(){return audiostep;};
+
+ AVStream *add_stream(AVFormatContext *oc, AVCodec **codec,enum AVCodecID codec_id);
+ void open_video(AVFormatContext *oc, AVCodec *codec, AVStream *st);
+ int open_audio(AVFormatContext *oc, AVCodec *codec, AVStream *st);
+
+ void write_audio_frame(AVFormatContext *oc, AVStream *st,uint16_t *samples);
+ void write_audio_frame(AVFormatContext *oc, AVStream *st,AVPacket *pkt);
+ void close_audio(AVFormatContext *oc, AVStream *st);
+
+ void write_video_frame(AVFormatContext *oc, AVStream *st, uint8_t *pixels);
+ void close_video(AVFormatContext *oc, AVStream *st);
+
+ private:
+ AVOutputFormat *fmt;
+ AVFormatContext *oc;
+ AVStream *audio_st, *video_st;
+ AVCodec *audio_codec, *video_codec;
+ double audio_pts, video_pts;
+
+ struct SwsContext *sws_ctx;
+
+ int audioframesize;
+ float audiostep;
+ int w;
+ int h;
+ int bitRate;
+ int frameRate;
+ std::string container;
+
+ int outputframe;
+
+ // video output //
+
+ AVFrame *frame;
+ AVPicture src_picture, dst_picture;
+ int frame_count;
+ uint8_t *outPixels;
+
+
+ //************************************************************//
+ // audio output //
+
+ float t, tincr, tincr2;
+ int audio_input_frame_size;
+
+
+ };
+
+ class audioloader{
+ public:
+ audioloader(){ready=false;sample_start=0;sample_end=0;};
+ bool setup(const std::string &filename);
+ AVFrame* get_frame();
+ uint16_t* get_samples(int num);
+ AVPacket* get_packet();
+ bool close();
+ bool ready;
+
+ AVCodecContext* codecContext;
+ AVFormatContext* formatContext;
+ int channels; //necessary to handle final packet -- unititialised after load/ problem?
+ private:
+ std::vector<uint16_t> buffer;
+ AVFrame* frame;
+
+ AVStream* audioStream;
+
+ AVPacket packet;
+ int sample_end;
+ int sample_start;
+ bool isPlanar;
+
+ };
+
+}
+
+
+
+#endif // libavwrapper_H
diff --git a/rotord/src/nodes_audio_analysis.h b/rotord/src/nodes_audio_analysis.h
new file mode 100644
index 0000000..e6c1e65
--- /dev/null
+++ b/rotord/src/nodes_audio_analysis.h
@@ -0,0 +1,47 @@
+#ifndef ROTOR_NODES_AUDIO_ANALYSIS
+#define ROTOR_NODES_AUDIO_ANALYSIS
+
+#include "rotor.h"
+#include "vampHost.h"
+
+namespace Rotor {
+ class Audio_analysis: public Base_audio_processor {
+ public:
+ Audio_analysis(){};
+ Audio_analysis(map<string,string> &settings) {
+ base_settings(settings);
+ soname=find_setting(settings,"soname");
+ id=find_setting(settings,"id");
+ outputNo=find_setting(settings,"outputNo",0);
+ };
+ Audio_analysis* clone(map<string,string> &_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 "<<id<<" of library "<<soname<<" found "<<analyser.features.size()<<" features "<<endl;
+ };
+ private:
+ string soname,id;
+ int outputNo;
+ vampHost::Analyser analyser;
+ map <string,float> params;
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/rotord/src/nodes_drawing.h b/rotord/src/nodes_drawing.h
new file mode 100644
index 0000000..11df2d6
--- /dev/null
+++ b/rotord/src/nodes_drawing.h
@@ -0,0 +1,43 @@
+#ifndef ROTOR_NODES_DRAWING
+#define ROTOR_NODES_DRAWING
+
+#include "rotor.h"
+#include <cairo.h>
+
+namespace Rotor {
+ class Draw: public Image_node {
+ public:
+ Draw(){image=nullptr;};
+ Draw(map<string,string> &settings) {
+ base_settings(settings);
+ };
+ ~Draw(){ if (image) delete image;};
+ Draw* clone(map<string,string> &_settings) { return new Draw(_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();
+ }
+ else image->setup(frame.w,frame.h);
+ }
+ else image->setup(frame.w,frame.h); //do this twice or use a goto
+ //draw onto new or input image
+ cairo_surface_t * cs = cairo_image_surface_create_for_data (image->RGBdata,
+ CAIRO_FORMAT_RGB24,
+ image->w,
+ image->h,
+ image->getStride());
+ cairo_t *c=cairo_create(cs);
+ cairo_rectangle(c, image->w/2, image->h/2, image->w, image->h);
+ cairo_set_source_rgb(c, 1.0, 0.0, 0.0);
+ cairo_fill(c);
+ return image;
+ }
+ private:
+ Image *image; //is an image generator
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/rotord/src/ofUtils.cpp b/rotord/src/ofUtils.cpp
new file mode 100755
index 0000000..7d7d9cc
--- /dev/null
+++ b/rotord/src/ofUtils.cpp
@@ -0,0 +1,745 @@
+#include "ofUtils.h"
+//#include "ofImage.h"
+//#include "ofTypes.h"
+//#include "ofGraphics.h"
+//#include "ofAppRunner.h"
+
+#include "Poco/String.h"
+#include "Poco/LocalDateTime.h"
+#include "Poco/DateTimeFormatter.h"
+
+#include <cctype> // for toupper
+
+
+
+/*
+#ifdef TARGET_WIN32
+ #ifndef _MSC_VER
+ #include <unistd.h> // this if for MINGW / _getcwd
+ #include <sys/param.h> // for MAXPATHLEN
+ #endif
+#endif
+
+
+#if defined(TARGET_OF_IPHONE) || defined(TARGET_OSX ) || defined(TARGET_LINUX)
+ #include <sys/time.h>
+#endif
+
+#ifdef TARGET_OSX
+ #ifndef TARGET_OF_IPHONE
+ #include <mach-o/dyld.h>
+ #include <sys/param.h> // for MAXPATHLEN
+ #endif
+#endif
+
+#ifdef TARGET_WIN32
+ #include <mmsystem.h>
+ #ifdef _MSC_VER
+ #include <direct.h>
+ #endif
+
+#endif
+*/
+
+#ifndef MAXPATHLEN
+ #define MAXPATHLEN 1024
+#endif
+
+
+static bool enableDataPath = true;
+//static unsigned long startTime = ofGetSystemTime(); // better at the first frame ?? (currently, there is some delay from static init, to running.
+//static unsigned long startTimeMicros = ofGetSystemTimeMicros();
+
+/*
+//--------------------------------------
+unsigned long ofGetElapsedTimeMillis(){
+ return ofGetSystemTime() - startTime;
+}
+
+//--------------------------------------
+unsigned long ofGetElapsedTimeMicros(){
+ return ofGetSystemTimeMicros() - startTimeMicros;
+}
+
+//--------------------------------------
+float ofGetElapsedTimef(){
+ return ofGetElapsedTimeMicros() / 1000000.0f;
+}
+
+//--------------------------------------
+void ofResetElapsedTimeCounter(){
+ startTime = ofGetSystemTime();
+ startTimeMicros = ofGetSystemTimeMicros();
+}
+*/
+//=======================================
+// this is from freeglut, and used internally:
+/* Platform-dependent time in milliseconds, as an unsigned 32-bit integer.
+ * This value wraps every 49.7 days, but integer overflows cancel
+ * when subtracting an initial start time, unless the total time exceeds
+ * 32-bit, where the GLUT API return value is also overflowed.
+ */
+/*
+unsigned long ofGetSystemTime( ) {
+ #ifndef TARGET_WIN32
+ struct timeval now;
+ gettimeofday( &now, NULL );
+ return now.tv_usec/1000 + now.tv_sec*1000;
+ #else
+ #if defined(_WIN32_WCE)
+ return GetTickCount();
+ #else
+ return timeGetTime();
+ #endif
+ #endif
+}
+*/
+
+/*
+unsigned long ofGetSystemTimeMicros( ) {
+ #ifndef TARGET_WIN32
+ struct timeval now;
+ gettimeofday( &now, NULL );
+ return now.tv_usec + now.tv_sec*1000000;
+ #else
+ #if defined(_WIN32_WCE)
+ return GetTickCount()*1000;
+ #else
+ return timeGetTime()*1000;
+ #endif
+ #endif
+}
+*/
+//--------------------------------------------------
+unsigned int ofGetUnixTime(){
+ return (unsigned int)time(NULL);
+}
+
+//default ofGetTimestampString returns in this format: 2011-01-15-18-29-35-299
+//--------------------------------------------------
+string ofGetTimestampString(){
+ string timeFormat = "%Y-%m-%d-%H-%M-%S-%i";
+ Poco::LocalDateTime now;
+ return Poco::DateTimeFormatter::format(now, timeFormat);
+}
+
+//specify the string format - eg: %Y-%m-%d-%H-%M-%S-%i ( 2011-01-15-18-29-35-299 )
+//--------------------------------------------------
+string ofGetTimestampString(string timestampFormat){
+ Poco::LocalDateTime now;
+ return Poco::DateTimeFormatter::format(now, timestampFormat);
+}
+
+//--------------------------------------------------
+int ofGetSeconds(){
+ time_t curr;
+ tm local;
+ time(&curr);
+ local =*(localtime(&curr));
+ return local.tm_sec;
+}
+
+//--------------------------------------------------
+int ofGetMinutes(){
+ time_t curr;
+ tm local;
+ time(&curr);
+ local =*(localtime(&curr));
+ return local.tm_min;
+}
+
+//--------------------------------------------------
+int ofGetHours(){
+ time_t curr;
+ tm local;
+ time(&curr);
+ local =*(localtime(&curr));
+ return local.tm_hour;
+}
+
+//--------------------------------------------------
+int ofGetYear(){
+ time_t curr;
+ tm local;
+ time(&curr);
+ local =*(localtime(&curr));
+ int year = local.tm_year + 1900;
+ return year;
+}
+
+//--------------------------------------------------
+int ofGetMonth(){
+ time_t curr;
+ tm local;
+ time(&curr);
+ local =*(localtime(&curr));
+ int month = local.tm_mon + 1;
+ return month;
+}
+
+//--------------------------------------------------
+int ofGetDay(){
+ time_t curr;
+ tm local;
+ time(&curr);
+ local =*(localtime(&curr));
+ return local.tm_mday;
+}
+
+//--------------------------------------------------
+int ofGetWeekday(){
+ time_t curr;
+ tm local;
+ time(&curr);
+ local =*(localtime(&curr));
+ return local.tm_wday;
+}
+
+//--------------------------------------------------
+void ofEnableDataPath(){
+ enableDataPath = true;
+}
+
+//--------------------------------------------------
+void ofDisableDataPath(){
+ enableDataPath = false;
+}
+
+//--------------------------------------------------
+//use ofSetDataPathRoot() to override this
+static string & dataPathRoot(){
+#if defined TARGET_OSX
+ static string * dataPathRoot = new string("../../../data/");
+#elif defined TARGET_ANDROID
+ static string * dataPathRoot = new string("sdcard/");
+#elif defined(TARGET_LINUX)
+ static string * dataPathRoot = new string(ofFilePath::join(ofFilePath::getCurrentExeDir(), "data/"));
+#else
+ static string * dataPathRoot = new string("data/");
+#endif
+ return *dataPathRoot;
+}
+
+static bool & isDataPathSet(){
+ static bool * dataPathSet = new bool(false);
+ return * dataPathSet;
+}
+
+//--------------------------------------------------
+void ofSetDataPathRoot(string newRoot){
+ string newPath = "";
+
+ #ifdef TARGET_OSX
+ #ifndef TARGET_OF_IPHONE
+ char path[MAXPATHLEN];
+ uint32_t size = sizeof(path);
+
+ if (_NSGetExecutablePath(path, &size) == 0){
+ //printf("executable path is %s\n", path);
+ string pathStr = string(path);
+
+ //theo: check this with having '/' as a character in a folder name - OSX treats the '/' as a ':'
+ //checked with spaces too!
+
+ vector < string> pathBrokenUp = ofSplitString( pathStr, "/");
+
+ newPath = "";
+
+ for(int i = 0; i < pathBrokenUp.size()-1; i++){
+ newPath += pathBrokenUp[i];
+ newPath += "/";
+ }
+
+ //cout << newPath << endl; // some sanity checks here
+ //system( "pwd" );
+
+ chdir ( newPath.c_str() );
+ //system("pwd");
+ }else{
+ ofLog(OF_LOG_FATAL_ERROR, "buffer too small; need size %u\n", size);
+ }
+ #endif
+ #endif
+
+ dataPathRoot() = newRoot;
+ isDataPathSet() = true;
+}
+
+//--------------------------------------------------
+string ofToDataPath(string path, bool makeAbsolute){
+
+ if (!isDataPathSet())
+ ofSetDataPathRoot(dataPathRoot());
+
+ if( enableDataPath ){
+
+ //check if absolute path has been passed or if data path has already been applied
+ //do we want to check for C: D: etc ?? like substr(1, 2) == ':' ??
+ if( path.length()==0 || (path.substr(0,1) != "/" && path.substr(1,1) != ":" && path.substr(0,dataPathRoot().length()) != dataPathRoot())){
+ path = dataPathRoot()+path;
+ }
+
+ if(makeAbsolute && (path.length()==0 || path.substr(0,1) != "/")){
+ /*
+ #if !defined( TARGET_OF_IPHONE) & !defined(TARGET_ANDROID)
+
+ #ifndef TARGET_WIN32
+ char currDir[1024];
+ path = "/"+path;
+ path = getcwd(currDir, 1024)+path;
+
+ #else
+
+ char currDir[1024];
+ path = "\\"+path;
+ path = _getcwd(currDir, 1024)+path;
+ std::replace( path.begin(), path.end(), '/', '\\' ); // fix any unixy paths...
+
+
+ #endif
+
+
+ #else
+ //do we need iphone specific code here?
+ #endif
+ */
+ }
+
+ }
+ return path;
+}
+
+//----------------------------------------
+template <>
+string ofToHex(const string& value) {
+ ostringstream out;
+ // how many bytes are in the string
+ int numBytes = value.size();
+ for(int i = 0; i < numBytes; i++) {
+ // print each byte as a 2-character wide hex value
+ out << setfill('0') << setw(2) << hex << (unsigned int) ((unsigned char)value[i]);
+ }
+ return out.str();
+}
+
+//----------------------------------------
+string ofToHex(const char* value) {
+ // this function is necessary if you want to print a string
+ // using a syntax like ofToHex("test")
+ return ofToHex((string) value);
+}
+
+//----------------------------------------
+int ofToInt(const string& intString) {
+ int x = 0;
+ istringstream cur(intString);
+ cur >> x;
+ return x;
+}
+
+//----------------------------------------
+int ofHexToInt(const string& intHexString) {
+ int x = 0;
+ istringstream cur(intHexString);
+ cur >> hex >> x;
+ return x;
+}
+
+//----------------------------------------
+char ofHexToChar(const string& charHexString) {
+ int x = 0;
+ istringstream cur(charHexString);
+ cur >> hex >> x;
+ return (char) x;
+}
+
+//----------------------------------------
+float ofHexToFloat(const string& floatHexString) {
+ union intFloatUnion {
+ int x;
+ float f;
+ } myUnion;
+ myUnion.x = 0;
+ istringstream cur(floatHexString);
+ cur >> hex >> myUnion.x;
+ return myUnion.f;
+}
+
+//----------------------------------------
+string ofHexToString(const string& stringHexString) {
+ stringstream out;
+ stringstream stream(stringHexString);
+ // a hex string has two characters per byte
+ int numBytes = stringHexString.size() / 2;
+ for(int i = 0; i < numBytes; i++) {
+ string curByte;
+ // grab two characters from the hex string
+ stream >> setw(2) >> curByte;
+ // prepare to parse the two characters
+ stringstream curByteStream(curByte);
+ int cur = 0;
+ // parse the two characters as a hex-encoded int
+ curByteStream >> hex >> cur;
+ // add the int as a char to our output stream
+ out << (char) cur;
+ }
+ return out.str();
+}
+
+//----------------------------------------
+float ofToFloat(const string& floatString) {
+ float x = 0;
+ istringstream cur(floatString);
+ cur >> x;
+ return x;
+}
+
+//----------------------------------------
+bool ofToBool(const string& boolString) {
+ static const string trueString = "true";
+ static const string falseString = "false";
+ string lower = Poco::toLower(boolString);
+ if(lower == trueString) {
+ return true;
+ }
+ if(lower == falseString) {
+ return false;
+ }
+ bool x = false;
+ istringstream cur(lower);
+ cur >> x;
+ return x;
+}
+
+//----------------------------------------
+char ofToChar(const string& charString) {
+ char x = '\0';
+ istringstream cur(charString);
+ cur >> x;
+ return x;
+}
+
+//----------------------------------------
+template <> string ofToBinary(const string& value) {
+ stringstream out;
+ int numBytes = value.size();
+ for(int i = 0; i < numBytes; i++) {
+ bitset<8> bitBuffer(value[i]);
+ out << bitBuffer;
+ }
+ return out.str();
+}
+
+//----------------------------------------
+string ofToBinary(const char* value) {
+ // this function is necessary if you want to print a string
+ // using a syntax like ofToBinary("test")
+ return ofToBinary((string) value);
+}
+
+//----------------------------------------
+int ofBinaryToInt(const string& value) {
+ const int intSize = sizeof(int) * 8;
+ bitset<intSize> binaryString(value);
+ return (int) binaryString.to_ulong();
+}
+
+//----------------------------------------
+char ofBinaryToChar(const string& value) {
+ const int charSize = sizeof(char) * 8;
+ bitset<charSize> binaryString(value);
+ return (char) binaryString.to_ulong();
+}
+
+//----------------------------------------
+float ofBinaryToFloat(const string& value) {
+ const int floatSize = sizeof(float) * 8;
+ bitset<floatSize> binaryString(value);
+ union ulongFloatUnion {
+ unsigned long result;
+ float f;
+ } myUFUnion;
+ myUFUnion.result = binaryString.to_ulong();
+ return myUFUnion.f;
+}
+//----------------------------------------
+string ofBinaryToString(const string& value) {
+ ostringstream out;
+ stringstream stream(value);
+ bitset<8> byteString;
+ int numBytes = value.size() / 8;
+ for(int i = 0; i < numBytes; i++) {
+ stream >> byteString;
+ out << (char) byteString.to_ulong();
+ }
+ return out.str();
+}
+
+//--------------------------------------------------
+vector <string> ofSplitString(const string & source, const string & delimiter, bool ignoreEmpty, bool trim) {
+ vector<string> result;
+ if (delimiter.empty()) {
+ result.push_back(source);
+ return result;
+ }
+ string::const_iterator substart = source.begin(), subend;
+ while (true) {
+ subend = search(substart, source.end(), delimiter.begin(), delimiter.end());
+ string sub(substart, subend);
+ if(trim) {
+ Poco::trimInPlace(sub);
+ }
+ if (!ignoreEmpty || !sub.empty()) {
+ result.push_back(sub);
+ }
+ if (subend == source.end()) {
+ break;
+ }
+ substart = subend + delimiter.size();
+ }
+ return result;
+}
+
+//--------------------------------------------------
+string ofJoinString(vector <string> stringElements, const string & delimiter){
+ string resultString = "";
+ int numElements = stringElements.size();
+
+ for(int k = 0; k < numElements; k++){
+ if( k < numElements-1 ){
+ resultString += stringElements[k] + delimiter;
+ } else {
+ resultString += stringElements[k];
+ }
+ }
+
+ return resultString;
+}
+
+//--------------------------------------------------
+void ofStringReplace(string& input, string searchStr, string replaceStr){
+ size_t uPos = 0;
+ size_t uFindLen = searchStr.length();
+ size_t uReplaceLen = replaceStr.length();
+
+ if( uFindLen == 0 ){
+ return;
+ }
+
+ for( ;(uPos = input.find( searchStr, uPos )) != std::string::npos; ){
+ input.replace( uPos, uFindLen, replaceStr );
+ uPos += uReplaceLen;
+ }
+}
+
+//--------------------------------------------------
+bool ofIsStringInString(string haystack, string needle){
+ return ( strstr(haystack.c_str(), needle.c_str() ) != NULL );
+}
+
+//--------------------------------------------------
+string ofToLower(const string & src){
+ string dst(src);
+ transform(src.begin(),src.end(),dst.begin(),::tolower);
+ return dst;
+}
+
+//--------------------------------------------------
+string ofToUpper(const string & src){
+ string dst(src);
+ transform(src.begin(),src.end(),dst.begin(),::toupper);
+ return dst;
+}
+
+//--------------------------------------------------
+string ofVAArgsToString(const char * format, ...){
+ // variadic args to string:
+ // http://www.codeproject.com/KB/string/string_format.aspx
+ static char aux_buffer[10000];
+ string retStr("");
+ if (NULL != format){
+
+ va_list marker;
+
+ // initialize variable arguments
+ va_start(marker, format);
+
+ // Get formatted string length adding one for NULL
+ size_t len = vsprintf(aux_buffer, format, marker) + 1;
+
+ // Reset variable arguments
+ va_end(marker);
+
+ if (len > 0)
+ {
+ va_list args;
+
+ // initialize variable arguments
+ va_start(args, format);
+
+ // Create a char vector to hold the formatted string.
+ vector<char> buffer(len, '\0');
+ vsprintf(&buffer[0], format, args);
+ retStr = &buffer[0];
+ va_end(args);
+ }
+
+ }
+ return retStr;
+}
+
+string ofVAArgsToString(const char * format, va_list args){
+ // variadic args to string:
+ // http://www.codeproject.com/KB/string/string_format.aspx
+ char aux_buffer[10000];
+ string retStr("");
+ if (NULL != format){
+
+ // Get formatted string length adding one for NULL
+ vsprintf(aux_buffer, format, args);
+ retStr = aux_buffer;
+
+ }
+ return retStr;
+}
+
+/*
+//--------------------------------------------------
+void ofLaunchBrowser(string url){
+
+ // http://support.microsoft.com/kb/224816
+
+ //make sure it is a properly formatted url
+ if(Poco::icompare(url.substr(0,7), "http://") != 0 &&
+ Poco::icompare(url.substr(0,8), "https://") != 0) {
+ ofLog(OF_LOG_WARNING, "ofLaunchBrowser: url must begin http:// or https://");
+ return;
+ }
+
+ //----------------------------
+ #ifdef TARGET_WIN32
+ //----------------------------
+
+ #if (_MSC_VER)
+ // microsoft visual studio yaks about strings, wide chars, unicode, etc
+ ShellExecuteA(NULL, "open", url.c_str(),
+ NULL, NULL, SW_SHOWNORMAL);
+ #else
+ ShellExecute(NULL, "open", url.c_str(),
+ NULL, NULL, SW_SHOWNORMAL);
+ #endif
+
+ //----------------------------
+ #endif
+ //----------------------------
+
+ //--------------------------------------
+ #ifdef TARGET_OSX
+ //--------------------------------------
+ // ok gotta be a better way then this,
+ // this is what I found...
+ string commandStr = "open "+url;
+ system(commandStr.c_str());
+ //----------------------------
+ #endif
+ //----------------------------
+
+ //--------------------------------------
+ #ifdef TARGET_LINUX
+ //--------------------------------------
+ string commandStr = "xdg-open "+url;
+ int ret = system(commandStr.c_str());
+ if(ret!=0) ofLog(OF_LOG_ERROR,"ofLaunchBrowser: couldn't open browser");
+ //----------------------------
+ #endif
+ //----------------------------
+}
+
+//--------------------------------------------------
+string ofGetVersionInfo(){
+ string version;
+ stringstream sstr;
+ sstr << "of version: " << OF_VERSION << endl;
+ return sstr.str();
+}
+*/
+//---- new to 006
+//from the forums http://www.openframeworks.cc/forum/viewtopic.php?t=1413
+/*
+//--------------------------------------------------
+void ofSaveScreen(string filename) {
+ ofImage screen;
+ screen.allocate(ofGetWidth(), ofGetHeight(), OF_IMAGE_COLOR);
+ screen.grabScreen(0, 0, ofGetWidth(), ofGetHeight());
+ screen.saveImage(filename);
+}
+
+//--------------------------------------------------
+void ofSaveViewport(string filename) {
+ // because ofSaveScreen doesn't related to viewports
+ ofImage screen;
+ ofRectangle view = ofGetCurrentViewport();
+ screen.allocate(view.width, view.height, OF_IMAGE_COLOR);
+ screen.grabScreen(0, 0, view.width, view.height);
+ screen.saveImage(filename);
+}
+
+//--------------------------------------------------
+int saveImageCounter = 0;
+void ofSaveFrame(bool bUseViewport){
+ string fileName = ofToString(saveImageCounter) + ".png";
+ if (bUseViewport){
+ ofSaveViewport(fileName);
+ } else {
+ ofSaveScreen(fileName);
+ }
+ saveImageCounter++;
+}
+
+//--------------------------------------------------
+string ofSystem(string command){
+ FILE * ret = NULL;
+#ifdef TARGET_WIN32
+ ret = _popen(command.c_str(),"r");
+#else
+ ret = popen(command.c_str(),"r");
+#endif
+
+ string strret;
+ char c;
+
+ if (ret == NULL){
+ ofLogError() << "ofSystem: error opening return file";
+ }else{
+ do {
+ c = fgetc (ret);
+ strret += c;
+ } while (c != EOF);
+ fclose (ret);
+ }
+
+ return strret;
+}
+
+//--------------------------------------------------
+ofTargetPlatform ofGetTargetPlatform(){
+#ifdef TARGET_LINUX
+ if(ofSystem("uname -m").find("x86_64")==0)
+ return OF_TARGET_LINUX64;
+ else
+ return OF_TARGET_LINUX;
+#elif defined(TARGET_OSX)
+ return OF_TARGET_OSX;
+#elif defined(TARGET_WIN32)
+ #if (_MSC_VER)
+ return OF_TARGET_WINVS;
+ #else
+ return OF_TARGET_WINGCC;
+ #endif
+#elif defined(TARGET_ANDROID)
+ return OF_TARGET_ANDROID;
+#elif defined(TARGET_OF_IPHONE)
+ return OF_TARGET_IPHONE;
+#endif
+}
+*/ \ No newline at end of file
diff --git a/rotord/src/ofUtils.h b/rotord/src/ofUtils.h
new file mode 100755
index 0000000..0567e22
--- /dev/null
+++ b/rotord/src/ofUtils.h
@@ -0,0 +1,223 @@
+#pragma once
+
+//#include "ofConstants.h"
+
+
+// core: ---------------------------
+#include <cstdio>
+#include <cstdarg>
+#include <cmath>
+#include <ctime>
+#include <cstdlib>
+#include <string>
+#include <iostream>
+#include <vector>
+#include <cstring>
+#include <sstream> //for ostringsream
+#include <iomanip> //for setprecision
+#include <fstream>
+#include <algorithm>
+
+#include <bitset> // for ofToBinary
+
+
+
+
+//#include "ofLog.h"
+
+/*#ifdef TARGET_WIN32 // for ofLaunchBrowser
+ #include <shellapi.h>
+#endif
+*/
+
+
+using namespace std;
+
+int ofNextPow2(int input);
+
+void ofResetElapsedTimeCounter(); // this happens on the first frame
+float ofGetElapsedTimef();
+unsigned long ofGetElapsedTimeMillis();
+unsigned long ofGetElapsedTimeMicros();
+int ofGetFrameNum();
+
+int ofGetSeconds();
+int ofGetMinutes();
+int ofGetHours();
+
+//number of seconds since 1970
+unsigned int ofGetUnixTime();
+
+
+/*
+unsigned long ofGetSystemTime( ); // system time in milliseconds;
+unsigned long ofGetSystemTimeMicros( ); // system time in microseconds;
+
+ //returns
+string ofGetTimestampString();
+string ofGetTimestampString(string timestampFormat);
+
+
+int ofGetYear();
+int ofGetMonth();
+int ofGetDay();
+int ofGetWeekday();
+
+void ofLaunchBrowser(string url);
+*/
+void ofEnableDataPath();
+void ofDisableDataPath();
+string ofToDataPath(string path, bool absolute=false);
+
+template<class T>
+void ofRandomize(vector<T>& values) {
+ random_shuffle(values.begin(), values.end());
+}
+
+template<class T, class BoolFunction>
+void ofRemove(vector<T>& values, BoolFunction shouldErase) {
+ values.erase(remove_if(values.begin(), values.end(), shouldErase), values.end());
+}
+
+template<class T>
+void ofSort(vector<T>& values) {
+ sort(values.begin(), values.end());
+}
+template<class T, class BoolFunction>
+void ofSort(vector<T>& values, BoolFunction compare) {
+ sort(values.begin(), values.end(), compare);
+}
+
+template <class T>
+unsigned int ofFind(const vector<T>& values, const T& target) {
+ return distance(values.begin(), find(values.begin(), values.end(), target));
+}
+
+template <class T>
+bool ofContains(const vector<T>& values, const T& target) {
+ return ofFind(values, target) != values.size();
+}
+
+//set the root path that ofToDataPath will use to search for files relative to the app
+//the path must have a trailing slash (/) !!!!
+void ofSetDataPathRoot( string root );
+
+template <class T>
+string ofToString(const T& value){
+ ostringstream out;
+ out << value;
+ return out.str();
+}
+
+/// like sprintf "%4f" format, in this example precision=4
+template <class T>
+string ofToString(const T& value, int precision){
+ ostringstream out;
+ out << fixed << setprecision(precision) << value;
+ return out.str();
+}
+
+/// like sprintf "% 4d" or "% 4f" format, in this example width=4, fill=' '
+template <class T>
+string ofToString(const T& value, int width, char fill ){
+ ostringstream out;
+ out << fixed << setfill(fill) << setw(width) << value;
+ return out.str();
+}
+
+/// like sprintf "%04.2d" or "%04.2f" format, in this example precision=2, width=4, fill='0'
+template <class T>
+string ofToString(const T& value, int precision, int width, char fill ){
+ ostringstream out;
+ out << fixed << setfill(fill) << setw(width) << setprecision(precision) << value;
+ return out.str();
+}
+
+template<class T>
+string ofToString(const vector<T>& values) {
+ stringstream out;
+ int n = values.size();
+ out << "{";
+ if(n > 0) {
+ for(int i = 0; i < n - 1; i++) {
+ out << values[i] << ", ";
+ }
+ out << values[n - 1];
+ }
+ out << "}";
+ return out.str();
+}
+
+template <class T>
+string ofToHex(const T& value) {
+ ostringstream out;
+ // pretend that the value is a bunch of bytes
+ unsigned char* valuePtr = (unsigned char*) &value;
+ // the number of bytes is determined by the datatype
+ int numBytes = sizeof(T);
+ // the bytes are stored backwards (least significant first)
+ for(int i = numBytes - 1; i >= 0; i--) {
+ // print each byte out as a 2-character wide hex value
+ out << setfill('0') << setw(2) << hex << (int) valuePtr[i];
+ }
+ return out.str();
+}
+template <>
+string ofToHex(const string& value);
+string ofToHex(const char* value);
+
+int ofHexToInt(const string& intHexString);
+char ofHexToChar(const string& charHexString);
+float ofHexToFloat(const string& floatHexString);
+string ofHexToString(const string& stringHexString);
+
+int ofToInt(const string& intString);
+char ofToChar(const string& charString);
+float ofToFloat(const string& floatString);
+bool ofToBool(const string& boolString);
+
+template <class T>
+string ofToBinary(const T& value) {
+ ostringstream out;
+ const char* data = (const char*) &value;
+ // the number of bytes is determined by the datatype
+ int numBytes = sizeof(T);
+ // the bytes are stored backwards (least significant first)
+ for(int i = numBytes - 1; i >= 0; i--) {
+ bitset<8> cur(data[i]);
+ out << cur;
+ }
+ return out.str();
+}
+template <>
+string ofToBinary(const string& value);
+string ofToBinary(const char* value);
+
+int ofBinaryToInt(const string& value);
+char ofBinaryToChar(const string& value);
+float ofBinaryToFloat(const string& value);
+string ofBinaryToString(const string& value);
+
+string ofGetVersionInfo();
+
+void ofSaveScreen(string filename);
+void ofSaveFrame(bool bUseViewport = false);
+void ofSaveViewport(string filename);
+
+//--------------------------------------------------
+vector <string> ofSplitString(const string & source, const string & delimiter, bool ignoreEmpty = false, bool trim = false);
+string ofJoinString(vector <string> stringElements, const string & delimiter);
+void ofStringReplace(string& input, string searchStr, string replaceStr);
+bool ofIsStringInString(string haystack, string needle);
+
+string ofToLower(const string & src);
+string ofToUpper(const string & src);
+
+string ofVAArgsToString(const char * format, ...);
+string ofVAArgsToString(const char * format, va_list args);
+
+string ofSystem(string command);
+
+//ofTargetPlatform ofGetTargetPlatform();
+
+
diff --git a/rotord/src/params.h b/rotord/src/params.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/rotord/src/params.h
diff --git a/rotord/src/rendercontext.cpp b/rotord/src/rendercontext.cpp
new file mode 100644
index 0000000..7362d64
--- /dev/null
+++ b/rotord/src/rendercontext.cpp
@@ -0,0 +1,386 @@
+#include "rotor.h"
+
+
+using namespace Rotor;
+void Render_context::runTask() {
+ while (!isCancelled()) {
+ int cmd=0;
+ mutex.lock();
+ if (work_queue.size()){
+ cmd=work_queue[0];
+ work_queue.pop_front();
+ }
+ mutex.unlock();
+ if(cmd==ANALYSE_AUDIO) {
+ state=ANALYSING_AUDIO;
+ 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(dynamic_cast<Base_audio_processor*>(a));
+ }
+ if (load_audio(audio_filename,processors)) {
+ audio_loaded=true;
+ state=IDLE;
+ }
+ else {
+ //an error occurred: TODO have to clean up allocated data. autoptr?
+ audio_loaded=false;
+ state=IDLE;
+ }
+ }
+ if(cmd==RENDER) {
+ state=RENDERING;
+ if(graph.video_render(output_filename,audio_filename,output_framerate,progress)){
+ state=IDLE;
+ }
+ else {
+ //an error occurred: TODO have to clean up allocated data. autoptr?
+ cerr<<"Rotor: render failed"<<endl;
+ state=IDLE;
+ }
+ }
+ sleep(100);
+ }
+ printf("Rotor: stopping thread\n");
+}
+void Render_context::add_queue(int item) {
+ mutex.lock();
+ work_queue.push_back(item);
+ mutex.unlock();
+}
+void Render_context::session_command(const std::vector<std::string>& command,xmlIO& XML,HTTPResponse::HTTPStatus& status){
+ Logger& logger = Logger::get("Rotor");
+ status=HTTPResponse::HTTP_BAD_REQUEST; //error by default
+ if (command[2]=="resolution") {
+ if (command[0]=="PUT") {
+ if (command.size()>2) {
+ if (state==IDLE) {
+ Poco::StringTokenizer t1(command[3],",");
+ if (t1.count()>1){
+ int w=ofToInt(t1[0]);
+ int h=ofToInt(t1[1]);
+ if (graph.set_resolution(w,h)){
+ logger.information("resolution set to "+t1[0]+"x"+t1[1]);
+ XML.addValue("status","resolution set to "+t1[0]+"x"+t1[1]);
+ status=HTTPResponse::HTTP_OK;
+ }
+ else {
+ logger.error("ERROR: invalid resolution request: "+t1[0]+"x"+t1[1]);
+ XML.addValue("error","invalid resolution request: "+t1[0]+"x"+t1[1]);
+ }
+ }
+ }
+ else {
+ XML.addValue("error","session busy");
+ }
+ }
+ }
+ }
+ if (command[2]=="audio") {
+ if (command[0]=="PUT") { //get audio file location and initiate analysis
+ if (command.size()>2) {
+ if (state==IDLE) {
+ audio_filename=media_dir+command[3]; //for now, store session variables in memory //check file exists
+ Poco::File f=Poco::File(audio_filename);
+ if (f.exists()) {
+ //pass to worker thread ??if engine is ready?? ??what if engine has finished but results aren't read??
+ add_queue(ANALYSE_AUDIO);
+ status=HTTPResponse::HTTP_OK;
+ logger.information("Starting audio analysis: "+command[3]);
+ XML.addValue("status","Starting audio analysis: "+command[3]);
+ }
+ else {
+ status=HTTPResponse::HTTP_NOT_FOUND;
+ logger.error("ERROR: audio file "+command[3]+" not found");
+ XML.addValue("error",command[3]+" not found");
+ }
+
+ }
+ else {
+ logger.error("ERROR: Session busy");
+ XML.addValue("error","Session busy");
+ }
+ }
+ }
+ if (command[0]=="GET") {
+ if (state==ANALYSING_AUDIO) {
+ status=HTTPResponse::HTTP_OK;
+ XML.addValue("status","Analysing audio");
+ char c[20];
+ sprintf(c,"%02f",progress);
+ XML.addValue("progress",string(c));
+ }
+ else if (audio_loaded) {
+ //not sure about this-- should this state be retained?
+ //can the data only be read once?
+ //for now
+ status=HTTPResponse::HTTP_OK;
+ XML.addValue("status","Audio ready");
+ XML.addValue("audio",audio_thumb->print());
+ }
+ else {
+ logger.error("ERROR: audio thumbnail requested but no audio loaded");
+ XML.addValue("error","No audio loaded");
+ }
+ }
+ if (command[0]=="DELETE") {
+ if (state==IDLE) {
+ audio_filename="";
+ logger.information("Audio deleted");
+ XML.addValue("status","Audio deleted");
+ status=HTTPResponse::HTTP_OK;
+ }
+ else {
+ logger.error("ERROR: Session busy");
+ XML.addValue("error","Session busy");
+ }
+ }
+ }
+ if (command[2]=="graph") {
+ if (command[0]=="GET") {
+ if (graph.loaded) {
+ status=HTTPResponse::HTTP_OK;
+ //XML.addValue("patchbay",graph.toString());
+ logger.information("Requested graph");
+ XML.loadFromBuffer(graph.toString());
+ }
+ else {
+ logger.error("ERROR: graph not loaded: check XML");
+ XML.addValue("error","graph not loaded: check XML");
+ }
+ }
+ if (command[0]=="PUT") { //get new graph from file
+ if (command.size()>2) {
+ //should interrupt whatever is happening?
+ //before begining to load from xml
+ if (state==IDLE) { //eventually not like this
+ if (graph.load(command[3])) {
+ status=HTTPResponse::HTTP_OK;
+ logger.information("Loaded graph from http PUT body");
+ XML.addValue("status","Loaded graph from PUT body");
+ if (audio_loaded) {
+ add_queue(ANALYSE_AUDIO);
+ status=HTTPResponse::HTTP_OK;
+ logger.information("Starting audio analysis for graph: "+command[3]);
+ XML.addValue("status","Starting audio analysis for graph: "+command[3]);
+ }
+ }
+ else {
+ string graph_filename=graph_dir+command[3];
+ Poco::File f=Poco::File(graph_filename);
+ if (f.exists()) {
+ if (graph.loadFile(graph_filename)) {
+ status=HTTPResponse::HTTP_OK;
+ //XML.addValue("patchbay",graph.toString());
+ //XML.loadFromBuffer(graph.toString());
+ XML=graph.xml;
+ //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
+ if (audio_loaded) {
+ add_queue(ANALYSE_AUDIO);
+ status=HTTPResponse::HTTP_OK;
+ logger.information("Starting audio analysis for graph: "+command[3]);
+ XML.addValue("status","Starting audio analysis for graph: "+command[3]);
+ }
+ }
+ else {
+ status=HTTPResponse::HTTP_INTERNAL_SERVER_ERROR; //~/sources/poco-1.4.6-all/Net/include/Poco/Net/HTTPResponse.h
+ logger.error("ERROR: graph not loaded: check XML");
+ XML.addValue("error","graph not loaded: check XML");
+ }
+ }
+ else {
+ status=HTTPResponse::HTTP_NOT_FOUND;
+ logger.error("ERROR: "+command[3]+" not found");
+ XML.addValue("error",command[3]+" not found");
+ }
+ }
+ }
+ }
+ }
+ if (command[0]=="DELETE") {
+ //for now
+ graph=Graph();
+ logger.information("graph deleted");
+ XML.addValue("status","graph deleted");
+ status=HTTPResponse::HTTP_OK;
+ }
+ }
+ if (command[2]=="signal") {
+ if (command[0]=="GET") { //generate xml from 1st signal output
+ if (state==IDLE) {
+ //direct call for testing
+ float framerate=25.0f;
+ //if (command.size()>2) {
+ // framerate=ofToFloat(command[3]);
+ //}
+ string signal_xml;
+ if (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
+ }
+ else {
+ status=HTTPResponse::HTTP_INTERNAL_SERVER_ERROR;
+ logger.error("ERROR: could not render output signal");
+ XML.addValue("error","could not render output signal");
+ }
+ //else {
+ // status=HTTPResponse::HTTP_NOT_FOUND;
+ // XML.addValue("error","Signal output not found in graph");
+ //}
+ }
+ else {
+ status=HTTPResponse::HTTP_SERVICE_UNAVAILABLE;
+ logger.error("ERROR: context busy");
+ XML.addValue("error","Context busy");
+ }
+ }
+ }
+ if (command[2]=="video") {
+ if (command[0]=="PUT") { //get vide file location and initiate analysis
+ if (command.size()>4) { //there should be a filename + a destination node
+ if (state==IDLE) {
+ string video_filename=media_dir+command[4];
+ //check file exists
+ Poco::File f=Poco::File(video_filename);
+ if (f.exists()) {
+ if (load_video(command[3],video_filename)) {
+ //pass to worker thread ??if engine is ready?? ??what if engine has finished but results aren't read??
+ //DUMMY RESPONSE
+ status=HTTPResponse::HTTP_OK;
+ logger.information("Succesfully loaded "+command[4]+" into video node "+command[3]);
+ XML.addValue("status","Succesfully loaded "+command[4]+" into video node "+command[3]);
+ }
+ else {
+ status=HTTPResponse::HTTP_INTERNAL_SERVER_ERROR;
+ logger.error("ERROR: could not load "+command[4]+" into video node "+command[3]);
+ XML.addValue("error","could not load "+command[4]+" into video node "+command[3]);
+ }
+ }
+ else {
+ status=HTTPResponse::HTTP_NOT_FOUND;
+ logger.error("ERROR: "+command[4]+" not found");
+ XML.addValue("error",command[4]+" not found");
+ }
+ }
+ 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[2]=="render") {
+ if (command[0]=="GET") {
+ if(state==RENDERING){
+ status=HTTPResponse::HTTP_OK;
+ XML.addValue("status","Rendering video");
+ XML.addValue("progress",ofToString(progress));
+ }
+ else {
+ logger.error("ERROR: Render progress requested but not rendering");
+ XML.addValue("error","Not rendering");
+ }
+ }
+ if (command[0]=="PUT") {
+ if (command.size()>2) {
+ if (state==IDLE) {
+ output_filename=output_dir+command[3];
+ if (command.size()>3) {
+// output_framerate=ofToFloat(command[4]);
+ }
+ add_queue(RENDER);
+ status=HTTPResponse::HTTP_OK;
+ logger.information("Starting render: "+command[3]);
+ XML.addValue("status","Starting render: "+command[3]);
+ }
+ 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: No output file specified");
+ XML.addValue("error","No output file specified");
+ }
+ }
+ if (command[0]=="DELETE") {
+ status=HTTPResponse::HTTP_OK;
+ logger.error("ERROR: Not implemented");
+ XML.addValue("status","DUMMY RESPONSE: cancelling render");
+ }
+ }
+}
+
+bool Render_context::load_audio(const string &filename,vector<Base_audio_processor*> processors){
+ Logger& logger = Logger::get("Rotor");
+ logger.information("Starting audio analysis");
+
+ libav::audioloader loader;
+ loader.setup(filename);
+
+ graph.duration=((float)loader.formatContext->duration)/AV_TIME_BASE;
+
+ int rate = loader.codecContext->sample_rate;
+ int samples = ((loader.formatContext->duration + 5000)*rate)/AV_TIME_BASE; //why 5000 more?
+ int channels= loader.codecContext->channels;
+ int bits = loader.codecContext->bits_per_raw_sample;
+
+ for (auto p: processors) {
+ if(!p->init(channels,bits,samples,rate) ){
+ logger.error("ERROR: Audio plugin failed to initialse");
+ return false;
+ }
+ }
+
+ AVFrame* frame=loader.get_frame();
+ int sample_processed=0;
+
+ while (frame)
+ {
+ //now we can pass the data to the processor(s)
+ for (auto p: processors) {
+ p->process_frame(frame->data[0],frame->nb_samples);
+ }
+ sample_processed+=frame->nb_samples;
+ //mutex.lock();
+ progress=((float)sample_processed)/samples; //atomic on 64 bit?
+ //mutex.unlock();
+
+ frame=loader.get_frame();
+ }
+
+ loader.close();
+
+ for (auto p: processors) {
+ p->cleanup();
+ p->print_summary();
+ }
+
+ logger.information("Finished audio analysis");
+ return true;
+}
+bool Render_context::load_video(const string &nodeID,const string &filename){
+ //this is a good standard example of how to find
+ //a node of a specific type by ID and do something
+ if (graph.nodes.find(nodeID)!=graph.nodes.end()){
+ if (graph.nodes[nodeID]->type=="video_loader") {
+ if (((Video_loader*)graph.nodes[nodeID])->load(filename)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
diff --git a/rotord/src/rotor.cpp b/rotord/src/rotor.cpp
new file mode 100755
index 0000000..8b72c50
--- /dev/null
+++ b/rotord/src/rotor.cpp
@@ -0,0 +1,361 @@
+#include "rotor.h"
+#include "nodes_audio_analysis.h"
+#include "nodes_drawing.h"
+
+using namespace Rotor;
+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("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("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());
+ add_type("draw",new Draw());
+}
+
+bool Signal_input::connect(Signal_node* source) {
+ if (source->output_type=="signal") {
+ connection=(Node*)source;
+ 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);
+ }
+}
+bool Image_input::connect(Image_node* source) {
+ if (source->output_type=="image") {
+ connection=(Node*)source;
+ return true;
+ }
+ else return false;
+}
+void Node::update_params(const Time_spec& time){ //compute connected parameters
+ for (auto p:parameter_inputs){
+ p->update(time);
+ }
+}
+bool Signal_output::render(const float duration, const float framerate,string &xml_out){
+ //testing signal routes
+ cerr << "Rotor: Signal_output rendering " << duration << " seconds at " << framerate << " frames per second" << endl;
+ 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;
+ }
+ }
+ xml_out+=("<signal_finished min='"+ofToString(min)+"' max='"+ofToString(max)+"'/>\n");
+ return true;
+}
+
+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.0/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;
+ }
+ 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();
+}
+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){
+
+ //
+ //setup defaults
+ int bitRate=5000000;
+ 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;
+
+ float spct=100.0f/duration;
+
+ if (exporter.setup(outW,outH,bitRate,framerate,container)) { //codecId,
+ if (exporter.record(output_filename)) {
+
+ libav::audioloader audioloader;
+
+ bool usingaudio=audioloader.setup(audio_filename);
+
+ cerr << "Rotor: Video_output rendering " << duration << " seconds at " << framerate << " fps, audio frame size: " << exporter.get_audio_framesize()<<endl;
+ //25fps video and 43.06640625fps audio? hmm
+ //how to get the timecodes correct for the interleaved files
+
+ struct timeval start, end;
+
+ gettimeofday(&start, NULL);
+
+
+ float vstep=1.0f/framerate;
+ float v=0.0f;
+ float vf=0.0f;
+ float af=0.0f;
+ while (vf<duration){ //-vstep) {
+ if (usingaudio) {
+ while (!fless(af,vf)) {
+ //insert audio frames until we are ahead of the video
+ exporter.encodeFrame(audioloader.get_samples(exporter.get_audio_framesize()));
+ af+=exporter.get_audio_step();
+
+ }
+ }
+
+
+ //[mp3 @ 0x7fffe40330e0] max_analyze_duration 5000000 reached at 5015510 microseconds
+ //[mp3 @ 0x7fffe4033ec0] Insufficient thread locking around avcodec_open/close()
+ //[mp3 @ 0x7fffe40330e0] Estimating duration from bitrate, this may be inaccurate
+ //[libx264 @ 0x7fffe8003940] using cpu capabilities: MMX2 SSE2Fast SSSE3 FastShuffle SSE4.2
+ //[libx264 @ 0x7fffe8003940] profile High, level 3.0
+ //[libx264 @ 0x7fffe8003940] 264 - core 123 r2189 35cf912 - H.264/MPEG-4 AVC codec - Copyleft 2003-2012 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=12 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=10 keyint_min=1 scenecut=40 intra_refresh=0 rc_lookahead=10 rc=abr mbtree=1 bitrate=400 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
+ //Assertion ff_avcodec_locked failed at libavcodec/utils.c:2967
+
+ //cerr<<"videoloader: "<<vf<<" seconds, vstep "<<vstep<<" ,asking for frame "<<((int)((vf*framerate)+0.5))<<endl;
+
+ Image* i=get_output(Frame_spec(vf,framerate,duration,outW,outH));
+ if (i) {
+ exporter.encodeFrame(i->RGBdata);
+
+ }
+ vf+=vstep;
+ progress=vf/duration;
+ }
+
+ exporter.finishRecord();
+
+ gettimeofday(&end, NULL);
+
+ float mtime = ((end.tv_sec-start.tv_sec) + (end.tv_usec-start.tv_usec)/1000000.0) + 0.5;
+
+ printf("Rotor Video_output: rendered in %02f seconds\n", mtime);
+
+ if (usingaudio) audioloader.close();
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool Video_loader::load(const string &filename){
+ /*
+ //gstreamer needs absolute paths ALWAYS
+ //string uri="file:///home/tim/workspace/rotor/rotord/"+filename;
+ Poco::Path path;
+ string uri="file://"+path.current()+filename;
+ //cerr << "video input: loading "<<uri<<endl;
+ if (player->loadMovie(uri)){
+ player->play();
+ player->setPaused(true);
+ player->setFrameByFrame(true);
+ player->update();
+ cerr<<"Rotor::Video_loader: "<<filename<<", "<<player->getDuration()<<" seconds "<<", "<<player->getWidth()<<"x"<<player->getHeight()<<endl;
+ image->setup_fromRGB(player->getWidth(),player->getHeight(),(uint8_t*) player->getPixels());
+ return true;
+ }
+ */
+ if (isLoaded) {
+ player.cleanup(); ///should be in decoder class?
+ isLoaded=false;
+ }
+ Poco::Path path;
+ string uri="file://"+filename;
+ isLoaded=player.open(uri);
+ if (isLoaded){
+ cerr<<"Rotor::Video_loader: "<<filename<<", "<<player.getNumberOfFrames()<<" frames "<<", "<<player.getWidth()<<"x"<<player.getHeight()<<endl;
+ return true;
+ }
+ cerr<<"Rotor::Video_loader: failed to load "<<filename<<endl;
+ return false;
+}
+Image* Video_loader::output(const Frame_spec &frame){
+ //wonder about the actual mechanism used by gstreamer
+ //have to implment callback when seek is ready?
+ //presume gstreamer caches a loaded frame?
+
+
+ //deal with reolution: swscale from avcodec or put scaler in pipeline?
+ //can image node point to buffer in gst rather than copying the pixels?
+
+ //to test using fp time to seek: need a short movie with synced audio
+
+ //fix actual duration and audio file
+ //trace frame that is being read
+ /*
+ if (player->isLoaded()){
+ //player->setPosition(frame.time);
+ int wanted=((int) (frame.time*frame.framerate))%(player->getTotalNumFrames()-2); //-2??
+ player->setFrame(wanted);
+ //while (player->getCurrentFrame()!=wanted){
+ // cerr << "seeking to "<<wanted<<" :"<<player->getCurrentFrame()<<endl;
+ //player->setFrame(wanted);
+ //player->update();
+ // sleep(.001);
+ //}
+ player->update();
+ image->RGBdata=player->getPixels(); //don't really know why this is needed every frame
+ //cerr<<"Video_loader: retrieving frame "<<((int) (frame.time*frame.framerate))<<endl;
+ return image;
+ }
+ */
+
+ if (isLoaded){
+ int wanted=(((int) ((frame.time*frame.framerate)+0.5))%(player.getNumberOfFrames())); //+1 is necessary because 1st frame in a video is number 1?
+
+
+ //if (wanted==99){
+ // cerr<<"videoloader: near the end"<<endl;
+ //}
+
+ //cerr<<"videoloader: requesting frame "<<wanted<<endl;
+ //if (wanted==68) {
+ // int nothing=0;
+ //}
+
+ if (!player.fetchFrame(frame.w,frame.h,wanted)) { //seek fail
+ cerr<<"Rotor: failed to seek frame"<<endl;
+ if (image.w>0) return &image; //just return the previous frame if possible
+ else return nullptr;
+ };
+ //cerr<<"Video_loader: setting up frame: lineoffset="<<(player.pFrameRGB->linesize[0]-(frame.w*3))<<endl;
+ image.setup_fromRGB(frame.w,frame.h,player.pFrameRGB->data[0],player.pFrameRGB->linesize[0]-(frame.w*3));
+ return &image;
+ }
+
+ //confusingly, crashes with files not made with short files?
+ //seems to be on last frame? - returns nullptr - still tries to clone?
+ //can't really return 1st frame instead, should get # of frames right 1st?
+ //think about what echo trails does on the last frame
+
+ return nullptr;
+};
diff --git a/rotord/src/rotor.h b/rotord/src/rotor.h
new file mode 100755
index 0000000..f922fcf
--- /dev/null
+++ b/rotord/src/rotor.h
@@ -0,0 +1,1391 @@
+#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
+*/
+
+#include <unordered_map>
+#include <deque>
+#include <math.h>
+#include <memory>
+#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 "xmlIO.h"
+#include "utils.h" //fequal
+#include "libavwrapper.h"
+#include "cvimage.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 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 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<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 &parameter,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<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");
+ }
+ 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_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); };
+ 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 Time: public Signal_node {
+ public:
+ Time(){};
+ Time(map<string,string> &settings) {
+ base_settings(settings);
+ };
+ Time* clone(map<string,string> &_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<string,string> &settings) {
+ base_settings(settings);
+ };
+ Track_time* clone(map<string,string> &_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<string,string> &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<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;
+ }
+ }
+ }
+ 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<string,string> &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<string,string> &_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<string,string> &settings) {
+ base_settings(settings);
+ divide_amount=ofToFloat(find_setting(settings,"amount"));
+ for (auto p:parameter_inputs){
+ if (p->parameter=="amount") p->receiver=&divide_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) {
+ 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;
+ }
+ }
+ return 0.0f;
+ }
+ };
+ class On_off: public Signal_node {
+ public:
+ On_off(){};
+ On_off(map<string,string> &settings) {
+ base_settings(settings);
+ };
+ On_off* clone(map<string,string> &_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<string,string> &settings) {
+ base_settings(settings);
+ seed=(Seed+find_setting(settings,"seed",0));
+ cerr<<"random:: seed "<<seed<<" ("<<Seed<<")"<<endl;
+ };
+ 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;
+ private:
+ };
+ class Signal_output: public Signal_node {
+ public:
+ Signal_output(){};
+ Signal_output(map<string,string> &settings) {
+ 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;
+ }
+ };
+ class Testcard: public Image_node {
+ public:
+ Testcard(){image=nullptr;};
+ Testcard(map<string,string> &settings) {
+ base_settings(settings);
+ image=new Image();
+ };
+ ~Testcard(){ if (image) delete image;};
+ 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->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(){image=nullptr;};
+ Invert(map<string,string> &settings) {
+ base_settings(settings);
+ image=new Image();
+ };
+ ~Invert(){ if (image) delete image;};
+ 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;
+ }
+ return nullptr;
+ }
+ private:
+ Image *image; //is an image generator
+ //bool invert;
+ };
+ class Video_output: public Image_node {
+ public:
+ Video_output(){};
+ Video_output(map<string,string> &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<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);
+
+ private:
+
+ };
+ class Video_loader: public Image_node {
+ public:
+ Video_loader(){};
+ Video_loader(map<string,string> &settings) {
+ base_settings(settings);
+ isLoaded=false;
+ };
+ ~Video_loader(){};
+ bool load(const string &filename);
+ Image *output(const Frame_spec &frame);
+ Video_loader* clone(map<string,string> &_settings) { return new Video_loader(_settings);};
+ bool isLoaded;
+ private:
+ libav::decoder player;
+ Image image;
+ };
+ class Video_cycler: public Image_node {
+ //cycles through video inputs in order
+ public:
+ Video_cycler(){};
+ Video_cycler(map<string,string> &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<string,string> &_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<string,string> &settings) {
+ 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;
+ }
+ 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 &image;
+ }
+ }
+ }
+ 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) {
+ 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 &image;
+ }
+ }
+ return nullptr;
+ }
+ Signal_greyscale* clone(map<string,string> &_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=nullptr;};
+ Image_arithmetic(map<string,string> &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;
+ cerr<<"image_arithmetic: mode "<<op<<", value "<<value<<endl;
+ }
+ 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; //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;
+ }
+ }
+ return nullptr;
+ }
+ 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=&gamma;
+ 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);
+ 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 inherited classes
+ out.setup(in.w,in.h);
+ for (int i=0;i<out.w*out.h*3;i++){
+ out.RGBdata[i]=LUT[in.RGBdata[i]];
+ }
+ }
+ 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;
+ }
+ }
+ }
+ return nullptr;
+ }
+ 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
+ //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?
+
+ //or is it actually best to use alpha keying after all!
+ public:
+ Echo_trails(){image=nullptr;};
+ Echo_trails(map<string,string> &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=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=&gamma;
+ 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;}
+ };
+ 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?
+ //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) {
+ //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]));
+ }
+ }
+ }
+ //for (int i=0;i<frame.w*frame.h*3;i++){
+ // image->RGBdata[i]=LUT[in->RGBdata[i]];
+ //}
+ lastframe=thisframe;
+ return image;
+ }
+ }
+ }
+ }
+ return nullptr;
+ }
+ 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_alpha 4
+ #define BLEND_screen_wrap 5
+ #define BLEND_multiply_wrap 6
+ #define BLEND_xor 7
+ 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;
+ };
+ void link_params() {
+ for (auto p:parameter_inputs){
+ if (p->parameter=="amount") p->receiver=&amount;
+ }
+ };
+ ~Blend(){ if (image) delete image;};
+ 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));
+ }
+ }
+ return nullptr;
+ }
+ private:
+ Image *image; //is an image generator
+ int mode;
+ float amount; //for blend
+ };
+ #define MIRROR_horiz 1
+ #define MIRROR_vert 2
+ #define MIRROR_horizR 3
+ #define MIRROR_vertR 4
+ class Mirror: public Image_node {
+ public:
+ Mirror(){image=nullptr;};
+ Mirror(map<string,string> &settings) {
+ 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* 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];
+ }
+ }
+ }
+ 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_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;
+ }
+ }
+ return nullptr;
+ }
+ private:
+ Image *image; //is an image generator
+ int mode;
+ };
+ class Monochrome: public Image_node {
+ public:
+ Monochrome(){};
+ Monochrome(map<string,string> &settings) {
+ 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;
+ }
+ }
+ return &image;
+ }
+ }
+ return nullptr;
+ }
+ private:
+ Image image;
+ };
+ class Transform: public Image_node {
+ //what is the best coordinate system to use?
+ //origin: corner or centre
+ //units: pixel or fractional
+ //aspect: scaled or homogenous
+ public:
+ Transform(){};
+ Transform(map<string,string> &settings) {
+ 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 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);
+
+
+ // Compute rotation matrix
+ //
+ cv::Point centre = cv::Point( oX*other->w, oY*other->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
+
+ //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 &image;
+ }
+ }
+ }
+ 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) {
+ base_settings(settings);
+ };
+ ~Alpha_merge(){ if (image) delete image;};
+ 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;
+ }
+ }
+ return nullptr;
+ }
+ private:
+ Image *image; //is an image generator
+ };
+ //-------------------------------------------------------------------
+ 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<string,string> &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<string,Node*> type_map;
+ };
+ class Graph{
+ public:
+ Graph(){duration=20.0f;loaded = false;outW=640;outH=360;};
+ Graph(const string& _uid,const string& _desc){init(_uid,_desc);};
+ void init(const string& _uid,const string& _desc){ uid=_uid;description=_desc;duration=20.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;
+ vector<Node*> 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 data);
+ bool loadFile(string &filename);
+ bool parseXml();
+ bool set_resolution(int w,int h);
+ UUID save(); //save to DB, returns UUID of saved graph
+ bool loaded;
+ float duration;
+ const string toString();
+ xmlIO xml;
+ private:
+ Node_factory factory;
+ int outW,outH;
+ };
+ 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<string,string> &_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"<<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 \ No newline at end of file
diff --git a/rotord/src/rotord.cpp b/rotord/src/rotord.cpp
new file mode 100755
index 0000000..6f2d765
--- /dev/null
+++ b/rotord/src/rotord.cpp
@@ -0,0 +1,230 @@
+#include "rotord.h"
+
+RenderContextHandler::RenderContextHandler(const std::string _content,const HTTPServerResponse::HTTPStatus _status){
+ content=_content;
+ status=_status;
+}
+
+
+void RenderContextHandler::handleRequest(HTTPServerRequest& request,HTTPServerResponse& response) {
+
+ response.setChunkedTransferEncoding(true);
+ response.setContentType("text/html");
+ response.setStatus(status);
+
+ std::ostream& ostr = response.send();
+
+ ostr << "<?xml version='1.0' encoding='ISO-8859-1'?>\n"; //this is the mysterious extra header
+ ostr << content;
+
+}
+
+
+HTTPRequestHandler* RotorRequestHandlerFactory::createRequestHandler(const HTTPServerRequest& request){
+
+
+ Poco::URI theuri=Poco::URI(request.getURI());
+ std::vector <std::string> command;
+ theuri.getPathSegments(command);
+
+ Logger& logger = Logger::get("Rotor");
+ logger.information(request.clientAddress().toString()+" "+request.getMethod());
+
+ HTTPResponse::HTTPStatus status=HTTPResponse::HTTP_BAD_REQUEST; //by default
+
+ std::string body;
+ std::ostringstream os;
+ os<<request.stream().rdbuf();
+ body=os.str();
+
+ xmlIO XML; //xml object handles the servers responses
+ XML.addTag("rotor");
+
+ //can we create a tinyxml object here and pass a pointer to it to the render context?
+ //can tinyxml output to a string? is there any reason to use poco instead?
+
+ if (command.size()) {
+ if (command[0]=="new") {
+ XML.pushTag("rotor");
+ if (request.getMethod()=="GET") {
+ string sID=idGen.createOne().toString(); //create() seems to cause problems
+ //Creates a new time-based UUID, using the MAC address of one of the system's ethernet adapters.
+ //Throws a SystemException if no MAC address can be obtained.
+ //
+ //seems to hang, to me
+ logger.information("starting thread "+sID);
+ manager.start(new Rotor::Render_context(sID));
+ //XML.addTag("sID");
+ XML.addValue("sID",sID);
+ status=HTTPResponse::HTTP_OK;
+ }
+ if (request.getMethod()=="PUT") { //unofficial manual thread name
+ if (body.size()) {
+ string sID=body;
+ bool found=false;
+ for (auto& task: manager.taskList()) {
+ if(task->name()==sID) {
+ logger.error("ERROR: tried to create thread with existing name "+sID);
+ XML.addValue("error","Render context /"+sID+"/ exists already");
+ found=true;
+ }
+ }
+ if (!found){
+ logger.information("starting thread "+sID);
+ manager.start(new Rotor::Render_context(sID));
+ XML.addValue("sID",sID);
+ status=HTTPResponse::HTTP_OK;
+ }
+ }
+ }
+ }
+ else if (command[0]=="list") {
+ XML.pushTag("rotor");
+ if (request.getMethod()=="GET") {
+ logger.information("sending tasklist");
+ //std::list < Poco::AutoPtr < Poco::Task > >::iterator it;
+ //it=manager.taskList().begin();
+ //for (it=manager.taskList().begin();it !=manager.taskList().end();++it) {
+ //content+="<sID>"+(*it)->name()+"</sID>\n";
+ //}
+
+ //massive problems making an iterator for the tasklist, the above crashes
+ //solution: auto type range-based for-loop
+ //this is c++11 specific but works
+
+ for (auto& task: manager.taskList()) { //c++11
+ XML.addValue("sID",task->name());
+ }
+ status=HTTPResponse::HTTP_OK;
+ }
+ }
+ else if (command[0]=="exit") {
+ logger.information("exiting");
+ exit(0);
+ }
+ else {
+ bool found=false;
+ for (auto& task: manager.taskList()) { //c++11
+ if(task->name()==command[0]) {
+ //valid session command
+ found=true;
+ XML.addAttribute("rotor","context",task->name(),0);
+ XML.pushTag("rotor");
+ if (command.size()==1) {
+ //just invoking sID
+ if (request.getMethod()=="DELETE") {
+ task->cancel();
+ status=HTTPResponse::HTTP_OK;
+ logger.information("deleted context "+command[0]);
+ XML.addValue("status","context deleted successfully");
+ }
+ else {
+ logger.error("ERROR: Render context invoked with no command: "+command[0]);
+ XML.addValue("error","Render context invoked with no command");
+ }
+ }
+ else { //session modifier command- to be passed to render context
+ //some commands need to return error codes
+ //ie where the audio file isn't found
+ //on the other hand, some commands need to know state of the renderer?
+
+
+ vector<string> sc; //method,id,command1,{command2,}{body}
+ sc.push_back(request.getMethod());
+ for (auto& i: command){
+ sc.push_back(i);
+ }
+ sc.push_back(body);
+
+ ((Poco::AutoPtr<Rotor::Render_context>)task)->session_command(sc,XML,status);
+
+ }
+ }
+ }
+ if (!found) {
+ status=HTTPResponse::HTTP_NOT_FOUND;
+ logger.error("ERROR: context not found: "+command[0]);
+ XML.pushTag("rotor");
+ XML.addValue("error","Render context not found");
+ }
+ }
+ }
+ else {
+ logger.error("ERROR: Empty request");
+ XML.addValue("error","Empty request");
+ }
+ string content;
+ XML.copyXmlToString(content);
+ return new RenderContextHandler(content, status);
+}
+
+
+RotorServer::RotorServer(): _helpRequested(false)
+{
+}
+
+RotorServer::~RotorServer()
+{
+}
+
+void RotorServer::initialize(Application& self){
+ loadConfiguration();
+ ServerApplication::initialize(self);
+}
+
+void RotorServer::uninitialize(){
+ ServerApplication::uninitialize();
+}
+
+void RotorServer::defineOptions(OptionSet& options) {
+ ServerApplication::defineOptions(options);
+ options.addOption(
+ Option("help", "h", "display argument help information")
+ .required(false)
+ .repeatable(false)
+ .callback(OptionCallback<RotorServer>(this, &RotorServer::handleHelp)
+ )
+ );
+}
+
+void RotorServer::handleHelp(const std::string& name, const std::string& value){
+ HelpFormatter helpFormatter(options());
+ helpFormatter.setCommand(commandName());
+ helpFormatter.setUsage("OPTIONS");
+ helpFormatter.setHeader(
+ "Rotor");
+ helpFormatter.format(std::cout);
+ stopOptionsProcessing();
+ _helpRequested = true;
+}
+
+int RotorServer::main(const std::vector<std::string>& args){
+ if (!_helpRequested) {
+
+ unsigned short port;
+
+ Logger& logger = Logger::get("Rotor");
+
+ xmlIO xml;
+ if(xml.loadFile("settings.xml") ){
+ port=xml.getAttribute("Rotor","port",9000,0);
+ }
+ else logger.information("settings.xml not found, using defaults");
+
+ logger.information("rotord running on port "+ofToString(port));
+
+ port = (unsigned short) config().getInt("port", port); //override from command line
+
+ std::string format(config().getString("format", DateTimeFormat::SORTABLE_FORMAT));
+
+
+
+ ServerSocket svs(port);
+ HTTPServer srv(new RotorRequestHandlerFactory(),svs, new HTTPServerParams);
+ srv.start();
+ waitForTerminationRequest();
+ srv.stop();
+ }
+ return Application::EXIT_OK;
+}
+ \ No newline at end of file
diff --git a/rotord/src/rotord.h b/rotord/src/rotord.h
new file mode 100755
index 0000000..7656c28
--- /dev/null
+++ b/rotord/src/rotord.h
@@ -0,0 +1,136 @@
+#include "Poco/Net/HTTPServer.h"
+#include "Poco/Net/HTTPRequestHandler.h"
+#include "Poco/Net/HTTPRequestHandlerFactory.h"
+#include "Poco/Net/HTTPServerParams.h"
+#include "Poco/Net/HTTPServerRequest.h"
+#include "Poco/Net/HTTPServerResponse.h"
+#include "Poco/Net/HTTPServerParams.h"
+#include "Poco/Net/ServerSocket.h"
+#include "Poco/Timestamp.h"
+#include "Poco/DateTimeFormatter.h"
+#include "Poco/DateTimeFormat.h"
+#include "Poco/Exception.h"
+#include "Poco/ThreadPool.h"
+#include "Poco/Task.h"
+#include "Poco/NotificationCenter.h"
+#include "Poco/TaskManager.h"
+#include "Poco/Util/ServerApplication.h"
+#include "Poco/Util/Option.h"
+#include "Poco/Util/OptionSet.h"
+#include "Poco/Util/HelpFormatter.h"
+#include "Poco/FileStream.h"
+#include "Poco/StreamCopier.h"
+#include "Poco/Net/HTTPStreamFactory.h"
+#include <iostream>
+
+#include <sstream>
+#include "Poco/URI.h"
+#include "Poco/Channel.h"
+#include "Poco/SplitterChannel.h"
+#include "Poco/ConsoleChannel.h"
+#include "Poco/FormattingChannel.h"
+#include "Poco/FileChannel.h"
+#include "Poco/Message.h"
+#include "Poco/Formatter.h"
+#include "Poco/PatternFormatter.h"
+#include "Poco/AutoPtr.h"
+
+using Poco::Net::ServerSocket;
+using Poco::Net::HTTPResponse;
+using Poco::Net::HTTPRequestHandler;
+using Poco::Net::HTTPRequestHandlerFactory;
+using Poco::Net::HTTPServer;
+using Poco::Net::HTTPServerRequest;
+using Poco::Net::HTTPServerResponse;
+using Poco::Net::HTTPServerParams;
+using Poco::Timestamp;
+using Poco::DateTimeFormatter;
+using Poco::DateTimeFormat;
+using Poco::ThreadPool;
+using Poco::TaskManager;
+using Poco::Util::ServerApplication;
+using Poco::Util::Application;
+using Poco::Util::Option;
+using Poco::Util::OptionSet;
+using Poco::Util::OptionCallback;
+using Poco::Util::HelpFormatter;
+using Poco::Net::HTTPStreamFactory;
+using Poco::Logger;
+using Poco::Channel;
+using Poco::SplitterChannel;
+using Poco::ConsoleChannel;
+using Poco::FormattingChannel;
+using Poco::Formatter;
+using Poco::PatternFormatter;
+using Poco::FileChannel;
+using Poco::Message;
+using Poco::AutoPtr;
+
+
+#include "rotor.h"
+
+
+class RenderContextHandler: public HTTPRequestHandler
+{
+ public:
+ RenderContextHandler(string _content,HTTPServerResponse::HTTPStatus _status);
+ void handleRequest(HTTPServerRequest& request,HTTPServerResponse& response);
+ private:
+ std::string content;
+ HTTPServerResponse::HTTPStatus status;
+};
+
+class RotorRequestHandlerFactory: public HTTPRequestHandlerFactory
+{
+ public:
+ HTTPRequestHandler* createRequestHandler(const HTTPServerRequest& request);
+ private:
+
+ std::unordered_map<std::string,Rotor::Render_context> context;
+ Poco::UUIDGenerator idGen;
+ Poco::TaskManager manager;
+};
+
+class RotorServer: public Poco::Util::ServerApplication
+{
+ public:
+ RotorServer();
+ ~RotorServer();
+ protected:
+ void initialize(Application& self);
+ void uninitialize();
+ void defineOptions(OptionSet& options);
+ void handleHelp(const std::string& name, const std::string& value);
+ int main(const std::vector<std::string>& args);
+ private:
+ bool _helpRequested;
+};
+
+RotorServer app; //needs to be global for logger
+
+int main(int argc, char** argv)
+{
+ AutoPtr<SplitterChannel> splitterChannel(new SplitterChannel());
+ AutoPtr<Channel> consoleChannel(new ConsoleChannel());
+ AutoPtr<Channel> fileChannel(new FileChannel("Rotord.log"));
+ AutoPtr<FileChannel> rotatedFileChannel(new FileChannel("Rotord_R.log"));
+
+ rotatedFileChannel->setProperty("rotation", "100");
+ rotatedFileChannel->setProperty("archive", "timestamp");
+
+ splitterChannel->addChannel(consoleChannel);
+ splitterChannel->addChannel(fileChannel);
+ splitterChannel->addChannel(rotatedFileChannel);
+
+ AutoPtr<Formatter> formatter(new PatternFormatter("%d-%m-%Y %H:%M:%S %s: %t"));
+ AutoPtr<Channel> formattingChannel(new FormattingChannel(formatter, splitterChannel));
+
+ Logger& sLog = Logger::create("Rotor", formattingChannel, Message::PRIO_TRACE);
+
+ Logger& logger = Logger::get("Rotor");
+ logger.information("starting rendering daemon");
+
+ HTTPStreamFactory::registerFactory();
+
+ return app.run(argc, argv);
+}
diff --git a/rotord/src/system.h b/rotord/src/system.h
new file mode 100644
index 0000000..15aa8c1
--- /dev/null
+++ b/rotord/src/system.h
@@ -0,0 +1,75 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Vamp
+
+ An API for audio analysis and feature extraction plugins.
+
+ Centre for Digital Music, Queen Mary, University of London.
+ Copyright 2006 Chris Cannam.
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use, copy,
+ modify, merge, publish, distribute, sublicense, and/or sell copies
+ of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ Except as contained in this notice, the names of the Centre for
+ Digital Music; Queen Mary, University of London; and Chris Cannam
+ shall not be used in advertising or otherwise to promote the sale,
+ use or other dealings in this Software without prior written
+ authorization.
+*/
+
+#ifndef _SYSTEM_H_
+#define _SYSTEM_H_
+
+#ifdef _WIN32
+
+#include <windows.h>
+
+#define DLOPEN(a,b) LoadLibrary((a).c_str())
+#define DLSYM(a,b) GetProcAddress((HINSTANCE)(a),(b))
+#define DLCLOSE(a) FreeLibrary((HINSTANCE)(a))
+#define DLERROR() ""
+
+#define PLUGIN_SUFFIX "dll"
+
+#else
+
+#include <dlfcn.h>
+
+#define DLOPEN(a,b) dlopen((a).c_str(),(b))
+#define DLSYM(a,b) dlsym((a),(b))
+#define DLCLOSE(a) dlclose((a))
+#define DLERROR() dlerror()
+
+#ifdef __APPLE__
+
+#define PLUGIN_SUFFIX "dylib"
+#define HAVE_OPENDIR 1
+
+#else
+
+#define PLUGIN_SUFFIX "so"
+#define HAVE_OPENDIR 1
+
+#endif /* __APPLE__ */
+
+#endif /* ! _WIN32 */
+
+#endif
+
diff --git a/rotord/src/tinyxml.cpp b/rotord/src/tinyxml.cpp
new file mode 100755
index 0000000..5de21f6
--- /dev/null
+++ b/rotord/src/tinyxml.cpp
@@ -0,0 +1,1888 @@
+/*
+www.sourceforge.net/projects/tinyxml
+Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+
+#include <ctype.h>
+
+#ifdef TIXML_USE_STL
+#include <sstream>
+#include <iostream>
+#endif
+
+#include "tinyxml.h"
+
+
+bool TiXmlBase::condenseWhiteSpace = true;
+
+// Microsoft compiler security
+FILE* TiXmlFOpen( const char* filename, const char* mode )
+{
+ #if defined(_MSC_VER) && (_MSC_VER >= 1400 )
+ FILE* fp = 0;
+ errno_t err = fopen_s( &fp, filename, mode );
+ if ( !err && fp )
+ return fp;
+ return 0;
+ #else
+ return fopen( filename, mode );
+ #endif
+}
+
+void TiXmlBase::EncodeString( const TIXML_STRING& str, TIXML_STRING* outString )
+{
+ int i=0;
+
+ while( i<(int)str.length() )
+ {
+ unsigned char c = (unsigned char) str[i];
+
+ if ( c == '&'
+ && i < ( (int)str.length() - 2 )
+ && str[i+1] == '#'
+ && str[i+2] == 'x' )
+ {
+ // Hexadecimal character reference.
+ // Pass through unchanged.
+ // &#xA9; -- copyright symbol, for example.
+ //
+ // The -1 is a bug fix from Rob Laveaux. It keeps
+ // an overflow from happening if there is no ';'.
+ // There are actually 2 ways to exit this loop -
+ // while fails (error case) and break (semicolon found).
+ // However, there is no mechanism (currently) for
+ // this function to return an error.
+ while ( i<(int)str.length()-1 )
+ {
+ outString->append( str.c_str() + i, 1 );
+ ++i;
+ if ( str[i] == ';' )
+ break;
+ }
+ }
+ else if ( c == '&' )
+ {
+ outString->append( entity[0].str, entity[0].strLength );
+ ++i;
+ }
+ else if ( c == '<' )
+ {
+ outString->append( entity[1].str, entity[1].strLength );
+ ++i;
+ }
+ else if ( c == '>' )
+ {
+ outString->append( entity[2].str, entity[2].strLength );
+ ++i;
+ }
+ else if ( c == '\"' )
+ {
+ outString->append( entity[3].str, entity[3].strLength );
+ ++i;
+ }
+ else if ( c == '\'' )
+ {
+ outString->append( entity[4].str, entity[4].strLength );
+ ++i;
+ }
+ else if ( c < 32 )
+ {
+ // Easy pass at non-alpha/numeric/symbol
+ // Below 32 is symbolic.
+ char buf[ 32 ];
+
+ #if defined(TIXML_SNPRINTF)
+ TIXML_SNPRINTF( buf, sizeof(buf), "&#x%02X;", (unsigned) ( c & 0xff ) );
+ #else
+ sprintf( buf, "&#x%02X;", (unsigned) ( c & 0xff ) );
+ #endif
+
+ //*ME: warning C4267: convert 'size_t' to 'int'
+ //*ME: Int-Cast to make compiler happy ...
+ outString->append( buf, (int)strlen( buf ) );
+ ++i;
+ }
+ else
+ {
+ //char realc = (char) c;
+ //outString->append( &realc, 1 );
+ *outString += (char) c; // somewhat more efficient function call.
+ ++i;
+ }
+ }
+}
+
+
+TiXmlNode::TiXmlNode( NodeType _type ) : TiXmlBase()
+{
+ parent = 0;
+ type = _type;
+ firstChild = 0;
+ lastChild = 0;
+ prev = 0;
+ next = 0;
+}
+
+
+TiXmlNode::~TiXmlNode()
+{
+ TiXmlNode* node = firstChild;
+ TiXmlNode* temp = 0;
+
+ while ( node )
+ {
+ temp = node;
+ node = node->next;
+ delete temp;
+ }
+}
+
+
+void TiXmlNode::CopyTo( TiXmlNode* target ) const
+{
+ target->SetValue (value.c_str() );
+ target->userData = userData;
+}
+
+
+void TiXmlNode::Clear()
+{
+ TiXmlNode* node = firstChild;
+ TiXmlNode* temp = 0;
+
+ while ( node )
+ {
+ temp = node;
+ node = node->next;
+ delete temp;
+ }
+
+ firstChild = 0;
+ lastChild = 0;
+}
+
+
+TiXmlNode* TiXmlNode::LinkEndChild( TiXmlNode* node )
+{
+ assert( node->parent == 0 || node->parent == this );
+ assert( node->GetDocument() == 0 || node->GetDocument() == this->GetDocument() );
+
+ if ( node->Type() == TiXmlNode::DOCUMENT )
+ {
+ delete node;
+ if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return 0;
+ }
+
+ node->parent = this;
+
+ node->prev = lastChild;
+ node->next = 0;
+
+ if ( lastChild )
+ lastChild->next = node;
+ else
+ firstChild = node; // it was an empty list.
+
+ lastChild = node;
+ return node;
+}
+
+
+TiXmlNode* TiXmlNode::InsertEndChild( const TiXmlNode& addThis )
+{
+ if ( addThis.Type() == TiXmlNode::DOCUMENT )
+ {
+ if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return 0;
+ }
+ TiXmlNode* node = addThis.Clone();
+ if ( !node )
+ return 0;
+
+ return LinkEndChild( node );
+}
+
+
+TiXmlNode* TiXmlNode::InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis )
+{
+ if ( !beforeThis || beforeThis->parent != this ) {
+ return 0;
+ }
+ if ( addThis.Type() == TiXmlNode::DOCUMENT )
+ {
+ if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return 0;
+ }
+
+ TiXmlNode* node = addThis.Clone();
+ if ( !node )
+ return 0;
+ node->parent = this;
+
+ node->next = beforeThis;
+ node->prev = beforeThis->prev;
+ if ( beforeThis->prev )
+ {
+ beforeThis->prev->next = node;
+ }
+ else
+ {
+ assert( firstChild == beforeThis );
+ firstChild = node;
+ }
+ beforeThis->prev = node;
+ return node;
+}
+
+
+TiXmlNode* TiXmlNode::InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis )
+{
+ if ( !afterThis || afterThis->parent != this ) {
+ return 0;
+ }
+ if ( addThis.Type() == TiXmlNode::DOCUMENT )
+ {
+ if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return 0;
+ }
+
+ TiXmlNode* node = addThis.Clone();
+ if ( !node )
+ return 0;
+ node->parent = this;
+
+ node->prev = afterThis;
+ node->next = afterThis->next;
+ if ( afterThis->next )
+ {
+ afterThis->next->prev = node;
+ }
+ else
+ {
+ assert( lastChild == afterThis );
+ lastChild = node;
+ }
+ afterThis->next = node;
+ return node;
+}
+
+
+TiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis )
+{
+ if ( replaceThis->parent != this )
+ return 0;
+
+ TiXmlNode* node = withThis.Clone();
+ if ( !node )
+ return 0;
+
+ node->next = replaceThis->next;
+ node->prev = replaceThis->prev;
+
+ if ( replaceThis->next )
+ replaceThis->next->prev = node;
+ else
+ lastChild = node;
+
+ if ( replaceThis->prev )
+ replaceThis->prev->next = node;
+ else
+ firstChild = node;
+
+ delete replaceThis;
+ node->parent = this;
+ return node;
+}
+
+
+bool TiXmlNode::RemoveChild( TiXmlNode* removeThis )
+{
+ if ( removeThis->parent != this )
+ {
+ assert( 0 );
+ return false;
+ }
+
+ if ( removeThis->next )
+ removeThis->next->prev = removeThis->prev;
+ else
+ lastChild = removeThis->prev;
+
+ if ( removeThis->prev )
+ removeThis->prev->next = removeThis->next;
+ else
+ firstChild = removeThis->next;
+
+ delete removeThis;
+ return true;
+}
+
+const TiXmlNode* TiXmlNode::FirstChild( const char * _value ) const
+{
+ const TiXmlNode* node;
+ for ( node = firstChild; node; node = node->next )
+ {
+ if ( strcmp( node->Value(), _value ) == 0 )
+ return node;
+ }
+ return 0;
+}
+
+
+const TiXmlNode* TiXmlNode::LastChild( const char * _value ) const
+{
+ const TiXmlNode* node;
+ for ( node = lastChild; node; node = node->prev )
+ {
+ if ( strcmp( node->Value(), _value ) == 0 )
+ return node;
+ }
+ return 0;
+}
+
+
+const TiXmlNode* TiXmlNode::IterateChildren( const TiXmlNode* previous ) const
+{
+ if ( !previous )
+ {
+ return FirstChild();
+ }
+ else
+ {
+ assert( previous->parent == this );
+ return previous->NextSibling();
+ }
+}
+
+
+const TiXmlNode* TiXmlNode::IterateChildren( const char * val, const TiXmlNode* previous ) const
+{
+ if ( !previous )
+ {
+ return FirstChild( val );
+ }
+ else
+ {
+ assert( previous->parent == this );
+ return previous->NextSibling( val );
+ }
+}
+
+
+const TiXmlNode* TiXmlNode::NextSibling( const char * _value ) const
+{
+ const TiXmlNode* node;
+ for ( node = next; node; node = node->next )
+ {
+ if ( strcmp( node->Value(), _value ) == 0 )
+ return node;
+ }
+ return 0;
+}
+
+
+const TiXmlNode* TiXmlNode::PreviousSibling( const char * _value ) const
+{
+ const TiXmlNode* node;
+ for ( node = prev; node; node = node->prev )
+ {
+ if ( strcmp( node->Value(), _value ) == 0 )
+ return node;
+ }
+ return 0;
+}
+
+
+void TiXmlElement::RemoveAttribute( const char * name )
+{
+ #ifdef TIXML_USE_STL
+ TIXML_STRING str( name );
+ TiXmlAttribute* node = attributeSet.Find( str );
+ #else
+ TiXmlAttribute* node = attributeSet.Find( name );
+ #endif
+ if ( node )
+ {
+ attributeSet.Remove( node );
+ delete node;
+ }
+}
+
+const TiXmlElement* TiXmlNode::FirstChildElement() const
+{
+ const TiXmlNode* node;
+
+ for ( node = FirstChild();
+ node;
+ node = node->NextSibling() )
+ {
+ if ( node->ToElement() )
+ return node->ToElement();
+ }
+ return 0;
+}
+
+
+const TiXmlElement* TiXmlNode::FirstChildElement( const char * _value ) const
+{
+ const TiXmlNode* node;
+
+ for ( node = FirstChild( _value );
+ node;
+ node = node->NextSibling( _value ) )
+ {
+ if ( node->ToElement() )
+ return node->ToElement();
+ }
+ return 0;
+}
+
+
+const TiXmlElement* TiXmlNode::NextSiblingElement() const
+{
+ const TiXmlNode* node;
+
+ for ( node = NextSibling();
+ node;
+ node = node->NextSibling() )
+ {
+ if ( node->ToElement() )
+ return node->ToElement();
+ }
+ return 0;
+}
+
+
+const TiXmlElement* TiXmlNode::NextSiblingElement( const char * _value ) const
+{
+ const TiXmlNode* node;
+
+ for ( node = NextSibling( _value );
+ node;
+ node = node->NextSibling( _value ) )
+ {
+ if ( node->ToElement() )
+ return node->ToElement();
+ }
+ return 0;
+}
+
+
+const TiXmlDocument* TiXmlNode::GetDocument() const
+{
+ const TiXmlNode* node;
+
+ for( node = this; node; node = node->parent )
+ {
+ if ( node->ToDocument() )
+ return node->ToDocument();
+ }
+ return 0;
+}
+
+
+TiXmlElement::TiXmlElement (const char * _value)
+ : TiXmlNode( TiXmlNode::ELEMENT )
+{
+ firstChild = lastChild = 0;
+ value = _value;
+}
+
+
+#ifdef TIXML_USE_STL
+TiXmlElement::TiXmlElement( const std::string& _value )
+ : TiXmlNode( TiXmlNode::ELEMENT )
+{
+ firstChild = lastChild = 0;
+ value = _value;
+}
+#endif
+
+
+TiXmlElement::TiXmlElement( const TiXmlElement& copy)
+ : TiXmlNode( TiXmlNode::ELEMENT )
+{
+ firstChild = lastChild = 0;
+ copy.CopyTo( this );
+}
+
+
+void TiXmlElement::operator=( const TiXmlElement& base )
+{
+ ClearThis();
+ base.CopyTo( this );
+}
+
+
+TiXmlElement::~TiXmlElement()
+{
+ ClearThis();
+}
+
+
+void TiXmlElement::ClearThis()
+{
+ Clear();
+ while( attributeSet.First() )
+ {
+ TiXmlAttribute* node = attributeSet.First();
+ attributeSet.Remove( node );
+ delete node;
+ }
+}
+
+
+const char* TiXmlElement::Attribute( const char* name ) const
+{
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( node )
+ return node->Value();
+ return 0;
+}
+
+
+#ifdef TIXML_USE_STL
+const std::string* TiXmlElement::Attribute( const std::string& name ) const
+{
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( node )
+ return &node->ValueStr();
+ return 0;
+}
+#endif
+
+
+const char* TiXmlElement::Attribute( const char* name, int* i ) const
+{
+ const char* s = Attribute( name );
+ if ( i )
+ {
+ if ( s ) {
+ *i = atoi( s );
+ }
+ else {
+ *i = 0;
+ }
+ }
+ return s;
+}
+
+
+#ifdef TIXML_USE_STL
+const std::string* TiXmlElement::Attribute( const std::string& name, int* i ) const
+{
+ const std::string* s = Attribute( name );
+ if ( i )
+ {
+ if ( s ) {
+ *i = atoi( s->c_str() );
+ }
+ else {
+ *i = 0;
+ }
+ }
+ return s;
+}
+#endif
+
+
+const char* TiXmlElement::Attribute( const char* name, double* d ) const
+{
+ const char* s = Attribute( name );
+ if ( d )
+ {
+ if ( s ) {
+ *d = atof( s );
+ }
+ else {
+ *d = 0;
+ }
+ }
+ return s;
+}
+
+
+#ifdef TIXML_USE_STL
+const std::string* TiXmlElement::Attribute( const std::string& name, double* d ) const
+{
+ const std::string* s = Attribute( name );
+ if ( d )
+ {
+ if ( s ) {
+ *d = atof( s->c_str() );
+ }
+ else {
+ *d = 0;
+ }
+ }
+ return s;
+}
+#endif
+
+
+int TiXmlElement::QueryIntAttribute( const char* name, int* ival ) const
+{
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( !node )
+ return TIXML_NO_ATTRIBUTE;
+ return node->QueryIntValue( ival );
+}
+
+
+#ifdef TIXML_USE_STL
+int TiXmlElement::QueryIntAttribute( const std::string& name, int* ival ) const
+{
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( !node )
+ return TIXML_NO_ATTRIBUTE;
+ return node->QueryIntValue( ival );
+}
+#endif
+
+
+int TiXmlElement::QueryDoubleAttribute( const char* name, double* dval ) const
+{
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( !node )
+ return TIXML_NO_ATTRIBUTE;
+ return node->QueryDoubleValue( dval );
+}
+
+
+#ifdef TIXML_USE_STL
+int TiXmlElement::QueryDoubleAttribute( const std::string& name, double* dval ) const
+{
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( !node )
+ return TIXML_NO_ATTRIBUTE;
+ return node->QueryDoubleValue( dval );
+}
+#endif
+
+
+void TiXmlElement::SetAttribute( const char * name, int val )
+{
+ char buf[64];
+ #if defined(TIXML_SNPRINTF)
+ TIXML_SNPRINTF( buf, sizeof(buf), "%d", val );
+ #else
+ sprintf( buf, "%d", val );
+ #endif
+ SetAttribute( name, buf );
+}
+
+
+#ifdef TIXML_USE_STL
+void TiXmlElement::SetAttribute( const std::string& name, int val )
+{
+ std::ostringstream oss;
+ oss << val;
+ SetAttribute( name, oss.str() );
+}
+#endif
+
+
+void TiXmlElement::SetDoubleAttribute( const char * name, double val )
+{
+ char buf[256];
+ #if defined(TIXML_SNPRINTF)
+ TIXML_SNPRINTF( buf, sizeof(buf), "%f", val );
+ #else
+ sprintf( buf, "%f", val );
+ #endif
+ SetAttribute( name, buf );
+}
+
+
+void TiXmlElement::SetAttribute( const char * cname, const char * cvalue )
+{
+ #ifdef TIXML_USE_STL
+ TIXML_STRING _name( cname );
+ TIXML_STRING _value( cvalue );
+ #else
+ const char* _name = cname;
+ const char* _value = cvalue;
+ #endif
+
+ TiXmlAttribute* node = attributeSet.Find( _name );
+ if ( node )
+ {
+ node->SetValue( _value );
+ return;
+ }
+
+ TiXmlAttribute* attrib = new TiXmlAttribute( cname, cvalue );
+ if ( attrib )
+ {
+ attributeSet.Add( attrib );
+ }
+ else
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ }
+}
+
+
+#ifdef TIXML_USE_STL
+void TiXmlElement::SetAttribute( const std::string& name, const std::string& _value )
+{
+ TiXmlAttribute* node = attributeSet.Find( name );
+ if ( node )
+ {
+ node->SetValue( _value );
+ return;
+ }
+
+ TiXmlAttribute* attrib = new TiXmlAttribute( name, _value );
+ if ( attrib )
+ {
+ attributeSet.Add( attrib );
+ }
+ else
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ }
+}
+#endif
+
+
+void TiXmlElement::Print( FILE* cfile, int depth ) const
+{
+ int i;
+ assert( cfile );
+ for ( i=0; i<depth; i++ ) {
+ fprintf( cfile, " " );
+ }
+
+ fprintf( cfile, "<%s", value.c_str() );
+
+ const TiXmlAttribute* attrib;
+ for ( attrib = attributeSet.First(); attrib; attrib = attrib->Next() )
+ {
+ fprintf( cfile, " " );
+ attrib->Print( cfile, depth );
+ }
+
+ // There are 3 different formatting approaches:
+ // 1) An element without children is printed as a <foo /> node
+ // 2) An element with only a text child is printed as <foo> text </foo>
+ // 3) An element with children is printed on multiple lines.
+ TiXmlNode* node;
+ if ( !firstChild )
+ {
+ fprintf( cfile, " />" );
+ }
+ else if ( firstChild == lastChild && firstChild->ToText() )
+ {
+ fprintf( cfile, ">" );
+ firstChild->Print( cfile, depth + 1 );
+ fprintf( cfile, "</%s>", value.c_str() );
+ }
+ else
+ {
+ fprintf( cfile, ">" );
+
+ for ( node = firstChild; node; node=node->NextSibling() )
+ {
+ if ( !node->ToText() )
+ {
+ fprintf( cfile, "\n" );
+ }
+ node->Print( cfile, depth+1 );
+ }
+ fprintf( cfile, "\n" );
+ for( i=0; i<depth; ++i ) {
+ fprintf( cfile, " " );
+ }
+ fprintf( cfile, "</%s>", value.c_str() );
+ }
+}
+
+
+void TiXmlElement::CopyTo( TiXmlElement* target ) const
+{
+ // superclass:
+ TiXmlNode::CopyTo( target );
+
+ // Element class:
+ // Clone the attributes, then clone the children.
+ const TiXmlAttribute* attribute = 0;
+ for( attribute = attributeSet.First();
+ attribute;
+ attribute = attribute->Next() )
+ {
+ target->SetAttribute( attribute->Name(), attribute->Value() );
+ }
+
+ TiXmlNode* node = 0;
+ for ( node = firstChild; node; node = node->NextSibling() )
+ {
+ target->LinkEndChild( node->Clone() );
+ }
+}
+
+bool TiXmlElement::Accept( TiXmlVisitor* visitor ) const
+{
+ if ( visitor->VisitEnter( *this, attributeSet.First() ) )
+ {
+ for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() )
+ {
+ if ( !node->Accept( visitor ) )
+ break;
+ }
+ }
+ return visitor->VisitExit( *this );
+}
+
+
+TiXmlNode* TiXmlElement::Clone() const
+{
+ TiXmlElement* clone = new TiXmlElement( Value() );
+ if ( !clone )
+ return 0;
+
+ CopyTo( clone );
+ return clone;
+}
+
+
+const char* TiXmlElement::GetText() const
+{
+ const TiXmlNode* child = this->FirstChild();
+ if ( child ) {
+ const TiXmlText* childText = child->ToText();
+ if ( childText ) {
+ return childText->Value();
+ }
+ }
+ return 0;
+}
+
+
+TiXmlDocument::TiXmlDocument() : TiXmlNode( TiXmlNode::DOCUMENT )
+{
+ tabsize = 4;
+ useMicrosoftBOM = false;
+ ClearError();
+}
+
+TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode::DOCUMENT )
+{
+ tabsize = 4;
+ useMicrosoftBOM = false;
+ value = documentName;
+ ClearError();
+}
+
+
+#ifdef TIXML_USE_STL
+TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiXmlNode::DOCUMENT )
+{
+ tabsize = 4;
+ useMicrosoftBOM = false;
+ value = documentName;
+ ClearError();
+}
+#endif
+
+
+TiXmlDocument::TiXmlDocument( const TiXmlDocument& copy ) : TiXmlNode( TiXmlNode::DOCUMENT )
+{
+ copy.CopyTo( this );
+}
+
+
+void TiXmlDocument::operator=( const TiXmlDocument& copy )
+{
+ Clear();
+ copy.CopyTo( this );
+}
+
+
+bool TiXmlDocument::LoadFile( TiXmlEncoding encoding )
+{
+ // See STL_STRING_BUG below.
+ //StringToBuffer buf( value );
+
+ return LoadFile( Value(), encoding );
+}
+
+
+bool TiXmlDocument::SaveFile() const
+{
+ // See STL_STRING_BUG below.
+// StringToBuffer buf( value );
+//
+// if ( buf.buffer && SaveFile( buf.buffer ) )
+// return true;
+//
+// return false;
+ return SaveFile( Value() );
+}
+
+bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding )
+{
+ // There was a really terrifying little bug here. The code:
+ // value = filename
+ // in the STL case, cause the assignment method of the std::string to
+ // be called. What is strange, is that the std::string had the same
+ // address as it's c_str() method, and so bad things happen. Looks
+ // like a bug in the Microsoft STL implementation.
+ // Add an extra string to avoid the crash.
+ TIXML_STRING filename( _filename );
+ value = filename;
+
+ // reading in binary mode so that tinyxml can normalize the EOL
+ FILE* file = TiXmlFOpen( value.c_str (), "rb" );
+
+ if ( file )
+ {
+ bool result = LoadFile( file, encoding );
+ fclose( file );
+ return result;
+ }
+ else
+ {
+ SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return false;
+ }
+}
+
+bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding )
+{
+ if ( !file )
+ {
+ SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return false;
+ }
+
+ // Delete the existing data:
+ Clear();
+ location.Clear();
+
+ // Get the file size, so we can pre-allocate the string. HUGE speed impact.
+ long length = 0;
+ fseek( file, 0, SEEK_END );
+ length = ftell( file );
+ fseek( file, 0, SEEK_SET );
+
+ // Strange case, but good to handle up front.
+ if ( length <= 0 )
+ {
+ SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return false;
+ }
+
+ // If we have a file, assume it is all one big XML file, and read it in.
+ // The document parser may decide the document ends sooner than the entire file, however.
+ TIXML_STRING data;
+ data.reserve( length );
+
+ // Subtle bug here. TinyXml did use fgets. But from the XML spec:
+ // 2.11 End-of-Line Handling
+ // <snip>
+ // <quote>
+ // ...the XML processor MUST behave as if it normalized all line breaks in external
+ // parsed entities (including the document entity) on input, before parsing, by translating
+ // both the two-character sequence #xD #xA and any #xD that is not followed by #xA to
+ // a single #xA character.
+ // </quote>
+ //
+ // It is not clear fgets does that, and certainly isn't clear it works cross platform.
+ // Generally, you expect fgets to translate from the convention of the OS to the c/unix
+ // convention, and not work generally.
+
+ /*
+ while( fgets( buf, sizeof(buf), file ) )
+ {
+ data += buf;
+ }
+ */
+
+ char* buf = new char[ length+1 ];
+ buf[0] = 0;
+
+ if ( fread( buf, length, 1, file ) != 1 ) {
+ delete [] buf;
+ SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return false;
+ }
+
+ const char* lastPos = buf;
+ const char* p = buf;
+
+ buf[length] = 0;
+ while( *p ) {
+ assert( p < (buf+length) );
+ if ( *p == 0xa ) {
+ // Newline character. No special rules for this. Append all the characters
+ // since the last string, and include the newline.
+ data.append( lastPos, (p-lastPos+1) ); // append, include the newline
+ ++p; // move past the newline
+ lastPos = p; // and point to the new buffer (may be 0)
+ assert( p <= (buf+length) );
+ }
+ else if ( *p == 0xd ) {
+ // Carriage return. Append what we have so far, then
+ // handle moving forward in the buffer.
+ if ( (p-lastPos) > 0 ) {
+ data.append( lastPos, p-lastPos ); // do not add the CR
+ }
+ data += (char)0xa; // a proper newline
+
+ if ( *(p+1) == 0xa ) {
+ // Carriage return - new line sequence
+ p += 2;
+ lastPos = p;
+ assert( p <= (buf+length) );
+ }
+ else {
+ // it was followed by something else...that is presumably characters again.
+ ++p;
+ lastPos = p;
+ assert( p <= (buf+length) );
+ }
+ }
+ else {
+ ++p;
+ }
+ }
+ // Handle any left over characters.
+ if ( p-lastPos ) {
+ data.append( lastPos, p-lastPos );
+ }
+ delete [] buf;
+ buf = 0;
+
+ Parse( data.c_str(), 0, encoding );
+
+ if ( Error() )
+ return false;
+ else
+ return true;
+}
+
+
+bool TiXmlDocument::SaveFile( const char * filename ) const
+{
+ // The old c stuff lives on...
+ FILE* fp = TiXmlFOpen( filename, "w" );
+ if ( fp )
+ {
+ bool result = SaveFile( fp );
+ fclose( fp );
+ return result;
+ }
+ return false;
+}
+
+
+bool TiXmlDocument::SaveFile( FILE* fp ) const
+{
+ if ( useMicrosoftBOM )
+ {
+ const unsigned char TIXML_UTF_LEAD_0 = 0xefU;
+ const unsigned char TIXML_UTF_LEAD_1 = 0xbbU;
+ const unsigned char TIXML_UTF_LEAD_2 = 0xbfU;
+
+ fputc( TIXML_UTF_LEAD_0, fp );
+ fputc( TIXML_UTF_LEAD_1, fp );
+ fputc( TIXML_UTF_LEAD_2, fp );
+ }
+ Print( fp, 0 );
+ return (ferror(fp) == 0);
+}
+
+
+void TiXmlDocument::CopyTo( TiXmlDocument* target ) const
+{
+ TiXmlNode::CopyTo( target );
+
+ target->error = error;
+ target->errorId = errorId;
+ target->errorDesc = errorDesc;
+ target->tabsize = tabsize;
+ target->errorLocation = errorLocation;
+ target->useMicrosoftBOM = useMicrosoftBOM;
+
+ TiXmlNode* node = 0;
+ for ( node = firstChild; node; node = node->NextSibling() )
+ {
+ target->LinkEndChild( node->Clone() );
+ }
+}
+
+
+TiXmlNode* TiXmlDocument::Clone() const
+{
+ TiXmlDocument* clone = new TiXmlDocument();
+ if ( !clone )
+ return 0;
+
+ CopyTo( clone );
+ return clone;
+}
+
+
+void TiXmlDocument::Print( FILE* cfile, int depth ) const
+{
+ assert( cfile );
+ for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() )
+ {
+ node->Print( cfile, depth );
+ fprintf( cfile, "\n" );
+ }
+}
+
+
+bool TiXmlDocument::Accept( TiXmlVisitor* visitor ) const
+{
+ if ( visitor->VisitEnter( *this ) )
+ {
+ for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() )
+ {
+ if ( !node->Accept( visitor ) )
+ break;
+ }
+ }
+ return visitor->VisitExit( *this );
+}
+
+
+const TiXmlAttribute* TiXmlAttribute::Next() const
+{
+ // We are using knowledge of the sentinel. The sentinel
+ // have a value or name.
+ if ( next->value.empty() && next->name.empty() )
+ return 0;
+ return next;
+}
+
+/*
+TiXmlAttribute* TiXmlAttribute::Next()
+{
+ // We are using knowledge of the sentinel. The sentinel
+ // have a value or name.
+ if ( next->value.empty() && next->name.empty() )
+ return 0;
+ return next;
+}
+*/
+
+const TiXmlAttribute* TiXmlAttribute::Previous() const
+{
+ // We are using knowledge of the sentinel. The sentinel
+ // have a value or name.
+ if ( prev->value.empty() && prev->name.empty() )
+ return 0;
+ return prev;
+}
+
+/*
+TiXmlAttribute* TiXmlAttribute::Previous()
+{
+ // We are using knowledge of the sentinel. The sentinel
+ // have a value or name.
+ if ( prev->value.empty() && prev->name.empty() )
+ return 0;
+ return prev;
+}
+*/
+
+void TiXmlAttribute::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const
+{
+ TIXML_STRING n, v;
+
+ EncodeString( name, &n );
+ EncodeString( value, &v );
+
+ if (value.find ('\"') == TIXML_STRING::npos) {
+ if ( cfile ) {
+ fprintf (cfile, "%s=\"%s\"", n.c_str(), v.c_str() );
+ }
+ if ( str ) {
+ (*str) += n; (*str) += "=\""; (*str) += v; (*str) += "\"";
+ }
+ }
+ else {
+ if ( cfile ) {
+ fprintf (cfile, "%s='%s'", n.c_str(), v.c_str() );
+ }
+ if ( str ) {
+ (*str) += n; (*str) += "='"; (*str) += v; (*str) += "'";
+ }
+ }
+}
+
+
+int TiXmlAttribute::QueryIntValue( int* ival ) const
+{
+ if ( TIXML_SSCANF( value.c_str(), "%d", ival ) == 1 )
+ return TIXML_SUCCESS;
+ return TIXML_WRONG_TYPE;
+}
+
+int TiXmlAttribute::QueryDoubleValue( double* dval ) const
+{
+ if ( TIXML_SSCANF( value.c_str(), "%lf", dval ) == 1 )
+ return TIXML_SUCCESS;
+ return TIXML_WRONG_TYPE;
+}
+
+void TiXmlAttribute::SetIntValue( int _value )
+{
+ char buf [64];
+ #if defined(TIXML_SNPRINTF)
+ TIXML_SNPRINTF(buf, sizeof(buf), "%d", _value);
+ #else
+ sprintf (buf, "%d", _value);
+ #endif
+ SetValue (buf);
+}
+
+void TiXmlAttribute::SetDoubleValue( double _value )
+{
+ char buf [256];
+ #if defined(TIXML_SNPRINTF)
+ TIXML_SNPRINTF( buf, sizeof(buf), "%lf", _value);
+ #else
+ sprintf (buf, "%lf", _value);
+ #endif
+ SetValue (buf);
+}
+
+int TiXmlAttribute::IntValue() const
+{
+ return atoi (value.c_str ());
+}
+
+double TiXmlAttribute::DoubleValue() const
+{
+ return atof (value.c_str ());
+}
+
+
+TiXmlComment::TiXmlComment( const TiXmlComment& copy ) : TiXmlNode( TiXmlNode::COMMENT )
+{
+ copy.CopyTo( this );
+}
+
+
+void TiXmlComment::operator=( const TiXmlComment& base )
+{
+ Clear();
+ base.CopyTo( this );
+}
+
+
+void TiXmlComment::Print( FILE* cfile, int depth ) const
+{
+ assert( cfile );
+ for ( int i=0; i<depth; i++ )
+ {
+ fprintf( cfile, " " );
+ }
+ fprintf( cfile, "<!--%s-->", value.c_str() );
+}
+
+
+void TiXmlComment::CopyTo( TiXmlComment* target ) const
+{
+ TiXmlNode::CopyTo( target );
+}
+
+
+bool TiXmlComment::Accept( TiXmlVisitor* visitor ) const
+{
+ return visitor->Visit( *this );
+}
+
+
+TiXmlNode* TiXmlComment::Clone() const
+{
+ TiXmlComment* clone = new TiXmlComment();
+
+ if ( !clone )
+ return 0;
+
+ CopyTo( clone );
+ return clone;
+}
+
+
+void TiXmlText::Print( FILE* cfile, int depth ) const
+{
+ assert( cfile );
+ if ( cdata )
+ {
+ int i;
+ fprintf( cfile, "\n" );
+ for ( i=0; i<depth; i++ ) {
+ fprintf( cfile, " " );
+ }
+ fprintf( cfile, "<![CDATA[%s]]>\n", value.c_str() ); // unformatted output
+ }
+ else
+ {
+ TIXML_STRING buffer;
+ EncodeString( value, &buffer );
+ fprintf( cfile, "%s", buffer.c_str() );
+ }
+}
+
+
+void TiXmlText::CopyTo( TiXmlText* target ) const
+{
+ TiXmlNode::CopyTo( target );
+ target->cdata = cdata;
+}
+
+
+bool TiXmlText::Accept( TiXmlVisitor* visitor ) const
+{
+ return visitor->Visit( *this );
+}
+
+
+TiXmlNode* TiXmlText::Clone() const
+{
+ TiXmlText* clone = 0;
+ clone = new TiXmlText( "" );
+
+ if ( !clone )
+ return 0;
+
+ CopyTo( clone );
+ return clone;
+}
+
+
+TiXmlDeclaration::TiXmlDeclaration( const char * _version,
+ const char * _encoding,
+ const char * _standalone )
+ : TiXmlNode( TiXmlNode::DECLARATION )
+{
+ version = _version;
+ encoding = _encoding;
+ standalone = _standalone;
+}
+
+
+#ifdef TIXML_USE_STL
+TiXmlDeclaration::TiXmlDeclaration( const std::string& _version,
+ const std::string& _encoding,
+ const std::string& _standalone )
+ : TiXmlNode( TiXmlNode::DECLARATION )
+{
+ version = _version;
+ encoding = _encoding;
+ standalone = _standalone;
+}
+#endif
+
+
+TiXmlDeclaration::TiXmlDeclaration( const TiXmlDeclaration& copy )
+ : TiXmlNode( TiXmlNode::DECLARATION )
+{
+ copy.CopyTo( this );
+}
+
+
+void TiXmlDeclaration::operator=( const TiXmlDeclaration& copy )
+{
+ Clear();
+ copy.CopyTo( this );
+}
+
+
+void TiXmlDeclaration::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const
+{
+ if ( cfile ) fprintf( cfile, "<?xml " );
+ if ( str ) (*str) += "<?xml ";
+
+ if ( !version.empty() ) {
+ if ( cfile ) fprintf (cfile, "version=\"%s\" ", version.c_str ());
+ if ( str ) { (*str) += "version=\""; (*str) += version; (*str) += "\" "; }
+ }
+ if ( !encoding.empty() ) {
+ if ( cfile ) fprintf (cfile, "encoding=\"%s\" ", encoding.c_str ());
+ if ( str ) { (*str) += "encoding=\""; (*str) += encoding; (*str) += "\" "; }
+ }
+ if ( !standalone.empty() ) {
+ if ( cfile ) fprintf (cfile, "standalone=\"%s\" ", standalone.c_str ());
+ if ( str ) { (*str) += "standalone=\""; (*str) += standalone; (*str) += "\" "; }
+ }
+ if ( cfile ) fprintf( cfile, "?>" );
+ if ( str ) (*str) += "?>";
+}
+
+
+void TiXmlDeclaration::CopyTo( TiXmlDeclaration* target ) const
+{
+ TiXmlNode::CopyTo( target );
+
+ target->version = version;
+ target->encoding = encoding;
+ target->standalone = standalone;
+}
+
+
+bool TiXmlDeclaration::Accept( TiXmlVisitor* visitor ) const
+{
+ return visitor->Visit( *this );
+}
+
+
+TiXmlNode* TiXmlDeclaration::Clone() const
+{
+ TiXmlDeclaration* clone = new TiXmlDeclaration();
+
+ if ( !clone )
+ return 0;
+
+ CopyTo( clone );
+ return clone;
+}
+
+
+void TiXmlUnknown::Print( FILE* cfile, int depth ) const
+{
+ for ( int i=0; i<depth; i++ )
+ fprintf( cfile, " " );
+ fprintf( cfile, "<%s>", value.c_str() );
+}
+
+
+void TiXmlUnknown::CopyTo( TiXmlUnknown* target ) const
+{
+ TiXmlNode::CopyTo( target );
+}
+
+
+bool TiXmlUnknown::Accept( TiXmlVisitor* visitor ) const
+{
+ return visitor->Visit( *this );
+}
+
+
+TiXmlNode* TiXmlUnknown::Clone() const
+{
+ TiXmlUnknown* clone = new TiXmlUnknown();
+
+ if ( !clone )
+ return 0;
+
+ CopyTo( clone );
+ return clone;
+}
+
+
+TiXmlAttributeSet::TiXmlAttributeSet()
+{
+ sentinel.next = &sentinel;
+ sentinel.prev = &sentinel;
+}
+
+
+TiXmlAttributeSet::~TiXmlAttributeSet()
+{
+ assert( sentinel.next == &sentinel );
+ assert( sentinel.prev == &sentinel );
+}
+
+
+void TiXmlAttributeSet::Add( TiXmlAttribute* addMe )
+{
+ #ifdef TIXML_USE_STL
+ assert( !Find( TIXML_STRING( addMe->Name() ) ) ); // Shouldn't be multiply adding to the set.
+ #else
+ assert( !Find( addMe->Name() ) ); // Shouldn't be multiply adding to the set.
+ #endif
+
+ addMe->next = &sentinel;
+ addMe->prev = sentinel.prev;
+
+ sentinel.prev->next = addMe;
+ sentinel.prev = addMe;
+}
+
+void TiXmlAttributeSet::Remove( TiXmlAttribute* removeMe )
+{
+ TiXmlAttribute* node;
+
+ for( node = sentinel.next; node != &sentinel; node = node->next )
+ {
+ if ( node == removeMe )
+ {
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+ node->next = 0;
+ node->prev = 0;
+ return;
+ }
+ }
+ assert( 0 ); // we tried to remove a non-linked attribute.
+}
+
+
+#ifdef TIXML_USE_STL
+const TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const
+{
+ for( const TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next )
+ {
+ if ( node->name == name )
+ return node;
+ }
+ return 0;
+}
+
+/*
+TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name )
+{
+ for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next )
+ {
+ if ( node->name == name )
+ return node;
+ }
+ return 0;
+}
+*/
+#endif
+
+
+const TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const
+{
+ for( const TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next )
+ {
+ if ( strcmp( node->name.c_str(), name ) == 0 )
+ return node;
+ }
+ return 0;
+}
+
+/*
+TiXmlAttribute* TiXmlAttributeSet::Find( const char* name )
+{
+ for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next )
+ {
+ if ( strcmp( node->name.c_str(), name ) == 0 )
+ return node;
+ }
+ return 0;
+}
+*/
+
+#ifdef TIXML_USE_STL
+std::istream& operator>> (std::istream & in, TiXmlNode & base)
+{
+ TIXML_STRING tag;
+ tag.reserve( 8 * 1000 );
+ base.StreamIn( &in, &tag );
+
+ base.Parse( tag.c_str(), 0, TIXML_DEFAULT_ENCODING );
+ return in;
+}
+#endif
+
+
+#ifdef TIXML_USE_STL
+std::ostream& operator<< (std::ostream & out, const TiXmlNode & base)
+{
+ TiXmlPrinter printer;
+ printer.SetStreamPrinting();
+ base.Accept( &printer );
+ out << printer.Str();
+
+ return out;
+}
+
+
+std::string& operator<< (std::string& out, const TiXmlNode& base )
+{
+ TiXmlPrinter printer;
+ printer.SetStreamPrinting();
+ base.Accept( &printer );
+ out.append( printer.Str() );
+
+ return out;
+}
+#endif
+
+
+TiXmlHandle TiXmlHandle::FirstChild() const
+{
+ if ( node )
+ {
+ TiXmlNode* child = node->FirstChild();
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+TiXmlHandle TiXmlHandle::FirstChild( const char * value ) const
+{
+ if ( node )
+ {
+ TiXmlNode* child = node->FirstChild( value );
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+TiXmlHandle TiXmlHandle::FirstChildElement() const
+{
+ if ( node )
+ {
+ TiXmlElement* child = node->FirstChildElement();
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+TiXmlHandle TiXmlHandle::FirstChildElement( const char * value ) const
+{
+ if ( node )
+ {
+ TiXmlElement* child = node->FirstChildElement( value );
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+TiXmlHandle TiXmlHandle::Child( int count ) const
+{
+ if ( node )
+ {
+ int i;
+ TiXmlNode* child = node->FirstChild();
+ for ( i=0;
+ child && i<count;
+ child = child->NextSibling(), ++i )
+ {
+ // nothing
+ }
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+TiXmlHandle TiXmlHandle::Child( const char* value, int count ) const
+{
+ if ( node )
+ {
+ int i;
+ TiXmlNode* child = node->FirstChild( value );
+ for ( i=0;
+ child && i<count;
+ child = child->NextSibling( value ), ++i )
+ {
+ // nothing
+ }
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+TiXmlHandle TiXmlHandle::ChildElement( int count ) const
+{
+ if ( node )
+ {
+ int i;
+ TiXmlElement* child = node->FirstChildElement();
+ for ( i=0;
+ child && i<count;
+ child = child->NextSiblingElement(), ++i )
+ {
+ // nothing
+ }
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+TiXmlHandle TiXmlHandle::ChildElement( const char* value, int count ) const
+{
+ if ( node )
+ {
+ int i;
+ TiXmlElement* child = node->FirstChildElement( value );
+ for ( i=0;
+ child && i<count;
+ child = child->NextSiblingElement( value ), ++i )
+ {
+ // nothing
+ }
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+bool TiXmlPrinter::VisitEnter( const TiXmlDocument& )
+{
+ return true;
+}
+
+bool TiXmlPrinter::VisitExit( const TiXmlDocument& )
+{
+ return true;
+}
+
+bool TiXmlPrinter::VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute )
+{
+ DoIndent();
+ buffer += "<";
+ buffer += element.Value();
+
+ for( const TiXmlAttribute* attrib = firstAttribute; attrib; attrib = attrib->Next() )
+ {
+ buffer += " ";
+ attrib->Print( 0, 0, &buffer );
+ }
+
+ if ( !element.FirstChild() )
+ {
+ buffer += " />";
+ DoLineBreak();
+ }
+ else
+ {
+ buffer += ">";
+ if ( element.FirstChild()->ToText()
+ && element.LastChild() == element.FirstChild()
+ && element.FirstChild()->ToText()->CDATA() == false )
+ {
+ simpleTextPrint = true;
+ // no DoLineBreak()!
+ }
+ else
+ {
+ DoLineBreak();
+ }
+ }
+ ++depth;
+ return true;
+}
+
+
+bool TiXmlPrinter::VisitExit( const TiXmlElement& element )
+{
+ --depth;
+ if ( !element.FirstChild() )
+ {
+ // nothing.
+ }
+ else
+ {
+ if ( simpleTextPrint )
+ {
+ simpleTextPrint = false;
+ }
+ else
+ {
+ DoIndent();
+ }
+ buffer += "</";
+ buffer += element.Value();
+ buffer += ">";
+ DoLineBreak();
+ }
+ return true;
+}
+
+
+bool TiXmlPrinter::Visit( const TiXmlText& text )
+{
+ if ( text.CDATA() )
+ {
+ DoIndent();
+ buffer += "<![CDATA[";
+ buffer += text.Value();
+ buffer += "]]>";
+ DoLineBreak();
+ }
+ else if ( simpleTextPrint )
+ {
+ TIXML_STRING str;
+ TiXmlBase::EncodeString( text.ValueTStr(), &str );
+ buffer += str;
+ }
+ else
+ {
+ DoIndent();
+ TIXML_STRING str;
+ TiXmlBase::EncodeString( text.ValueTStr(), &str );
+ buffer += str;
+ DoLineBreak();
+ }
+ return true;
+}
+
+
+bool TiXmlPrinter::Visit( const TiXmlDeclaration& declaration )
+{
+ DoIndent();
+ declaration.Print( 0, 0, &buffer );
+ DoLineBreak();
+ return true;
+}
+
+
+bool TiXmlPrinter::Visit( const TiXmlComment& comment )
+{
+ DoIndent();
+ buffer += "<!--";
+ buffer += comment.Value();
+ buffer += "-->";
+ DoLineBreak();
+ return true;
+}
+
+
+bool TiXmlPrinter::Visit( const TiXmlUnknown& unknown )
+{
+ DoIndent();
+ buffer += "<";
+ buffer += unknown.Value();
+ buffer += ">";
+ DoLineBreak();
+ return true;
+}
+
diff --git a/rotord/src/tinyxml.h b/rotord/src/tinyxml.h
new file mode 100755
index 0000000..7958de5
--- /dev/null
+++ b/rotord/src/tinyxml.h
@@ -0,0 +1,1807 @@
+/*
+www.sourceforge.net/projects/tinyxml
+Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+
+
+#ifndef TINYXML_INCLUDED
+#define TINYXML_INCLUDED
+
+#ifdef _MSC_VER
+#pragma warning( push )
+#pragma warning( disable : 4530 )
+#pragma warning( disable : 4786 )
+#endif
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+// Help out windows:
+#if defined( _DEBUG ) && !defined( DEBUG )
+#define DEBUG
+#endif
+
+#define TIXML_USE_STL // for now, OFXML will use STL for string stuff
+
+#ifdef TIXML_USE_STL
+ #include <string>
+ #include <iostream>
+ #include <sstream>
+ #define TIXML_STRING std::string
+#else
+ #include "tinystr.h"
+ #define TIXML_STRING TiXmlString
+#endif
+
+// Deprecated library function hell. Compilers want to use the
+// new safe versions. This probably doesn't fully address the problem,
+// but it gets closer. There are too many compilers for me to fully
+// test. If you get compilation troubles, undefine TIXML_SAFE
+#define TIXML_SAFE
+
+#ifdef TIXML_SAFE
+ #if defined(_MSC_VER) && (_MSC_VER >= 1400 )
+ // Microsoft visual studio, version 2005 and higher.
+ #define TIXML_SNPRINTF _snprintf_s
+ #define TIXML_SNSCANF _snscanf_s
+ #define TIXML_SSCANF sscanf_s
+ #elif defined(_MSC_VER) && (_MSC_VER >= 1200 )
+ // Microsoft visual studio, version 6 and higher.
+ //#pragma message( "Using _sn* functions." )
+ #define TIXML_SNPRINTF _snprintf
+ #define TIXML_SNSCANF _snscanf
+ #define TIXML_SSCANF sscanf
+ #elif defined(__GNUC__) && (__GNUC__ >= 3 )
+ // GCC version 3 and higher.s
+ //#warning( "Using sn* functions." )
+ #define TIXML_SNPRINTF snprintf
+ #define TIXML_SNSCANF snscanf
+ #define TIXML_SSCANF sscanf
+ #else
+ #define TIXML_SSCANF sscanf
+ #endif
+#endif
+
+class TiXmlDocument;
+class TiXmlElement;
+class TiXmlComment;
+class TiXmlUnknown;
+class TiXmlAttribute;
+class TiXmlText;
+class TiXmlDeclaration;
+class TiXmlParsingData;
+
+const int TIXML_MAJOR_VERSION = 2;
+const int TIXML_MINOR_VERSION = 5;
+const int TIXML_PATCH_VERSION = 3;
+
+/* Internal structure for tracking location of items
+ in the XML file.
+*/
+struct TiXmlCursor
+{
+ TiXmlCursor() { Clear(); }
+ void Clear() { row = col = -1; }
+
+ int row; // 0 based.
+ int col; // 0 based.
+};
+
+
+/**
+ If you call the Accept() method, it requires being passed a TiXmlVisitor
+ class to handle callbacks. For nodes that contain other nodes (Document, Element)
+ you will get called with a VisitEnter/VisitExit pair. Nodes that are always leaves
+ are simple called with Visit().
+
+ If you return 'true' from a Visit method, recursive parsing will continue. If you return
+ false, <b>no children of this node or its sibilings</b> will be Visited.
+
+ All flavors of Visit methods have a default implementation that returns 'true' (continue
+ visiting). You need to only override methods that are interesting to you.
+
+ Generally Accept() is called on the TiXmlDocument, although all nodes suppert Visiting.
+
+ You should never change the document from a callback.
+
+ @sa TiXmlNode::Accept()
+*/
+class TiXmlVisitor
+{
+public:
+ virtual ~TiXmlVisitor() {}
+
+ /// Visit a document.
+ virtual bool VisitEnter( const TiXmlDocument& /*doc*/ ) { return true; }
+ /// Visit a document.
+ virtual bool VisitExit( const TiXmlDocument& /*doc*/ ) { return true; }
+
+ /// Visit an element.
+ virtual bool VisitEnter( const TiXmlElement& /*element*/, const TiXmlAttribute* /*firstAttribute*/ ) { return true; }
+ /// Visit an element.
+ virtual bool VisitExit( const TiXmlElement& /*element*/ ) { return true; }
+
+ /// Visit a declaration
+ virtual bool Visit( const TiXmlDeclaration& /*declaration*/ ) { return true; }
+ /// Visit a text node
+ virtual bool Visit( const TiXmlText& /*text*/ ) { return true; }
+ /// Visit a comment node
+ virtual bool Visit( const TiXmlComment& /*comment*/ ) { return true; }
+ /// Visit an unknow node
+ virtual bool Visit( const TiXmlUnknown& /*unknown*/ ) { return true; }
+};
+
+// Only used by Attribute::Query functions
+enum
+{
+ TIXML_SUCCESS,
+ TIXML_NO_ATTRIBUTE,
+ TIXML_WRONG_TYPE
+};
+
+
+// Used by the parsing routines.
+enum TiXmlEncoding
+{
+ TIXML_ENCODING_UNKNOWN,
+ TIXML_ENCODING_UTF8,
+ TIXML_ENCODING_LEGACY
+};
+
+const TiXmlEncoding TIXML_DEFAULT_ENCODING = TIXML_ENCODING_UNKNOWN;
+
+/** TiXmlBase is a base class for every class in TinyXml.
+ It does little except to establish that TinyXml classes
+ can be printed and provide some utility functions.
+
+ In XML, the document and elements can contain
+ other elements and other types of nodes.
+
+ @verbatim
+ A Document can contain: Element (container or leaf)
+ Comment (leaf)
+ Unknown (leaf)
+ Declaration( leaf )
+
+ An Element can contain: Element (container or leaf)
+ Text (leaf)
+ Attributes (not on tree)
+ Comment (leaf)
+ Unknown (leaf)
+
+ A Decleration contains: Attributes (not on tree)
+ @endverbatim
+*/
+class TiXmlBase
+{
+ friend class TiXmlNode;
+ friend class TiXmlElement;
+ friend class TiXmlDocument;
+
+public:
+ TiXmlBase() : userData(0) {}
+ virtual ~TiXmlBase() {}
+
+ /** All TinyXml classes can print themselves to a filestream
+ or the string class (TiXmlString in non-STL mode, std::string
+ in STL mode.) Either or both cfile and str can be null.
+
+ This is a formatted print, and will insert
+ tabs and newlines.
+
+ (For an unformatted stream, use the << operator.)
+ */
+ virtual void Print( FILE* cfile, int depth ) const = 0;
+
+ /** The world does not agree on whether white space should be kept or
+ not. In order to make everyone happy, these global, static functions
+ are provided to set whether or not TinyXml will condense all white space
+ into a single space or not. The default is to condense. Note changing this
+ value is not thread safe.
+ */
+ static void SetCondenseWhiteSpace( bool condense ) { condenseWhiteSpace = condense; }
+
+ /// Return the current white space setting.
+ static bool IsWhiteSpaceCondensed() { return condenseWhiteSpace; }
+
+ /** Return the position, in the original source file, of this node or attribute.
+ The row and column are 1-based. (That is the first row and first column is
+ 1,1). If the returns values are 0 or less, then the parser does not have
+ a row and column value.
+
+ Generally, the row and column value will be set when the TiXmlDocument::Load(),
+ TiXmlDocument::LoadFile(), or any TiXmlNode::Parse() is called. It will NOT be set
+ when the DOM was created from operator>>.
+
+ The values reflect the initial load. Once the DOM is modified programmatically
+ (by adding or changing nodes and attributes) the new values will NOT update to
+ reflect changes in the document.
+
+ There is a minor performance cost to computing the row and column. Computation
+ can be disabled if TiXmlDocument::SetTabSize() is called with 0 as the value.
+
+ @sa TiXmlDocument::SetTabSize()
+ */
+ int Row() const { return location.row + 1; }
+ int Column() const { return location.col + 1; } ///< See Row()
+
+ void SetUserData( void* user ) { userData = user; } ///< Set a pointer to arbitrary user data.
+ void* GetUserData() { return userData; } ///< Get a pointer to arbitrary user data.
+ const void* GetUserData() const { return userData; } ///< Get a pointer to arbitrary user data.
+
+ // Table that returs, for a given lead byte, the total number of bytes
+ // in the UTF-8 sequence.
+ static const int utf8ByteTable[256];
+
+ virtual const char* Parse( const char* p,
+ TiXmlParsingData* data,
+ TiXmlEncoding encoding /*= TIXML_ENCODING_UNKNOWN */ ) = 0;
+
+ /** Expands entities in a string. Note this should not contian the tag's '<', '>', etc,
+ or they will be transformed into entities!
+ */
+ static void EncodeString( const TIXML_STRING& str, TIXML_STRING* out );
+
+ enum
+ {
+ TIXML_NO_ERROR = 0,
+ TIXML_ERROR,
+ TIXML_ERROR_OPENING_FILE,
+ TIXML_ERROR_OUT_OF_MEMORY,
+ TIXML_ERROR_PARSING_ELEMENT,
+ TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME,
+ TIXML_ERROR_READING_ELEMENT_VALUE,
+ TIXML_ERROR_READING_ATTRIBUTES,
+ TIXML_ERROR_PARSING_EMPTY,
+ TIXML_ERROR_READING_END_TAG,
+ TIXML_ERROR_PARSING_UNKNOWN,
+ TIXML_ERROR_PARSING_COMMENT,
+ TIXML_ERROR_PARSING_DECLARATION,
+ TIXML_ERROR_DOCUMENT_EMPTY,
+ TIXML_ERROR_EMBEDDED_NULL,
+ TIXML_ERROR_PARSING_CDATA,
+ TIXML_ERROR_DOCUMENT_TOP_ONLY,
+
+ TIXML_ERROR_STRING_COUNT
+ };
+
+protected:
+
+ static const char* SkipWhiteSpace( const char*, TiXmlEncoding encoding );
+ inline static bool IsWhiteSpace( char c )
+ {
+ return ( isspace( (unsigned char) c ) || c == '\n' || c == '\r' );
+ }
+ inline static bool IsWhiteSpace( int c )
+ {
+ if ( c < 256 )
+ return IsWhiteSpace( (char) c );
+ return false; // Again, only truly correct for English/Latin...but usually works.
+ }
+
+ #ifdef TIXML_USE_STL
+ static bool StreamWhiteSpace( std::istream * in, TIXML_STRING * tag );
+ static bool StreamTo( std::istream * in, int character, TIXML_STRING * tag );
+ #endif
+
+ /* Reads an XML name into the string provided. Returns
+ a pointer just past the last character of the name,
+ or 0 if the function has an error.
+ */
+ static const char* ReadName( const char* p, TIXML_STRING* name, TiXmlEncoding encoding );
+
+ /* Reads text. Returns a pointer past the given end tag.
+ Wickedly complex options, but it keeps the (sensitive) code in one place.
+ */
+ static const char* ReadText( const char* in, // where to start
+ TIXML_STRING* text, // the string read
+ bool ignoreWhiteSpace, // whether to keep the white space
+ const char* endTag, // what ends this text
+ bool ignoreCase, // whether to ignore case in the end tag
+ TiXmlEncoding encoding ); // the current encoding
+
+ // If an entity has been found, transform it into a character.
+ static const char* GetEntity( const char* in, char* value, int* length, TiXmlEncoding encoding );
+
+ // Get a character, while interpreting entities.
+ // The length can be from 0 to 4 bytes.
+ inline static const char* GetChar( const char* p, char* _value, int* length, TiXmlEncoding encoding )
+ {
+ assert( p );
+ if ( encoding == TIXML_ENCODING_UTF8 )
+ {
+ *length = utf8ByteTable[ *((const unsigned char*)p) ];
+ assert( *length >= 0 && *length < 5 );
+ }
+ else
+ {
+ *length = 1;
+ }
+
+ if ( *length == 1 )
+ {
+ if ( *p == '&' )
+ return GetEntity( p, _value, length, encoding );
+ *_value = *p;
+ return p+1;
+ }
+ else if ( *length )
+ {
+ //strncpy( _value, p, *length ); // lots of compilers don't like this function (unsafe),
+ // and the null terminator isn't needed
+ for( int i=0; p[i] && i<*length; ++i ) {
+ _value[i] = p[i];
+ }
+ return p + (*length);
+ }
+ else
+ {
+ // Not valid text.
+ return 0;
+ }
+ }
+
+ // Return true if the next characters in the stream are any of the endTag sequences.
+ // Ignore case only works for english, and should only be relied on when comparing
+ // to English words: StringEqual( p, "version", true ) is fine.
+ static bool StringEqual( const char* p,
+ const char* endTag,
+ bool ignoreCase,
+ TiXmlEncoding encoding );
+
+ static const char* errorString[ TIXML_ERROR_STRING_COUNT ];
+
+ TiXmlCursor location;
+
+ /// Field containing a generic user pointer
+ void* userData;
+
+ // None of these methods are reliable for any language except English.
+ // Good for approximation, not great for accuracy.
+ static int IsAlpha( unsigned char anyByte, TiXmlEncoding encoding );
+ static int IsAlphaNum( unsigned char anyByte, TiXmlEncoding encoding );
+ inline static int ToLower( int v, TiXmlEncoding encoding )
+ {
+ if ( encoding == TIXML_ENCODING_UTF8 )
+ {
+ if ( v < 128 ) return tolower( v );
+ return v;
+ }
+ else
+ {
+ return tolower( v );
+ }
+ }
+ static void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length );
+
+private:
+ TiXmlBase( const TiXmlBase& ); // not implemented.
+ void operator=( const TiXmlBase& base ); // not allowed.
+
+ struct Entity
+ {
+ const char* str;
+ unsigned int strLength;
+ char chr;
+ };
+ enum
+ {
+ NUM_ENTITY = 5,
+ MAX_ENTITY_LENGTH = 6
+
+ };
+ static Entity entity[ NUM_ENTITY ];
+ static bool condenseWhiteSpace;
+};
+
+
+/** The parent class for everything in the Document Object Model.
+ (Except for attributes).
+ Nodes have siblings, a parent, and children. A node can be
+ in a document, or stand on its own. The type of a TiXmlNode
+ can be queried, and it can be cast to its more defined type.
+*/
+class TiXmlNode : public TiXmlBase
+{
+ friend class TiXmlDocument;
+ friend class TiXmlElement;
+
+public:
+ #ifdef TIXML_USE_STL
+
+ /** An input stream operator, for every class. Tolerant of newlines and
+ formatting, but doesn't expect them.
+ */
+ friend std::istream& operator >> (std::istream& in, TiXmlNode& base);
+
+ /** An output stream operator, for every class. Note that this outputs
+ without any newlines or formatting, as opposed to Print(), which
+ includes tabs and new lines.
+
+ The operator<< and operator>> are not completely symmetric. Writing
+ a node to a stream is very well defined. You'll get a nice stream
+ of output, without any extra whitespace or newlines.
+
+ But reading is not as well defined. (As it always is.) If you create
+ a TiXmlElement (for example) and read that from an input stream,
+ the text needs to define an element or junk will result. This is
+ true of all input streams, but it's worth keeping in mind.
+
+ A TiXmlDocument will read nodes until it reads a root element, and
+ all the children of that root element.
+ */
+ friend std::ostream& operator<< (std::ostream& out, const TiXmlNode& base);
+
+ /// Appends the XML node or attribute to a std::string.
+ friend std::string& operator<< (std::string& out, const TiXmlNode& base );
+
+ #endif
+
+ /** The types of XML nodes supported by TinyXml. (All the
+ unsupported types are picked up by UNKNOWN.)
+ */
+ enum NodeType
+ {
+ DOCUMENT,
+ ELEMENT,
+ COMMENT,
+ UNKNOWN,
+ TEXT,
+ DECLARATION,
+ TYPECOUNT
+ };
+
+ virtual ~TiXmlNode();
+
+ /** The meaning of 'value' changes for the specific type of
+ TiXmlNode.
+ @verbatim
+ Document: filename of the xml file
+ Element: name of the element
+ Comment: the comment text
+ Unknown: the tag contents
+ Text: the text string
+ @endverbatim
+
+ The subclasses will wrap this function.
+ */
+ const char *Value() const { return value.c_str (); }
+
+ #ifdef TIXML_USE_STL
+ /** Return Value() as a std::string. If you only use STL,
+ this is more efficient than calling Value().
+ Only available in STL mode.
+ */
+ const std::string& ValueStr() const { return value; }
+ #endif
+
+ const TIXML_STRING& ValueTStr() const { return value; }
+
+ /** Changes the value of the node. Defined as:
+ @verbatim
+ Document: filename of the xml file
+ Element: name of the element
+ Comment: the comment text
+ Unknown: the tag contents
+ Text: the text string
+ @endverbatim
+ */
+ void SetValue(const char * _value) { value = _value;}
+
+ #ifdef TIXML_USE_STL
+ /// STL std::string form.
+ void SetValue( const std::string& _value ) { value = _value; }
+ #endif
+
+ /// Delete all the children of this node. Does not affect 'this'.
+ void Clear();
+
+ /// One step up the DOM.
+ TiXmlNode* Parent() { return parent; }
+ const TiXmlNode* Parent() const { return parent; }
+
+ const TiXmlNode* FirstChild() const { return firstChild; } ///< The first child of this node. Will be null if there are no children.
+ TiXmlNode* FirstChild() { return firstChild; }
+ const TiXmlNode* FirstChild( const char * value ) const; ///< The first child of this node with the matching 'value'. Will be null if none found.
+ /// The first child of this node with the matching 'value'. Will be null if none found.
+ TiXmlNode* FirstChild( const char * _value ) {
+ // Call through to the const version - safe since nothing is changed. Exiting syntax: cast this to a const (always safe)
+ // call the method, cast the return back to non-const.
+ return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->FirstChild( _value ));
+ }
+ const TiXmlNode* LastChild() const { return lastChild; } /// The last child of this node. Will be null if there are no children.
+ TiXmlNode* LastChild() { return lastChild; }
+
+ const TiXmlNode* LastChild( const char * value ) const; /// The last child of this node matching 'value'. Will be null if there are no children.
+ TiXmlNode* LastChild( const char * _value ) {
+ return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->LastChild( _value ));
+ }
+
+ #ifdef TIXML_USE_STL
+ const TiXmlNode* FirstChild( const std::string& _value ) const { return FirstChild (_value.c_str ()); } ///< STL std::string form.
+ TiXmlNode* FirstChild( const std::string& _value ) { return FirstChild (_value.c_str ()); } ///< STL std::string form.
+ const TiXmlNode* LastChild( const std::string& _value ) const { return LastChild (_value.c_str ()); } ///< STL std::string form.
+ TiXmlNode* LastChild( const std::string& _value ) { return LastChild (_value.c_str ()); } ///< STL std::string form.
+ #endif
+
+ /** An alternate way to walk the children of a node.
+ One way to iterate over nodes is:
+ @verbatim
+ for( child = parent->FirstChild(); child; child = child->NextSibling() )
+ @endverbatim
+
+ IterateChildren does the same thing with the syntax:
+ @verbatim
+ child = 0;
+ while( child = parent->IterateChildren( child ) )
+ @endverbatim
+
+ IterateChildren takes the previous child as input and finds
+ the next one. If the previous child is null, it returns the
+ first. IterateChildren will return null when done.
+ */
+ const TiXmlNode* IterateChildren( const TiXmlNode* previous ) const;
+ TiXmlNode* IterateChildren( const TiXmlNode* previous ) {
+ return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( previous ) );
+ }
+
+ /// This flavor of IterateChildren searches for children with a particular 'value'
+ const TiXmlNode* IterateChildren( const char * value, const TiXmlNode* previous ) const;
+ TiXmlNode* IterateChildren( const char * _value, const TiXmlNode* previous ) {
+ return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( _value, previous ) );
+ }
+
+ #ifdef TIXML_USE_STL
+ const TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) const { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form.
+ TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form.
+ #endif
+
+ /** Add a new node related to this. Adds a child past the LastChild.
+ Returns a pointer to the new object or NULL if an error occured.
+ */
+ TiXmlNode* InsertEndChild( const TiXmlNode& addThis );
+
+
+ /** Add a new node related to this. Adds a child past the LastChild.
+
+ NOTE: the node to be added is passed by pointer, and will be
+ henceforth owned (and deleted) by tinyXml. This method is efficient
+ and avoids an extra copy, but should be used with care as it
+ uses a different memory model than the other insert functions.
+
+ @sa InsertEndChild
+ */
+ TiXmlNode* LinkEndChild( TiXmlNode* addThis );
+
+ /** Add a new node related to this. Adds a child before the specified child.
+ Returns a pointer to the new object or NULL if an error occured.
+ */
+ TiXmlNode* InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis );
+
+ /** Add a new node related to this. Adds a child after the specified child.
+ Returns a pointer to the new object or NULL if an error occured.
+ */
+ TiXmlNode* InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis );
+
+ /** Replace a child of this node.
+ Returns a pointer to the new object or NULL if an error occured.
+ */
+ TiXmlNode* ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis );
+
+ /// Delete a child of this node.
+ bool RemoveChild( TiXmlNode* removeThis );
+
+ /// Navigate to a sibling node.
+ const TiXmlNode* PreviousSibling() const { return prev; }
+ TiXmlNode* PreviousSibling() { return prev; }
+
+ /// Navigate to a sibling node.
+ const TiXmlNode* PreviousSibling( const char * ) const;
+ TiXmlNode* PreviousSibling( const char *_prev ) {
+ return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->PreviousSibling( _prev ) );
+ }
+
+ #ifdef TIXML_USE_STL
+ const TiXmlNode* PreviousSibling( const std::string& _value ) const { return PreviousSibling (_value.c_str ()); } ///< STL std::string form.
+ TiXmlNode* PreviousSibling( const std::string& _value ) { return PreviousSibling (_value.c_str ()); } ///< STL std::string form.
+ const TiXmlNode* NextSibling( const std::string& _value) const { return NextSibling (_value.c_str ()); } ///< STL std::string form.
+ TiXmlNode* NextSibling( const std::string& _value) { return NextSibling (_value.c_str ()); } ///< STL std::string form.
+ #endif
+
+ /// Navigate to a sibling node.
+ const TiXmlNode* NextSibling() const { return next; }
+ TiXmlNode* NextSibling() { return next; }
+
+ /// Navigate to a sibling node with the given 'value'.
+ const TiXmlNode* NextSibling( const char * ) const;
+ TiXmlNode* NextSibling( const char* _next ) {
+ return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->NextSibling( _next ) );
+ }
+
+ /** Convenience function to get through elements.
+ Calls NextSibling and ToElement. Will skip all non-Element
+ nodes. Returns 0 if there is not another element.
+ */
+ const TiXmlElement* NextSiblingElement() const;
+ TiXmlElement* NextSiblingElement() {
+ return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement() );
+ }
+
+ /** Convenience function to get through elements.
+ Calls NextSibling and ToElement. Will skip all non-Element
+ nodes. Returns 0 if there is not another element.
+ */
+ const TiXmlElement* NextSiblingElement( const char * ) const;
+ TiXmlElement* NextSiblingElement( const char *_next ) {
+ return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement( _next ) );
+ }
+
+ #ifdef TIXML_USE_STL
+ const TiXmlElement* NextSiblingElement( const std::string& _value) const { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form.
+ TiXmlElement* NextSiblingElement( const std::string& _value) { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form.
+ #endif
+
+ /// Convenience function to get through elements.
+ const TiXmlElement* FirstChildElement() const;
+ TiXmlElement* FirstChildElement() {
+ return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement() );
+ }
+
+ /// Convenience function to get through elements.
+ const TiXmlElement* FirstChildElement( const char * _value ) const;
+ TiXmlElement* FirstChildElement( const char * _value ) {
+ return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement( _value ) );
+ }
+
+ #ifdef TIXML_USE_STL
+ const TiXmlElement* FirstChildElement( const std::string& _value ) const { return FirstChildElement (_value.c_str ()); } ///< STL std::string form.
+ TiXmlElement* FirstChildElement( const std::string& _value ) { return FirstChildElement (_value.c_str ()); } ///< STL std::string form.
+ #endif
+
+ /** Query the type (as an enumerated value, above) of this node.
+ The possible types are: DOCUMENT, ELEMENT, COMMENT,
+ UNKNOWN, TEXT, and DECLARATION.
+ */
+ int Type() const { return type; }
+
+ /** Return a pointer to the Document this node lives in.
+ Returns null if not in a document.
+ */
+ const TiXmlDocument* GetDocument() const;
+ TiXmlDocument* GetDocument() {
+ return const_cast< TiXmlDocument* >( (const_cast< const TiXmlNode* >(this))->GetDocument() );
+ }
+
+ /// Returns true if this node has no children.
+ bool NoChildren() const { return !firstChild; }
+
+ virtual const TiXmlDocument* ToDocument() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual const TiXmlElement* ToElement() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual const TiXmlComment* ToComment() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual const TiXmlUnknown* ToUnknown() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual const TiXmlText* ToText() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual const TiXmlDeclaration* ToDeclaration() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+
+ virtual TiXmlDocument* ToDocument() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual TiXmlElement* ToElement() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual TiXmlComment* ToComment() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual TiXmlUnknown* ToUnknown() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual TiXmlText* ToText() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual TiXmlDeclaration* ToDeclaration() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+
+ /** Create an exact duplicate of this node and return it. The memory must be deleted
+ by the caller.
+ */
+ virtual TiXmlNode* Clone() const = 0;
+
+ /** Accept a hierchical visit the nodes in the TinyXML DOM. Every node in the
+ XML tree will be conditionally visited and the host will be called back
+ via the TiXmlVisitor interface.
+
+ This is essentially a SAX interface for TinyXML. (Note however it doesn't re-parse
+ the XML for the callbacks, so the performance of TinyXML is unchanged by using this
+ interface versus any other.)
+
+ The interface has been based on ideas from:
+
+ - http://www.saxproject.org/
+ - http://c2.com/cgi/wiki?HierarchicalVisitorPattern
+
+ Which are both good references for "visiting".
+
+ An example of using Accept():
+ @verbatim
+ TiXmlPrinter printer;
+ tinyxmlDoc.Accept( &printer );
+ const char* xmlcstr = printer.CStr();
+ @endverbatim
+ */
+ virtual bool Accept( TiXmlVisitor* visitor ) const = 0;
+
+protected:
+ TiXmlNode( NodeType _type );
+
+ // Copy to the allocated object. Shared functionality between Clone, Copy constructor,
+ // and the assignment operator.
+ void CopyTo( TiXmlNode* target ) const;
+
+ #ifdef TIXML_USE_STL
+ // The real work of the input operator.
+ virtual void StreamIn( std::istream* in, TIXML_STRING* tag ) = 0;
+ #endif
+
+ // Figure out what is at *p, and parse it. Returns null if it is not an xml node.
+ TiXmlNode* Identify( const char* start, TiXmlEncoding encoding );
+
+ TiXmlNode* parent;
+ NodeType type;
+
+ TiXmlNode* firstChild;
+ TiXmlNode* lastChild;
+
+ TIXML_STRING value;
+
+ TiXmlNode* prev;
+ TiXmlNode* next;
+
+private:
+ TiXmlNode( const TiXmlNode& ); // not implemented.
+ void operator=( const TiXmlNode& base ); // not allowed.
+};
+
+
+/** An attribute is a name-value pair. Elements have an arbitrary
+ number of attributes, each with a unique name.
+
+ @note The attributes are not TiXmlNodes, since they are not
+ part of the tinyXML document object model. There are other
+ suggested ways to look at this problem.
+*/
+class TiXmlAttribute : public TiXmlBase
+{
+ friend class TiXmlAttributeSet;
+
+public:
+ /// Construct an empty attribute.
+ TiXmlAttribute() : TiXmlBase()
+ {
+ document = 0;
+ prev = next = 0;
+ }
+
+ #ifdef TIXML_USE_STL
+ /// std::string constructor.
+ TiXmlAttribute( const std::string& _name, const std::string& _value )
+ {
+ name = _name;
+ value = _value;
+ document = 0;
+ prev = next = 0;
+ }
+ #endif
+
+ /// Construct an attribute with a name and value.
+ TiXmlAttribute( const char * _name, const char * _value )
+ {
+ name = _name;
+ value = _value;
+ document = 0;
+ prev = next = 0;
+ }
+
+ const char* Name() const { return name.c_str(); } ///< Return the name of this attribute.
+ const char* Value() const { return value.c_str(); } ///< Return the value of this attribute.
+ #ifdef TIXML_USE_STL
+ const std::string& ValueStr() const { return value; } ///< Return the value of this attribute.
+ #endif
+ int IntValue() const; ///< Return the value of this attribute, converted to an integer.
+ double DoubleValue() const; ///< Return the value of this attribute, converted to a double.
+
+ // Get the tinyxml string representation
+ const TIXML_STRING& NameTStr() const { return name; }
+
+ /** QueryIntValue examines the value string. It is an alternative to the
+ IntValue() method with richer error checking.
+ If the value is an integer, it is stored in 'value' and
+ the call returns TIXML_SUCCESS. If it is not
+ an integer, it returns TIXML_WRONG_TYPE.
+
+ A specialized but useful call. Note that for success it returns 0,
+ which is the opposite of almost all other TinyXml calls.
+ */
+ int QueryIntValue( int* _value ) const;
+ /// QueryDoubleValue examines the value string. See QueryIntValue().
+ int QueryDoubleValue( double* _value ) const;
+
+ void SetName( const char* _name ) { name = _name; } ///< Set the name of this attribute.
+ void SetValue( const char* _value ) { value = _value; } ///< Set the value.
+
+ void SetIntValue( int _value ); ///< Set the value from an integer.
+ void SetDoubleValue( double _value ); ///< Set the value from a double.
+
+ #ifdef TIXML_USE_STL
+ /// STL std::string form.
+ void SetName( const std::string& _name ) { name = _name; }
+ /// STL std::string form.
+ void SetValue( const std::string& _value ) { value = _value; }
+ #endif
+
+ /// Get the next sibling attribute in the DOM. Returns null at end.
+ const TiXmlAttribute* Next() const;
+ TiXmlAttribute* Next() {
+ return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Next() );
+ }
+
+ /// Get the previous sibling attribute in the DOM. Returns null at beginning.
+ const TiXmlAttribute* Previous() const;
+ TiXmlAttribute* Previous() {
+ return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Previous() );
+ }
+
+ bool operator==( const TiXmlAttribute& rhs ) const { return rhs.name == name; }
+ bool operator<( const TiXmlAttribute& rhs ) const { return name < rhs.name; }
+ bool operator>( const TiXmlAttribute& rhs ) const { return name > rhs.name; }
+
+ /* Attribute parsing starts: first letter of the name
+ returns: the next char after the value end quote
+ */
+ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );
+
+ // Prints this Attribute to a FILE stream.
+ virtual void Print( FILE* cfile, int depth ) const {
+ Print( cfile, depth, 0 );
+ }
+ void Print( FILE* cfile, int depth, TIXML_STRING* str ) const;
+
+ // [internal use]
+ // Set the document pointer so the attribute can report errors.
+ void SetDocument( TiXmlDocument* doc ) { document = doc; }
+
+private:
+ TiXmlAttribute( const TiXmlAttribute& ); // not implemented.
+ void operator=( const TiXmlAttribute& base ); // not allowed.
+
+ TiXmlDocument* document; // A pointer back to a document, for error reporting.
+ TIXML_STRING name;
+ TIXML_STRING value;
+ TiXmlAttribute* prev;
+ TiXmlAttribute* next;
+};
+
+
+/* A class used to manage a group of attributes.
+ It is only used internally, both by the ELEMENT and the DECLARATION.
+
+ The set can be changed transparent to the Element and Declaration
+ classes that use it, but NOT transparent to the Attribute
+ which has to implement a next() and previous() method. Which makes
+ it a bit problematic and prevents the use of STL.
+
+ This version is implemented with circular lists because:
+ - I like circular lists
+ - it demonstrates some independence from the (typical) doubly linked list.
+*/
+class TiXmlAttributeSet
+{
+public:
+ TiXmlAttributeSet();
+ ~TiXmlAttributeSet();
+
+ void Add( TiXmlAttribute* attribute );
+ void Remove( TiXmlAttribute* attribute );
+
+ const TiXmlAttribute* First() const { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; }
+ TiXmlAttribute* First() { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; }
+ const TiXmlAttribute* Last() const { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; }
+ TiXmlAttribute* Last() { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; }
+
+ const TiXmlAttribute* Find( const char* _name ) const;
+ TiXmlAttribute* Find( const char* _name ) {
+ return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttributeSet* >(this))->Find( _name ) );
+ }
+ #ifdef TIXML_USE_STL
+ const TiXmlAttribute* Find( const std::string& _name ) const;
+ TiXmlAttribute* Find( const std::string& _name ) {
+ return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttributeSet* >(this))->Find( _name ) );
+ }
+
+ #endif
+
+private:
+ //*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute (sentinel-element),
+ //*ME: this class must be also use a hidden/disabled copy-constructor !!!
+ TiXmlAttributeSet( const TiXmlAttributeSet& ); // not allowed
+ void operator=( const TiXmlAttributeSet& ); // not allowed (as TiXmlAttribute)
+
+ TiXmlAttribute sentinel;
+};
+
+
+/** The element is a container class. It has a value, the element name,
+ and can contain other elements, text, comments, and unknowns.
+ Elements also contain an arbitrary number of attributes.
+*/
+class TiXmlElement : public TiXmlNode
+{
+public:
+ /// Construct an element.
+ TiXmlElement (const char * in_value);
+
+ #ifdef TIXML_USE_STL
+ /// std::string constructor.
+ TiXmlElement( const std::string& _value );
+ #endif
+
+ TiXmlElement( const TiXmlElement& );
+
+ void operator=( const TiXmlElement& base );
+
+ virtual ~TiXmlElement();
+
+ /** Given an attribute name, Attribute() returns the value
+ for the attribute of that name, or null if none exists.
+ */
+ const char* Attribute( const char* name ) const;
+
+ /** Given an attribute name, Attribute() returns the value
+ for the attribute of that name, or null if none exists.
+ If the attribute exists and can be converted to an integer,
+ the integer value will be put in the return 'i', if 'i'
+ is non-null.
+ */
+ const char* Attribute( const char* name, int* i ) const;
+
+ /** Given an attribute name, Attribute() returns the value
+ for the attribute of that name, or null if none exists.
+ If the attribute exists and can be converted to an double,
+ the double value will be put in the return 'd', if 'd'
+ is non-null.
+ */
+ const char* Attribute( const char* name, double* d ) const;
+
+ /** QueryIntAttribute examines the attribute - it is an alternative to the
+ Attribute() method with richer error checking.
+ If the attribute is an integer, it is stored in 'value' and
+ the call returns TIXML_SUCCESS. If it is not
+ an integer, it returns TIXML_WRONG_TYPE. If the attribute
+ does not exist, then TIXML_NO_ATTRIBUTE is returned.
+ */
+ int QueryIntAttribute( const char* name, int* _value ) const;
+ /// QueryDoubleAttribute examines the attribute - see QueryIntAttribute().
+ int QueryDoubleAttribute( const char* name, double* _value ) const;
+ /// QueryFloatAttribute examines the attribute - see QueryIntAttribute().
+ int QueryFloatAttribute( const char* name, float* _value ) const {
+ double d;
+ int result = QueryDoubleAttribute( name, &d );
+ if ( result == TIXML_SUCCESS ) {
+ *_value = (float)d;
+ }
+ return result;
+ }
+
+ #ifdef TIXML_USE_STL
+ /** Template form of the attribute query which will try to read the
+ attribute into the specified type. Very easy, very powerful, but
+ be careful to make sure to call this with the correct type.
+
+ NOTE: This method doesn't work correctly for 'string' types.
+
+ @return TIXML_SUCCESS, TIXML_WRONG_TYPE, or TIXML_NO_ATTRIBUTE
+ */
+ template< typename T > int QueryValueAttribute( const std::string& name, T* outValue ) const
+ {
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( !node )
+ return TIXML_NO_ATTRIBUTE;
+
+ std::stringstream sstream( node->ValueStr() );
+ sstream >> *outValue;
+ if ( !sstream.fail() )
+ return TIXML_SUCCESS;
+ return TIXML_WRONG_TYPE;
+ }
+ /*
+ This is - in theory - a bug fix for "QueryValueAtribute returns truncated std::string"
+ but template specialization is hard to get working cross-compiler. Leaving the bug for now.
+
+ // The above will fail for std::string because the space character is used as a seperator.
+ // Specialize for strings. Bug [ 1695429 ] QueryValueAtribute returns truncated std::string
+ template<> int QueryValueAttribute( const std::string& name, std::string* outValue ) const
+ {
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( !node )
+ return TIXML_NO_ATTRIBUTE;
+ *outValue = node->ValueStr();
+ return TIXML_SUCCESS;
+ }
+ */
+ #endif
+
+ /** Sets an attribute of name to a given value. The attribute
+ will be created if it does not exist, or changed if it does.
+ */
+ void SetAttribute( const char* name, const char * _value );
+
+ #ifdef TIXML_USE_STL
+ const std::string* Attribute( const std::string& name ) const;
+ const std::string* Attribute( const std::string& name, int* i ) const;
+ const std::string* Attribute( const std::string& name, double* d ) const;
+ int QueryIntAttribute( const std::string& name, int* _value ) const;
+ int QueryDoubleAttribute( const std::string& name, double* _value ) const;
+
+ /// STL std::string form.
+ void SetAttribute( const std::string& name, const std::string& _value );
+ ///< STL std::string form.
+ void SetAttribute( const std::string& name, int _value );
+ #endif
+
+ /** Sets an attribute of name to a given value. The attribute
+ will be created if it does not exist, or changed if it does.
+ */
+ void SetAttribute( const char * name, int value );
+
+ /** Sets an attribute of name to a given value. The attribute
+ will be created if it does not exist, or changed if it does.
+ */
+ void SetDoubleAttribute( const char * name, double value );
+
+ /** Deletes an attribute with the given name.
+ */
+ void RemoveAttribute( const char * name );
+ #ifdef TIXML_USE_STL
+ void RemoveAttribute( const std::string& name ) { RemoveAttribute (name.c_str ()); } ///< STL std::string form.
+ #endif
+
+ const TiXmlAttribute* FirstAttribute() const { return attributeSet.First(); } ///< Access the first attribute in this element.
+ TiXmlAttribute* FirstAttribute() { return attributeSet.First(); }
+ const TiXmlAttribute* LastAttribute() const { return attributeSet.Last(); } ///< Access the last attribute in this element.
+ TiXmlAttribute* LastAttribute() { return attributeSet.Last(); }
+
+ /** Convenience function for easy access to the text inside an element. Although easy
+ and concise, GetText() is limited compared to getting the TiXmlText child
+ and accessing it directly.
+
+ If the first child of 'this' is a TiXmlText, the GetText()
+ returns the character string of the Text node, else null is returned.
+
+ This is a convenient method for getting the text of simple contained text:
+ @verbatim
+ <foo>This is text</foo>
+ const char* str = fooElement->GetText();
+ @endverbatim
+
+ 'str' will be a pointer to "This is text".
+
+ Note that this function can be misleading. If the element foo was created from
+ this XML:
+ @verbatim
+ <foo><b>This is text</b></foo>
+ @endverbatim
+
+ then the value of str would be null. The first child node isn't a text node, it is
+ another element. From this XML:
+ @verbatim
+ <foo>This is <b>text</b></foo>
+ @endverbatim
+ GetText() will return "This is ".
+
+ WARNING: GetText() accesses a child node - don't become confused with the
+ similarly named TiXmlHandle::Text() and TiXmlNode::ToText() which are
+ safe type casts on the referenced node.
+ */
+ const char* GetText() const;
+
+ /// Creates a new Element and returns it - the returned element is a copy.
+ virtual TiXmlNode* Clone() const;
+ // Print the Element to a FILE stream.
+ virtual void Print( FILE* cfile, int depth ) const;
+
+ /* Attribtue parsing starts: next char past '<'
+ returns: next char past '>'
+ */
+ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );
+
+ virtual const TiXmlElement* ToElement() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+ virtual TiXmlElement* ToElement() { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+
+ /** Walk the XML tree visiting this node and all of its children.
+ */
+ virtual bool Accept( TiXmlVisitor* visitor ) const;
+
+protected:
+
+ void CopyTo( TiXmlElement* target ) const;
+ void ClearThis(); // like clear, but initializes 'this' object as well
+
+ // Used to be public [internal use]
+ #ifdef TIXML_USE_STL
+ virtual void StreamIn( std::istream * in, TIXML_STRING * tag );
+ #endif
+ /* [internal use]
+ Reads the "value" of the element -- another element, or text.
+ This should terminate with the current end tag.
+ */
+ const char* ReadValue( const char* in, TiXmlParsingData* prevData, TiXmlEncoding encoding );
+
+private:
+
+ TiXmlAttributeSet attributeSet;
+};
+
+
+/** An XML comment.
+*/
+class TiXmlComment : public TiXmlNode
+{
+public:
+ /// Constructs an empty comment.
+ TiXmlComment() : TiXmlNode( TiXmlNode::COMMENT ) {}
+ /// Construct a comment from text.
+ TiXmlComment( const char* _value ) : TiXmlNode( TiXmlNode::COMMENT ) {
+ SetValue( _value );
+ }
+ TiXmlComment( const TiXmlComment& );
+ void operator=( const TiXmlComment& base );
+
+ virtual ~TiXmlComment() {}
+
+ /// Returns a copy of this Comment.
+ virtual TiXmlNode* Clone() const;
+ // Write this Comment to a FILE stream.
+ virtual void Print( FILE* cfile, int depth ) const;
+
+ /* Attribtue parsing starts: at the ! of the !--
+ returns: next char past '>'
+ */
+ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );
+
+ virtual const TiXmlComment* ToComment() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+ virtual TiXmlComment* ToComment() { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+
+ /** Walk the XML tree visiting this node and all of its children.
+ */
+ virtual bool Accept( TiXmlVisitor* visitor ) const;
+
+protected:
+ void CopyTo( TiXmlComment* target ) const;
+
+ // used to be public
+ #ifdef TIXML_USE_STL
+ virtual void StreamIn( std::istream * in, TIXML_STRING * tag );
+ #endif
+// virtual void StreamOut( TIXML_OSTREAM * out ) const;
+
+private:
+
+};
+
+
+/** XML text. A text node can have 2 ways to output the next. "normal" output
+ and CDATA. It will default to the mode it was parsed from the XML file and
+ you generally want to leave it alone, but you can change the output mode with
+ SetCDATA() and query it with CDATA().
+*/
+class TiXmlText : public TiXmlNode
+{
+ friend class TiXmlElement;
+public:
+ /** Constructor for text element. By default, it is treated as
+ normal, encoded text. If you want it be output as a CDATA text
+ element, set the parameter _cdata to 'true'
+ */
+ TiXmlText (const char * initValue ) : TiXmlNode (TiXmlNode::TEXT)
+ {
+ SetValue( initValue );
+ cdata = false;
+ }
+ virtual ~TiXmlText() {}
+
+ #ifdef TIXML_USE_STL
+ /// Constructor.
+ TiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TEXT)
+ {
+ SetValue( initValue );
+ cdata = false;
+ }
+ #endif
+
+ TiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TEXT ) { copy.CopyTo( this ); }
+ void operator=( const TiXmlText& base ) { base.CopyTo( this ); }
+
+ // Write this text object to a FILE stream.
+ virtual void Print( FILE* cfile, int depth ) const;
+
+ /// Queries whether this represents text using a CDATA section.
+ bool CDATA() const { return cdata; }
+ /// Turns on or off a CDATA representation of text.
+ void SetCDATA( bool _cdata ) { cdata = _cdata; }
+
+ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );
+
+ virtual const TiXmlText* ToText() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+ virtual TiXmlText* ToText() { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+
+ /** Walk the XML tree visiting this node and all of its children.
+ */
+ virtual bool Accept( TiXmlVisitor* content ) const;
+
+protected :
+ /// [internal use] Creates a new Element and returns it.
+ virtual TiXmlNode* Clone() const;
+ void CopyTo( TiXmlText* target ) const;
+
+ bool Blank() const; // returns true if all white space and new lines
+ // [internal use]
+ #ifdef TIXML_USE_STL
+ virtual void StreamIn( std::istream * in, TIXML_STRING * tag );
+ #endif
+
+private:
+ bool cdata; // true if this should be input and output as a CDATA style text element
+};
+
+
+/** In correct XML the declaration is the first entry in the file.
+ @verbatim
+ <?xml version="1.0" standalone="yes"?>
+ @endverbatim
+
+ TinyXml will happily read or write files without a declaration,
+ however. There are 3 possible attributes to the declaration:
+ version, encoding, and standalone.
+
+ Note: In this version of the code, the attributes are
+ handled as special cases, not generic attributes, simply
+ because there can only be at most 3 and they are always the same.
+*/
+class TiXmlDeclaration : public TiXmlNode
+{
+public:
+ /// Construct an empty declaration.
+ TiXmlDeclaration() : TiXmlNode( TiXmlNode::DECLARATION ) {}
+
+#ifdef TIXML_USE_STL
+ /// Constructor.
+ TiXmlDeclaration( const std::string& _version,
+ const std::string& _encoding,
+ const std::string& _standalone );
+#endif
+
+ /// Construct.
+ TiXmlDeclaration( const char* _version,
+ const char* _encoding,
+ const char* _standalone );
+
+ TiXmlDeclaration( const TiXmlDeclaration& copy );
+ void operator=( const TiXmlDeclaration& copy );
+
+ virtual ~TiXmlDeclaration() {}
+
+ /// Version. Will return an empty string if none was found.
+ const char *Version() const { return version.c_str (); }
+ /// Encoding. Will return an empty string if none was found.
+ const char *Encoding() const { return encoding.c_str (); }
+ /// Is this a standalone document?
+ const char *Standalone() const { return standalone.c_str (); }
+
+ /// Creates a copy of this Declaration and returns it.
+ virtual TiXmlNode* Clone() const;
+ // Print this declaration to a FILE stream.
+ virtual void Print( FILE* cfile, int depth, TIXML_STRING* str ) const;
+ virtual void Print( FILE* cfile, int depth ) const {
+ Print( cfile, depth, 0 );
+ }
+
+ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );
+
+ virtual const TiXmlDeclaration* ToDeclaration() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+ virtual TiXmlDeclaration* ToDeclaration() { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+
+ /** Walk the XML tree visiting this node and all of its children.
+ */
+ virtual bool Accept( TiXmlVisitor* visitor ) const;
+
+protected:
+ void CopyTo( TiXmlDeclaration* target ) const;
+ // used to be public
+ #ifdef TIXML_USE_STL
+ virtual void StreamIn( std::istream * in, TIXML_STRING * tag );
+ #endif
+
+private:
+
+ TIXML_STRING version;
+ TIXML_STRING encoding;
+ TIXML_STRING standalone;
+};
+
+
+/** Any tag that tinyXml doesn't recognize is saved as an
+ unknown. It is a tag of text, but should not be modified.
+ It will be written back to the XML, unchanged, when the file
+ is saved.
+
+ DTD tags get thrown into TiXmlUnknowns.
+*/
+class TiXmlUnknown : public TiXmlNode
+{
+public:
+ TiXmlUnknown() : TiXmlNode( TiXmlNode::UNKNOWN ) {}
+ virtual ~TiXmlUnknown() {}
+
+ TiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::UNKNOWN ) { copy.CopyTo( this ); }
+ void operator=( const TiXmlUnknown& copy ) { copy.CopyTo( this ); }
+
+ /// Creates a copy of this Unknown and returns it.
+ virtual TiXmlNode* Clone() const;
+ // Print this Unknown to a FILE stream.
+ virtual void Print( FILE* cfile, int depth ) const;
+
+ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );
+
+ virtual const TiXmlUnknown* ToUnknown() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+ virtual TiXmlUnknown* ToUnknown() { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+
+ /** Walk the XML tree visiting this node and all of its children.
+ */
+ virtual bool Accept( TiXmlVisitor* content ) const;
+
+protected:
+ void CopyTo( TiXmlUnknown* target ) const;
+
+ #ifdef TIXML_USE_STL
+ virtual void StreamIn( std::istream * in, TIXML_STRING * tag );
+ #endif
+
+private:
+
+};
+
+
+/** Always the top level node. A document binds together all the
+ XML pieces. It can be saved, loaded, and printed to the screen.
+ The 'value' of a document node is the xml file name.
+*/
+class TiXmlDocument : public TiXmlNode
+{
+public:
+ /// Create an empty document, that has no name.
+ TiXmlDocument();
+ /// Create a document with a name. The name of the document is also the filename of the xml.
+ TiXmlDocument( const char * documentName );
+
+ // Altered header
+ bool ReadFromMemory( const char* pBuf, size_t sz, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING);
+
+ #ifdef TIXML_USE_STL
+ /// Constructor.
+ TiXmlDocument( const std::string& documentName );
+ #endif
+
+ TiXmlDocument( const TiXmlDocument& copy );
+ void operator=( const TiXmlDocument& copy );
+
+ virtual ~TiXmlDocument() {}
+
+ /** Load a file using the current document value.
+ Returns true if successful. Will delete any existing
+ document data before loading.
+ */
+ bool LoadFile( TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING );
+ /// Save a file using the current document value. Returns true if successful.
+ bool SaveFile() const;
+ /// Load a file using the given filename. Returns true if successful.
+ bool LoadFile( const char * filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING );
+ /// Save a file using the given filename. Returns true if successful.
+ bool SaveFile( const char * filename ) const;
+ /** Load a file using the given FILE*. Returns true if successful. Note that this method
+ doesn't stream - the entire object pointed at by the FILE*
+ will be interpreted as an XML file. TinyXML doesn't stream in XML from the current
+ file location. Streaming may be added in the future.
+ */
+ bool LoadFile( FILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING );
+ /// Save a file using the given FILE*. Returns true if successful.
+ bool SaveFile( FILE* ) const;
+
+ #ifdef TIXML_USE_STL
+ bool LoadFile( const std::string& filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ) ///< STL std::string version.
+ {
+// StringToBuffer f( filename );
+// return ( f.buffer && LoadFile( f.buffer, encoding ));
+ return LoadFile( filename.c_str(), encoding );
+ }
+ bool SaveFile( const std::string& filename ) const ///< STL std::string version.
+ {
+// StringToBuffer f( filename );
+// return ( f.buffer && SaveFile( f.buffer ));
+ return SaveFile( filename.c_str() );
+ }
+ #endif
+
+ /** Parse the given null terminated block of xml data. Passing in an encoding to this
+ method (either TIXML_ENCODING_LEGACY or TIXML_ENCODING_UTF8 will force TinyXml
+ to use that encoding, regardless of what TinyXml might otherwise try to detect.
+ */
+ virtual const char* Parse( const char* p, TiXmlParsingData* data = 0, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING );
+
+ /** Get the root element -- the only top level element -- of the document.
+ In well formed XML, there should only be one. TinyXml is tolerant of
+ multiple elements at the document level.
+ */
+ const TiXmlElement* RootElement() const { return FirstChildElement(); }
+ TiXmlElement* RootElement() { return FirstChildElement(); }
+
+ /** If an error occurs, Error will be set to true. Also,
+ - The ErrorId() will contain the integer identifier of the error (not generally useful)
+ - The ErrorDesc() method will return the name of the error. (very useful)
+ - The ErrorRow() and ErrorCol() will return the location of the error (if known)
+ */
+ bool Error() const { return error; }
+
+ /// Contains a textual (english) description of the error if one occurs.
+ const char * ErrorDesc() const { return errorDesc.c_str (); }
+
+ /** Generally, you probably want the error string ( ErrorDesc() ). But if you
+ prefer the ErrorId, this function will fetch it.
+ */
+ int ErrorId() const { return errorId; }
+
+ /** Returns the location (if known) of the error. The first column is column 1,
+ and the first row is row 1. A value of 0 means the row and column wasn't applicable
+ (memory errors, for example, have no row/column) or the parser lost the error. (An
+ error in the error reporting, in that case.)
+
+ @sa SetTabSize, Row, Column
+ */
+ int ErrorRow() const { return errorLocation.row+1; }
+ int ErrorCol() const { return errorLocation.col+1; } ///< The column where the error occured. See ErrorRow()
+
+ /** SetTabSize() allows the error reporting functions (ErrorRow() and ErrorCol())
+ to report the correct values for row and column. It does not change the output
+ or input in any way.
+
+ By calling this method, with a tab size
+ greater than 0, the row and column of each node and attribute is stored
+ when the file is loaded. Very useful for tracking the DOM back in to
+ the source file.
+
+ The tab size is required for calculating the location of nodes. If not
+ set, the default of 4 is used. The tabsize is set per document. Setting
+ the tabsize to 0 disables row/column tracking.
+
+ Note that row and column tracking is not supported when using operator>>.
+
+ The tab size needs to be enabled before the parse or load. Correct usage:
+ @verbatim
+ TiXmlDocument doc;
+ doc.SetTabSize( 8 );
+ doc.Load( "myfile.xml" );
+ @endverbatim
+
+ @sa Row, Column
+ */
+ void SetTabSize( int _tabsize ) { tabsize = _tabsize; }
+
+ int TabSize() const { return tabsize; }
+
+ /** If you have handled the error, it can be reset with this call. The error
+ state is automatically cleared if you Parse a new XML block.
+ */
+ void ClearError() { error = false;
+ errorId = 0;
+ errorDesc = "";
+ errorLocation.row = errorLocation.col = 0;
+ //errorLocation.last = 0;
+ }
+
+ /** Write the document to standard out using formatted printing ("pretty print"). */
+ void Print() const { Print( stdout, 0 ); }
+
+ /* Write the document to a string using formatted printing ("pretty print"). This
+ will allocate a character array (new char[]) and return it as a pointer. The
+ calling code pust call delete[] on the return char* to avoid a memory leak.
+ */
+ //char* PrintToMemory() const;
+
+ /// Print this Document to a FILE stream.
+ virtual void Print( FILE* cfile, int depth = 0 ) const;
+ // [internal use]
+ void SetError( int err, const char* errorLocation, TiXmlParsingData* prevData, TiXmlEncoding encoding );
+
+ virtual const TiXmlDocument* ToDocument() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+ virtual TiXmlDocument* ToDocument() { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+
+ /** Walk the XML tree visiting this node and all of its children.
+ */
+ virtual bool Accept( TiXmlVisitor* content ) const;
+
+protected :
+ // [internal use]
+ virtual TiXmlNode* Clone() const;
+ #ifdef TIXML_USE_STL
+ virtual void StreamIn( std::istream * in, TIXML_STRING * tag );
+ #endif
+
+private:
+ void CopyTo( TiXmlDocument* target ) const;
+
+ bool error;
+ int errorId;
+ TIXML_STRING errorDesc;
+ int tabsize;
+ TiXmlCursor errorLocation;
+ bool useMicrosoftBOM; // the UTF-8 BOM were found when read. Note this, and try to write.
+};
+
+
+/**
+ A TiXmlHandle is a class that wraps a node pointer with null checks; this is
+ an incredibly useful thing. Note that TiXmlHandle is not part of the TinyXml
+ DOM structure. It is a separate utility class.
+
+ Take an example:
+ @verbatim
+ <Document>
+ <Element attributeA = "valueA">
+ <Child attributeB = "value1" />
+ <Child attributeB = "value2" />
+ </Element>
+ <Document>
+ @endverbatim
+
+ Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very
+ easy to write a *lot* of code that looks like:
+
+ @verbatim
+ TiXmlElement* root = document.FirstChildElement( "Document" );
+ if ( root )
+ {
+ TiXmlElement* element = root->FirstChildElement( "Element" );
+ if ( element )
+ {
+ TiXmlElement* child = element->FirstChildElement( "Child" );
+ if ( child )
+ {
+ TiXmlElement* child2 = child->NextSiblingElement( "Child" );
+ if ( child2 )
+ {
+ // Finally do something useful.
+ @endverbatim
+
+ And that doesn't even cover "else" cases. TiXmlHandle addresses the verbosity
+ of such code. A TiXmlHandle checks for null pointers so it is perfectly safe
+ and correct to use:
+
+ @verbatim
+ TiXmlHandle docHandle( &document );
+ TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement();
+ if ( child2 )
+ {
+ // do something useful
+ @endverbatim
+
+ Which is MUCH more concise and useful.
+
+ It is also safe to copy handles - internally they are nothing more than node pointers.
+ @verbatim
+ TiXmlHandle handleCopy = handle;
+ @endverbatim
+
+ What they should not be used for is iteration:
+
+ @verbatim
+ int i=0;
+ while ( true )
+ {
+ TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", i ).ToElement();
+ if ( !child )
+ break;
+ // do something
+ ++i;
+ }
+ @endverbatim
+
+ It seems reasonable, but it is in fact two embedded while loops. The Child method is
+ a linear walk to find the element, so this code would iterate much more than it needs
+ to. Instead, prefer:
+
+ @verbatim
+ TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).FirstChild( "Child" ).ToElement();
+
+ for( child; child; child=child->NextSiblingElement() )
+ {
+ // do something
+ }
+ @endverbatim
+*/
+class TiXmlHandle
+{
+public:
+ /// Create a handle from any node (at any depth of the tree.) This can be a null pointer.
+ TiXmlHandle( TiXmlNode* _node ) { this->node = _node; }
+ /// Copy constructor
+ TiXmlHandle( const TiXmlHandle& ref ) { this->node = ref.node; }
+ TiXmlHandle operator=( const TiXmlHandle& ref ) { this->node = ref.node; return *this; }
+
+ /// Return a handle to the first child node.
+ TiXmlHandle FirstChild() const;
+ /// Return a handle to the first child node with the given name.
+ TiXmlHandle FirstChild( const char * value ) const;
+ /// Return a handle to the first child element.
+ TiXmlHandle FirstChildElement() const;
+ /// Return a handle to the first child element with the given name.
+ TiXmlHandle FirstChildElement( const char * value ) const;
+
+ /** Return a handle to the "index" child with the given name.
+ The first child is 0, the second 1, etc.
+ */
+ TiXmlHandle Child( const char* value, int index ) const;
+ /** Return a handle to the "index" child.
+ The first child is 0, the second 1, etc.
+ */
+ TiXmlHandle Child( int index ) const;
+ /** Return a handle to the "index" child element with the given name.
+ The first child element is 0, the second 1, etc. Note that only TiXmlElements
+ are indexed: other types are not counted.
+ */
+ TiXmlHandle ChildElement( const char* value, int index ) const;
+ /** Return a handle to the "index" child element.
+ The first child element is 0, the second 1, etc. Note that only TiXmlElements
+ are indexed: other types are not counted.
+ */
+ TiXmlHandle ChildElement( int index ) const;
+
+ #ifdef TIXML_USE_STL
+ TiXmlHandle FirstChild( const std::string& _value ) const { return FirstChild( _value.c_str() ); }
+ TiXmlHandle FirstChildElement( const std::string& _value ) const { return FirstChildElement( _value.c_str() ); }
+
+ TiXmlHandle Child( const std::string& _value, int index ) const { return Child( _value.c_str(), index ); }
+ TiXmlHandle ChildElement( const std::string& _value, int index ) const { return ChildElement( _value.c_str(), index ); }
+ #endif
+
+ /** Return the handle as a TiXmlNode. This may return null.
+ */
+ TiXmlNode* ToNode() const { return node; }
+ /** Return the handle as a TiXmlElement. This may return null.
+ */
+ TiXmlElement* ToElement() const { return ( ( node && node->ToElement() ) ? node->ToElement() : 0 ); }
+ /** Return the handle as a TiXmlText. This may return null.
+ */
+ TiXmlText* ToText() const { return ( ( node && node->ToText() ) ? node->ToText() : 0 ); }
+ /** Return the handle as a TiXmlUnknown. This may return null.
+ */
+ TiXmlUnknown* ToUnknown() const { return ( ( node && node->ToUnknown() ) ? node->ToUnknown() : 0 ); }
+
+ /** @deprecated use ToNode.
+ Return the handle as a TiXmlNode. This may return null.
+ */
+ TiXmlNode* Node() const { return ToNode(); }
+ /** @deprecated use ToElement.
+ Return the handle as a TiXmlElement. This may return null.
+ */
+ TiXmlElement* Element() const { return ToElement(); }
+ /** @deprecated use ToText()
+ Return the handle as a TiXmlText. This may return null.
+ */
+ TiXmlText* Text() const { return ToText(); }
+ /** @deprecated use ToUnknown()
+ Return the handle as a TiXmlUnknown. This may return null.
+ */
+ TiXmlUnknown* Unknown() const { return ToUnknown(); }
+
+private:
+ TiXmlNode* node;
+};
+
+
+/** Print to memory functionality. The TiXmlPrinter is useful when you need to:
+
+ -# Print to memory (especially in non-STL mode)
+ -# Control formatting (line endings, etc.)
+
+ When constructed, the TiXmlPrinter is in its default "pretty printing" mode.
+ Before calling Accept() you can call methods to control the printing
+ of the XML document. After TiXmlNode::Accept() is called, the printed document can
+ be accessed via the CStr(), Str(), and Size() methods.
+
+ TiXmlPrinter uses the Visitor API.
+ @verbatim
+ TiXmlPrinter printer;
+ printer.SetIndent( "\t" );
+
+ doc.Accept( &printer );
+ fprintf( stdout, "%s", printer.CStr() );
+ @endverbatim
+*/
+class TiXmlPrinter : public TiXmlVisitor
+{
+public:
+ TiXmlPrinter() : depth( 0 ), simpleTextPrint( false ),
+ buffer(), indent( " " ), lineBreak( "\n" ) {}
+
+ virtual bool VisitEnter( const TiXmlDocument& doc );
+ virtual bool VisitExit( const TiXmlDocument& doc );
+
+ virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute );
+ virtual bool VisitExit( const TiXmlElement& element );
+
+ virtual bool Visit( const TiXmlDeclaration& declaration );
+ virtual bool Visit( const TiXmlText& text );
+ virtual bool Visit( const TiXmlComment& comment );
+ virtual bool Visit( const TiXmlUnknown& unknown );
+
+ /** Set the indent characters for printing. By default 4 spaces
+ but tab (\t) is also useful, or null/empty string for no indentation.
+ */
+ void SetIndent( const char* _indent ) { indent = _indent ? _indent : "" ; }
+ /// Query the indention string.
+ const char* Indent() { return indent.c_str(); }
+ /** Set the line breaking string. By default set to newline (\n).
+ Some operating systems prefer other characters, or can be
+ set to the null/empty string for no indenation.
+ */
+ void SetLineBreak( const char* _lineBreak ) { lineBreak = _lineBreak ? _lineBreak : ""; }
+ /// Query the current line breaking string.
+ const char* LineBreak() { return lineBreak.c_str(); }
+
+ /** Switch over to "stream printing" which is the most dense formatting without
+ linebreaks. Common when the XML is needed for network transmission.
+ */
+ void SetStreamPrinting() { indent = "";
+ lineBreak = "";
+ }
+ /// Return the result.
+ const char* CStr() { return buffer.c_str(); }
+ /// Return the length of the result string.
+ size_t Size() { return buffer.size(); }
+
+ #ifdef TIXML_USE_STL
+ /// Return the result.
+ const std::string& Str() { return buffer; }
+ #endif
+
+private:
+ void DoIndent() {
+ for( int i=0; i<depth; ++i )
+ buffer += indent;
+ }
+ void DoLineBreak() {
+ buffer += lineBreak;
+ }
+
+ int depth;
+ bool simpleTextPrint;
+ TIXML_STRING buffer;
+ TIXML_STRING indent;
+ TIXML_STRING lineBreak;
+};
+
+
+#ifdef _MSC_VER
+#pragma warning( pop )
+#endif
+
+#endif
+
diff --git a/rotord/src/tinyxmlerror.cpp b/rotord/src/tinyxmlerror.cpp
new file mode 100755
index 0000000..d24f63b
--- /dev/null
+++ b/rotord/src/tinyxmlerror.cpp
@@ -0,0 +1,53 @@
+/*
+www.sourceforge.net/projects/tinyxml
+Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+
+#include "tinyxml.h"
+
+// The goal of the seperate error file is to make the first
+// step towards localization. tinyxml (currently) only supports
+// english error messages, but the could now be translated.
+//
+// It also cleans up the code a bit.
+//
+
+const char* TiXmlBase::errorString[ TIXML_ERROR_STRING_COUNT ] =
+{
+ "No error",
+ "Error",
+ "Failed to open file",
+ "Memory allocation failed.",
+ "Error parsing Element.",
+ "Failed to read Element name",
+ "Error reading Element value.",
+ "Error reading Attributes.",
+ "Error: empty tag.",
+ "Error reading end tag.",
+ "Error parsing Unknown.",
+ "Error parsing Comment.",
+ "Error parsing Declaration.",
+ "Error document empty.",
+ "Error null (0) or unexpected EOF found in input stream.",
+ "Error parsing CDATA.",
+ "Error when TiXmlDocument added to document, because TiXmlDocument can only be at the root.",
+};
diff --git a/rotord/src/tinyxmlparser.cpp b/rotord/src/tinyxmlparser.cpp
new file mode 100755
index 0000000..c672283
--- /dev/null
+++ b/rotord/src/tinyxmlparser.cpp
@@ -0,0 +1,1719 @@
+/*
+www.sourceforge.net/projects/tinyxml
+Original code (2.0 and earlier )copyright (c) 2000-2002 Lee Thomason (www.grinninglizard.com)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+
+#include <ctype.h>
+#include <stddef.h>
+
+#include "tinyxml.h"
+
+//#define DEBUG_PARSER
+#if defined( DEBUG_PARSER )
+# if defined( DEBUG ) && defined( _MSC_VER )
+# include <windows.h>
+# define TIXML_LOG OutputDebugString
+# else
+# define TIXML_LOG printf
+# endif
+#endif
+
+// Note tha "PutString" hardcodes the same list. This
+// is less flexible than it appears. Changing the entries
+// or order will break putstring.
+TiXmlBase::Entity TiXmlBase::entity[ NUM_ENTITY ] =
+{
+ { "&amp;", 5, '&' },
+ { "&lt;", 4, '<' },
+ { "&gt;", 4, '>' },
+ { "&quot;", 6, '\"' },
+ { "&apos;", 6, '\'' }
+};
+
+// Bunch of unicode info at:
+// http://www.unicode.org/faq/utf_bom.html
+// Including the basic of this table, which determines the #bytes in the
+// sequence from the lead byte. 1 placed for invalid sequences --
+// although the result will be junk, pass it through as much as possible.
+// Beware of the non-characters in UTF-8:
+// ef bb bf (Microsoft "lead bytes")
+// ef bf be
+// ef bf bf
+
+const unsigned char TIXML_UTF_LEAD_0 = 0xefU;
+const unsigned char TIXML_UTF_LEAD_1 = 0xbbU;
+const unsigned char TIXML_UTF_LEAD_2 = 0xbfU;
+
+const int TiXmlBase::utf8ByteTable[256] =
+{
+ // 0 1 2 3 4 5 6 7 8 9 a b c d e f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 End of ASCII range
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80 0x80 to 0xc1 invalid
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0
+ 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 0xc2 to 0xdf 2 byte
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xe0 0xe0 to 0xef 3 byte
+ 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid
+};
+
+
+void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length )
+{
+ const unsigned long BYTE_MASK = 0xBF;
+ const unsigned long BYTE_MARK = 0x80;
+ const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
+
+ if (input < 0x80)
+ *length = 1;
+ else if ( input < 0x800 )
+ *length = 2;
+ else if ( input < 0x10000 )
+ *length = 3;
+ else if ( input < 0x200000 )
+ *length = 4;
+ else
+ { *length = 0; return; } // This code won't covert this correctly anyway.
+
+ output += *length;
+
+ // Scary scary fall throughs.
+ switch (*length)
+ {
+ case 4:
+ --output;
+ *output = (char)((input | BYTE_MARK) & BYTE_MASK);
+ input >>= 6;
+ case 3:
+ --output;
+ *output = (char)((input | BYTE_MARK) & BYTE_MASK);
+ input >>= 6;
+ case 2:
+ --output;
+ *output = (char)((input | BYTE_MARK) & BYTE_MASK);
+ input >>= 6;
+ case 1:
+ --output;
+ *output = (char)(input | FIRST_BYTE_MARK[*length]);
+ }
+}
+
+
+/*static*/ int TiXmlBase::IsAlpha( unsigned char anyByte, TiXmlEncoding /*encoding*/ )
+{
+ // This will only work for low-ascii, everything else is assumed to be a valid
+ // letter. I'm not sure this is the best approach, but it is quite tricky trying
+ // to figure out alhabetical vs. not across encoding. So take a very
+ // conservative approach.
+
+// if ( encoding == TIXML_ENCODING_UTF8 )
+// {
+ if ( anyByte < 127 )
+ return isalpha( anyByte );
+ else
+ return 1; // What else to do? The unicode set is huge...get the english ones right.
+// }
+// else
+// {
+// return isalpha( anyByte );
+// }
+}
+
+
+/*static*/ int TiXmlBase::IsAlphaNum( unsigned char anyByte, TiXmlEncoding /*encoding*/ )
+{
+ // This will only work for low-ascii, everything else is assumed to be a valid
+ // letter. I'm not sure this is the best approach, but it is quite tricky trying
+ // to figure out alhabetical vs. not across encoding. So take a very
+ // conservative approach.
+
+// if ( encoding == TIXML_ENCODING_UTF8 )
+// {
+ if ( anyByte < 127 )
+ return isalnum( anyByte );
+ else
+ return 1; // What else to do? The unicode set is huge...get the english ones right.
+// }
+// else
+// {
+// return isalnum( anyByte );
+// }
+}
+
+
+class TiXmlParsingData
+{
+ friend class TiXmlDocument;
+ public:
+ void Stamp( const char* now, TiXmlEncoding encoding );
+
+ const TiXmlCursor& Cursor() { return cursor; }
+
+ private:
+ // Only used by the document!
+ TiXmlParsingData( const char* start, int _tabsize, int row, int col )
+ {
+ assert( start );
+ stamp = start;
+ tabsize = _tabsize;
+ cursor.row = row;
+ cursor.col = col;
+ }
+
+ TiXmlCursor cursor;
+ const char* stamp;
+ int tabsize;
+};
+
+
+void TiXmlParsingData::Stamp( const char* now, TiXmlEncoding encoding )
+{
+ assert( now );
+
+ // Do nothing if the tabsize is 0.
+ if ( tabsize < 1 )
+ {
+ return;
+ }
+
+ // Get the current row, column.
+ int row = cursor.row;
+ int col = cursor.col;
+ const char* p = stamp;
+ assert( p );
+
+ while ( p < now )
+ {
+ // Treat p as unsigned, so we have a happy compiler.
+ const unsigned char* pU = (const unsigned char*)p;
+
+ // Code contributed by Fletcher Dunn: (modified by lee)
+ switch (*pU) {
+ case 0:
+ // We *should* never get here, but in case we do, don't
+ // advance past the terminating null character, ever
+ return;
+
+ case '\r':
+ // bump down to the next line
+ ++row;
+ col = 0;
+ // Eat the character
+ ++p;
+
+ // Check for \r\n sequence, and treat this as a single character
+ if (*p == '\n') {
+ ++p;
+ }
+ break;
+
+ case '\n':
+ // bump down to the next line
+ ++row;
+ col = 0;
+
+ // Eat the character
+ ++p;
+
+ // Check for \n\r sequence, and treat this as a single
+ // character. (Yes, this bizarre thing does occur still
+ // on some arcane platforms...)
+ if (*p == '\r') {
+ ++p;
+ }
+ break;
+
+ case '\t':
+ // Eat the character
+ ++p;
+
+ // Skip to next tab stop
+ col = (col / tabsize + 1) * tabsize;
+ break;
+
+ case TIXML_UTF_LEAD_0:
+ if ( encoding == TIXML_ENCODING_UTF8 )
+ {
+ if ( *(p+1) && *(p+2) )
+ {
+ // In these cases, don't advance the column. These are
+ // 0-width spaces.
+ if ( *(pU+1)==TIXML_UTF_LEAD_1 && *(pU+2)==TIXML_UTF_LEAD_2 )
+ p += 3;
+ else if ( *(pU+1)==0xbfU && *(pU+2)==0xbeU )
+ p += 3;
+ else if ( *(pU+1)==0xbfU && *(pU+2)==0xbfU )
+ p += 3;
+ else
+ { p +=3; ++col; } // A normal character.
+ }
+ }
+ else
+ {
+ ++p;
+ ++col;
+ }
+ break;
+
+ default:
+ if ( encoding == TIXML_ENCODING_UTF8 )
+ {
+ // Eat the 1 to 4 byte utf8 character.
+ int step = TiXmlBase::utf8ByteTable[*((const unsigned char*)p)];
+ if ( step == 0 )
+ step = 1; // Error case from bad encoding, but handle gracefully.
+ p += step;
+
+ // Just advance one column, of course.
+ ++col;
+ }
+ else
+ {
+ ++p;
+ ++col;
+ }
+ break;
+ }
+ }
+ cursor.row = row;
+ cursor.col = col;
+ assert( cursor.row >= -1 );
+ assert( cursor.col >= -1 );
+ stamp = p;
+ assert( stamp );
+}
+
+
+const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding )
+{
+ if ( !p || !*p )
+ {
+ return 0;
+ }
+ if ( encoding == TIXML_ENCODING_UTF8 )
+ {
+ while ( *p )
+ {
+ const unsigned char* pU = (const unsigned char*)p;
+
+ // Skip the stupid Microsoft UTF-8 Byte order marks
+ if ( *(pU+0)==TIXML_UTF_LEAD_0
+ && *(pU+1)==TIXML_UTF_LEAD_1
+ && *(pU+2)==TIXML_UTF_LEAD_2 )
+ {
+ p += 3;
+ continue;
+ }
+ else if(*(pU+0)==TIXML_UTF_LEAD_0
+ && *(pU+1)==0xbfU
+ && *(pU+2)==0xbeU )
+ {
+ p += 3;
+ continue;
+ }
+ else if(*(pU+0)==TIXML_UTF_LEAD_0
+ && *(pU+1)==0xbfU
+ && *(pU+2)==0xbfU )
+ {
+ p += 3;
+ continue;
+ }
+
+ if ( IsWhiteSpace( *p ) || *p == '\n' || *p =='\r' ) // Still using old rules for white space.
+ ++p;
+ else
+ break;
+ }
+ }
+ else
+ {
+ while ( (*p && IsWhiteSpace( *p )) || *p == '\n' || *p =='\r' )
+ ++p;
+ }
+
+ return p;
+}
+
+#ifdef TIXML_USE_STL
+/*static*/ bool TiXmlBase::StreamWhiteSpace( std::istream * in, TIXML_STRING * tag )
+{
+ for( ;; )
+ {
+ if ( !in->good() ) return false;
+
+ int c = in->peek();
+ // At this scope, we can't get to a document. So fail silently.
+ if ( !IsWhiteSpace( c ) || c <= 0 )
+ return true;
+
+ *tag += (char) in->get();
+ }
+}
+
+/*static*/ bool TiXmlBase::StreamTo( std::istream * in, int character, TIXML_STRING * tag )
+{
+ //assert( character > 0 && character < 128 ); // else it won't work in utf-8
+ while ( in->good() )
+ {
+ int c = in->peek();
+ if ( c == character )
+ return true;
+ if ( c <= 0 ) // Silent failure: can't get document at this scope
+ return false;
+
+ in->get();
+ *tag += (char) c;
+ }
+ return false;
+}
+#endif
+
+// One of TinyXML's more performance demanding functions. Try to keep the memory overhead down. The
+// "assign" optimization removes over 10% of the execution time.
+//
+const char* TiXmlBase::ReadName( const char* p, TIXML_STRING * name, TiXmlEncoding encoding )
+{
+ // Oddly, not supported on some comilers,
+ //name->clear();
+ // So use this:
+ *name = "";
+ assert( p );
+
+ // Names start with letters or underscores.
+ // Of course, in unicode, tinyxml has no idea what a letter *is*. The
+ // algorithm is generous.
+ //
+ // After that, they can be letters, underscores, numbers,
+ // hyphens, or colons. (Colons are valid ony for namespaces,
+ // but tinyxml can't tell namespaces from names.)
+ if ( p && *p
+ && ( IsAlpha( (unsigned char) *p, encoding ) || *p == '_' ) )
+ {
+ const char* start = p;
+ while( p && *p
+ && ( IsAlphaNum( (unsigned char ) *p, encoding )
+ || *p == '_'
+ || *p == '-'
+ || *p == '.'
+ || *p == ':' ) )
+ {
+ //(*name) += *p; // expensive
+ ++p;
+ }
+ if ( p-start > 0 ) {
+ name->assign( start, p-start );
+ }
+ return p;
+ }
+ return 0;
+}
+
+const char* TiXmlBase::GetEntity( const char* p, char* value, int* length, TiXmlEncoding encoding )
+{
+ // Presume an entity, and pull it out.
+ TIXML_STRING ent;
+ int i;
+ *length = 0;
+
+ if ( *(p+1) && *(p+1) == '#' && *(p+2) )
+ {
+ unsigned long ucs = 0;
+ ptrdiff_t delta = 0;
+ unsigned mult = 1;
+
+ if ( *(p+2) == 'x' )
+ {
+ // Hexadecimal.
+ if ( !*(p+3) ) return 0;
+
+ const char* q = p+3;
+ q = strchr( q, ';' );
+
+ if ( !q || !*q ) return 0;
+
+ delta = q-p;
+ --q;
+
+ while ( *q != 'x' )
+ {
+ if ( *q >= '0' && *q <= '9' )
+ ucs += mult * (*q - '0');
+ else if ( *q >= 'a' && *q <= 'f' )
+ ucs += mult * (*q - 'a' + 10);
+ else if ( *q >= 'A' && *q <= 'F' )
+ ucs += mult * (*q - 'A' + 10 );
+ else
+ return 0;
+ mult *= 16;
+ --q;
+ }
+ }
+ else
+ {
+ // Decimal.
+ if ( !*(p+2) ) return 0;
+
+ const char* q = p+2;
+ q = strchr( q, ';' );
+
+ if ( !q || !*q ) return 0;
+
+ delta = q-p;
+ --q;
+
+ while ( *q != '#' )
+ {
+ if ( *q >= '0' && *q <= '9' )
+ ucs += mult * (*q - '0');
+ else
+ return 0;
+ mult *= 10;
+ --q;
+ }
+ }
+ if ( encoding == TIXML_ENCODING_UTF8 )
+ {
+ // convert the UCS to UTF-8
+ ConvertUTF32ToUTF8( ucs, value, length );
+ }
+ else
+ {
+ *value = (char)ucs;
+ *length = 1;
+ }
+ return p + delta + 1;
+ }
+
+ // Now try to match it.
+ for( i=0; i<NUM_ENTITY; ++i )
+ {
+ if ( strncmp( entity[i].str, p, entity[i].strLength ) == 0 )
+ {
+ assert( strlen( entity[i].str ) == entity[i].strLength );
+ *value = entity[i].chr;
+ *length = 1;
+ return ( p + entity[i].strLength );
+ }
+ }
+
+ // So it wasn't an entity, its unrecognized, or something like that.
+ *value = *p; // Don't put back the last one, since we return it!
+ //*length = 1; // Leave unrecognized entities - this doesn't really work.
+ // Just writes strange XML.
+ return p+1;
+}
+
+
+bool TiXmlBase::StringEqual( const char* p,
+ const char* tag,
+ bool ignoreCase,
+ TiXmlEncoding encoding )
+{
+ assert( p );
+ assert( tag );
+ if ( !p || !*p )
+ {
+ assert( 0 );
+ return false;
+ }
+
+ const char* q = p;
+
+ if ( ignoreCase )
+ {
+ while ( *q && *tag && ToLower( *q, encoding ) == ToLower( *tag, encoding ) )
+ {
+ ++q;
+ ++tag;
+ }
+
+ if ( *tag == 0 )
+ return true;
+ }
+ else
+ {
+ while ( *q && *tag && *q == *tag )
+ {
+ ++q;
+ ++tag;
+ }
+
+ if ( *tag == 0 ) // Have we found the end of the tag, and everything equal?
+ return true;
+ }
+ return false;
+}
+
+const char* TiXmlBase::ReadText( const char* p,
+ TIXML_STRING * text,
+ bool trimWhiteSpace,
+ const char* endTag,
+ bool caseInsensitive,
+ TiXmlEncoding encoding )
+{
+ *text = "";
+ if ( !trimWhiteSpace // certain tags always keep whitespace
+ || !condenseWhiteSpace ) // if true, whitespace is always kept
+ {
+ // Keep all the white space.
+ while ( p && *p
+ && !StringEqual( p, endTag, caseInsensitive, encoding )
+ )
+ {
+ int len;
+ char cArr[4] = { 0, 0, 0, 0 };
+ p = GetChar( p, cArr, &len, encoding );
+ text->append( cArr, len );
+ }
+ }
+ else
+ {
+ bool whitespace = false;
+
+ // Remove leading white space:
+ p = SkipWhiteSpace( p, encoding );
+ while ( p && *p
+ && !StringEqual( p, endTag, caseInsensitive, encoding ) )
+ {
+ if ( *p == '\r' || *p == '\n' )
+ {
+ whitespace = true;
+ ++p;
+ }
+ else if ( IsWhiteSpace( *p ) )
+ {
+ whitespace = true;
+ ++p;
+ }
+ else
+ {
+ // If we've found whitespace, add it before the
+ // new character. Any whitespace just becomes a space.
+ if ( whitespace )
+ {
+ (*text) += ' ';
+ whitespace = false;
+ }
+ int len;
+ char cArr[4] = { 0, 0, 0, 0 };
+ p = GetChar( p, cArr, &len, encoding );
+ if ( len == 1 )
+ (*text) += cArr[0]; // more efficient
+ else
+ text->append( cArr, len );
+ }
+ }
+ }
+ if ( p )
+ p += strlen( endTag );
+ return p;
+}
+
+#ifdef TIXML_USE_STL
+
+void TiXmlDocument::StreamIn( std::istream * in, TIXML_STRING * tag )
+{
+ // The basic issue with a document is that we don't know what we're
+ // streaming. Read something presumed to be a tag (and hope), then
+ // identify it, and call the appropriate stream method on the tag.
+ //
+ // This "pre-streaming" will never read the closing ">" so the
+ // sub-tag can orient itself.
+
+ if ( !StreamTo( in, '<', tag ) )
+ {
+ SetError( TIXML_ERROR_PARSING_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+
+ while ( in->good() )
+ {
+ int tagIndex = (int) tag->length();
+ while ( in->good() && in->peek() != '>' )
+ {
+ int c = in->get();
+ if ( c <= 0 )
+ {
+ SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ break;
+ }
+ (*tag) += (char) c;
+ }
+
+ if ( in->good() )
+ {
+ // We now have something we presume to be a node of
+ // some sort. Identify it, and call the node to
+ // continue streaming.
+ TiXmlNode* node = Identify( tag->c_str() + tagIndex, TIXML_DEFAULT_ENCODING );
+
+ if ( node )
+ {
+ node->StreamIn( in, tag );
+ bool isElement = node->ToElement() != 0;
+ delete node;
+ node = 0;
+
+ // If this is the root element, we're done. Parsing will be
+ // done by the >> operator.
+ if ( isElement )
+ {
+ return;
+ }
+ }
+ else
+ {
+ SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+ }
+ }
+ // We should have returned sooner.
+ SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN );
+}
+
+#endif
+
+const char* TiXmlDocument::Parse( const char* p, TiXmlParsingData* prevData, TiXmlEncoding encoding )
+{
+ ClearError();
+
+ // Parse away, at the document level. Since a document
+ // contains nothing but other tags, most of what happens
+ // here is skipping white space.
+ if ( !p || !*p )
+ {
+ SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return 0;
+ }
+
+ // Note that, for a document, this needs to come
+ // before the while space skip, so that parsing
+ // starts from the pointer we are given.
+ location.Clear();
+ if ( prevData )
+ {
+ location.row = prevData->cursor.row;
+ location.col = prevData->cursor.col;
+ }
+ else
+ {
+ location.row = 0;
+ location.col = 0;
+ }
+ TiXmlParsingData data( p, TabSize(), location.row, location.col );
+ location = data.Cursor();
+
+ if ( encoding == TIXML_ENCODING_UNKNOWN )
+ {
+ // Check for the Microsoft UTF-8 lead bytes.
+ const unsigned char* pU = (const unsigned char*)p;
+ if ( *(pU+0) && *(pU+0) == TIXML_UTF_LEAD_0
+ && *(pU+1) && *(pU+1) == TIXML_UTF_LEAD_1
+ && *(pU+2) && *(pU+2) == TIXML_UTF_LEAD_2 )
+ {
+ encoding = TIXML_ENCODING_UTF8;
+ useMicrosoftBOM = true;
+ }
+ }
+
+ p = SkipWhiteSpace( p, encoding );
+ if ( !p )
+ {
+ SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return 0;
+ }
+
+ while ( p && *p )
+ {
+ TiXmlNode* node = Identify( p, encoding );
+ if ( node )
+ {
+ p = node->Parse( p, &data, encoding );
+ LinkEndChild( node );
+ }
+ else
+ {
+ break;
+ }
+
+ // Did we get encoding info?
+ if ( encoding == TIXML_ENCODING_UNKNOWN
+ && node->ToDeclaration() )
+ {
+ TiXmlDeclaration* dec = node->ToDeclaration();
+ const char* enc = dec->Encoding();
+ assert( enc );
+
+ if ( *enc == 0 )
+ encoding = TIXML_ENCODING_UTF8;
+ else if ( StringEqual( enc, "UTF-8", true, TIXML_ENCODING_UNKNOWN ) )
+ encoding = TIXML_ENCODING_UTF8;
+ else if ( StringEqual( enc, "UTF8", true, TIXML_ENCODING_UNKNOWN ) )
+ encoding = TIXML_ENCODING_UTF8; // incorrect, but be nice
+ else
+ encoding = TIXML_ENCODING_LEGACY;
+ }
+
+ p = SkipWhiteSpace( p, encoding );
+ }
+
+ // Was this empty?
+ if ( !firstChild ) {
+ SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, encoding );
+ return 0;
+ }
+
+ // All is well.
+ return p;
+}
+
+void TiXmlDocument::SetError( int err, const char* pError, TiXmlParsingData* data, TiXmlEncoding encoding )
+{
+ // The first error in a chain is more accurate - don't set again!
+ if ( error )
+ return;
+
+ assert( err > 0 && err < TIXML_ERROR_STRING_COUNT );
+ error = true;
+ errorId = err;
+ errorDesc = errorString[ errorId ];
+
+ errorLocation.Clear();
+ if ( pError && data )
+ {
+ data->Stamp( pError, encoding );
+ errorLocation = data->Cursor();
+ }
+}
+
+
+TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding )
+{
+ TiXmlNode* returnNode = 0;
+
+ p = SkipWhiteSpace( p, encoding );
+ if( !p || !*p || *p != '<' )
+ {
+ return 0;
+ }
+
+ TiXmlDocument* doc = GetDocument();
+ p = SkipWhiteSpace( p, encoding );
+
+ if ( !p || !*p )
+ {
+ return 0;
+ }
+
+ // What is this thing?
+ // - Elements start with a letter or underscore, but xml is reserved.
+ // - Comments: <!--
+ // - Decleration: <?xml
+ // - Everthing else is unknown to tinyxml.
+ //
+
+ const char* xmlHeader = { "<?xml" };
+ const char* commentHeader = { "<!--" };
+ const char* dtdHeader = { "<!" };
+ const char* cdataHeader = { "<![CDATA[" };
+
+ if ( StringEqual( p, xmlHeader, true, encoding ) )
+ {
+ #ifdef DEBUG_PARSER
+ TIXML_LOG( "XML parsing Declaration\n" );
+ #endif
+ returnNode = new TiXmlDeclaration();
+ }
+ else if ( StringEqual( p, commentHeader, false, encoding ) )
+ {
+ #ifdef DEBUG_PARSER
+ TIXML_LOG( "XML parsing Comment\n" );
+ #endif
+ returnNode = new TiXmlComment();
+ }
+ else if ( StringEqual( p, cdataHeader, false, encoding ) )
+ {
+ #ifdef DEBUG_PARSER
+ TIXML_LOG( "XML parsing CDATA\n" );
+ #endif
+ TiXmlText* text = new TiXmlText( "" );
+ text->SetCDATA( true );
+ returnNode = text;
+ }
+ else if ( StringEqual( p, dtdHeader, false, encoding ) )
+ {
+ #ifdef DEBUG_PARSER
+ TIXML_LOG( "XML parsing Unknown(1)\n" );
+ #endif
+ returnNode = new TiXmlUnknown();
+ }
+ else if ( IsAlpha( *(p+1), encoding )
+ || *(p+1) == '_' )
+ {
+ #ifdef DEBUG_PARSER
+ TIXML_LOG( "XML parsing Element\n" );
+ #endif
+ returnNode = new TiXmlElement( "" );
+ }
+ else
+ {
+ #ifdef DEBUG_PARSER
+ TIXML_LOG( "XML parsing Unknown(2)\n" );
+ #endif
+ returnNode = new TiXmlUnknown();
+ }
+
+ if ( returnNode )
+ {
+ // Set the parent, so it can report errors
+ returnNode->parent = this;
+ }
+ else
+ {
+ if ( doc )
+ doc->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ }
+ return returnNode;
+}
+
+#ifdef TIXML_USE_STL
+
+void TiXmlElement::StreamIn (std::istream * in, TIXML_STRING * tag)
+{
+ // We're called with some amount of pre-parsing. That is, some of "this"
+ // element is in "tag". Go ahead and stream to the closing ">"
+ while( in->good() )
+ {
+ int c = in->get();
+ if ( c <= 0 )
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document )
+ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+ (*tag) += (char) c ;
+
+ if ( c == '>' )
+ break;
+ }
+
+ if ( tag->length() < 3 ) return;
+
+ // Okay...if we are a "/>" tag, then we're done. We've read a complete tag.
+ // If not, identify and stream.
+
+ if ( tag->at( tag->length() - 1 ) == '>'
+ && tag->at( tag->length() - 2 ) == '/' )
+ {
+ // All good!
+ return;
+ }
+ else if ( tag->at( tag->length() - 1 ) == '>' )
+ {
+ // There is more. Could be:
+ // text
+ // cdata text (which looks like another node)
+ // closing tag
+ // another node.
+ for ( ;; )
+ {
+ StreamWhiteSpace( in, tag );
+
+ // Do we have text?
+ if ( in->good() && in->peek() != '<' )
+ {
+ // Yep, text.
+ TiXmlText text( "" );
+ text.StreamIn( in, tag );
+
+ // What follows text is a closing tag or another node.
+ // Go around again and figure it out.
+ continue;
+ }
+
+ // We now have either a closing tag...or another node.
+ // We should be at a "<", regardless.
+ if ( !in->good() ) return;
+ assert( in->peek() == '<' );
+ int tagIndex = (int) tag->length();
+
+ bool closingTag = false;
+ bool firstCharFound = false;
+
+ for( ;; )
+ {
+ if ( !in->good() )
+ return;
+
+ int c = in->peek();
+ if ( c <= 0 )
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document )
+ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+
+ if ( c == '>' )
+ break;
+
+ *tag += (char) c;
+ in->get();
+
+ // Early out if we find the CDATA id.
+ if ( c == '[' && tag->size() >= 9 )
+ {
+ size_t len = tag->size();
+ const char* start = tag->c_str() + len - 9;
+ if ( strcmp( start, "<![CDATA[" ) == 0 ) {
+ assert( !closingTag );
+ break;
+ }
+ }
+
+ if ( !firstCharFound && c != '<' && !IsWhiteSpace( c ) )
+ {
+ firstCharFound = true;
+ if ( c == '/' )
+ closingTag = true;
+ }
+ }
+ // If it was a closing tag, then read in the closing '>' to clean up the input stream.
+ // If it was not, the streaming will be done by the tag.
+ if ( closingTag )
+ {
+ if ( !in->good() )
+ return;
+
+ int c = in->get();
+ if ( c <= 0 )
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document )
+ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+ assert( c == '>' );
+ *tag += (char) c;
+
+ // We are done, once we've found our closing tag.
+ return;
+ }
+ else
+ {
+ // If not a closing tag, id it, and stream.
+ const char* tagloc = tag->c_str() + tagIndex;
+ TiXmlNode* node = Identify( tagloc, TIXML_DEFAULT_ENCODING );
+ if ( !node )
+ return;
+ node->StreamIn( in, tag );
+ delete node;
+ node = 0;
+
+ // No return: go around from the beginning: text, closing tag, or node.
+ }
+ }
+ }
+}
+#endif
+
+const char* TiXmlElement::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )
+{
+ p = SkipWhiteSpace( p, encoding );
+ TiXmlDocument* document = GetDocument();
+
+ if ( !p || !*p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, 0, 0, encoding );
+ return 0;
+ }
+
+ if ( data )
+ {
+ data->Stamp( p, encoding );
+ location = data->Cursor();
+ }
+
+ if ( *p != '<' )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, p, data, encoding );
+ return 0;
+ }
+
+ p = SkipWhiteSpace( p+1, encoding );
+
+ // Read the name.
+ const char* pErr = p;
+
+ p = ReadName( p, &value, encoding );
+ if ( !p || !*p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, pErr, data, encoding );
+ return 0;
+ }
+
+ TIXML_STRING endTag ("</");
+ endTag += value;
+ endTag += ">";
+
+ // Check for and read attributes. Also look for an empty
+ // tag or an end tag.
+ while ( p && *p )
+ {
+ pErr = p;
+ p = SkipWhiteSpace( p, encoding );
+ if ( !p || !*p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding );
+ return 0;
+ }
+ if ( *p == '/' )
+ {
+ ++p;
+ // Empty tag.
+ if ( *p != '>' )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_PARSING_EMPTY, p, data, encoding );
+ return 0;
+ }
+ return (p+1);
+ }
+ else if ( *p == '>' )
+ {
+ // Done with attributes (if there were any.)
+ // Read the value -- which can include other
+ // elements -- read the end tag, and return.
+ ++p;
+ p = ReadValue( p, data, encoding ); // Note this is an Element method, and will set the error if one happens.
+ if ( !p || !*p ) {
+ // We were looking for the end tag, but found nothing.
+ // Fix for [ 1663758 ] Failure to report error on bad XML
+ if ( document ) document->SetError( TIXML_ERROR_READING_END_TAG, p, data, encoding );
+ return 0;
+ }
+
+ // We should find the end tag now
+ if ( StringEqual( p, endTag.c_str(), false, encoding ) )
+ {
+ p += endTag.length();
+ return p;
+ }
+ else
+ {
+ if ( document ) document->SetError( TIXML_ERROR_READING_END_TAG, p, data, encoding );
+ return 0;
+ }
+ }
+ else
+ {
+ // Try to read an attribute:
+ TiXmlAttribute* attrib = new TiXmlAttribute();
+ if ( !attrib )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, pErr, data, encoding );
+ return 0;
+ }
+
+ attrib->SetDocument( document );
+ pErr = p;
+ p = attrib->Parse( p, data, encoding );
+
+ if ( !p || !*p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, pErr, data, encoding );
+ delete attrib;
+ return 0;
+ }
+
+ // Handle the strange case of double attributes:
+ #ifdef TIXML_USE_STL
+ TiXmlAttribute* node = attributeSet.Find( attrib->NameTStr() );
+ #else
+ TiXmlAttribute* node = attributeSet.Find( attrib->Name() );
+ #endif
+ if ( node )
+ {
+ node->SetValue( attrib->Value() );
+ delete attrib;
+ return 0;
+ }
+
+ attributeSet.Add( attrib );
+ }
+ }
+ return p;
+}
+
+
+const char* TiXmlElement::ReadValue( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )
+{
+ TiXmlDocument* document = GetDocument();
+
+ // Read in text and elements in any order.
+ const char* pWithWhiteSpace = p;
+ p = SkipWhiteSpace( p, encoding );
+
+ while ( p && *p )
+ {
+ if ( *p != '<' )
+ {
+ // Take what we have, make a text element.
+ TiXmlText* textNode = new TiXmlText( "" );
+
+ if ( !textNode )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, encoding );
+ return 0;
+ }
+
+ if ( TiXmlBase::IsWhiteSpaceCondensed() )
+ {
+ p = textNode->Parse( p, data, encoding );
+ }
+ else
+ {
+ // Special case: we want to keep the white space
+ // so that leading spaces aren't removed.
+ p = textNode->Parse( pWithWhiteSpace, data, encoding );
+ }
+
+ if ( !textNode->Blank() )
+ LinkEndChild( textNode );
+ else
+ delete textNode;
+ }
+ else
+ {
+ // We hit a '<'
+ // Have we hit a new element or an end tag? This could also be
+ // a TiXmlText in the "CDATA" style.
+ if ( StringEqual( p, "</", false, encoding ) )
+ {
+ return p;
+ }
+ else
+ {
+ TiXmlNode* node = Identify( p, encoding );
+ if ( node )
+ {
+ p = node->Parse( p, data, encoding );
+ LinkEndChild( node );
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+ pWithWhiteSpace = p;
+ p = SkipWhiteSpace( p, encoding );
+ }
+
+ if ( !p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_READING_ELEMENT_VALUE, 0, 0, encoding );
+ }
+ return p;
+}
+
+
+#ifdef TIXML_USE_STL
+void TiXmlUnknown::StreamIn( std::istream * in, TIXML_STRING * tag )
+{
+ while ( in->good() )
+ {
+ int c = in->get();
+ if ( c <= 0 )
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document )
+ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+ (*tag) += (char) c;
+
+ if ( c == '>' )
+ {
+ // All is well.
+ return;
+ }
+ }
+}
+#endif
+
+
+const char* TiXmlUnknown::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )
+{
+ TiXmlDocument* document = GetDocument();
+ p = SkipWhiteSpace( p, encoding );
+
+ if ( data )
+ {
+ data->Stamp( p, encoding );
+ location = data->Cursor();
+ }
+ if ( !p || !*p || *p != '<' )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_PARSING_UNKNOWN, p, data, encoding );
+ return 0;
+ }
+ ++p;
+ value = "";
+
+ while ( p && *p && *p != '>' )
+ {
+ value += *p;
+ ++p;
+ }
+
+ if ( !p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_PARSING_UNKNOWN, 0, 0, encoding );
+ }
+ if ( *p == '>' )
+ return p+1;
+ return p;
+}
+
+#ifdef TIXML_USE_STL
+void TiXmlComment::StreamIn( std::istream * in, TIXML_STRING * tag )
+{
+ while ( in->good() )
+ {
+ int c = in->get();
+ if ( c <= 0 )
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document )
+ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+
+ (*tag) += (char) c;
+
+ if ( c == '>'
+ && tag->at( tag->length() - 2 ) == '-'
+ && tag->at( tag->length() - 3 ) == '-' )
+ {
+ // All is well.
+ return;
+ }
+ }
+}
+#endif
+
+
+const char* TiXmlComment::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )
+{
+ TiXmlDocument* document = GetDocument();
+ value = "";
+
+ p = SkipWhiteSpace( p, encoding );
+
+ if ( data )
+ {
+ data->Stamp( p, encoding );
+ location = data->Cursor();
+ }
+ const char* startTag = "<!--";
+ const char* endTag = "-->";
+
+ if ( !StringEqual( p, startTag, false, encoding ) )
+ {
+ document->SetError( TIXML_ERROR_PARSING_COMMENT, p, data, encoding );
+ return 0;
+ }
+ p += strlen( startTag );
+
+ // [ 1475201 ] TinyXML parses entities in comments
+ // Oops - ReadText doesn't work, because we don't want to parse the entities.
+ // p = ReadText( p, &value, false, endTag, false, encoding );
+ //
+ // from the XML spec:
+ /*
+ [Definition: Comments may appear anywhere in a document outside other markup; in addition,
+ they may appear within the document type declaration at places allowed by the grammar.
+ They are not part of the document's character data; an XML processor MAY, but need not,
+ make it possible for an application to retrieve the text of comments. For compatibility,
+ the string "--" (double-hyphen) MUST NOT occur within comments.] Parameter entity
+ references MUST NOT be recognized within comments.
+
+ An example of a comment:
+
+ <!-- declarations for <head> & <body> -->
+ */
+
+ value = "";
+ // Keep all the white space.
+ while ( p && *p && !StringEqual( p, endTag, false, encoding ) )
+ {
+ value.append( p, 1 );
+ ++p;
+ }
+ if ( p )
+ p += strlen( endTag );
+
+ return p;
+}
+
+
+const char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )
+{
+ p = SkipWhiteSpace( p, encoding );
+ if ( !p || !*p ) return 0;
+
+// int tabsize = 4;
+// if ( document )
+// tabsize = document->TabSize();
+
+ if ( data )
+ {
+ data->Stamp( p, encoding );
+ location = data->Cursor();
+ }
+ // Read the name, the '=' and the value.
+ const char* pErr = p;
+ p = ReadName( p, &name, encoding );
+ if ( !p || !*p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding );
+ return 0;
+ }
+ p = SkipWhiteSpace( p, encoding );
+ if ( !p || !*p || *p != '=' )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding );
+ return 0;
+ }
+
+ ++p; // skip '='
+ p = SkipWhiteSpace( p, encoding );
+ if ( !p || !*p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding );
+ return 0;
+ }
+
+ const char* end;
+ const char SINGLE_QUOTE = '\'';
+ const char DOUBLE_QUOTE = '\"';
+
+ if ( *p == SINGLE_QUOTE )
+ {
+ ++p;
+ end = "\'"; // single quote in string
+ p = ReadText( p, &value, false, end, false, encoding );
+ }
+ else if ( *p == DOUBLE_QUOTE )
+ {
+ ++p;
+ end = "\""; // double quote in string
+ p = ReadText( p, &value, false, end, false, encoding );
+ }
+ else
+ {
+ // All attribute values should be in single or double quotes.
+ // But this is such a common error that the parser will try
+ // its best, even without them.
+ value = "";
+ while ( p && *p // existence
+ && !IsWhiteSpace( *p ) && *p != '\n' && *p != '\r' // whitespace
+ && *p != '/' && *p != '>' ) // tag end
+ {
+ if ( *p == SINGLE_QUOTE || *p == DOUBLE_QUOTE ) {
+ // [ 1451649 ] Attribute values with trailing quotes not handled correctly
+ // We did not have an opening quote but seem to have a
+ // closing one. Give up and throw an error.
+ if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding );
+ return 0;
+ }
+ value += *p;
+ ++p;
+ }
+ }
+ return p;
+}
+
+#ifdef TIXML_USE_STL
+void TiXmlText::StreamIn( std::istream * in, TIXML_STRING * tag )
+{
+ while ( in->good() )
+ {
+ int c = in->peek();
+ if ( !cdata && (c == '<' ) )
+ {
+ return;
+ }
+ if ( c <= 0 )
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document )
+ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+
+ (*tag) += (char) c;
+ in->get(); // "commits" the peek made above
+
+ if ( cdata && c == '>' && tag->size() >= 3 ) {
+ size_t len = tag->size();
+ if ( (*tag)[len-2] == ']' && (*tag)[len-3] == ']' ) {
+ // terminator of cdata.
+ return;
+ }
+ }
+ }
+}
+#endif
+
+const char* TiXmlText::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )
+{
+ value = "";
+ TiXmlDocument* document = GetDocument();
+
+ if ( data )
+ {
+ data->Stamp( p, encoding );
+ location = data->Cursor();
+ }
+
+ const char* const startTag = "<![CDATA[";
+ const char* const endTag = "]]>";
+
+ if ( cdata || StringEqual( p, startTag, false, encoding ) )
+ {
+ cdata = true;
+
+ if ( !StringEqual( p, startTag, false, encoding ) )
+ {
+ document->SetError( TIXML_ERROR_PARSING_CDATA, p, data, encoding );
+ return 0;
+ }
+ p += strlen( startTag );
+
+ // Keep all the white space, ignore the encoding, etc.
+ while ( p && *p
+ && !StringEqual( p, endTag, false, encoding )
+ )
+ {
+ value += *p;
+ ++p;
+ }
+
+ TIXML_STRING dummy;
+ p = ReadText( p, &dummy, false, endTag, false, encoding );
+ return p;
+ }
+ else
+ {
+ bool ignoreWhite = true;
+
+ const char* end = "<";
+ p = ReadText( p, &value, ignoreWhite, end, false, encoding );
+ if ( p )
+ return p-1; // don't truncate the '<'
+ return 0;
+ }
+}
+
+#ifdef TIXML_USE_STL
+void TiXmlDeclaration::StreamIn( std::istream * in, TIXML_STRING * tag )
+{
+ while ( in->good() )
+ {
+ int c = in->get();
+ if ( c <= 0 )
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document )
+ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+ (*tag) += (char) c;
+
+ if ( c == '>' )
+ {
+ // All is well.
+ return;
+ }
+ }
+}
+#endif
+
+const char* TiXmlDeclaration::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding _encoding )
+{
+ p = SkipWhiteSpace( p, _encoding );
+ // Find the beginning, find the end, and look for
+ // the stuff in-between.
+ TiXmlDocument* document = GetDocument();
+ if ( !p || !*p || !StringEqual( p, "<?xml", true, _encoding ) )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_PARSING_DECLARATION, 0, 0, _encoding );
+ return 0;
+ }
+ if ( data )
+ {
+ data->Stamp( p, _encoding );
+ location = data->Cursor();
+ }
+ p += 5;
+
+ version = "";
+ encoding = "";
+ standalone = "";
+
+ while ( p && *p )
+ {
+ if ( *p == '>' )
+ {
+ ++p;
+ return p;
+ }
+
+ p = SkipWhiteSpace( p, _encoding );
+ if ( StringEqual( p, "version", true, _encoding ) )
+ {
+ TiXmlAttribute attrib;
+ p = attrib.Parse( p, data, _encoding );
+ version = attrib.Value();
+ }
+ else if ( StringEqual( p, "encoding", true, _encoding ) )
+ {
+ TiXmlAttribute attrib;
+ p = attrib.Parse( p, data, _encoding );
+ encoding = attrib.Value();
+ }
+ else if ( StringEqual( p, "standalone", true, _encoding ) )
+ {
+ TiXmlAttribute attrib;
+ p = attrib.Parse( p, data, _encoding );
+ standalone = attrib.Value();
+ }
+ else
+ {
+ // Read over whatever it is.
+ while( p && *p && *p != '>' && !IsWhiteSpace( *p ) )
+ ++p;
+ }
+ }
+ return 0;
+}
+
+bool TiXmlText::Blank() const
+{
+ for ( unsigned i=0; i<value.length(); i++ )
+ if ( !IsWhiteSpace( value[i] ) )
+ return false;
+ return true;
+}
+
+
+bool TiXmlDocument::ReadFromMemory( const char* pBuf, size_t sz, TiXmlEncoding encoding)
+{
+ // Delete the existing data:
+ Clear();
+ location.Clear();
+
+ // Get the file size, so we can pre-allocate the string. HUGE speed impact.
+ long length = (long) sz;
+
+ // Strange case, but good to handle up front.
+ if ( length == 0 )
+ {
+ SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return false;
+ }
+
+ // If we have a file, assume it is all one big XML file, and read it in.
+ // The document parser may decide the document ends sooner than the entire file, however.
+ TIXML_STRING data;
+ data.reserve( length );
+
+
+ char* buf = new char[ length+1 ];
+ memset(buf,0,length+1);
+
+ memcpy(buf, pBuf, length);
+
+ const char* lastPos = buf;
+ const char* p = buf;
+
+ buf[length] = 0;
+ while( *p ) {
+ assert( p < (buf+length) );
+ if ( *p == 0xa ) {
+ // Newline character. No special rules for this. Append all the characters
+ // since the last string, and include the newline.
+ data.append( lastPos, (p-lastPos+1) ); // append, include the newline
+ ++p; // move past the newline
+ lastPos = p; // and point to the new buffer (may be 0)
+ assert( p <= (buf+length) );
+ }
+ else if ( *p == 0xd ) {
+ // Carriage return. Append what we have so far, then
+ // handle moving forward in the buffer.
+ if ( (p-lastPos) > 0 ) {
+ data.append( lastPos, p-lastPos ); // do not add the CR
+ }
+ data += (char)0xa; // a proper newline
+
+ if ( *(p+1) == 0xa ) {
+ // Carriage return - new line sequence
+ p += 2;
+ lastPos = p;
+ assert( p <= (buf+length) );
+ }
+ else {
+ // it was followed by something else...that is presumably characters again.
+ ++p;
+ lastPos = p;
+ assert( p <= (buf+length) );
+ }
+ }
+ else {
+ ++p;
+ }
+ }
+ // Handle any left over characters.
+ if ( p-lastPos ) {
+ data.append( lastPos, p-lastPos );
+ }
+ delete [] buf;
+ buf = 0;
+
+ Parse( data.c_str(), 0, encoding );
+
+ if ( Error() )
+ return false;
+ else
+ return true;
+}
diff --git a/rotord/src/utils.cpp b/rotord/src/utils.cpp
new file mode 100644
index 0000000..9828124
--- /dev/null
+++ b/rotord/src/utils.cpp
@@ -0,0 +1,29 @@
+#include "utils.h"
+
+using namespace std;
+
+//float equality
+bool fequal(const float u,const float v){
+ if (abs(u-v)<FLOAT_THRESHOLD) return true;
+ else return false;
+};
+bool fless_or_equal(const float u,const float v){
+ //v is less or equal to u
+ if (u-v>-FLOAT_THRESHOLD) return true;
+ else return false;
+};
+bool fgreater_or_equal(const float u,const float v){
+ //v is more or equal to u
+ if (v-u>-FLOAT_THRESHOLD) return true;
+ else return false;
+};
+bool fless(const float u,const float v){
+ //v is less than u
+ if (u-v>FLOAT_THRESHOLD) return true;
+ else return false;
+};
+bool fgreater(const float u,const float v){
+ //v is greater than u
+ if (v-u>FLOAT_THRESHOLD) return true;
+ else return false;
+};
diff --git a/rotord/src/utils.h b/rotord/src/utils.h
new file mode 100644
index 0000000..3859afe
--- /dev/null
+++ b/rotord/src/utils.h
@@ -0,0 +1,10 @@
+#include <cmath>
+
+#define FLOAT_THRESHOLD .001f
+
+//float equality
+bool fequal(const float u,const float v);
+bool fless_or_equal(const float u,const float v);
+bool fgreater_or_equal(const float u,const float v);
+bool fless(const float u,const float v);
+bool fgreater(const float u,const float v);
diff --git a/rotord/src/vampHost.cpp b/rotord/src/vampHost.cpp
new file mode 100644
index 0000000..65755eb
--- /dev/null
+++ b/rotord/src/vampHost.cpp
@@ -0,0 +1,815 @@
+#include "vampHost.h"
+
+int vampHost::runPlugin(string myname, string soname, string id, string output,
+ int outputNo, string inputFile, ostream& out, bool useFrames)
+{
+ PluginLoader *loader = PluginLoader::getInstance();
+
+ PluginLoader::PluginKey key = loader->composePluginKey(soname, id);
+
+ SNDFILE *sndfile;
+ SF_INFO sfinfo;
+ memset(&sfinfo, 0, sizeof(SF_INFO));
+
+ sndfile = sf_open(inputFile.c_str(), SFM_READ, &sfinfo);
+ if (!sndfile) {
+ cerr << myname << ": ERROR: Failed to open input file \""
+ << inputFile << "\": " << sf_strerror(sndfile) << endl;
+ return 1;
+ }
+
+ Plugin *plugin = loader->loadPlugin
+ (key, sfinfo.samplerate, PluginLoader::ADAPT_ALL_SAFE);
+ if (!plugin) {
+ cerr << myname << ": ERROR: Failed to load plugin \"" << id
+ << "\" from library \"" << soname << "\"" << endl;
+ sf_close(sndfile);
+ return 1;
+ }
+
+ cerr << "Running plugin: \"" << plugin->getIdentifier() << "\"..." << endl;
+
+ // Note that the following would be much simpler if we used a
+ // PluginBufferingAdapter as well -- i.e. if we had passed
+ // PluginLoader::ADAPT_ALL to loader->loadPlugin() above, instead
+ // of ADAPT_ALL_SAFE. Then we could simply specify our own block
+ // size, keep the step size equal to the block size, and ignore
+ // the plugin's bleatings. However, there are some issues with
+ // using a PluginBufferingAdapter that make the results sometimes
+ // technically different from (if effectively the same as) the
+ // un-adapted plugin, so we aren't doing that here. See the
+ // PluginBufferingAdapter documentation for details.
+
+ int blockSize = plugin->getPreferredBlockSize();
+ int stepSize = plugin->getPreferredStepSize();
+
+ if (blockSize == 0) {
+ blockSize = 1024;
+ }
+ if (stepSize == 0) {
+ if (plugin->getInputDomain() == Plugin::FrequencyDomain) {
+ stepSize = blockSize/2;
+ } else {
+ stepSize = blockSize;
+ }
+ } else if (stepSize > blockSize) {
+ cerr << "WARNING: stepSize " << stepSize << " > blockSize " << blockSize << ", resetting blockSize to ";
+ if (plugin->getInputDomain() == Plugin::FrequencyDomain) {
+ blockSize = stepSize * 2;
+ } else {
+ blockSize = stepSize;
+ }
+ cerr << blockSize << endl;
+ }
+ int overlapSize = blockSize - stepSize;
+ sf_count_t currentStep = 0;
+ int finalStepsRemaining = max(1, (blockSize / stepSize) - 1); // at end of file, this many part-silent frames needed after we hit EOF
+
+ int channels = sfinfo.channels;
+
+ float *filebuf = new float[blockSize * channels];
+ float **plugbuf = new float*[channels];
+ for (int c = 0; c < channels; ++c) plugbuf[c] = new float[blockSize + 2];
+
+ cerr << "Using block size = " << blockSize << ", step size = "
+ << stepSize << endl;
+
+ // The channel queries here are for informational purposes only --
+ // a PluginChannelAdapter is being used automatically behind the
+ // scenes, and it will take case of any channel mismatch
+
+ int minch = plugin->getMinChannelCount();
+ int maxch = plugin->getMaxChannelCount();
+ cerr << "Plugin accepts " << minch << " -> " << maxch << " channel(s)" << endl;
+ cerr << "Sound file has " << channels << " (will mix/augment if necessary)" << endl;
+
+ Plugin::OutputList outputs = plugin->getOutputDescriptors();
+ Plugin::OutputDescriptor od;
+
+ int returnValue = 1;
+ int progress = 0;
+
+ RealTime rt;
+ PluginWrapper *wrapper = 0;
+ RealTime adjustment = RealTime::zeroTime;
+
+ if (outputs.empty()) {
+ cerr << "ERROR: Plugin has no outputs!" << endl;
+ goto done;
+ }
+
+ if (outputNo < 0) {
+
+ for (size_t oi = 0; oi < outputs.size(); ++oi) {
+ if (outputs[oi].identifier == output) {
+ outputNo = oi;
+ break;
+ }
+ }
+
+ if (outputNo < 0) {
+ cerr << "ERROR: Non-existent output \"" << output << "\" requested" << endl;
+ goto done;
+ }
+
+ } else {
+
+ if (int(outputs.size()) <= outputNo) {
+ cerr << "ERROR: Output " << outputNo << " requested, but plugin has only " << outputs.size() << " output(s)" << endl;
+ goto done;
+ }
+ }
+
+ od = outputs[outputNo];
+ cerr << "Output is: \"" << od.identifier << "\"" << endl;
+
+ if (!plugin->initialise(channels, stepSize, blockSize)) {
+ cerr << "ERROR: Plugin initialise (channels = " << channels
+ << ", stepSize = " << stepSize << ", blockSize = "
+ << blockSize << ") failed." << endl;
+ goto done;
+ }
+
+ wrapper = dynamic_cast<PluginWrapper *>(plugin);
+ if (wrapper) {
+ // See documentation for
+ // PluginInputDomainAdapter::getTimestampAdjustment
+ PluginInputDomainAdapter *ida =
+ wrapper->getWrapper<PluginInputDomainAdapter>();
+ if (ida) adjustment = ida->getTimestampAdjustment();
+ }
+
+ // Here we iterate over the frames, avoiding asking the numframes in case it's streaming input.
+ do {
+
+ int count;
+
+ if ((blockSize==stepSize) || (currentStep==0)) {
+ // read a full fresh block
+ if ((count = sf_readf_float(sndfile, filebuf, blockSize)) < 0) {
+ cerr << "ERROR: sf_readf_float failed: " << sf_strerror(sndfile) << endl;
+ break;
+ }
+ if (count != blockSize) --finalStepsRemaining;
+ } else {
+ // otherwise shunt the existing data down and read the remainder.
+ memmove(filebuf, filebuf + (stepSize * channels), overlapSize * channels * sizeof(float));
+ if ((count = sf_readf_float(sndfile, filebuf + (overlapSize * channels), stepSize)) < 0) {
+ cerr << "ERROR: sf_readf_float failed: " << sf_strerror(sndfile) << endl;
+ break;
+ }
+ if (count != stepSize) --finalStepsRemaining;
+ count += overlapSize;
+ }
+
+ for (int c = 0; c < channels; ++c) {
+ int j = 0;
+ while (j < count) {
+ plugbuf[c][j] = filebuf[j * sfinfo.channels + c];
+ ++j;
+ }
+ while (j < blockSize) {
+ plugbuf[c][j] = 0.0f;
+ ++j;
+ }
+ }
+
+ rt = RealTime::frame2RealTime(currentStep * stepSize, sfinfo.samplerate);
+
+ vampHost::printFeatures
+ (RealTime::realTime2Frame(rt + adjustment, sfinfo.samplerate),
+ sfinfo.samplerate, outputNo, plugin->process(plugbuf, rt),
+ out, useFrames);
+
+ if (sfinfo.frames > 0){
+ int pp = progress;
+ progress = lrintf((float(currentStep * stepSize) / sfinfo.frames) * 100.f);
+ if (progress != pp && out) {
+ cerr << "\r" << progress << "%";
+ }
+ }
+
+ ++currentStep;
+
+ } while (finalStepsRemaining > 0);
+
+ if (out) cerr << "\rDone" << endl;
+
+ rt = RealTime::frame2RealTime(currentStep * stepSize, sfinfo.samplerate);
+
+ vampHost::printFeatures(RealTime::realTime2Frame(rt + adjustment, sfinfo.samplerate),
+ sfinfo.samplerate, outputNo,
+ plugin->getRemainingFeatures(), out, useFrames);
+
+ returnValue = 0;
+
+done:
+ delete plugin;
+ sf_close(sndfile);
+ return returnValue;
+}
+
+int vampHost::rotorRunPlugin(string soname, string id, string output,
+ int outputNo, string inputFile, vector<float>& out, float& progress)
+{
+ PluginLoader *loader = PluginLoader::getInstance();
+
+ PluginLoader::PluginKey key = loader->composePluginKey(soname, id);
+
+ SNDFILE *sndfile;
+ SF_INFO sfinfo;
+ memset(&sfinfo, 0, sizeof(SF_INFO));
+
+ sndfile = sf_open(inputFile.c_str(), SFM_READ, &sfinfo);
+ if (!sndfile) {
+ cerr << ": ERROR: Failed to open input file \""
+ << inputFile << "\": " << sf_strerror(sndfile) << endl;
+ return 1;
+ }
+
+ Plugin *plugin = loader->loadPlugin
+ (key, sfinfo.samplerate, PluginLoader::ADAPT_ALL_SAFE);
+ if (!plugin) {
+ cerr << ": ERROR: Failed to load plugin \"" << id
+ << "\" from library \"" << soname << "\"" << endl;
+ sf_close(sndfile);
+ return 1;
+ }
+
+ cerr << "Running plugin: \"" << plugin->getIdentifier() << "\"..." << endl;
+
+ // Note that the following would be much simpler if we used a
+ // PluginBufferingAdapter as well -- i.e. if we had passed
+ // PluginLoader::ADAPT_ALL to loader->loadPlugin() above, instead
+ // of ADAPT_ALL_SAFE. Then we could simply specify our own block
+ // size, keep the step size equal to the block size, and ignore
+ // the plugin's bleatings. However, there are some issues with
+ // using a PluginBufferingAdapter that make the results sometimes
+ // technically different from (if effectively the same as) the
+ // un-adapted plugin, so we aren't doing that here. See the
+ // PluginBufferingAdapter documentation for details.
+
+ int blockSize = plugin->getPreferredBlockSize();
+ int stepSize = plugin->getPreferredStepSize();
+
+ if (blockSize == 0) {
+ blockSize = 1024;
+ }
+ if (stepSize == 0) {
+ if (plugin->getInputDomain() == Plugin::FrequencyDomain) {
+ stepSize = blockSize/2;
+ } else {
+ stepSize = blockSize;
+ }
+ } else if (stepSize > blockSize) {
+ cerr << "WARNING: stepSize " << stepSize << " > blockSize " << blockSize << ", resetting blockSize to ";
+ if (plugin->getInputDomain() == Plugin::FrequencyDomain) {
+ blockSize = stepSize * 2;
+ } else {
+ blockSize = stepSize;
+ }
+ cerr << blockSize << endl;
+ }
+ int overlapSize = blockSize - stepSize;
+ sf_count_t currentStep = 0;
+ int finalStepsRemaining = max(1, (blockSize / stepSize) - 1); // at end of file, this many part-silent frames needed after we hit EOF
+
+ int channels = sfinfo.channels;
+
+ float *filebuf = new float[blockSize * channels];
+ float **plugbuf = new float*[channels];
+ for (int c = 0; c < channels; ++c) plugbuf[c] = new float[blockSize + 2];
+
+ cerr << "Using block size = " << blockSize << ", step size = "
+ << stepSize << endl;
+
+ // The channel queries here are for informational purposes only --
+ // a PluginChannelAdapter is being used automatically behind the
+ // scenes, and it will take case of any channel mismatch
+
+ int minch = plugin->getMinChannelCount();
+ int maxch = plugin->getMaxChannelCount();
+ cerr << "Plugin accepts " << minch << " -> " << maxch << " channel(s)" << endl;
+ cerr << "Sound file has " << channels << " (will mix/augment if necessary)" << endl;
+
+ Plugin::OutputList outputs = plugin->getOutputDescriptors();
+ Plugin::OutputDescriptor od;
+
+ int returnValue = 1;
+ int prog = 0;
+
+ RealTime rt;
+ PluginWrapper *wrapper = 0;
+ RealTime adjustment = RealTime::zeroTime;
+
+ if (outputs.empty()) {
+ cerr << "ERROR: Plugin has no outputs!" << endl;
+ goto done;
+ }
+
+ if (outputNo < 0) {
+
+ for (size_t oi = 0; oi < outputs.size(); ++oi) {
+ if (outputs[oi].identifier == output) {
+ outputNo = oi;
+ break;
+ }
+ }
+
+ if (outputNo < 0) {
+ cerr << "ERROR: Non-existent output \"" << output << "\" requested" << endl;
+ goto done;
+ }
+
+ } else {
+
+ if (int(outputs.size()) <= outputNo) {
+ cerr << "ERROR: Output " << outputNo << " requested, but plugin has only " << outputs.size() << " output(s)" << endl;
+ goto done;
+ }
+ }
+
+ od = outputs[outputNo];
+ cerr << "Output is: \"" << od.identifier << "\"" << endl;
+
+ if (!plugin->initialise(channels, stepSize, blockSize)) {
+ cerr << "ERROR: Plugin initialise (channels = " << channels
+ << ", stepSize = " << stepSize << ", blockSize = "
+ << blockSize << ") failed." << endl;
+ goto done;
+ }
+
+ wrapper = dynamic_cast<PluginWrapper *>(plugin);
+ if (wrapper) {
+ // See documentation for
+ // PluginInputDomainAdapter::getTimestampAdjustment
+ PluginInputDomainAdapter *ida =
+ wrapper->getWrapper<PluginInputDomainAdapter>();
+ if (ida) adjustment = ida->getTimestampAdjustment();
+ }
+
+ // Here we iterate over the frames, avoiding asking the numframes in case it's streaming input.
+ do {
+
+ int count;
+
+ if ((blockSize==stepSize) || (currentStep==0)) {
+ // read a full fresh block
+ if ((count = sf_readf_float(sndfile, filebuf, blockSize)) < 0) {
+ cerr << "ERROR: sf_readf_float failed: " << sf_strerror(sndfile) << endl;
+ break;
+ }
+ if (count != blockSize) --finalStepsRemaining;
+ } else {
+ // otherwise shunt the existing data down and read the remainder.
+ memmove(filebuf, filebuf + (stepSize * channels), overlapSize * channels * sizeof(float));
+ if ((count = sf_readf_float(sndfile, filebuf + (overlapSize * channels), stepSize)) < 0) {
+ cerr << "ERROR: sf_readf_float failed: " << sf_strerror(sndfile) << endl;
+ break;
+ }
+ if (count != stepSize) --finalStepsRemaining;
+ count += overlapSize;
+ }
+
+ for (int c = 0; c < channels; ++c) {
+ int j = 0;
+ while (j < count) {
+ plugbuf[c][j] = filebuf[j * sfinfo.channels + c];
+ ++j;
+ }
+ while (j < blockSize) {
+ plugbuf[c][j] = 0.0f;
+ ++j;
+ }
+ }
+
+ rt = RealTime::frame2RealTime(currentStep * stepSize, sfinfo.samplerate);
+
+ vampHost::rotorGetFeatures(RealTime::realTime2Frame(rt + adjustment, sfinfo.samplerate),sfinfo.samplerate, outputNo,plugin->getRemainingFeatures(), out, progress);
+
+ if (sfinfo.frames > 0){
+ int pp = prog;
+ prog = lrintf((float(currentStep * stepSize) / sfinfo.frames) * 100.f);
+ if (prog != pp ) {
+ cerr << "\r" << progress << "%";
+ }
+ }
+
+ ++currentStep;
+
+ } while (finalStepsRemaining > 0);
+
+ cerr << "\rDone" << endl;
+
+ rt = RealTime::frame2RealTime(currentStep * stepSize, sfinfo.samplerate);
+
+ vampHost::rotorGetFeatures(RealTime::realTime2Frame(rt + adjustment, sfinfo.samplerate),sfinfo.samplerate, outputNo,plugin->getRemainingFeatures(), out, progress);
+
+ returnValue = 0;
+
+done:
+ delete plugin;
+ sf_close(sndfile);
+ return returnValue;
+}
+
+void vampHost::printFeatures(int frame, int sr, int output,
+ Plugin::FeatureSet features, ostream& out, bool useFrames)
+{
+ if (features[output].size()) {
+ cout << "." << features[output].size();
+ }
+ for (unsigned int i = 0; i < features[output].size(); ++i) {
+
+ if (useFrames) {
+
+ int displayFrame = frame;
+
+ if (features[output][i].hasTimestamp) {
+ displayFrame = RealTime::realTime2Frame
+ (features[output][i].timestamp, sr);
+ }
+
+ out << displayFrame;
+
+ if (features[output][i].hasDuration) {
+ displayFrame = RealTime::realTime2Frame
+ (features[output][i].duration, sr);
+ out << "," << displayFrame;
+ }
+
+ out << ":";
+
+ } else {
+
+ RealTime rt = RealTime::frame2RealTime(frame, sr);
+
+ if (features[output][i].hasTimestamp) {
+ rt = features[output][i].timestamp;
+ }
+
+ out << rt.toString();
+
+ if (features[output][i].hasDuration) {
+ rt = features[output][i].duration;
+ out<< "," << rt.toString();
+ }
+
+ out << ":";
+ }
+
+ for (unsigned int j = 0; j < features[output][i].values.size(); ++j) {
+ out<< " " << features[output][i].values[j];
+ }
+ out << " " << features[output][i].label;
+
+ out << endl;
+ }
+
+}
+
+
+
+void vampHost::rotorGetFeatures(int frame, int sr, int output,Plugin::FeatureSet features, vector<float>& out, float& progress)
+{
+ if (features[output].size()) {
+ cout << "." << features[output].size();
+ }
+ for (unsigned int i = 0; i < features[output].size(); ++i) {
+
+
+
+ int displayFrame = frame;
+
+ if (features[output][i].hasTimestamp) {
+ displayFrame = RealTime::realTime2Frame
+ (features[output][i].timestamp, sr);
+ }
+
+ cout << displayFrame;
+
+
+ cout << endl;
+ }
+
+}
+
+
+int vampHost::QMAnalyser::process(const string inputFile){
+ //vampHost::runPlugin("",settings.soname,settings.filtername, "",0, settings.inputFile, ostr,true);
+ //would run the plugin, outputting progress to cerr and the data to ostr
+ //
+ //int runPlugin(string myname, string soname, string id, string output,int outputNo, string inputFile, ostream& out, bool frames);
+
+
+ //we want to run a specific plugin, outputting progress to a mutex-protected passed variable
+ //and ultimately passing the data back as a string?
+ //or capture it as an array of floats?
+ //get the progress as a float
+ //how to handle errors?
+
+ //debugger fucking up! program stalls after 1 request in debug!?
+
+ string soname="qm-vamp-plugins";
+ string id="qm-tempotracker";
+ string myname="";
+ string output="";
+ int outputNo=0;
+
+ vampHost::rotorRunPlugin(soname,id,output,outputNo,inputFile,beats,progress);
+
+}
+
+void vampHost::getTimestamps(int output,Plugin::FeatureSet features, vector<float>& out){
+
+ /*
+ vamp-simple-host qm-vamp-plugins:qm-tempotracker 01.wav
+
+ 0.046439908: 156.61 bpm
+ 0.429569160: 156.61 bpm
+ 0.812698412: 161.50 bpm
+ 1.184217686: 152.00 bpm
+
+
+ vamp-simple-host qm-vamp-plugins:qm-segmenter 01.wav
+
+ 0.000000000: 4 4
+ 23.800000000: 6 6
+ 44.600000000: 5 5
+ 55.000000000: 7 7
+ 72.800000000: 1 1
+ 90.600000000: 2 2
+ 109.200000000: 5 5
+ 116.000000000: 3 3
+ 143.800000000: 5 5
+ 153.400000000: 3 3
+ 163.000000000: 8 8
+
+ seems to be FP seconds then another metric
+ for now we can just take the first part
+
+ features[output][i].timestamp is of type RealTime: represents time values to nanosecond precision
+ int sec;
+ int nsec;
+ 1 sec = 10^9 nanosec
+
+ actually maybe this would be the way to go for rotor- avoiding rounding errors etc
+ for now - ideally will get a float representation
+
+ features[output][i].values is a vector of floats + a description
+ WE DON'T CARE ABOUT ANYTHING <.01 seconds
+
+ static long realTime2Frame(const RealTime &r, unsigned int sampleRate);
+
+ get a vector of floats out, using frames, presuming data has a timestamp
+
+
+ this is crashing with "Aborted (core dumped)"
+ if we check for timestamp
+
+ */
+
+ cout << "." << features[output].size();
+
+ //if (!features[output][0].hasTimestamp) {
+ // cerr << output << " channel, getTimestamps: error, featureset doesn't support timestamp" << endl;
+ //}_
+ //else {
+ for (unsigned int i = 0; i < features[output].size(); ++i) {
+ out.push_back( ((float)RealTime::realTime2Frame(features[output][i].timestamp, 1000))*.001f);
+ cout << "feature found.\n";
+ }
+ //}
+}
+float vampHost::QMAnalyser::get_progress(){
+ float p;
+ mutex.lock();
+ p=progress;
+ mutex.unlock();
+ return p;
+}
+bool vampHost::Analyser::init(const string &soname,const string &id,const int &_channels,const int &_bits,const int &_samples,const int &_rate,int _outputNo,const map<string,float> &params){
+
+ //stuff that only happens once
+ channels =_channels;
+ samples=_samples;
+ rate=_rate;
+ bits=_bits;
+ outputNo=_outputNo;
+ //output=_output;
+
+ //http://www.mega-nerd.com/libsndfile/api.html#note1
+ //libsndfile returns -1..1 for fp data
+ bytes=(bits>>3);
+ stride=channels*bytes;
+ scale=(1.0f/pow(2.0f,bits));
+
+ features.clear(); //in case of reuse
+ features[0.0f]=0;
+
+ loader = PluginLoader::getInstance();
+ key = loader->composePluginKey(soname, id);
+ plugin = loader->loadPlugin(key, _rate, PluginLoader::ADAPT_ALL_SAFE);
+ if (!plugin) {
+ cerr << ": ERROR: Failed to load plugin \"" << id
+ << "\" from library \"" << soname << "\"" << endl;
+ return false;
+ }
+
+ cerr << "Running plugin: \"" << plugin->getIdentifier() << "\"... Domain:";
+
+ if (plugin->getInputDomain() == Plugin::FrequencyDomain) {
+ cerr << "frequency" << endl;
+ }
+ else {
+
+ cerr << "time" << endl;
+
+ }
+
+ blockSize = plugin->getPreferredBlockSize();
+ stepSize = plugin->getPreferredStepSize();
+
+ if (blockSize == 0) {
+ blockSize = 1024;
+ }
+ if (stepSize == 0) {
+ if (plugin->getInputDomain() == Plugin::FrequencyDomain) {
+ stepSize = blockSize/2;
+ } else {
+ stepSize = blockSize;
+ }
+ }
+ else if (stepSize > blockSize) {
+ cerr << "WARNING: stepSize " << stepSize << " > blockSize " << blockSize << ", resetting blockSize to ";
+ if (plugin->getInputDomain() == Plugin::FrequencyDomain) {
+ blockSize = stepSize * 2;
+ } else {
+ blockSize = stepSize;
+ }
+ cerr << blockSize << endl;
+ }
+ overlapSize = blockSize - stepSize;
+ currentStep = 0;
+ finalStepsRemaining = max(1, (blockSize / stepSize) - 1); // at end of file, this many part-silent frames needed after we hit EOF
+
+ plugbuf = new float*[channels];
+ for (int c = 0; c < channels; ++c) plugbuf[c] = new float[blockSize + 2];
+
+ cerr << "Using block size = " << blockSize << ", step size = "
+ << stepSize << endl;
+
+ // The channel queries here are for informational purposes only --
+ // a PluginChannelAdapter is being used automatically behind the
+ // scenes, and it will take case of any channel mismatch
+
+ int minch = plugin->getMinChannelCount();
+ int maxch = plugin->getMaxChannelCount();
+ cerr << "Plugin accepts " << minch << " -> " << maxch << " channel(s)" << endl;
+ cerr << "Sound file has " << channels << " (will mix/augment if necessary)" << endl;
+
+ Plugin::OutputList outputs = plugin->getOutputDescriptors();
+ Plugin::OutputDescriptor od;
+
+ int returnValue = 1;
+ int prog = 0;
+
+ RealTime rt;
+ PluginWrapper *wrapper = 0;
+ RealTime adjustment = RealTime::zeroTime;
+
+ if (outputs.empty()) {
+ cerr << "ERROR: Plugin has no outputs!" << endl;
+ return false;
+ }
+
+ if (outputNo < 0) {
+ for (size_t oi = 0; oi < outputs.size(); ++oi) {
+ if (outputs[oi].identifier == output) {
+ outputNo = oi;
+ break;
+ }
+ }
+ if (outputNo < 0) {
+ cerr << "ERROR: Non-existent output \"" << output << "\" requested" << endl;
+ return false;
+ }
+ }
+ else {
+ if (int(outputs.size()) <= outputNo) {
+ cerr << "ERROR: Output " << outputNo << " requested, but plugin has only " << outputs.size() << " output(s)" << endl;
+ return false;
+ }
+ }
+ od = outputs[outputNo];
+ cerr << "Output number "<<outputNo<<": \"" << od.identifier << "\"" << endl;
+
+
+ for (auto i:params){
+ plugin->setParameter(i.first,i.second);
+ cerr << "Set plugin parameter: "<<i.first<<" : "<<i.second<<endl;
+ }
+
+ if (!plugin->initialise(channels, stepSize, blockSize)) {
+ cerr << "ERROR: Plugin initialise (channels = " << channels
+ << ", stepSize = " << stepSize << ", blockSize = "
+ << blockSize << ") failed." << endl;
+ return false;
+ }
+
+ wrapper = dynamic_cast<PluginWrapper *>(plugin);
+ if (wrapper) {
+ // See documentation for
+ // PluginInputDomainAdapter::getTimestampAdjustment
+ PluginInputDomainAdapter *ida =wrapper->getWrapper<PluginInputDomainAdapter>();
+ if (ida) adjustment = ida->getTimestampAdjustment();
+ }
+
+ //everything is prepared to start consuming data in blocks
+
+ in_block=0;
+ blocks_processed=0;
+ currentStep=0;
+
+ featureNo=1;
+
+ return true;
+}
+void vampHost::Analyser::process_frame(uint8_t *data,int samples_in_frame){
+ int sample=0;
+
+ uint16_t *_data=(uint16_t*)data;
+ //process the whole frame which may be f>1<f blocks
+ //when the frame is finished leave the partial block for the next frame
+ while(sample<samples_in_frame) {
+ while(sample<samples_in_frame&&in_block<blockSize) {
+ for (int i=0;i<channels;i++) {
+ //unsigned int this_val=0;
+ // this_val+=data[(sample*stride)+(i*bytes)+j]<<((1-j)*8);
+ //}
+ //plugbuf[i][in_block]=((float)((int16_t)this_val))*scale;
+ plugbuf[i][in_block]=((float)_data[sample])*scale;
+ }
+ in_block++;
+ sample++;
+ }
+ if (in_block==blockSize) {
+ //block is ready to be processed
+ //cerr<<plugin->getIdentifier()<<" processed block "<<blocks_processed<<endl;
+
+ //I /think/ that the vamp plugin keeps processing through the plugbuf until it encounters 0s
+ rt = RealTime::frame2RealTime(currentStep * stepSize, rate); //48000); //setting different rate doesn't affect it
+
+ 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;
+ featureNo++;
+ }
+
+ //shunt it down
+ for (int i=0;i<blockSize-stepSize;i++){
+ for (int j=0;j<channels;j++){
+ plugbuf[j][i]=plugbuf[j][i+stepSize];
+ }
+ }
+
+ in_block-=stepSize;
+ currentStep++;
+ }
+ }
+}
+void vampHost::Analyser::cleanup(){
+
+ //process final block
+ while(in_block<blockSize) {
+ for (int i=0;i<channels;i++) {
+ plugbuf[i][in_block]=0.0f;
+ }
+ in_block++;
+ }
+
+ rt = RealTime::frame2RealTime(currentStep * stepSize, rate); // //setting different
+
+ 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;
+ 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;
+ featureNo++;
+ }
+
+ //cerr<<plugin->getIdentifier()<<" found "<<(features.size()-1)<<" features"<<endl;
+ //deal with left over data?
+ for (int c = 0; c < channels; ++c) {
+ delete[] plugbuf[c];
+ }
+ delete[] plugbuf;
+ delete plugin;
+}
diff --git a/rotord/src/vampHost.h b/rotord/src/vampHost.h
new file mode 100644
index 0000000..d1dfc81
--- /dev/null
+++ b/rotord/src/vampHost.h
@@ -0,0 +1,92 @@
+#include <vamp-hostsdk/PluginHostAdapter.h>
+#include <vamp-hostsdk/PluginInputDomainAdapter.h>
+#include <vamp-hostsdk/PluginLoader.h>
+
+#include "Poco/Mutex.h"
+
+#include <iostream>
+#include <fstream>
+#include <set>
+#include <sndfile.h>
+
+#include <cstring>
+#include <cstdlib>
+
+#include "system.h"
+
+#include <cmath>
+
+/*
+line 366: is returnValue the fail/succeed return value?
+*/
+
+using namespace std;
+
+using Vamp::Plugin;
+using Vamp::PluginHostAdapter;
+using Vamp::RealTime;
+using Vamp::HostExt::PluginLoader;
+using Vamp::HostExt::PluginWrapper;
+using Vamp::HostExt::PluginInputDomainAdapter;
+
+#define HOST_VERSION "1.5"
+
+namespace vampHost {
+
+ class Settings{
+ public:
+ Settings(string _so="",string _filter="",string _input="") {
+ soname=_so;
+ filtername=_filter;
+ inputFile=_input;
+ }
+ string soname;
+ string filtername;
+ string inputFile;
+ };
+ class QMAnalyser{
+ public:
+ int process(const string soundfile);
+ float get_progress();
+ vector<float> beats;
+ private:
+ float progress;
+ Poco::Mutex mutex; //lock for progress data
+ };
+ class Analyser{
+ //can load any vamp analysis plugin and present its data with a unified interface
+ public:
+ bool init(const string &soname,const string &id,const int &_channels,const int &_bits,const int &_samples,const int &_rate,int outputNo,const map<string,float> &params);
+ void process_frame(uint8_t *data,int samples_in_frame);
+ void cleanup();
+
+ map<double,int> 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
+
+ private:
+ PluginLoader *loader;
+ PluginLoader::PluginKey key;
+ Plugin *plugin;
+ RealTime rt;
+ int channels,bits,samples,rate;
+ int bytes,stride;
+ float scale;
+ int blockSize,stepSize,overlapSize,finalStepsRemaining,currentStep,outputNo;
+ int in_block,blocks_processed;
+ string output;
+ float **plugbuf;
+
+ int featureNo;
+
+ };
+
+ string getQMBeats(const string soundfile);
+ void printFeatures(int, int, int, Plugin::FeatureSet, ostream &, bool frames);
+ void getTimestamps(int output,Plugin::FeatureSet features, vector<float>& out);
+ int runPlugin(string myname, string soname, string id, string output,int outputNo, string inputFile, ostream& out, bool frames);
+
+ int rotorRunPlugin(string soname, string id, string output,int outputNo, string inputFile, vector<float>& out, float& progress);
+ void rotorGetFeatures(int frame, int sr, int output,Plugin::FeatureSet features, vector<float>& out, float& progress);
+
+}
diff --git a/rotord/src/xmlIO.cpp b/rotord/src/xmlIO.cpp
new file mode 100755
index 0000000..3a7ec61
--- /dev/null
+++ b/rotord/src/xmlIO.cpp
@@ -0,0 +1,673 @@
+#include "xmlIO.h"
+
+#include <vector>
+#include <string>
+#include <iostream>
+
+//----------------------------------------
+// a pretty useful tokenization system:
+static vector<string> tokenize(const string & str, const string & delim);
+static vector<string> tokenize(const string & str, const string & delim)
+{
+ vector<string> tokens;
+
+ size_t p0 = 0, p1 = string::npos;
+ while(p0 != string::npos)
+ {
+ p1 = str.find_first_of(delim, p0);
+ if(p1 != p0)
+ {
+ string token = str.substr(p0, p1 - p0);
+ tokens.push_back(token);
+ }
+ p0 = str.find_first_not_of(delim, p1);
+ }
+ return tokens;
+}
+//----------------------------------------
+
+//----------------------------------------
+xmlIO::xmlIO():
+ storedHandle(NULL)
+{
+ level = 0;
+ //we do this so that we have a valid handle
+ //without the need for loadFile
+ storedHandle = TiXmlHandle(&doc);
+}
+
+//----------------------------------------
+xmlIO::xmlIO(const string& xmlFile):
+ storedHandle(NULL)
+{
+ level = 0;
+ //we do this so that we have a valid handle
+ //without the need for loadFile
+ storedHandle = TiXmlHandle(&doc);
+ loadFile(xmlFile);
+}
+
+//---------------------------------------------------------
+xmlIO::~xmlIO()
+{
+}
+
+//---------------------------------------------------------
+void xmlIO::setVerbose(bool _verbose){
+}
+
+//---------------------------------------------------------
+void xmlIO::clear(){
+ //we clear from our root level
+ //this is usually the document
+ //but if we are pushed - it could
+ //be all the tags inside of the pushed
+ //node - including the node itself!
+
+ storedHandle.ToNode()->Clear();
+}
+
+//---------------------------------------------------------
+bool xmlIO::loadFile(const string& xmlFile){
+
+ //string fullXmlFile = ofToDataPath(xmlFile);
+
+ bool loadOkay = doc.LoadFile(xmlFile);
+
+ //theo removed bool check as it would
+ //return false if the file exists but was
+ //empty
+
+ //our push pop level should be set to 0!
+ level = 0;
+
+ storedHandle = TiXmlHandle(&doc);
+ return loadOkay;
+}
+
+//---------------------------------------------------------
+bool xmlIO::saveFile(const string& xmlFile){
+
+ //string fullXmlFile = ofToDataPath(xmlFile);
+ return doc.SaveFile(xmlFile);
+}
+
+//---------------------------------------------------------
+bool xmlIO::saveFile(){
+ return doc.SaveFile();
+}
+
+//---------------------------------------------------------
+void xmlIO::clearTagContents(const string& tag, int which){
+ //we check it first to see if it exists
+ //otherwise setValue will make a new empty tag
+ if( tagExists(tag, which) )setValue(tag, "", which);
+}
+
+//---------------------------------------------------------
+void xmlIO::removeTag(const string& tag, int which){
+
+ vector<string> tokens = tokenize(tag,":");
+
+ //no tags so we return
+ if( tokens.size() == 0 ) return;
+
+ //grab the handle from the level we are at
+ //normally this is the doc but could be a pushed node
+ TiXmlHandle tagHandle = storedHandle;
+
+ if(which < 0) which = 0;
+
+ for(int x=0;x<(int)tokens.size();x++){
+
+ //we only support multi tags
+ //with same name at root level
+ if(x > 0) which = 0;
+
+ TiXmlHandle isRealHandle = tagHandle.ChildElement( tokens.at(x), which);
+
+ if ( !isRealHandle.ToNode() ) break;
+ else{
+ if (x == (int)tokens.size()-1){
+ //if we are at the last tag and it exists
+ //we use its parent to remove it - haha
+ tagHandle.ToNode()->RemoveChild( isRealHandle.ToNode() );
+ }
+ tagHandle = isRealHandle;
+ }
+ }
+}
+
+//---------------------------------------------------------
+int xmlIO::getValue(const string& tag, int defaultValue, int which){
+ TiXmlHandle valHandle(NULL);
+ if (readTag(tag, valHandle, which)){
+ return ofToInt(valHandle.ToText()->Value());
+ }
+ return defaultValue;
+}
+
+//---------------------------------------------------------
+double xmlIO::getValue(const string& tag, double defaultValue, int which){
+ TiXmlHandle valHandle(NULL);
+ if (readTag(tag, valHandle, which)){
+ return ofToFloat(valHandle.ToText()->Value());
+ }
+ return defaultValue;
+}
+
+//---------------------------------------------------------
+string xmlIO::getValue(const string& tag, const string& defaultValue, int which){
+ TiXmlHandle valHandle(NULL);
+ if (readTag(tag, valHandle, which)){
+ return valHandle.ToText()->ValueStr();
+ }
+ return defaultValue;
+}
+
+//---------------------------------------------------------
+bool xmlIO::readTag(const string& tag, TiXmlHandle& valHandle, int which){
+
+ vector<string> tokens = tokenize(tag,":");
+
+ TiXmlHandle tagHandle = storedHandle;
+ for(int x=0;x<(int)tokens.size();x++){
+ if(x == 0)tagHandle = tagHandle.ChildElement(tokens.at(x), which);
+ else tagHandle = tagHandle.FirstChildElement( tokens.at(x) );
+ }
+
+ // once we've walked, let's get that value...
+ valHandle = tagHandle.Child( 0 );
+ return (valHandle.ToText() != NULL);
+}
+
+
+//---------------------------------------------------------
+bool xmlIO::pushTag(const string& tag, int which){
+
+ int pos = tag.find(":");
+
+ // Either find the tag specified, or the first tag if colon-seperated.
+ string tagToFind((pos > 0) ? tag.substr(0,pos) :tag);
+
+ //we only allow to push one tag at a time.
+ TiXmlHandle isRealHandle = storedHandle.ChildElement(tagToFind, which);
+
+ if( isRealHandle.ToNode() ){
+ storedHandle = isRealHandle;
+ level++;
+ return true;
+ }else{
+ //ofLog( OF_LOG_ERROR, "pushTag - <" + tag + "> tag not found");
+ }
+
+ return false;
+}
+
+//---------------------------------------------------------
+int xmlIO::popTag(){
+
+ if(level >= 1){
+ TiXmlHandle parent( (storedHandle.ToNode() )->Parent() );
+ storedHandle = parent;
+ level--;
+ }else{
+ storedHandle = TiXmlHandle(&doc);
+ level = 0;
+ }
+
+ return level;
+}
+
+//---------------------------------------------------------
+int xmlIO::getPushLevel(){
+ return level;
+}
+
+//---------------------------------------------------------
+bool xmlIO::tagExists(const string& tag, int which){
+
+ vector<string> tokens = tokenize(tag,":");
+
+ bool found = false;
+
+ //grab the handle from the level we are at
+ //normally this is the doc but could be a pushed node
+ TiXmlHandle tagHandle = storedHandle;
+
+ if(which < 0) which = 0;
+
+ for(int x=0;x<(int)tokens.size();x++){
+
+ //we only support multi tags
+ //with same name at root level
+ if(x > 0) which = 0;
+
+ TiXmlHandle isRealHandle = tagHandle.ChildElement( tokens.at(x), which);
+
+ //as soon as we find a tag that doesn't exist
+ //we return false;
+ if ( !isRealHandle.ToNode() ){
+ found = false;
+ break;
+ }
+ else{
+ found = true;
+ tagHandle = isRealHandle;
+ }
+ }
+
+ return found;
+}
+
+
+//---------------------------------------------------------
+int xmlIO::getNumTags(const string& tag){
+ //this only works for tags at the current root level
+
+ int pos = tag.find(":");
+
+ // Either find the tag specified, or the first tag if colon-seperated.
+ string tagToFind((pos > 0) ? tag.substr(0,pos) :tag);
+
+ //grab the handle from the level we are at
+ //normally this is the doc but could be a pushed node
+ TiXmlHandle tagHandle = storedHandle;
+
+ int count = 0;
+
+ //ripped from tinyXML as doing this ourselves once is a LOT! faster
+ //than having this called n number of times in a while loop - we go from n*n iterations to n iterations
+
+ TiXmlElement* child = ( storedHandle.FirstChildElement( tagToFind ) ).ToElement();
+ for (count = 0; child; child = child->NextSiblingElement( tagToFind ), ++count){
+ //nothing
+ }
+
+ return count;
+}
+
+
+
+//---------------------------------------------------------
+int xmlIO::writeTag(const string& tag, const string& valueStr, int which){
+
+ vector<string> tokens = tokenize(tag,":");
+
+ // allocate on the stack
+ vector<TiXmlElement> elements;
+ elements.reserve(tokens.size());
+ for(int x=0;x<(int)tokens.size();x++)
+ elements.push_back(tokens.at(x));
+
+
+ TiXmlText Value(valueStr);
+
+ // search our way up - do these tags exist?
+ // find the first that DOESNT exist, then move backwards...
+ TiXmlHandle tagHandle = storedHandle;
+
+ bool addNewTag = false;
+ if(which == -1)addNewTag = true;
+
+ for(int x=0;x<(int)tokens.size();x++){
+
+ if( x > 0 ){
+ //multi tags of same name
+ //only for the root level
+ which = 0;
+ addNewTag = false;
+ }
+
+ TiXmlHandle isRealHandle = tagHandle.ChildElement( tokens.at(x), which);
+
+ if ( !isRealHandle.ToNode() || addNewTag){
+
+ for(int i=(int)tokens.size()-1;i>=x;i--){
+ if (i == (int)tokens.size()-1){
+ elements[i].InsertEndChild(Value);
+ } else {
+ elements[i].InsertEndChild(elements[i+1]);
+ }
+ }
+
+ tagHandle.ToNode()->InsertEndChild(elements[x]);
+
+ break;
+
+ } else {
+ tagHandle = isRealHandle;
+ if (x == (int)tokens.size()-1){
+ // what we want to change : TiXmlHandle valHandle = tagHandle.Child( 0 );
+ tagHandle.ToNode()->Clear();
+ tagHandle.ToNode()->InsertEndChild(Value);
+ }
+ }
+ }
+
+
+ //lets count how many tags with our name exist so we can return an index
+
+ //ripped from tinyXML as doing this ourselves once is a LOT! faster
+ //than having this called n number of times in a while loop - we go from n*n iterations to n iterations
+ int numSameTags;
+ TiXmlElement* child = ( storedHandle.FirstChildElement( tokens.at(0) ) ).ToElement();
+ for (numSameTags = 0; child; child = child->NextSiblingElement( tokens.at(0) ), ++numSameTags){
+ //nothing
+ }
+
+ return numSameTags;
+}
+
+//---------------------------------------------------------
+int xmlIO::setValue(const string& tag, int value, int which){
+ int tagID = writeTag(tag, ofToString(value).c_str(), which) -1;
+ return tagID;
+}
+
+//---------------------------------------------------------
+int xmlIO::setValue(const string& tag, double value, int which){
+ int tagID = writeTag(tag, ofToString(value).c_str(), which) -1;
+ return tagID;
+}
+
+//---------------------------------------------------------
+int xmlIO::setValue(const string& tag, const string& value, int which){
+ int tagID = writeTag(tag, value, which) -1;
+ return tagID;
+}
+
+//---------------------------------------------------------
+int xmlIO::addValue(const string& tag, int value){
+ int tagID = writeTag(tag, ofToString(value).c_str(), -1) -1;
+ return tagID;
+}
+
+//---------------------------------------------------------
+int xmlIO::addValue(const string& tag, double value){
+ int tagID = writeTag(tag, ofToString(value).c_str(), -1) -1;
+ return tagID;
+}
+
+//---------------------------------------------------------
+int xmlIO::addValue(const string& tag, const string& value){
+ int tagID = writeTag(tag, value, -1) -1;
+ return tagID;
+}
+
+//---------------------------------------------------------
+int xmlIO::addTag(const string& tag){
+ int tagID = writeTag(tag, "", -1) -1;
+ return tagID;
+}
+
+/*******************
+* Attribute addons *
+*******************/
+
+//---------------------------------------------------------
+int xmlIO::addAttribute(const string& tag, const string& attribute, int value, int which){
+ int tagID = writeAttribute(tag, attribute, ofToString(value).c_str(), which) -1;
+ return tagID;
+}
+
+//---------------------------------------------------------
+int xmlIO::addAttribute(const string& tag, const string& attribute, int value){
+ return addAttribute(tag,attribute,value,-1);
+}
+
+//---------------------------------------------------------
+int xmlIO::addAttribute(const string& tag, const string& attribute, double value, int which){
+ int tagID = writeAttribute(tag, attribute, ofToString(value).c_str(), which) -1;
+ return tagID;
+}
+
+//---------------------------------------------------------
+int xmlIO::addAttribute(const string& tag, const string& attribute, double value){
+ return addAttribute(tag,attribute,value,-1);
+}
+
+//---------------------------------------------------------
+int xmlIO::addAttribute(const string& tag, const string& attribute, const string& value, int which){
+ int tagID = writeAttribute(tag, attribute, value, which) -1;
+ return tagID;
+}
+
+//---------------------------------------------------------
+int xmlIO::addAttribute(const string& tag, const string& attribute, const string& value){
+ return addAttribute(tag,attribute,value,-1);
+}
+
+//---------------------------------------------------------
+void xmlIO::removeAttribute(const string& tag, const string& attribute, int which){
+ vector<string> tokens = tokenize(tag,":");
+ TiXmlHandle tagHandle = storedHandle;
+ for (int x = 0; x < (int)tokens.size(); x++) {
+ if (x == 0)
+ tagHandle = tagHandle.ChildElement(tokens.at(x), which);
+ else
+ tagHandle = tagHandle.FirstChildElement(tokens.at(x));
+ }
+
+ if (tagHandle.ToElement()) {
+ TiXmlElement* elem = tagHandle.ToElement();
+ elem->RemoveAttribute(attribute);
+ }
+}
+
+//---------------------------------------------------------
+void xmlIO::clearTagAttributes(const string& tag, int which){
+ vector<string> names;
+ getAttributeNames( tag, names, which );
+ for (vector<string>::iterator i = names.begin(); i != names.end(); i++)
+ removeAttribute(tag, *i, which);
+}
+
+//---------------------------------------------------------
+int xmlIO::getNumAttributes(const string& tag, int which){
+ vector<string> tokens = tokenize(tag,":");
+ TiXmlHandle tagHandle = storedHandle;
+ for (int x = 0; x < (int)tokens.size(); x++) {
+ if (x == 0)
+ tagHandle = tagHandle.ChildElement(tokens.at(x), which);
+ else
+ tagHandle = tagHandle.FirstChildElement(tokens.at(x));
+ }
+
+ if (tagHandle.ToElement()) {
+ TiXmlElement* elem = tagHandle.ToElement();
+
+ // Do stuff with the element here
+ TiXmlAttribute* first = elem->FirstAttribute();
+ if (first) {
+ int count = 1;
+ for (TiXmlAttribute* curr = first; curr != elem->LastAttribute(); curr = curr->Next())
+ count++;
+ return count;
+ }
+ }
+ return 0;
+}
+
+//---------------------------------------------------------
+bool xmlIO::attributeExists(const string& tag, const string& attribute, int which){
+ vector<string> tokens = tokenize(tag,":");
+ TiXmlHandle tagHandle = storedHandle;
+ for (int x = 0; x < (int)tokens.size(); x++) {
+ if (x == 0)
+ tagHandle = tagHandle.ChildElement(tokens.at(x), which);
+ else
+ tagHandle = tagHandle.FirstChildElement(tokens.at(x));
+ }
+
+ if (tagHandle.ToElement()) {
+ TiXmlElement* elem = tagHandle.ToElement();
+
+ // Do stuff with the element here
+ for (TiXmlAttribute* a = elem->FirstAttribute(); a; a = a->Next()) {
+ if (a->Name() == attribute)
+ return true;
+ }
+ }
+ return false;
+}
+
+//---------------------------------------------------------
+bool xmlIO::getAttributeNames(const string& tag, vector<string>& outNames, int which){
+ vector<string> tokens = tokenize(tag,":");
+ TiXmlHandle tagHandle = storedHandle;
+ for (int x = 0; x < (int)tokens.size(); x++) {
+ if (x == 0)
+ tagHandle = tagHandle.ChildElement(tokens.at(x), which);
+ else
+ tagHandle = tagHandle.FirstChildElement(tokens.at(x));
+ }
+
+ if (tagHandle.ToElement()) {
+ TiXmlElement* elem = tagHandle.ToElement();
+
+ // Do stuff with the element here
+ for (TiXmlAttribute* a = elem->FirstAttribute(); a; a = a->Next())
+ outNames.push_back( string(a->Name()) );
+ }
+ return !outNames.empty();
+}
+
+//---------------------------------------------------------
+int xmlIO::getAttribute(const string& tag, const string& attribute, int defaultValue, int which){
+ int value = defaultValue;
+ readIntAttribute(tag, attribute, value, which);
+ return value;
+}
+
+//---------------------------------------------------------
+double xmlIO::getAttribute(const string& tag, const string& attribute, double defaultValue, int which){
+ double value = defaultValue;
+ readDoubleAttribute(tag, attribute, value, which);
+ return value;
+}
+
+//---------------------------------------------------------
+string xmlIO::getAttribute(const string& tag, const string& attribute, const string& defaultValue, int which){
+ string value = defaultValue;
+ readStringAttribute(tag, attribute, value, which);
+ return value;
+}
+
+//---------------------------------------------------------
+int xmlIO::setAttribute(const string& tag, const string& attribute, int value, int which){
+ char valueStr[255];
+ sprintf(valueStr, "%i", value);
+ int tagID = writeAttribute(tag, attribute, valueStr, which) -1;
+ return tagID;
+}
+
+//---------------------------------------------------------
+int xmlIO::setAttribute(const string& tag, const string& attribute, double value, int which){
+ char valueStr[255];
+ sprintf(valueStr, "%lf", value);
+ int tagID = writeAttribute(tag, attribute, valueStr, which) -1;
+ return tagID;
+}
+
+//---------------------------------------------------------
+int xmlIO::setAttribute(const string& tag, const string& attribute, const string& value, int which){
+ int tagID = writeAttribute(tag, attribute, value, which) -1;
+ return tagID;
+}
+
+//---------------------------------------------------------
+TiXmlElement* xmlIO::getElementForAttribute(const string& tag, int which){
+ vector<string> tokens = tokenize(tag,":");
+ TiXmlHandle tagHandle = storedHandle;
+ for (int x = 0; x < (int)tokens.size(); x++) {
+ if (x == 0)
+ tagHandle = tagHandle.ChildElement(tokens.at(x), which);
+ else
+ tagHandle = tagHandle.FirstChildElement(tokens.at(x));
+ }
+ return tagHandle.ToElement();
+}
+
+//---------------------------------------------------------
+bool xmlIO::readIntAttribute(const string& tag, const string& attribute, int& outValue, int which){
+
+ TiXmlElement* elem = getElementForAttribute(tag, which);
+ if (elem)
+ return (elem->QueryIntAttribute(attribute, &outValue) == TIXML_SUCCESS);
+ return false;
+}
+
+//---------------------------------------------------------
+bool xmlIO::readDoubleAttribute(const string& tag, const string& attribute, double& outValue, int which){
+
+ TiXmlElement* elem = getElementForAttribute(tag, which);
+ if (elem)
+ return (elem->QueryDoubleAttribute(attribute, &outValue) == TIXML_SUCCESS);
+ return false;
+}
+
+//---------------------------------------------------------
+bool xmlIO::readStringAttribute(const string& tag, const string& attribute, string& outValue, int which){
+
+ TiXmlElement* elem = getElementForAttribute(tag, which);
+ if (elem)
+ {
+ const string* value = elem->Attribute(attribute);
+ if (value)
+ {
+ outValue = *value;
+ return true;
+ }
+ }
+ return false;
+}
+
+//---------------------------------------------------------
+int xmlIO::writeAttribute(const string& tag, const string& attribute, const string& valueString, int which){
+ vector<string> tokens = tokenize(tag,":");
+ TiXmlHandle tagHandle = storedHandle;
+ for (int x = 0; x < (int)tokens.size(); x++) {
+ if (x == 0)
+ tagHandle = tagHandle.ChildElement(tokens.at(x), which);
+ else
+ tagHandle = tagHandle.FirstChildElement(tokens.at(x));
+ }
+
+ int ret = 0;
+ if (tagHandle.ToElement()) {
+ TiXmlElement* elem = tagHandle.ToElement();
+ elem->SetAttribute(attribute, valueString);
+
+ // Do we really need this? We could just ignore this and remove the 'addAttribute' functions...
+ // Now, just get the ID.
+ int numSameTags;
+ TiXmlElement* child = ( storedHandle.FirstChildElement( tokens.at(0) ) ).ToElement();
+ for (numSameTags = 0; child; child = child->NextSiblingElement( tokens.at(0) ), ++numSameTags) {
+ // nothing
+ }
+ ret = numSameTags;
+ }
+ return ret;
+}
+
+//---------------------------------------------------------
+bool xmlIO::loadFromBuffer( string buffer )
+{
+
+ int size = buffer.size();
+
+ bool loadOkay = doc.ReadFromMemory( buffer.c_str(), size);//, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING);
+
+ return loadOkay;
+
+}
+//---------------------------------------------------------
+void xmlIO::copyXmlToString(string & str)
+{
+ TiXmlPrinter printer;
+ doc.Accept(&printer);
+
+ str = printer.CStr();
+}
+
diff --git a/rotord/src/xmlIO.h b/rotord/src/xmlIO.h
new file mode 100755
index 0000000..84db7ca
--- /dev/null
+++ b/rotord/src/xmlIO.h
@@ -0,0 +1,169 @@
+#ifndef __xmlIO_
+#define __xmlIO_
+
+//#include "ofMain.h"
+//based on xmlSettings from openframeworks, manythanks!
+#include <string.h>
+#include <vector>
+#include "tinyxml.h"
+#include "ofUtils.h"
+
+using namespace std;
+
+/*
+ Q: what is the which = 0 argument?
+
+ A: Glad you asked - most of the time you can ignore this and treat it as if it weren't there
+ But if specified it selects the nth tag with the same tag name at the current root of the document
+ Normally this just means the top level tags in the document - but if you use the pushTag and popTag
+ you can temporarily set the root of the document to be that specified tag.
+ The main idea is to allow you to have multiple tags with the same name.
+
+ So here is an example without pushTag
+
+ <time>102229</time> <-- which = 0
+ <time>298292</time> <-- which = 1
+ <time>393393</time> <-- which = 2
+ <time>447373</time> <-- which = 3
+
+ But if we wanted to group these into multiple <recording> tags and have multiple time values inside
+ we can use push and pop to move into the recording tags as if they were the document root
+
+ <recording> <-- we temporarily push into here with pushTag("recording", 0);
+ <time>19222</time> <-- to set this we call setValue("time", 19222, 0); ( which = 0 )
+ <time>23232</time> <-- to set this we call setValue("time", 23232, 1); ( which = 1 )
+ </recording> <-- we pop back out here with popTag();
+
+ <recording> <-- we temporarily push into here with pushTag("recording", 1); <-- now we use 1 to select the 2nd recording tag
+ <time>33342</time> <-- setValue("time", 33342, 0); ( which = 0 )
+ <time>22722</time> <-- setValue("time", 22722, 0); ( which = 1 )
+ </recording>
+
+*/
+
+
+#define MAX_TAG_VALUE_LENGTH_IN_CHARS 1024
+
+class xmlIO{
+
+ public:
+ xmlIO();
+ xmlIO(const string& xmlFile);
+
+ ~xmlIO();
+
+ void setVerbose(bool _verbose);
+
+ bool loadFile(const string& xmlFile);
+ bool saveFile(const string& xmlFile);
+ bool saveFile();
+
+ void clearTagContents(const string& tag, int which = 0);
+ void removeTag(const string& tag, int which = 0);
+
+ bool tagExists(const string& tag, int which = 0);
+
+ // removes all tags from within either the whole document
+ // or the tag you are currently at using pushTag
+ void clear();
+
+ int getValue(const string& tag, int defaultValue, int which = 0);
+ double getValue(const string& tag, double defaultValue, int which = 0);
+ string getValue(const string& tag, const string& defaultValue, int which = 0);
+
+ int setValue(const string& tag, int value, int which = 0);
+ int setValue(const string& tag, double value, int which = 0);
+ int setValue(const string& tag, const string& value, int which = 0);
+
+ //advanced
+
+ //-- pushTag/popTag
+ //pushing a tag moves you inside it which has the effect of
+ //temporarily treating the tag you are in as the document root
+ //all setValue, readValue and getValue commands are then be relative to the tag you pushed.
+ //this can be used with addValue to create multiple tags of the same name within
+ //the pushed tag - normally addValue only lets you create multiple tags of the same
+ //at the top most level.
+
+ bool pushTag(const string& tag, int which = 0);
+ int popTag();
+ int getPushLevel();
+
+ //-- numTags
+ //this only works for tags at the current root level
+ //use pushTag and popTag to get number of tags whithin other tags
+ // both getNumTags("PT"); and getNumTags("PT:X"); will just return the
+ //number of <PT> tags at the current root level.
+ int getNumTags(const string& tag);
+
+ //-- addValue/addTag
+ //adds a tag to the document even if a tag with the same name
+ //already exists - returns an index which can then be used to
+ //modify the tag by passing it as the last argument to setValue
+
+ //-- important - this only works for top level tags
+ // to put multiple tags inside other tags - use pushTag() and popTag()
+
+ int addValue(const string& tag, int value);
+ int addValue(const string& tag, double value);
+ int addValue(const string& tag, const string& value);
+
+ int addTag(const string& tag); //adds an empty tag at the current level
+
+
+ // Attribute-related methods
+ int addAttribute(const string& tag, const string& attribute, int value, int which = 0);
+ int addAttribute(const string& tag, const string& attribute, double value, int which = 0);
+ int addAttribute(const string& tag, const string& attribute, const string& value, int which = 0);
+
+ int addAttribute(const string& tag, const string& attribute, int value);
+ int addAttribute(const string& tag, const string& attribute, double value);
+ int addAttribute(const string& tag, const string& attribute, const string& value);
+
+ void removeAttribute(const string& tag, const string& attribute, int which = 0);
+ void clearTagAttributes(const string& tag, int which = 0);
+
+ int getNumAttributes(const string& tag, int which = 0);
+
+ bool attributeExists(const string& tag, const string& attribute, int which = 0);
+
+ bool getAttributeNames(const string& tag, vector<string>& outNames, int which = 0);
+
+ int getAttribute(const string& tag, const string& attribute, int defaultValue, int which = 0);
+ double getAttribute(const string& tag, const string& attribute, double defaultValue, int which = 0);
+ string getAttribute(const string& tag, const string& attribute, const string& defaultValue, int which = 0);
+
+ int setAttribute(const string& tag, const string& attribute, int value, int which = 0);
+ int setAttribute(const string& tag, const string& attribute, double value, int which = 0);
+ int setAttribute(const string& tag, const string& attribute, const string& value, int which = 0);
+
+ int setAttribute(const string& tag, const string& attribute, int value);
+ int setAttribute(const string& tag, const string& attribute, double value);
+ int setAttribute(const string& tag, const string& attribute, const string& value);
+
+ bool loadFromBuffer( string buffer );
+ void copyXmlToString(string & str);
+
+ TiXmlDocument doc;
+ bool bDocLoaded;
+
+ protected:
+
+ TiXmlHandle storedHandle;
+ int level;
+
+
+ int writeTag(const string& tag, const string& valueString, int which = 0);
+ bool readTag(const string& tag, TiXmlHandle& valHandle, int which = 0); // max 1024 chars...
+
+
+ int writeAttribute(const string& tag, const string& attribute, const string& valueString, int which = 0);
+
+ TiXmlElement* getElementForAttribute(const string& tag, int which);
+ bool readIntAttribute(const string& tag, const string& attribute, int& valueString, int which);
+ bool readDoubleAttribute(const string& tag, const string& attribute, double& outValue, int which);
+ bool readStringAttribute(const string& tag, const string& attribute, string& outValue, int which);
+};
+
+#endif
+