summaryrefslogtreecommitdiff
path: root/NT
diff options
context:
space:
mode:
Diffstat (limited to 'NT')
-rw-r--r--NT/src/graph.cpp65
-rw-r--r--NT/src/graph.h10
-rw-r--r--NT/src/libavwrapper.cpp724
-rw-r--r--NT/src/libavwrapper.h239
-rw-r--r--NT/src/nodes.h1
-rw-r--r--NT/src/nodes_audio_analysis.cpp475
-rw-r--r--NT/src/nodes_audio_analysis.h329
-rw-r--r--NT/src/rendercontext.cpp171
-rw-r--r--NT/src/rendercontext.h33
-rw-r--r--NT/src/rotor.h1
-rw-r--r--NT/src/rotord.obin136232 -> 0 bytes
-rw-r--r--NT/src/tinyxml.obin499688 -> 0 bytes
-rw-r--r--NT/src/tinyxmlerror.obin213368 -> 0 bytes
-rw-r--r--NT/src/tinyxmlparser.obin355616 -> 0 bytes
-rw-r--r--NT/src/vampHost.cpp416
-rw-r--r--NT/src/vampHost.h95
16 files changed, 2456 insertions, 103 deletions
diff --git a/NT/src/graph.cpp b/NT/src/graph.cpp
index 50db271..ad89061 100644
--- a/NT/src/graph.cpp
+++ b/NT/src/graph.cpp
@@ -18,6 +18,71 @@ bool Graph::check_audio(string audio,string path){
}
return false;
}
+bool Graph::load_audio(const std::string &filename) {
+ Logger& logger = Logger::get(id);
+ if (filename.size()==0) {
+ logger.information("ERROR: empty filename for audio analysis");
+ return false;
+ }
+ libav::audio_decoder loader;
+ if (!loader.open(filename)) {
+ logger.error("ERROR: Could not open audio: "+filename);
+ return false;
+ }
+ logger.information("Analysing "+filename);
+
+ duration=loader.get_duration();
+
+ int rate = loader.get_sample_rate();
+ int samples = loader.get_number_samples();
+ int channels= loader.get_number_channels();
+ int bits = loader.get_bit_depth();
+
+ vector<Audio_processor*> processors;
+ //processors.push_back(audio_thumb);
+ for (auto a: nodes) {
+ if (dynamic_cast<Audio_processor*>(a.second)){
+ processors.push_back(dynamic_cast<Audio_processor*>(a.second));
+ }
+ }
+ for (auto p: processors) {
+ if(!p->init(channels,bits,samples,rate) ){
+ logger.error("ERROR: Audio plugin failed to initialse");
+ return false;
+ }
+ }
+
+ bool finished=false;
+ uint16_t *audio=new uint16_t[1024*loader.get_number_channels()];
+ uint64_t sample=0;
+
+ while (!finished&&!cancelled)
+ {
+ if (loader.get_samples(audio,sample,1024)) {
+ //now we can pass the data to the processor(s)
+ for (auto p: processors) {
+ p->process_frame((uint8_t*)audio,1024);
+ }
+ sample+=1024;
+ //mutex.lock();
+ progress=((double)sample)/samples; //atomic on 64 bit?
+ //mutex.unlock();
+
+ }
+ else finished=true;
+ }
+
+ loader.cleanup();
+
+ for (auto p: processors) {
+ p->cleanup();
+ p->print_summary();
+ }
+
+ logger.information("Finished audio analysis");
+ //audio_loaded=true;
+ return true;
+}
bool Graph::load_file(std::string filename,std::string media_path){
Logger& logger = Logger::get(id);
Poco::File f=Poco::File(filename);
diff --git a/NT/src/graph.h b/NT/src/graph.h
index cef2fb8..00aee48 100644
--- a/NT/src/graph.h
+++ b/NT/src/graph.h
@@ -56,7 +56,7 @@ namespace Rotor {
//--context// Json::Value preview(std::string &node ,std::string &format,int frame,int w,int h);
bool check_audio (std::string audio ,std::string path);
//--context// Json::Value print_features (std::string &node);
- //bool load_audio(const std::string &filename, std::vector<Audio_processor*> processors);
+ bool load_audio(const std::string &filename);
//bool load_video(const std::string &node_id,const std::string &filename);
//load audio and video should be methods of the nodes themselves?
bool loaded;
@@ -69,11 +69,17 @@ namespace Rotor {
//Log_name=_Log_name;
//}
- //Audio_thumbnailer *audio_thumb;
+ //Audio_thumbnail *audio_thumb;
+ //how does audio thumbnail fit in with the new plan?
+ //a graph has A audio thumbnail to represent THE audio track?
+ //is this important any more? I don't think Josep uses it?
+
+ //also need to review workflow with audio
private:
std::string id;
std::unordered_map<std::string,Node*> nodes;
+ double duration;
};
}
diff --git a/NT/src/libavwrapper.cpp b/NT/src/libavwrapper.cpp
new file mode 100644
index 0000000..4a8315f
--- /dev/null
+++ b/NT/src/libavwrapper.cpp
@@ -0,0 +1,724 @@
+#include "libavwrapper.h"
+
+Poco::Mutex mutex;
+
+#include <stdexcept>
+#include <iostream>
+#include <cassert>
+
+using namespace std;
+using namespace Poco;
+
+static bool b_is_one_time_inited=false;
+static int sws_flags = SWS_BICUBIC;
+
+void libav::maybeInitFFMpegLib()
+{
+ if (b_is_one_time_inited)
+ return;
+ FFMS_Init(0, 0); //should do for all
+ //av_register_all();
+ //avcodec_register_all();
+ avformat_network_init();
+ b_is_one_time_inited = true;
+}
+
+ void libav::video_decoder::cleanup(){
+ if (loaded) {
+ Mutex::ScopedLock lock(mutex);
+ FFMS_DestroyVideoSource(source);
+ loaded=false;
+ }
+}
+
+bool libav::video_decoder::open(const std::string& filename){
+ cleanup();
+ Mutex::ScopedLock lock(mutex);
+ Poco::File f=Poco::File(filename);
+ if (!f.exists()) {
+ cerr<<"ERROR: "<<filename<<" does not exist"<<endl;
+ return false;
+ }
+
+ //first check if an index object exists
+ Poco::StringTokenizer tokens(filename,".");
+ string idxfile="";
+ if (tokens.count()>1){
+ for (uint32_t i=0;i<tokens.count()-1;i++){
+ idxfile+=tokens[i];
+ idxfile+=".";
+ }
+ idxfile+="idx";
+ }
+ else idxfile=filename+".idx";
+
+ f=Poco::File(idxfile);
+ bool makeindex=true;
+ FFMS_Index *index;
+ if (f.exists()) {
+ index=FFMS_ReadIndex(idxfile.c_str(),&err);
+ if (index) {
+ cerr<<"FFMS2: loaded index "<<idxfile<<endl;
+ if (FFMS_IndexBelongsToFile(index,filename.c_str(),&err)==0){
+ makeindex=false;
+ }
+ }
+ }
+ if (makeindex) {
+ index = FFMS_MakeIndex(filename.c_str(), 0, 0, NULL, NULL, FFMS_IEH_IGNORE, NULL, NULL, &err);
+ if (index) {
+ FFMS_WriteIndex(idxfile.c_str(),index,&err);
+ cerr<<"FFMS2: created index "<<idxfile<<endl;
+ }
+ cerr<<"FFMS2: "<<filename<<" cannot be indexed "<<endl;
+ }
+ if (index == NULL) {
+ std::cerr<<"ffmpegsource: "<<err.Buffer<<std::endl;
+ return false;
+ }
+ int trackno = FFMS_GetFirstTrackOfType(index, FFMS_TYPE_VIDEO, &err);
+ if (trackno < 0) {
+ std::cerr<<"ffmpegsource: "<<err.Buffer<<std::endl;
+ return false;
+ }
+ source = FFMS_CreateVideoSource(filename.c_str(), trackno, index, 1, FFMS_SEEK_NORMAL, &err);
+ if (source == NULL) {
+ std::cerr<<"ffmpegsource: "<<err.Buffer<<std::endl;
+ return false;
+ }
+ FFMS_DestroyIndex(index);
+ props = FFMS_GetVideoProperties(source);
+ const FFMS_Frame *propframe = FFMS_GetFrame(source, 0, &err);
+ w=propframe->EncodedWidth;
+ h=propframe->EncodedHeight;
+ //propframe->EncodedPixelFormat;
+
+ if (FFMS_SetOutputFormatV2(source, pixfmts, propframe->EncodedWidth, propframe->EncodedHeight, FFMS_RESIZER_BICUBIC, &err)) {
+ std::cerr<<"ffmpegsource: "<<err.Buffer<<std::endl;
+ return false;
+ }
+
+ //std::cerr<<"ffmpegsource: successfully opened "<<filename<<std::endl;
+ loaded=true;
+ return loaded;
+}
+
+bool libav::audio_decoder::open(const std::string& filename){
+ Mutex::ScopedLock lock(mutex);
+ loaded=false;
+ FFMS_Index *index = FFMS_MakeIndex(filename.c_str(),-1, 0, NULL, NULL, FFMS_IEH_IGNORE, NULL, NULL, &err);
+ if (index == NULL) {
+ std::cerr<<"ffmpegsource error making index for "<<filename<<":"<<err.Buffer<<std::endl;
+ return false;
+ }
+ int trackno = FFMS_GetFirstTrackOfType(index, FFMS_TYPE_AUDIO, &err);
+ if (trackno < 0) {
+ std::cerr<<"ffmpegsource error finding audio track in "<<filename<<":"<<err.Buffer<<std::endl;
+ return false;
+ }
+ std::cerr<<"ffmpegsource found audio track "<<trackno<<" in "<<filename<<":"<<err.Buffer<<std::endl;
+ source = FFMS_CreateAudioSource(filename.c_str(), trackno, index, FFMS_DELAY_TIME_ZERO, &err);
+ if (source == NULL) {
+ std::cerr<<"ffmpegsource error creating audio source from "<<filename<<":"<<err.Buffer<<std::endl;
+ return false;
+ }
+ FFMS_DestroyIndex(index);
+ props = FFMS_GetAudioProperties(source);
+
+ std::cerr<<"ffmpegsource: successfully opened "<<filename<<std::endl;
+ loaded=true;
+ return loaded;
+}
+ void libav::audio_decoder::cleanup(){
+ if (loaded) {
+ Mutex::ScopedLock lock(mutex);
+ FFMS_DestroyAudioSource(source);
+ loaded=false;
+ }
+}
+
+bool libav::exporter::setup(int w,int h, int bitRate, int frameRate, std::string container, bool _fragmentation){
+
+ maybeInitFFMpegLib();
+
+ fragmentation=_fragmentation;
+
+ this->w=w;
+ this->h=h;
+ this->bitRate=bitRate;
+ this->frameRate=frameRate;
+ this->container=container;
+
+ if (NULL != sws_ctx) {
+ sws_freeContext(sws_ctx);
+ sws_ctx = NULL;
+ }
+
+ sws_ctx = sws_getContext(w, h, AV_PIX_FMT_RGB24,
+ w, h, AV_PIX_FMT_YUV420P,
+ sws_flags, NULL, NULL, NULL);
+
+ 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;
+
+ av_opt_set(oc->priv_data, "profile", "baseline", AV_OPT_SEARCH_CHILDREN);
+
+
+ // 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_H264; //AV_CODEC_ID_MPEG4
+ fmt->audio_codec=AV_CODEC_ID_AAC; //guessing, 011013
+
+
+ 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);
+ 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 = 75; /* emit one intra frame every 75 frames */
+ video_st->codec->pix_fmt = PIX_FMT_YUV420P;
+
+ if (bitRate){
+ cerr<<"Libav::exporter setting bitrate to "<<bitRate<<endl;
+
+ video_st->codec->bit_rate = bitRate; //need to deal with resolution etc
+ video_st->codec->rc_max_rate = bitRate;
+ video_st->codec->rc_min_rate = 0;
+
+ //added 131113
+ video_st->codec->rc_buffer_size = bitRate * 2;
+ }
+
+ //av_dict_set(&options, "profile", "baseline", 0);
+ //video_st->codec->flags2 = CODEC_FLAG2_FASTPSKIP;
+ video_st->codec->profile = FF_PROFILE_H264_BASELINE;
+ video_st->codec->level = 13;
+
+
+ //how to set profile????? 190913
+ //this causes a crash
+ //av_opt_set(video_st->codec->priv_data,"profile",FF_PROFILE_H264_CONSTRAINED_BASELINE, 0);
+ //
+ // http://libav-users.943685.n4.nabble.com/Libav-user-encoding-bitrate-wrong-td4433740.html
+ // http://libav-users.943685.n4.nabble.com/Libav-user-H264-encoding-set-target-average-bitrate-td3912529.html
+ // http://trac.ffmpeg.org/wiki/x264EncodingGuide
+
+
+
+ // Now that all the parameters are set, we can open the audio and
+ // * video codecs and allocate the necessary encode buffers. //
+
+ if (video_st) {
+ if (!open_video(oc, video_codec, video_st)){
+ return false;
+ }
+ }
+ if (audio_st) {
+ audioframesize=open_audio(oc, audio_codec, audio_st);
+ audiostep=((double)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::ScopedLock lock(mutex);
+ int ret = avio_open(&oc->pb, filename.c_str(), AVIO_FLAG_WRITE);
+ if (ret < 0) {
+ std::cerr <<"Could not open " << filename.c_str() << std::endl;
+ return false;
+ }
+ }
+
+ AVDictionary *opts = NULL; // "create" an empty dictionary
+
+ // https://libav.org/avprobe.html#MOV_002fMP4_002fISMV
+
+ //THIS DOES SEEM TO SET CONTAINER OPTS= AS MOOV_SIZE MAKES THE MOVIE BANJAXED
+ //av_dict_set(&opts, "moov_size", "20000", 0);
+ if (fragmentation) {
+ av_dict_set(&opts, "movflags","frag_keyframe", 0); //makes a video watchable in chrome, quicktime & mplayer
+ //av_dict_set(&opts, "movflags","empty_moov",1);
+ }
+
+
+ // Write the stream header, if any. //
+ int ret = avformat_write_header(oc, &opts);
+ if (ret < 0) {
+ std::cerr <<"Error occurred when opening output file:" <<endl; // av_err2str(ret) << std::endl;
+ return false;
+ }
+ //#include <libavformat/movenc.h>
+ //mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s)
+ /*ret = mov_write_moov_tag(oc, NULL,NULL);
+ if (ret < 0) {
+ std::cerr <<"Error occurred when writing moov atom " <<endl; // << 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,bool keyframe){
+ video_pts = (double)video_st->pts.val * video_st->time_base.num / video_st->time_base.den;
+ write_video_frame(oc, video_st, pixels,keyframe);
+ 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::ScopedLock lock(mutex);
+ avio_close(oc->pb);
+ }
+
+ // free the stream //
+ avformat_free_context(oc);
+}
+
+AVStream* libav::exporter::add_stream(AVFormatContext *oc, AVCodec **codec,enum AVCodecID codec_id)
+ {
+ //removed some of the repeats from record() 141113
+ 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 = 160000;
+ 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 = bitRate; //need to deal with resolution etc
+
+ // c->rc_max_rate = bitRate;
+ //c->rc_min_rate = 0;
+ //c->rc_buffer_size = Profile()->m_videoMaxVopSize; //??
+
+ /*
+ all taken out 091113 to try to stop making incompatible movies
+ */
+
+
+ // 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;
+ }
+
+bool libav::exporter::open_video(AVFormatContext *oc, AVCodec *codec, AVStream *st)
+ {
+
+ int ret;
+ AVCodecContext *c = st->codec;
+
+
+ // open the codec //
+ {
+ Mutex::ScopedLock lock(mutex);
+ ret = avcodec_open2(c, codec, NULL); //OPTIONS CAN GO HERE
+
+ }
+ if (ret < 0) {
+ //string e=av_err2str(ret);
+ cerr<<"Could not open video codec, avcodec_open2 returned "<<ret<<endl;
+ return false;
+ }
+
+ // allocate and init a re-usable frame //
+ frame = avcodec_alloc_frame();
+
+ // moved to constructor and freeing in destructor -- stills crashes the same
+ if (!frame) {
+ cerr<<"Could not allocate video frame "<<endl;
+ return false;
+ }
+
+ // Allocate the encoded raw picture. //
+ ret = avpicture_alloc(&dst_picture, c->pix_fmt, c->width, c->height);
+ if (ret < 0) {
+ //av_err2str(ret)<<
+ cerr<<"Could not allocate picture"<<endl;
+ return false;
+ }
+
+ // 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) {
+ //<<av_err2str(ret)
+ cerr<<"Could not allocate temporary picture"<<endl;
+ return false;
+ }
+ }
+
+ // 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));
+ return true;
+ }
+
+ 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); 111013 NOT NECESSARY
+ 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;
+
+ //avcodec_free_frame(&frame); ///removed 091013
+
+
+ // Write the compressed frame to the media file. //
+ ret = av_interleaved_write_frame(oc, &pkt);
+ av_free_packet(&pkt);
+ if (ret != 0) {
+ //fprintf(stderr, "Error while writing audio frame: %s\n",
+ // av_err2str(ret));
+ exit(1);
+ }
+
+ }
+
+ void libav::exporter::write_audio_frame(AVFormatContext *oc, AVStream *st,AVPacket *pkt)
+ {
+
+ 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); ///added 091013
+ }
+
+ 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, bool keyframe)
+ {
+ int ret;
+
+ AVCodecContext *c = st->codec;
+
+ 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); ///removed 101013 NOT NECESSARY
+
+ 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);
+ av_free_packet(&pkt); ///added 091013
+ } else {
+ AVPacket pkt = { 0 };
+ int got_packet;
+
+ if (keyframe) pkt.flags |= AV_PKT_FLAG_KEY;
+
+ 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;
+ }
+ av_free_packet(&pkt); ///added 091013
+ }
+
+
+
+
+ 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();
+ } \ No newline at end of file
diff --git a/NT/src/libavwrapper.h b/NT/src/libavwrapper.h
new file mode 100644
index 0000000..162a77e
--- /dev/null
+++ b/NT/src/libavwrapper.h
@@ -0,0 +1,239 @@
+#ifndef libavwrapper_H
+#define libavwrapper_H
+
+#ifndef UINT64_C
+#define UINT64_C(c) (c ## ULL)
+#endif
+
+#include "Poco/Mutex.h"
+#include "Poco/ScopedLock.h"
+#include "Poco/StringTokenizer.h"
+#include "Poco/File.h"
+
+extern Poco::Mutex mutex; //application wide mutex
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavutil/pixfmt.h>
+#include <libavutil/opt.h>
+#include <libswscale/swscale.h>
+}
+
+#include <string>
+#include <stdexcept>
+#include <iostream>
+#include <fstream>
+#include <math.h>
+#include <vector>
+
+#include <ffms.h>
+
+
+namespace libav {
+ // Some libavcodec calls are not reentrant
+ void maybeInitFFMpegLib();
+
+ class video_decoder
+ {
+ public:
+ video_decoder(){
+ maybeInitFFMpegLib();
+ pixfmts[0] = FFMS_GetPixFmt("rgb24");
+ pixfmts[1] = -1;
+ h=0;
+ w=0;
+ source=NULL;
+ loaded=false;
+ err.Buffer = errmsg;
+ err.BufferSize = sizeof(errmsg);
+ err.ErrorType = FFMS_ERROR_SUCCESS;
+ err.SubType = FFMS_ERROR_SUCCESS;
+ }
+ ~video_decoder(){
+ cleanup();
+ }
+ void cleanup();
+ bool open(const std::string& filename);
+ double get_framerate(){
+ if (loaded) return (((double)props->FPSNumerator)/((double)props->FPSDenominator));
+ else return -1.0;
+ }
+ int get_number_frames(){
+ if (loaded) return props->NumFrames;
+ else return -1;
+ }
+ int get_number_channels(){
+ return 3; //this is what we convert to
+ }
+ int get_width(){
+ return w;
+ }
+ int get_height(){
+ return h;
+ }
+ bool fetch_frame(int width,int height,int wanted){
+ if (FFMS_SetOutputFormatV2(source, pixfmts, width, height, FFMS_RESIZER_BICUBIC, &err)) {
+ std::cerr<<"ffmpegsource: "<<err.Buffer<<std::endl;
+ return false;
+ }
+ frame = FFMS_GetFrame(source, wanted%props->NumFrames, &err);
+ if (frame == NULL) {
+ std::cerr<<"ffmpegsource: "<<err.Buffer<<std::endl;
+ return false;
+ }
+ return true;
+
+ }
+
+ FFMS_VideoSource *source;
+ const FFMS_VideoProperties *props;
+ const FFMS_Frame *frame;
+ FFMS_ErrorInfo err;
+ char errmsg[1024];
+ int pixfmts[2];
+ bool loaded;
+ int h,w;
+ };
+
+ class audio_decoder
+ {
+ public:
+ audio_decoder(){
+ maybeInitFFMpegLib();
+ source=nullptr;
+ props=nullptr;
+ loaded=false;
+ err.Buffer = errmsg;
+ err.BufferSize = sizeof(errmsg);
+ err.ErrorType = FFMS_ERROR_SUCCESS;
+ err.SubType = FFMS_ERROR_SUCCESS;
+ }
+ ~audio_decoder(){
+ cleanup();
+ }
+ void cleanup();
+ bool open(const std::string& filename);
+ int get_format(){
+ if (props) return props->SampleFormat;
+ else return 0;
+ }
+ int get_sample_rate(){
+ if (props) return props->SampleRate;
+ else return 0;
+ }
+ int get_bit_depth(){
+ if (props) return props->BitsPerSample;
+ else return 0;
+ }
+ int get_number_channels(){
+ if (props) return props->Channels;
+ else return 0;
+ }
+ int get_number_samples(){
+ if (props) return props->NumSamples;
+ else return 0;
+ }
+ int64_t get_channel_layout(){
+ if (props) return props->ChannelLayout;
+ else return 0;
+ }
+ double get_duration(){
+ if (props) return ((double)props->NumSamples)/props->SampleRate;
+ else return 0;
+ }
+ bool get_samples(void *buf,int64_t start, int64_t count){
+ if (source) {
+ if (FFMS_GetAudio(source, buf, start, count, &err)) {
+ std::cerr<<"ffmpegsource: "<<err.Buffer<<std::endl;
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ FFMS_AudioSource *source;
+ const FFMS_AudioProperties *props;
+ FFMS_Frame *frame;
+ FFMS_ErrorInfo err;
+ char errmsg[1024];
+ bool loaded;
+ };
+
+ class exporter {
+ public:
+ exporter(){
+ sws_ctx = NULL;
+ fragmentation=false;
+ }
+ virtual ~exporter(){
+ if (NULL != sws_ctx) {
+ sws_freeContext(sws_ctx);
+ sws_ctx = NULL;
+ }
+ };
+ bool setup(int w,int h, int bitRate, int frameRate, std::string container, bool _fragmentation);
+ 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 keyframe=false);
+ bool encodeFrame(uint16_t *samples);
+ void finishRecord();
+ int get_audio_framesize(){return audioframesize;};
+ double get_audio_step(){return audiostep;};
+
+ AVStream *add_stream(AVFormatContext *oc, AVCodec **codec,enum AVCodecID codec_id); //AVCodecID
+ bool 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, bool keyframe=false);
+ 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;
+ double 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;
+
+ bool fragmentation;
+
+
+ //************************************************************//
+ // audio output //
+
+ double t, tincr, tincr2;
+ int audio_input_frame_size;
+
+
+ };
+
+}
+
+
+
+#endif // libavwrapper_H
diff --git a/NT/src/nodes.h b/NT/src/nodes.h
index 21b1fc4..cf1fd3e 100644
--- a/NT/src/nodes.h
+++ b/NT/src/nodes.h
@@ -2,6 +2,7 @@
#define ROTOR_NODES_H
#include "rotor.h"
+#include "nodes_audio_analysis.h"
using namespace std;
diff --git a/NT/src/nodes_audio_analysis.cpp b/NT/src/nodes_audio_analysis.cpp
new file mode 100644
index 0000000..1890b8c
--- /dev/null
+++ b/NT/src/nodes_audio_analysis.cpp
@@ -0,0 +1,475 @@
+#include "nodes_audio_analysis.h"
+
+namespace Rotor{
+ bool Audio_thumbnailer::init(int _channels,int _bits,int _samples,int _rate) {
+ //base_audio_processor::init(_channels,_bits,_samples);
+ channels=_channels;
+ bits=_bits;
+ samples=_samples;
+ samples_per_column=samples/width;
+ offset=0x1<<(bits-1); //signed audio
+ scale=1.0/offset;
+
+ out_sample=0; //sample in whole track
+ 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) {
+ //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
+ //why does valgrind complain here about uninitialised vars
+ double mean=pow(accum/samples,0.5f);
+ audiodata.push_back(mean);
+ sample=0;
+ samples=0;
+ accum=0.0;
+ }
+ }
+ return out_sample;
+ }
+ void Audio_thumbnailer::print_vector(xmlIO XML){
+ string vdata;
+ int i=0;
+ for (auto sample: audiodata){
+ if (i>0) vdata+=",";
+ vdata+=toString(sample);
+ i++;
+ }
+ XML.addValue("data",vdata);
+ }
+ bool Vamp_node::init(int _channels,int _bits,int _samples, int _rate) {
+ //need these to make sense of data
+ channels=_channels;
+ bits=_bits;
+ samples=_samples;
+
+ features.clear();
+ 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 Vamp_node::process_frame(uint8_t *data,int samples_in_frame) {
+ analyser.process_frame(data,samples_in_frame);
+ return 1;
+ }
+ void Vamp_node::cleanup() {
+ analyser.cleanup();
+ features=analyser.features;
+ }
+ string Vamp_node::get_features(){
+ string data;
+ for (auto i: features) {
+ data=data+" ["+toString(i.second.number)+":"+toString(i.first);
+ if (i.second.values.size()) {
+ data+=" (";
+ bool first=true;
+ for (auto j: i.second.values) {
+ if (first){
+ first=false;
+ }
+ else data+=",";
+ data=data+toString(j);
+ }
+ data+=") ";
+ }
+ data+="]";
+ }
+ return data;
+ }
+ bool sortsegments(std::pair<int,double> i,std::pair<int,double> j){
+ return (i.second<j.second);
+ }
+
+ bool sortseggrps(std::pair<double,vector<pair<double,int> > > i,std::pair<double,vector<pair<double,int> > > j){
+ return (i.first<j.first);
+ }
+ bool sortgroupmembers(pair<double,int> i,pair<double,int> j){
+ return (i.first<j.first);
+ }
+ void Intensity_segmenter::cleanup(){
+ //algorithm idea:
+ //get average tempo and intensity for each segment and store them
+ //scale by the range to get a value from 0.0 to 1.0
+ //add tempo and intensity according to a weighting
+ //score the results (ie 1st place, 2nd place) to end up with a set of integer numbers
+
+ //how to group with similarity?
+ //segments come with similarity groups
+ // 1 - are the wanted git checksegments less than discovered?
+ // N - do nothing
+ // 2 - get intensity and tempo averages
+ // 2 - count the groups
+ // 3 - are the groups less than the discovered segments?
+ // N - group by intensity as normal
+ // 4 - are the groups less than the wanted levels?
+
+
+ //for (auto a:analysers) a.second.cleanup(); //WHY NOT WORK - its as if the call is const
+ analysers["segmenter"].cleanup();
+ analysers["tempo"].cleanup();
+ analysers["intensity"].cleanup();
+
+ //combine with similarity numbers
+ // 1. count similarity numbers
+
+ map<int,vector<int> > similarities;
+
+ //what do we want to know about these similarities?
+ // how many are there? map.size()
+ // how many members are in each one? map[item].size()
+ // which members are they? auto m: map[item]
+
+ uint32_t i=0;
+ for (auto f:analysers["segmenter"].features) {
+ if (f.second.values.size()) {
+ int group=f.second.values[0];
+ if (similarities.find(group)==similarities.end()){
+ similarities[group]={};
+ }
+ similarities[group].push_back(i);
+ }
+ i++;
+ }
+ /*
+ for (auto s:similarities) {
+ string list="";
+ for (int j=0;j<s.second.size();j++){
+ if (j>0) list+=",";
+ list +=toString(s.second[j]);
+ }
+ cerr<<"group "<<s.first<<" ["<<list<<"]"<<endl;
+ }
+ */
+
+ cerr<<analysers["segmenter"].features.size()<<" segments"<<endl;
+ cerr<<analysers["tempo"].features.size()<<" tempo features"<<endl;
+ cerr<<analysers["intensity"].features.size()<<" intensity features"<<endl;
+ i=0;
+ double min_tempo=9999999.0;
+ double min_intensity=9999999.0;
+ double max_tempo=0.0;
+ double max_intensity=0.0;
+ vector<double> tempos;
+ vector<double> intensities;
+ vector<double> times;
+ auto g=++analysers["segmenter"].features.begin();
+ for (auto f=analysers["segmenter"].features.begin();g!=analysers["segmenter"].features.end();f++,g++,i++){
+ times.push_back(f->first);
+ //integrate tempo and intensity algorithmically
+ double tempo=0;
+ if (analysers["tempo"].features.size()) {
+ double pt=f->first;
+ double pv=analysers["tempo"].get_value(f->first);
+ for (auto u=analysers["tempo"].features.upper_bound(f->first);u!=analysers["tempo"].features.upper_bound(g->first);u++){
+ tempo +=(u->first-pt)*(u->second.values[0]+pv)*0.5f; //area of the slice
+ pt=u->first;
+ pv=u->second.values[0];
+ }
+ tempo +=(g->first-pt)*(analysers["tempo"].get_value(g->first)+pv)*0.5f; //area of the last slice
+ tempo /=g->first-f->first; //average value;
+ }
+ if (tempo>max_tempo) max_tempo=tempo;
+ if (tempo<min_tempo) min_tempo=tempo;
+ tempos.push_back(tempo);
+
+ double intensity=0;
+ if (analysers["intensity"].features.size()) {
+ double pt=f->first;
+ double pv=analysers["intensity"].get_value(f->first);
+ for (auto u=analysers["intensity"].features.upper_bound(f->first);u!=analysers["intensity"].features.upper_bound(g->first);u++){
+ intensity +=(u->first-pt)*(u->second.values[0]+pv)*0.5f; //area of the slice
+ pt=u->first;
+ pv=u->second.values[0];
+ }
+ intensity +=(g->first-pt)*(analysers["intensity"].get_value(g->first)+pv)*0.5f; //area of the last slice
+ intensity /=g->first-f->first; //average value;
+ }
+ if (intensity>max_intensity) max_intensity=intensity;
+ if (intensity<min_intensity) min_intensity=intensity;
+ intensities.push_back(intensity);
+
+ //cerr<<"segment "<<i<<": "<<f->first<<" to "<<g->first<<" average tempo: "<<tempo<<" ,intensity: "<<intensity<<" ,weighted: "<<(tempo*parameters["tempo_weight"]->value)+(intensity*parameters["intensity_weight"]->value)<<endl;
+ }
+ //
+ //
+ //need to calculate the last segment
+ //
+ //
+ //either by adding a bit of code here or by adding a dummy feature at the track duration, previously
+ //
+ //
+ //
+ //
+
+
+ //make relative scale 0.0-1.0 and save weighted totals
+ vector< pair<int,double>> totals;
+ vector<double> totalsmap;
+ for (i=0;i<tempos.size();i++){
+ tempos[i]=(tempos[i]-min_tempo)/(max_tempo-min_tempo);
+ intensities[i]=(intensities[i]-min_intensity)/(max_intensity-min_intensity);
+ totals.push_back(make_pair(i,(tempos[i]*parameters["tempo_weight"]->value)+(intensities[i]*parameters["intensity_weight"]->value)));
+ totalsmap.push_back((tempos[i]*parameters["tempo_weight"]->value)+(intensities[i]*parameters["intensity_weight"]->value));
+ }
+
+ /*
+ //sort and convert to features
+ std::sort(totals.begin(),totals.end(),sortsegments);
+ for (i=0;i<totals.size();i++) {
+ cerr<<"segment "<<totals[i].first<<" average intensity: "<<totals[i].second<<endl;
+ }
+ vector<double> bucketoffsets;
+ for (auto t:totals) bucketoffsets.push_back(0.0);
+ if (parameters["levels"]->value>0.0&&parameters["levels"]->value<totals.size()){
+ //use bucketoffsets to redistribute into smaller number of buckets
+ int numbertoredistribute=totals.size()-((int)parameters["levels"]->value);
+ double numberperbin=((double)numbertoredistribute/totals.size());
+ double toadd=0.5f;
+ int added=0;
+ for (int j=0;j<totals.size();j++){
+ int numbertoadd=min(numbertoredistribute-added,(int)toadd);
+ toadd=(toadd+numberperbin)-numbertoadd;
+ added+=numbertoadd;
+ bucketoffsets[j]=added;
+ }
+ if (numbertoredistribute>0) {
+ cerr<<"reducing number of levels by "<<numbertoredistribute<<", offsets:"<<endl;
+ for (auto o:bucketoffsets) {
+ cerr<<o<<":";
+ }
+ cerr<<endl;
+ }
+ }
+
+ for (i=0;i<totals.size();i++){
+ vampHost::feature f;
+ f.values.push_back((double)i-bucketoffsets[i]);
+ features[times[totals[i].first]]=f;
+ }
+/*
+ /*
+sort intensity totals
+find out how many segments will share levels apart from similarity levels
+start with a structure:
+map<inputnum,vector<pair<tempo,inputnum>>
+start grouping by similarity
+if there are more similarity groups than wantedgroups, start by grouping similarities
+otherwise take biggest similarity groups and split them by intensity
+if there are still too many groups, merge closest smallest groups
+finally sort by intensity to map output
+
+nned to retrieve total intensity by segment
+ */
+ // segment group_intensity seg_intense segment
+ vector<pair<double,vector<pair<double,int> > > > seggrps;
+ vector<pair<double,vector<pair<double,int> > > > oldgrps;
+ for (i=0;i<totalsmap.size();i++){
+ vector<pair<double,int> > data;
+ data.push_back(make_pair(totalsmap[i],i));
+ oldgrps.push_back(make_pair(totalsmap[i],data));
+ }
+
+ for (auto s:similarities){
+ //similarities is a collection of similarity groups in no particular order, referring to segment nos
+ //at this point seggrps is in segment order
+ if (s.second.size()>1){
+ for (int j=s.second.size()-1;j>0;j--){
+ oldgrps[s.second[0]].second.push_back(make_pair(totalsmap[s.second[j]],s.second[j]));
+ //keep running average// should be by area?
+ //seggrps[s.second[0]].first+=(totalsmap[s.second[j]]*(1.0/max(1,(int)seggrps[s.second[0]].second.size()-1)));
+ //double div=seggrps[s.second[0]].second.size()==1?1.0:((double)seggrps[s.second[0]].second.size()-1/(double)seggrps[s.second[0]].second.size());
+ //neat! this gives 1,1/2,2/3,3/4..
+ //seggrps[s.second[0]].first*=div;
+
+ //seggrps.erase(seggrps.begin()+s.second[j]);
+ //after this has happened, seggrpgs indexing can be invalid
+
+ }
+
+
+ //easier is to
+ double avg=0.0f;
+ for (auto p:oldgrps[s.second[0]].second) avg+=p.first;
+ avg/=oldgrps[s.second[0]].second.size();
+ oldgrps[s.second[0]].first=avg;
+
+ }
+ seggrps.push_back(oldgrps[s.second[0]]);
+ }
+
+ //cerr<<"similarities assigned, "<<(totalsmap.size()-seggrps.size())<<" segments merged"<<endl;
+
+ /*
+ i=0;
+ for (auto s:seggrps) {
+ string list="";
+ for (int j=0;j<s.second.size();j++){
+ if (j>0) list+=",";
+ list +=toString(s.second[j].second);
+ }
+ cerr<<"segment "<<i<<" ["<<list<<"]"<<endl;
+ i++;
+ }
+ */
+ //sort the contents by intensity
+ std::sort(seggrps.begin(),seggrps.end(),sortseggrps);
+ //cerr<<"groups sorted by intensity:"<<endl;
+ //possible mergers will be with groups with adjacent intensity
+ i=0;
+ /*
+ for (auto s:seggrps) {
+ string list="";
+ for (int j=0;j<s.second.size();j++){
+ if (j>0) list+=",";
+ list +=toString(s.second[j].second);
+ }
+ cerr<<"segment "<<i<<" ["<<list<<"]"<<endl;
+ i++;
+ }
+ */
+
+ if (((int)parameters["levels"]->value)>0) {
+ if (seggrps.size()>(int)parameters["levels"]->value){
+ while (seggrps.size()>(int)parameters["levels"]->value){
+ //reduce similarity groups
+ //decide the best 2 to merge
+ vector<double> diffs;
+ for (int j=0;j<seggrps.size()-1;j++) diffs.push_back(seggrps[j+1].first-seggrps[j].first);
+ int smallest=0;
+ for (int j=1;j<diffs.size();j++) if (diffs[j]<diffs[smallest]) smallest=j;
+ for (int j=0;j<seggrps[smallest].second.size();j++) {
+ seggrps[smallest+1].second.push_back(seggrps[smallest].second[j]);
+ //cerr<<"copied segment "<<(seggrps[smallest].second[j].second)<<" from group "<<smallest<<" to group "<<(smallest+1)<<endl;
+ }
+ //recalculate intensity average
+ double avg=0.0f;
+ for (auto p:seggrps[smallest+1].second) avg+=p.first;
+ avg/=seggrps[smallest+1].second.size();
+ seggrps[smallest+1].first=avg;
+
+ seggrps.erase(seggrps.begin()+smallest);
+ //cerr<<"removed group "<<smallest<<endl;
+ }
+ cerr<<"intensities merged, "<<seggrps.size()<<" levels remain"<<endl;
+ }
+ //cerr<<seggrps.size()<<" groups, "<<(int)parameters["levels"]->value<<" levels requested, "<<(int)totalsmap.size()<<" original segments"<<endl;
+ if (seggrps.size()<min((int)parameters["levels"]->value,(int)totalsmap.size())){
+ while (seggrps.size()<min((int)parameters["levels"]->value,(int)totalsmap.size())) {
+ //split groups
+ //calculate standard deviation of intensity variation
+ vector<double> devs;
+ for (int j=0;j<seggrps.size()-1;j++) {
+ double avg=0.0;
+ double dev=0.0;
+ for (int k=0;k<seggrps[j].second.size();k++){
+ avg+=seggrps[j].second[k].first;
+ }
+ avg/=seggrps[j].second.size();
+ for (int k=0;k<seggrps[j].second.size();k++){
+ dev+=pow(avg-k<seggrps[j].second[k].first,2.0);
+ }
+ dev/=seggrps[j].second.size();
+ devs.push_back(pow(dev,0.5));
+ }
+ //find group with largest standard deviation
+ int largest=0;
+ for (int j=1;j<devs.size();j++) if (devs[j]>devs[largest]) largest=j;
+ //sanity check: if there are any groups that can be split they will have larger SD than singleton groups
+ //sort members of the group
+ std::sort(seggrps[largest].second.begin(),seggrps[largest].second.end(),sortgroupmembers);
+ //create a new group
+ std::pair<double,vector<pair<double,int> > > newgroup;
+ //cerr<<"splitting group "<<largest<<" with "<<seggrps[largest].second.size()<<" segments: new group will have "<<seggrps[largest].second.size()-(seggrps[largest].second.size()/2)<<" segments"<<endl;
+ for (int j=seggrps[largest].second.size()-1;j>(seggrps[largest].second.size()/2)-1;j--) {
+ newgroup.second.push_back(seggrps[largest].second[j]);
+ seggrps[largest].second.erase(seggrps[largest].second.begin()+j);
+ }
+
+ //refresh averages for the 2 groups
+ double avg=0.0f;
+ for (auto p:seggrps[largest].second) avg+=p.first;
+ avg/=seggrps[largest].second.size();
+ seggrps[largest].first=avg;
+
+ avg=0.0f;
+ for (auto p:newgroup.second) avg+=p.first;
+ avg/=newgroup.second.size();
+ newgroup.first=avg;
+
+ //add the new group
+ seggrps.push_back(newgroup);
+ //cerr<<" added new group with "<<newgroup.second.size()<<" segments"<<endl;
+ }
+ cerr<<"similaritity groups split, "<<seggrps.size()<<" levels total"<<endl;
+ //seggrps are now out of order
+ std::sort(seggrps.begin(),seggrps.end(),sortseggrps);
+
+ }
+
+ }
+
+ map<int,int> outputvalues;
+ for (int j=0;j<seggrps.size();j++){
+ string list="";
+ for (int k=0;k<seggrps[j].second.size();k++){
+ outputvalues[seggrps[j].second[k].second]=j;
+ if (k>0) list+=",";
+ list +=toString(seggrps[j].second[k].second);
+ }
+ //cerr<<"output value: "<<j<<" assigned to ["<<list<<"]"<<endl;
+ }
+
+
+ for (i=0;i<totals.size();i++){
+ vampHost::feature f;
+ f.values.push_back(outputvalues[i]);
+ features[times[totals[i].first]]=f;
+ }
+
+ }
+}
+
+/*
+A data structure to represent segments and their mapping to output levels
+how do we merge the intensity groups with the similarities?
+
+we create a list
+
+
+
+
+... we iterate through the list of segments and place the right output number
+
+
+*/
diff --git a/NT/src/nodes_audio_analysis.h b/NT/src/nodes_audio_analysis.h
new file mode 100644
index 0000000..4542e19
--- /dev/null
+++ b/NT/src/nodes_audio_analysis.h
@@ -0,0 +1,329 @@
+#ifndef ROTOR_NODES_AUDIO_ANALYSIS
+#define ROTOR_NODES_AUDIO_ANALYSIS
+
+#include "rotor.h"
+#include "vampHost.h"
+
+namespace Rotor {
+#define VAMPHOST_Timeline 1
+#define VAMPHOST_Timesteps 2
+#define VAMPHOST_Valueline 3
+#define VAMPHOST_Values 4
+ class Audio_processor: public Node_type<double> {
+ public:
+ Audio_processor(){};
+ virtual ~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(){};
+ virtual string get_features(){return "";};
+ int channels,bits,samples,rate;
+ };
+ //actual nodes-------------------------------------------------
+ class Audio_thumbnailer: public Audio_processor {
+ public:
+ Audio_thumbnailer(){
+ height=128;
+ width=512; //fit
+
+ //trying to reduce valgrind errors
+ out_sample=0; //sample in whole track
+ sample=0;
+ samples=0;
+ accum=0.0;
+ };
+ ~Audio_thumbnailer(){};
+ Audio_thumbnailer* clone(Json::Value& _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);
+ void print_vector(xmlIO XML);
+ const double get_output(const Frame_parameters &frame){return 0.0;};
+ vector<double> audiodata;
+ int height,width,samples_per_column;
+ int out_sample,sample,samples;
+ int offset;
+ double scale,accum;
+ };
+ class Vamp_node: public Audio_processor {
+ //base class for vamp plugin hosts
+ public:
+ Vamp_node(){
+ create_attribute("mode","Data output mode","Mode","timeline",{"timeline","timesteps","valueline","values"});
+ };
+ bool init(int _channels,int _bits,int _samples,int _rate);
+ void cleanup();
+ int process_frame(uint8_t *data,int samples_in_frame);
+ const double output(const Time_spec &time) {
+ if (features.size()) {
+ auto i=features.upper_bound(time.time); //the first element in the container whose key is considered to go after k
+ double uk;
+ double v1,v2;
+ v1=v2=0.0;
+ if (i==features.end()) {
+ uk=time.duration;
+ }
+ else {
+ uk=i->first;
+ if (i->second.values.size()) v2=i->second.values[0];
+ }
+ i--;
+ double lk=i->first;
+ int ln=i->second.number;
+ if (i->second.values.size()) v1=i->second.values[0];
+ switch (attributes["mode"]->intVal){
+ case VAMPHOST_Timeline:
+ return (((time.time-lk)/(uk-lk))+ln);
+ case VAMPHOST_Timesteps:
+ return (double)ln;
+ case VAMPHOST_Valueline:
+ return ((time.time-lk)/(uk-lk))+v1; //((((time.time-lk)/(uk-lk))*(v2-v1))+v1);
+ case VAMPHOST_Values:
+ return v1;
+ }
+ //}
+ //return (--features.end())->second.values[0];
+ }
+ return 0.0;
+ }
+ string get_features();
+ void print_summary(){
+ cerr<<"vamp plugin "<<id<<" of library "<<soname<<" found "<<features.size()<<" features "<<endl;
+ };
+ protected:
+ string soname,id;
+ int outputNo;
+ map <string,float> params;
+ map<double,vampHost::feature> features;
+ private:
+ vampHost::Analyser analyser;
+ };
+ class Audio_analysis: public Vamp_node {
+ //vamp node that allows the user to choose a plugin
+ public:
+ Audio_analysis(){
+ //create_attribute("soname","Plugin library to use","Plugin library","vamp-example-plugins");
+ //create_attribute("id","ID of Plugin to use","Plugin ID","percussiononsets");
+ //create_attribute("analyser","Analyser Plugin to use","Analyser plugin","barbeattracker",{"barbeattracker","segmenter"});
+ create_parameter("outputNo","number","Plugin output to use","Output number",0.0);
+ //title="Audio analysis";
+ //description="Analyse audio and output";
+ NODEID="b769f54e-2d0b-11e3-87dd-f73fc7b1c636";
+ };
+ Audio_analysis(map<string,string> &settings):Audio_analysis() {
+ base_settings(settings);
+ soname=find_setting(settings,"soname");
+ id=find_setting(settings,"id");
+ outputNo=find_setting(settings,"outputNo",0);
+ };
+ ~Audio_analysis(){};
+ Audio_analysis* clone(map<string,string> &_settings) { return new Audio_analysis(_settings);};
+ private:
+ };
+ class Audio_analysis2: public Vamp_node {
+ //reworked the plugin loader
+ public:
+ Audio_analysis2(){
+ //create_attribute("soname","Plugin library to use","Plugin library","vamp-example-plugins",{"horiz","vert","horizR","vertR"});
+ //create_attribute("id","ID of Plugin to use","Plugin ID","percussiononsets",{"horiz","vert","horizR","vertR"});
+ create_attribute("analyser","Analyser Plugin to use","Analyser plugin","barbeattracker",{"adaptivespectrum","barbeattracker","chromagram","dwt","mfcc","onsetdetector","segmenter","similarity","tempotracker","transcription"});
+ create_parameter("outputNo","number","Plugin output to use","Output number",0.0);
+ title="Audio analysis";
+ description="Analyse audio and output";
+ NODEID="b769f54e-2d0b-11e3-87dd-f73fc7b1c636";
+ plugins.push_back(make_pair("qm-adaptivespectrogram","qm-vamp-plugins"));
+ plugins.push_back(make_pair("qm-barbeattracker","qm-vamp-plugins"));
+ plugins.push_back(make_pair("qm-chromagram","qm-vamp-plugins"));
+ plugins.push_back(make_pair("qm-dwt","qm-vamp-plugins"));
+ plugins.push_back(make_pair("qm-mfcc","qm-vamp-plugins"));
+ plugins.push_back(make_pair("qm-onsetdetector","qm-vamp-plugins"));
+ plugins.push_back(make_pair("qm-segmenter","qm-vamp-plugins"));
+ plugins.push_back(make_pair("qm-similarity","qm-vamp-plugins"));
+ plugins.push_back(make_pair("qm-tempotracker","qm-vamp-plugins"));
+ plugins.push_back(make_pair("qm-transcription","qm-vamp-plugins"));
+ };
+ Audio_analysis2(map<string,string> &settings):Audio_analysis2() {
+ base_settings(settings);
+ soname=plugins[attributes["analyser"]->intVal-1].second;
+ id=plugins[attributes["analyser"]->intVal-1].first;
+ outputNo=find_setting(settings,"outputNo",0);
+ };
+ ~Audio_analysis2(){};
+ void init_attribute(const string &attr){
+ if (attr=="analyser") {
+ soname=plugins[attributes["analyser"]->intVal-1].second;
+ id=plugins[attributes["analyser"]->intVal-1].first;
+ }
+ if (attr=="outputNo") {
+ outputNo=parameters["outputNo"]->value;
+ }
+ };
+ Audio_analysis2* clone(map<string,string> &_settings) { return new Audio_analysis2(_settings);};
+ private:
+ vector< pair<string,string>> plugins;
+ };
+ class Act_segmenter: public Vamp_node {
+ //vamp node that applies a ruleset to manage a set of acts via a cycler
+ public:
+ Act_segmenter(){
+ create_parameter("outputNo","number","Plugin output to use","Output number",0.0);
+ create_parameter("acts","number","Number of acts defined","Acts",1.0);
+ title="Act manager";
+ description="Applies a ruleset to manage acts based on segments";
+ NODEID="c55359a2-2d0b-11e3-8a3d-53fa9c2b8859";
+ };
+ Act_segmenter(map<string,string> &settings):Act_segmenter() {
+ base_settings(settings);
+ soname="qm-vamp-plugins";
+ id="qm-segmenter";
+ outputNo=find_setting(settings,"outputNo",0);
+ };
+ ~Act_segmenter(){};
+ Act_segmenter* clone(map<string,string> &_settings) { return new Act_segmenter(_settings);};
+ void cleanup(){
+ Vamp_node::cleanup();
+ map<double,vampHost::feature> acts; //temporary storage for new set of features
+ vector<int> act_count;
+ for (int i=0;i<(int)parameters["acts"]->value;i++) act_count.push_back(0);
+
+ if (features.size()<=(uint32_t)parameters["acts"]->value+1){
+ //iteratively split segments and refresh durations
+ //this could work well on the original data
+ //pick the longest and split it in two
+ //refresh durations - this can be a function
+ //keep going
+ //finally copy out
+ while (features.size()<(uint32_t)parameters["acts"]->value+1){
+ map<int,double> durations;
+ map<int,double> times;
+ int i=0;
+ for (map<double,vampHost::feature>::iterator f=features.begin();f!=features.end();++f){
+ auto g=f;
+ if (++g!=features.end()){
+ durations[i]=g->first-f->first;
+ times[i]=f->first;
+ }
+ i++;
+ }
+ double f=0;
+ int n=-1;
+ for (auto d: durations){
+ if (d.second>f){
+ f=d.second;
+ n=d.first;
+ }
+ }
+ features[times[n]+(durations[n]/2)]=features[times[n]];
+ }
+ int j=0;
+ for (auto f: features){
+ f.second.number=j;
+ acts[f.first]=f.second;
+ j++;
+ }
+ }
+ else { //there are extra segments to assign to acts
+ //assign act numbers to segments
+
+ //work through the segments
+ //have a list of segments that are to be filled
+ //start at the ends and work inwards
+ //have a list of how many times each segment has been used
+
+ //start with a set of the segments numbers
+ //(the aim: to access the segment numbers by the time that they start)
+ map<double,vampHost::feature> segments=features;
+ segments.erase(--segments.end());
+
+ //assign the acts from the beginning and end in towards the middle
+ //when all slots have bveen used it picks the middle item and uses surrrounding items again
+ //there is a limit of item use based on how near it is to the edge
+ bool take_start=true;
+ while (segments.size()){
+ int act=-1;
+ vampHost::feature f;
+ double t;
+ if (take_start){
+ f=(*segments.begin()).second;
+ t=(*segments.begin()).first;
+ segments.erase(segments.begin());
+ for (int i=0;i<(int)parameters["acts"]->value-1;i++) {
+ if (act_count[i]==0||(act_count[i+1]>act_count[i]&&act_count[i]<i)) {
+ act=i;
+ break;
+ }
+ }
+ }
+ else {
+ f=(*--segments.end()).second;
+ t=(*--segments.end()).first;
+ segments.erase(--segments.end());
+ for (int i=(int)parameters["acts"]->value-1;i>0;i--) {
+ if (act_count[i]==0||(act_count[i-1]>act_count[i]&&act_count[i]<(int)parameters["acts"]->value-i)) {
+ act=i;
+ break;
+ }
+ }
+
+ }
+ if (act==-1){ //all slots are filled equally
+ act=(int)parameters["acts"]->value/2;
+ }
+ act_count[act]++;
+ f.number=act;
+ acts[t]=f;
+ take_start=!take_start;
+ }
+ }
+ //add last item back on
+ acts[(*--features.end()).first]=features[(*--features.end()).first];
+ features=acts;
+ }
+ private:
+
+ };
+ class Intensity_segmenter: public Vamp_node {
+ //vamp node that applies a ruleset to manage a set of acts via a cycler
+ public:
+ Intensity_segmenter(){
+ title="Intensity segmenter";
+ description="Combines the output of segmentation, tempo, and intensity analysis plugins";
+ NODEID="6ce236b6-4080-11e3-90b7-74d02b29f6a6";
+ analysers["segmenter"]=vampHost::Analyser();
+ analysers["tempo"]=vampHost::Analyser();
+ analysers["intensity"]=vampHost::Analyser();
+ create_parameter("intensity_weight","number","intensity weight","Intensity weighting",1.0);
+ create_parameter("tempo_weight","number","tempo weight","Tempo weighting",1.0);
+ create_parameter("levels","number","levels","Number of intensity levels",0.0);
+ };
+ Intensity_segmenter(map<string,string> &settings):Intensity_segmenter() {
+ base_settings(settings);
+ };
+ ~Intensity_segmenter(){};
+ Intensity_segmenter* clone(map<string,string> &_settings) { return new Intensity_segmenter(_settings);};
+ bool init(int _channels,int _bits,int _samples,int _rate) {
+ features.clear();
+ return analysers["segmenter"].init("qm-vamp-plugins","qm-segmenter",_channels,_bits,_samples,_rate,0,params)\
+ &&analysers["tempo"].init("qm-vamp-plugins","qm-tempotracker",_channels,_bits,_samples,_rate,2,params)\
+ &&analysers["intensity"].init("bbc-vamp-plugins","bbc-intensity",_channels,_bits,_samples,_rate,0,params);
+ }
+ int process_frame(uint8_t *data,int samples_in_frame) {
+ //for (auto a:analysers) a.second.process_frame(data,samples_in_frame); //WHY NOT WORK
+ analysers["segmenter"].process_frame(data,samples_in_frame);
+ analysers["tempo"].process_frame(data,samples_in_frame);
+ analysers["intensity"].process_frame(data,samples_in_frame);
+ return 1;
+ }
+ void cleanup(); //all the fun happens here
+ void print_summary(){
+ cerr<<"Intensity segmenter found "<<features.size()<<" features "<<endl;
+ };
+ private:
+ map<string,vampHost::Analyser> analysers;
+ };
+}
+
+
+
+#endif
diff --git a/NT/src/rendercontext.cpp b/NT/src/rendercontext.cpp
index 3309b16..a03723f 100644
--- a/NT/src/rendercontext.cpp
+++ b/NT/src/rendercontext.cpp
@@ -5,6 +5,64 @@ using namespace std;
using Poco::Net::HTTPResponse;
using Poco::Logger;
+void Render_context::runTask() {
+ while (!isCancelled()) {
+ Session_task cmd;
+ mutex.lock();
+ if (work_queue.size()){
+ cmd=work_queue[0];
+ work_queue.pop_front();
+ graph.cancelled=false;
+ }
+ mutex.unlock();
+ if(cmd.task==ANALYSE_AUDIO) {
+ state=ANALYSING_AUDIO;
+ graph.load_audio(cmd.body);
+ state=IDLE;
+ }
+ if(cmd.task==RENDER) {
+ state=RENDERING;
+ renders[cmd.uid]=Render_status(RENDERING);
+ if(graph.video_render(output_filename,output_framerate,start,stop)){
+ state=IDLE;
+ if (graph.cancelled) renders[cmd.uid].status=CANCELLED;
+ else renders[cmd.uid].status=RENDER_READY;
+ }
+ else {
+ //an error occurred: TODO have to clean up allocated data. autoptr?
+ cerr<<"Rotor: render failed"<<endl;
+ state=IDLE;
+ renders[cmd.uid].status=FAILED;
+ }
+ }
+ if(cmd.task==LOAD_GRAPH) {
+ state=LOADING_GRAPH;
+ if (graph_filename!="") {
+ if (!graph.loadFile(graph_filename,media_dir)){
+ cerr<<"Rotor: failed to load graph from "<<graph_filename<<endl;
+ }
+ }
+ else if (graph_body!="") {
+ if (!graph.load(graph_body,media_dir)) {
+ cerr<<"Rotor: failed to load graph from body request"<<endl;
+ }
+ }
+ if (graph.loaded){
+ if (graph.audio_loaded) {
+ add_queue(Session_task(cmd.uid,ANALYSE_AUDIO));
+ cerr<<"Rotor: starting audio analysis for graph"<<endl;
+ }
+ }
+ state=IDLE;
+ }
+ sleep(100);
+ }
+}
+void Render_context::add_queue(Session_task item) {
+ mutex.lock();
+ work_queue.push_back(item);
+ mutex.unlock();
+}
string Render_context::text_render(string node_id){
Logger& logger = Logger::get(id);
Node* p;
@@ -44,23 +102,18 @@ void Render_context::session_command(const Session_command& command,xmlIO& XML,H
Logger& logger = Logger::get(id);
status=HTTPResponse::HTTP_BAD_REQUEST; //error by default
- if (command.commands[1]=="resolution") {
+ if (command.commands[1]=="profile") {
if (command.method=="PUT") {
if (command.body!="") {
if (state!=RENDERING) {
- Poco::StringTokenizer t1(command.body,",");
- if (t1.count()>1){
- int w=toInt(t1[0]);
- int h=toInt(t1[1]);
- if (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]);
- }
+ if (set_profile(command.body)){
+ logger.information("profile set to "+command.body);
+ XML.addValue("status","profile set to "+command.body);
+ status=HTTPResponse::HTTP_OK;
+ }
+ else {
+ logger.error("ERROR: invalid profile request: "+command.body);
+ XML.addValue("error","invalid profile request: "+command.body);
}
}
else {
@@ -69,65 +122,6 @@ void Render_context::session_command(const Session_command& command,xmlIO& XML,H
}
}
}
- if (command.commands[1]=="bitrate") {
- if (command.method=="PUT") {
- int b=toInt(command.body);
- if (graph.set_bitrate(b)){
- logger.information("bitrate set to "+command.body);
- XML.addValue("status","bitrate set to "+command.body);
- status=HTTPResponse::HTTP_OK;
- }
- else {
- logger.error("ERROR: Could not set bitrate set to "+command.body);
- XML.addValue("error","Could not set bitrate set to "+command.body);
- }
- }
- else {
- status=HTTPResponse::HTTP_BAD_REQUEST;
- logger.error("ERROR: Bad request");
- XML.addValue("error","Bad request");
- }
- }
- if (command.commands[1]=="fragmentation") {
- if (command.method=="PUT") {
- bool f=(toInt(command.body)!=0);
- if (graph.set_fragmentation(f)){
- string fs=f?"on":"off";
- logger.information("MP4 fragmentation "+fs);
- XML.addValue("status","MP4 fragmentation "+fs);
- status=HTTPResponse::HTTP_OK;
- }
- else {
- logger.error("ERROR: Could not set MP4 fragmentation");
- XML.addValue("error","Could not set MP4 fragmentation");
- }
- }
- else {
- status=HTTPResponse::HTTP_BAD_REQUEST;
- logger.error("ERROR: Bad request");
- XML.addValue("error","Bad request");
- }
- }
- if (command.commands[1]=="dash") {
- if (command.method=="PUT") {
- bool f=(toInt(command.body)!=0);
- if (graph.set_dash(f)){
- string fs=f?"on":"off";
- logger.information("MPEG-DASH output "+fs);
- XML.addValue("status","MPEG-DASH output "+fs);
- status=HTTPResponse::HTTP_OK;
- }
- else {
- logger.error("ERROR: Could not set MPEG-DASH output");
- XML.addValue("error","Could not set MPEG-DASH output");
- }
- }
- else {
- status=HTTPResponse::HTTP_BAD_REQUEST;
- logger.error("ERROR: Bad request");
- XML.addValue("error","Bad request");
- }
- }
if (command.commands[1]=="audio") {
if (command.method=="PUT") { //get audio file location and initiate analysis
if (command.body!="") {
@@ -135,7 +129,6 @@ void Render_context::session_command(const Session_command& command,xmlIO& XML,H
graph.audio_filename=media_dir+command.body; //for now, store session variables in memory //check file exists
Poco::File f=Poco::File(graph.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(Session_task(command.uid,ANALYSE_AUDIO));
status=HTTPResponse::HTTP_OK;
logger.information("Audio analysis: "+command.body);
@@ -162,6 +155,7 @@ void Render_context::session_command(const Session_command& command,xmlIO& XML,H
sprintf(c,"%02f",graph.progress);
XML.addValue("progress",string(c));
}
+ /*
else if (graph.audio_loaded) {
//not sure about this-- should this state be retained?
//can the data only be read once?
@@ -175,6 +169,7 @@ void Render_context::session_command(const Session_command& command,xmlIO& XML,H
logger.error("ERROR: audio thumbnail requested but no audio loaded");
XML.addValue("error","No audio loaded");
}
+ */
}
if (command.method=="DELETE") {
if (state==IDLE) {
@@ -216,29 +211,25 @@ void Render_context::session_command(const Session_command& command,xmlIO& XML,H
//before begining to load from xml
if (state==IDLE) { //eventually not like this
if (command.body!="") {
- graph_filename="";
- graph_body="";
- if (Poco::File(graph_dir+command.body).exists()) {
- graph_filename=graph_dir+command.body;
- add_queue(Session_task(command.uid,LOAD_GRAPH));
+ string graph_filename=graph_dir+command.body;
+ string graph_body="";
+ if (Poco::File(graph_filename).exists()) {;
+ add_queue(Session_task(command.uid,LOAD_GRAPH,graph_filename));
status=HTTPResponse::HTTP_OK;
logger.information("Loading graph from file: "+command.body);
XML.addValue("status","Loading graph from file: "+command.body);
//XML.addValue("render_id",command.uid); process ID?
}
else {
- xmlIO xml;
bool readable=true;
- if (!xml.loadFromBuffer(command.body)){
- Json::Value root; // will contains the root value after parsing.
- Json::Reader reader;
- if ( !reader.parse( command.body, root ) )
- {
- status=HTTPResponse::HTTP_BAD_REQUEST;
- logger.error("ERROR: Could not load graph");
- XML.addValue("error","Could not load graph");
- readable=false;
- }
+ Json::Value root; // will contains the root value after parsing.
+ Json::Reader reader;
+ if ( !reader.parse( command.body, root ) )
+ {
+ status=HTTPResponse::HTTP_BAD_REQUEST;
+ logger.error("ERROR: Could not load graph");
+ XML.addValue("error","Could not load graph");
+ readable=false;
}
if (readable) {
graph_body=command.body;
diff --git a/NT/src/rendercontext.h b/NT/src/rendercontext.h
index a84cad8..d1acf55 100644
--- a/NT/src/rendercontext.h
+++ b/NT/src/rendercontext.h
@@ -11,6 +11,7 @@ TJR Jan 2014
-------------------------*/
#include "Poco/Task.h"
+#include "Poco/StringTokenizer.h"
#include "rotor.h"
#include "graph.h"
@@ -49,6 +50,12 @@ namespace Rotor {
string uid,method,id,body;
vector<string> commands;
};
+ class Session_task {
+ public:
+ Session_task(const string &_uid="",int _task=0):uid(_uid),task(_task) {};
+ string uid;
+ int task;
+ };
class Render_status {
public:
Render_status():status(0),progress(0.0){};
@@ -63,8 +70,9 @@ namespace Rotor {
};
class Render_context: public Poco::Task {
public:
- Render_context(const std::string& _id): Task(_id) {
+ Render_context(const std::string& _id,const std::string& _media_dir=""): Task(_id) {
id=_id;
+ media_dir=_media_dir;
graph.init(id);
//set up log
AutoPtr<SplitterChannel> splitterChannel(new SplitterChannel());
@@ -75,6 +83,8 @@ namespace Rotor {
AutoPtr<Formatter> formatter(new PatternFormatter("%d-%m-%Y %H:%M:%S %s: %t"));
AutoPtr<Channel> formattingChannel(new FormattingChannel(formatter, splitterChannel));
Logger& logger = Logger::create(id, formattingChannel, Message::PRIO_TRACE);
+
+ //load profiles
logger.information("started thread");
}
~Render_context(){
@@ -82,14 +92,10 @@ namespace Rotor {
cancel();
logger.information("stopped thread");
}
- void runTask() {
- while (!isCancelled()) {
- sleep(100);
- }
- }
- bool set_preset(std::string& _preset){
- if (presets.find(_preset)==presets.end()) return false;
- preset=_preset;
+ void add_queue(Session_task item);
+ bool set_profile(const std::string& _profile){
+ if (profiles.find(_profile)==profiles.end()) return false;
+ profile=_profile;
return true;
}
void session_command(const Session_command& command,xmlIO& XML,Poco::Net::HTTPResponse::HTTPStatus& status);
@@ -105,10 +111,15 @@ namespace Rotor {
std::string text_render(std::string node_id="");
Graph graph;
private:
+ int state;
std::string id;
std::unordered_map<std::string,Render_status> renders;
- std::unordered_map<std::string,Render_settings> presets;
- std::string preset;
+ std::unordered_map<std::string,Render_settings> profiles;
+ std::string profile;
+ std::string media_dir;
+
+ std::deque<Session_task> work_queue;
+ Poco::Mutex mutex;
};
};
diff --git a/NT/src/rotor.h b/NT/src/rotor.h
index f687694..1ed02dc 100644
--- a/NT/src/rotor.h
+++ b/NT/src/rotor.h
@@ -23,6 +23,7 @@ What next?
#include <typeinfo>
#include "xmlIO.h"
+#include "libavwrapper.h"
#include "Poco/Logger.h"
#include "Poco/Channel.h"
diff --git a/NT/src/rotord.o b/NT/src/rotord.o
deleted file mode 100644
index e73e551..0000000
--- a/NT/src/rotord.o
+++ /dev/null
Binary files differ
diff --git a/NT/src/tinyxml.o b/NT/src/tinyxml.o
deleted file mode 100644
index 811f6ab..0000000
--- a/NT/src/tinyxml.o
+++ /dev/null
Binary files differ
diff --git a/NT/src/tinyxmlerror.o b/NT/src/tinyxmlerror.o
deleted file mode 100644
index 49bba63..0000000
--- a/NT/src/tinyxmlerror.o
+++ /dev/null
Binary files differ
diff --git a/NT/src/tinyxmlparser.o b/NT/src/tinyxmlparser.o
deleted file mode 100644
index 1b4101c..0000000
--- a/NT/src/tinyxmlparser.o
+++ /dev/null
Binary files differ
diff --git a/NT/src/vampHost.cpp b/NT/src/vampHost.cpp
new file mode 100644
index 0000000..a70e795
--- /dev/null
+++ b/NT/src/vampHost.cpp
@@ -0,0 +1,416 @@
+#include "vampHost.h"
+
+
+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<double>& out, double& 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;
+ }
+
+}
+
+
+
+void vampHost::getTimestamps(int output,Plugin::FeatureSet features, vector<double>& 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 double representation
+
+ features[output][i].values is a vector of doubles + a description
+ WE DON'T CARE ABOUT ANYTHING <.01 seconds
+
+ static long realTime2Frame(const RealTime &r, unsigned int sampleRate);
+
+ get a vector of doubles 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( ((double)RealTime::realTime2Frame(features[output][i].timestamp, 1000))*.001f);
+ cout << "feature found.\n";
+ }
+ //}
+}
+
+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.0/pow(2.0,bits));
+
+ features.clear(); //in case of reuse
+ features[0.0]=feature();
+
+ 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;
+
+ 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;
+ }
+
+ cerr << "Vamphost Plugin initialised: (channels = " << channels
+ << ", stepSize = " << stepSize << ", blockSize = "
+ << blockSize << ")" << endl;
+
+ 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=0;
+
+ 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]=((double)((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);
+
+ double t;
+
+ for (unsigned int i = 0; i < feat[outputNo].size(); ++i) {
+ feature f;
+ f.number=featureNo;
+ f.values=feat[outputNo][i].values;
+ //fix for plugins that don't set timestamp properly
+ t=((double)feat[outputNo][i].timestamp.sec)+(((double)feat[outputNo][i].timestamp.nsec)*.000000001);
+ if (t<.01) t=((rt.sec)+(rt.nsec)*.000000001);
+ features[t]=f;
+ featureNo++;
+ }
+
+ //if (feat[outputNo].size()>0) cerr<<"vamphost: "<<t<<":"<<features.size()<<" features"<<endl;
+
+ //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];
+ }
+ }
+
+ //if (feat[outputNo].size()>0) cerr<<plugin->getIdentifier()<<" step:"<<currentStep<<" sample:"<<currentStep * stepSize<<" features:"<<features.size()<<endl;
+
+ in_block-=stepSize;
+ currentStep++; //WTF this number does not increase when called the 2nd way
+ }
+ }
+}
+void vampHost::Analyser::cleanup(){
+ //process final block
+ while(in_block<blockSize) {
+ for (int i=0;i<channels;i++) {
+ plugbuf[i][in_block]=0.0;
+ }
+ 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) {
+ feature f;
+ f.number=featureNo;
+ f.values=feat[outputNo][i].values;
+ features[((double)feat[outputNo][i].timestamp.sec)+(((double)feat[outputNo][i].timestamp.nsec)*.000000001)]=f;
+ featureNo++;
+ }
+
+ feat=plugin->getRemainingFeatures();
+
+ for (unsigned int i = 0; i < feat[outputNo].size(); ++i) {
+ feature f;
+ f.number=featureNo;
+ f.values=feat[outputNo][i].values;
+ features[((double)feat[outputNo][i].timestamp.sec)+(((double)feat[outputNo][i].timestamp.nsec)*.000000001)]=f;
+ featureNo++;
+ }
+
+ //always make a final blank feature at the end
+ feature f;
+ f.number=featureNo;
+ f.values={};
+ features[((double)rt.sec)+(((double)rt.nsec)*.000000001f)]=f;
+
+ //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;
+}
+double vampHost::Analyser::get_value(const double &time) {
+ if (features.size()) {
+ auto i=features.upper_bound(time); //the first element in the container whose key is considered to go after k
+ if (i!=features.end()){
+ double uk=i->first;
+ double v1,v2;
+ v1=v2=0.0;
+ if (i->second.values.size()) v2=i->second.values[0];
+ i--;
+ double lk=i->first;
+ if (i->second.values.size()) v1=i->second.values[0];
+
+ return ((((time-lk)/(uk-lk))*(v2-v1))+v1);
+ }
+ }
+ return 0.0;
+}
diff --git a/NT/src/vampHost.h b/NT/src/vampHost.h
new file mode 100644
index 0000000..a8b0bbb
--- /dev/null
+++ b/NT/src/vampHost.h
@@ -0,0 +1,95 @@
+#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 "libavwrapper.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 {
+ struct feature{
+ feature():number(0){};
+ int number;
+ vector<float> values;
+ };
+ 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);
+ double get_progress();
+ vector<float> beats;
+ private:
+ double 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();
+ double get_value(const double &time);
+ //map<double,int> features;
+ map<double,feature> features;
+ //map<time,featureNo>
+ //this is the best way to store features: because map allows to search for the key below and above the present time
+
+ private:
+ PluginLoader *loader;
+ PluginLoader::PluginKey key;
+ Plugin *plugin;
+ RealTime rt;
+ int channels,bits,samples,rate;
+ int bytes,stride;
+ double 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<double>& out);
+ void rotorGetFeatures(int frame, int sr, int output,Plugin::FeatureSet features, vector<double>& out, double& progress);
+
+}